diff --git a/GPL.txt b/GPL.txt deleted file mode 100644 index d511905c1..000000000 --- a/GPL.txt +++ /dev/null @@ -1,339 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - , 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..65c5ca88a --- /dev/null +++ b/LICENSE @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/LICENSE.md b/LICENSE.md deleted file mode 100644 index fd134ec9e..000000000 --- a/LICENSE.md +++ /dev/null @@ -1,12 +0,0 @@ -The server code and utilities are released under GPLv3. - -We also include some small libraries for convienence that may be under different licensing: - -SocketLib - GPL -LibXML - ZLib License -StackWalker - New BSD License -ZLib - ZLib License -MySQL - GPL -Perl - GPL / ActiveState (under the assumption that this is a free project). -CPPUnit - GLP -StringUtilities - Apache \ No newline at end of file diff --git a/README.md b/README.md index e4ae8893c..0012d694b 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,24 @@ forum, although pull requests will be much quicker and easier on all parties. - **User Discord Channel**: `#general` - **Developer Discord Channel**: `#eqemucoders` -Resources ---- +## Resources - [EQEmulator Forums](http://www.eqemulator.org/forums) - [EQEmulator Wiki](https://github.com/EQEmu/Server/wiki) +- [EQEmulator Wiki](http://wiki.eqemulator.org/i?M=Wiki) + +## Related Repositories +* [ProjectEQ Quests](https://github.com/ProjectEQ/projecteqquests) +* [Maps](https://github.com/Akkadius/EQEmuMaps) +* [Installer Resources](https://github.com/Akkadius/EQEmuInstall) +* [Zone Utilities](https://github.com/EQEmu/zone-utilities) - Various utilities and libraries for parsing, rendering and manipulating EQ Zone files. + +## Other License Info + +* The server code and utilities are released under **GPLv3** +* We also include some small libraries for convienence that may be under different licensing + * SocketLib - GPL LibXML + * zlib - zlib license + * MariaDB/MySQL - GPL + * GPL Perl - GPL / ActiveState (under the assumption that this is a free project) + * CPPUnit - GLP StringUtilities - Apache + * LUA - MIT diff --git a/changelog.txt b/changelog.txt index 9568f8392..72434e602 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,32 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- +== 10/08/2017 == +Mackal: Rework regens + +Regen will now match whats reported by modern clients, besides where they lie due to known bugs + +HP and END regens are now based on the BaseData.txt values allowing easy customization +Those cases: + - The client always applies hunger penalties, it appears they don't exist anymore on live you can turn them on with a rule + - The way the client gets buff mana/end regen benefits incorrectly applies the bard mod making these values lie sometimes + +== 9/17/2017 == +Akkadius: Add model/race offset to FixZ calc (KLS) +Akkadius: Fix 95% of food/water consumption issues, if there are additional modifiers for race/class combos - those will need to be applied + +Stages should be put in place if not already: +https://wiki.project1999.com/Food_and_drink#Stages_of_Hunger_and_Thirst + +Values stored in the database are 0-6000, previously we capped it at 6000 but previous math would have normal values in the 60k+ range in order for food to be consumed at a reasonable rate. We are now using more native logic where 1 = 1 minute, following logic: + +(Minutes) +0 - 5 - This is a snack. +6 - 20 - This is a meal. +21 - 30 - This is a hearty meal. +31 - 40 - This is a banquet size meal. +41 - 50 - This meal is a feast! +51 - 60 - This is an enduring meal! +61 - X - This is a miraculous meal! == 7/14/2017 == Akkadius: HP Update tuning - HP Updates are now forced when a client is targeted diff --git a/common/base_data.h b/common/base_data.h index fb2766f45..4d8cf4a9f 100644 --- a/common/base_data.h +++ b/common/base_data.h @@ -24,8 +24,8 @@ struct BaseDataStruct double base_hp; double base_mana; double base_end; - double unk1; - double unk2; + double hp_regen; + double end_regen; double hp_factor; double mana_factor; double endurance_factor; diff --git a/common/emu_legacy.h b/common/emu_legacy.h index a624be883..0f3cf0a62 100644 --- a/common/emu_legacy.h +++ b/common/emu_legacy.h @@ -78,6 +78,8 @@ namespace EQEmu SLOT_CURSOR_BAG_END = 340, SLOT_TRIBUTE_BEGIN = 400, SLOT_TRIBUTE_END = 404, + SLOT_GUILD_TRIBUTE_BEGIN = 450, + SLOT_GUILD_TRIBUTE_END = 451, SLOT_BANK_BEGIN = 2000, SLOT_BANK_END = 2023, SLOT_BANK_BAGS_BEGIN = 2031, diff --git a/common/emu_oplist.h b/common/emu_oplist.h index 29a20cb14..89e0126a8 100644 --- a/common/emu_oplist.h +++ b/common/emu_oplist.h @@ -9,6 +9,7 @@ N(OP_AcceptNewTask), N(OP_AckPacket), N(OP_Action), N(OP_Action2), +N(OP_AddNimbusEffect), N(OP_AdventureData), N(OP_AdventureDetails), N(OP_AdventureFinish), @@ -339,6 +340,7 @@ N(OP_MOTD), N(OP_MoveCoin), N(OP_MoveDoor), N(OP_MoveItem), +N(OP_MoveMultipleItems), N(OP_MoveLogDisregard), N(OP_MoveLogRequest), N(OP_MultiLineMsg), diff --git a/common/eq_packet_structs.h b/common/eq_packet_structs.h index 79b8f7250..8452813f5 100644 --- a/common/eq_packet_structs.h +++ b/common/eq_packet_structs.h @@ -551,6 +551,7 @@ struct BlockedBuffs_Struct /*86*/ uint16 Flags; }; +// same for adding struct RemoveNimbusEffect_Struct { /*00*/ uint32 spawnid; // Spawn ID @@ -854,6 +855,7 @@ static const uint32 MAX_PP_REF_SPELLBOOK = 480; // Set for Player Profile size r static const uint32 MAX_PP_REF_MEMSPELL = 9; // Set for Player Profile size retain static const uint32 MAX_PP_SKILL = PACKET_SKILL_ARRAY_SIZE; // 100 - actual skills buffer size +static const uint32 MAX_PP_INNATE_SKILL = 25; static const uint32 MAX_PP_AA_ARRAY = 240; static const uint32 MAX_GROUP_MEMBERS = 6; static const uint32 MAX_RECAST_TYPES = 20; @@ -993,7 +995,8 @@ struct PlayerProfile_Struct /*4768*/ int32 platinum_shared; // Platinum shared between characters /*4772*/ uint8 unknown4808[24]; /*4796*/ uint32 skills[MAX_PP_SKILL]; // [400] List of skills // 100 dword buffer -/*5196*/ uint8 unknown5132[184]; +/*5196*/ uint32 InnateSkills[MAX_PP_INNATE_SKILL]; +/*5296*/ uint8 unknown5132[84]; /*5380*/ uint32 pvp2; // /*5384*/ uint32 unknown5420; // /*5388*/ uint32 pvptype; // @@ -4764,6 +4767,7 @@ struct BuffIconEntry_Struct uint32 spell_id; int32 tics_remaining; uint32 num_hits; + char caster[64]; }; struct BuffIcon_Struct @@ -4773,6 +4777,7 @@ struct BuffIcon_Struct uint16 count; uint8 type; // 0 = self buff window, 1 = self target window, 4 = group, 5 = PC, 7 = NPC int32 tic_timer; + int32 name_lengths; // so ahh we kind of do these packets hacky, this is the total length of all the names to make creating the real packets in the translators easier BuffIconEntry_Struct entries[0]; }; diff --git a/common/eqemu_logsys.h b/common/eqemu_logsys.h index 56542c07d..a54edc234 100644 --- a/common/eqemu_logsys.h +++ b/common/eqemu_logsys.h @@ -88,6 +88,8 @@ enum LogCategory { Headless_Client, HP_Update, FixZ, + Food, + Traps, MaxCategoryID /* Don't Remove this*/ }; @@ -140,7 +142,9 @@ static const char* LogCategoryName[LogCategory::MaxCategoryID] = { "Client Login", "Headless Client", "HP Update", - "FixZ" + "FixZ", + "Food", + "Traps" }; } diff --git a/common/features.h b/common/features.h index 550c3fa7b..486255f28 100644 --- a/common/features.h +++ b/common/features.h @@ -276,6 +276,11 @@ enum { #define SAYLINK_ITEM_ID 0xFFFFF +// consumption timers for food/drink here instead of rules because the client +// uses these. Times in ms. +#define CONSUMPTION_TIMER 46000 +#define CONSUMPTION_MNK_TIMER 92000 + /* Developer configuration diff --git a/common/patches/rof.cpp b/common/patches/rof.cpp index 7ce6f900f..b655cffb0 100644 --- a/common/patches/rof.cpp +++ b/common/patches/rof.cpp @@ -460,7 +460,7 @@ namespace RoF { SETUP_VAR_ENCODE(BuffIcon_Struct); - uint32 sz = 12 + (17 * emu->count); + uint32 sz = 12 + (17 * emu->count) + emu->name_lengths; // 17 includes nullterm __packet->size = sz; __packet->pBuffer = new unsigned char[sz]; memset(__packet->pBuffer, 0, sz); @@ -476,7 +476,7 @@ namespace RoF __packet->WriteUInt32(emu->entries[i].spell_id); __packet->WriteUInt32(emu->entries[i].tics_remaining); __packet->WriteUInt32(emu->entries[i].num_hits); // Unknown - __packet->WriteString(""); + __packet->WriteString(emu->entries[i].caster); } __packet->WriteUInt8(emu->type); // Unknown @@ -2184,11 +2184,11 @@ namespace RoF outapp->WriteUInt32(emu->skills[r]); } - outapp->WriteUInt32(25); // Unknown count + outapp->WriteUInt32(structs::MAX_PP_INNATE_SKILL); // Innate Skills count - for (uint32 r = 0; r < 25; r++) + for (uint32 r = 0; r < structs::MAX_PP_INNATE_SKILL; r++) { - outapp->WriteUInt32(0); // Unknown + outapp->WriteUInt32(emu->InnateSkills[r]); // Innate Skills (regen, slam, etc) } outapp->WriteUInt32(structs::MAX_PP_DISCIPLINES); // Discipline count diff --git a/common/patches/rof2.cpp b/common/patches/rof2.cpp index afb994526..baf6d6b09 100644 --- a/common/patches/rof2.cpp +++ b/common/patches/rof2.cpp @@ -528,7 +528,7 @@ namespace RoF2 { SETUP_VAR_ENCODE(BuffIcon_Struct); - uint32 sz = 12 + (17 * emu->count); + uint32 sz = 12 + (17 * emu->count) + emu->name_lengths; // 17 includes nullterm __packet->size = sz; __packet->pBuffer = new unsigned char[sz]; memset(__packet->pBuffer, 0, sz); @@ -544,7 +544,7 @@ namespace RoF2 __packet->WriteUInt32(emu->entries[i].spell_id); __packet->WriteUInt32(emu->entries[i].tics_remaining); __packet->WriteUInt32(emu->entries[i].num_hits); // Unknown - __packet->WriteString(""); + __packet->WriteString(emu->entries[i].caster); } __packet->WriteUInt8(emu->type); // Unknown @@ -2261,11 +2261,11 @@ namespace RoF2 outapp->WriteUInt32(emu->skills[r]); } - outapp->WriteUInt32(25); // Unknown count + outapp->WriteUInt32(structs::MAX_PP_INNATE_SKILL); // Innate Skills count - for (uint32 r = 0; r < 25; r++) + for (uint32 r = 0; r < structs::MAX_PP_INNATE_SKILL; r++) { - outapp->WriteUInt32(0); // Unknown + outapp->WriteUInt32(emu->InnateSkills[r]); // Innate Skills (regen, slam, etc) } outapp->WriteUInt32(structs::MAX_PP_DISCIPLINES); // Discipline count diff --git a/common/patches/rof2_limits.h b/common/patches/rof2_limits.h index 6f4e649ff..0dd7067a9 100644 --- a/common/patches/rof2_limits.h +++ b/common/patches/rof2_limits.h @@ -164,7 +164,7 @@ namespace RoF2 ItemPacket11 = 111, ItemPacket12 = 112, ItemPacketRecovery = 113, - ItemPacket14 = 115 + ItemPacket14 = 115 // Parcel? adds to merchant window too }; } /*item*/ diff --git a/common/patches/rof2_structs.h b/common/patches/rof2_structs.h index 976753bb2..5aa11977f 100644 --- a/common/patches/rof2_structs.h +++ b/common/patches/rof2_structs.h @@ -131,6 +131,7 @@ static const uint32 MAX_PP_LANGUAGE = 32; // was 25 static const uint32 MAX_PP_SPELLBOOK = 720; // was 480 static const uint32 MAX_PP_MEMSPELL = 16; // was 12 static const uint32 MAX_PP_SKILL = PACKET_SKILL_ARRAY_SIZE; // 100 - actual skills buffer size +static const uint32 MAX_PP_INNATE_SKILL = 25; static const uint32 MAX_PP_AA_ARRAY = 300; static const uint32 MAX_PP_DISCIPLINES = 300; // was 200 static const uint32 MAX_GROUP_MEMBERS = 6; @@ -617,7 +618,7 @@ struct NewZone_Struct { /*0704*/ char zone_short_name2[32]; //zone file name? excludes instance number which can be in previous version. /*0736*/ char WeatherString[32]; /*0768*/ char SkyString2[32]; - /*0800*/ int32 SkyRelated2; //seen -1 + /*0800*/ int32 SkyRelated2; //seen -1 -- maybe some default sky time? /*0804*/ char WeatherString2[32]; // /*0836*/ float WeatherChangeTime; // not sure :P /*0840*/ uint32 Climate; @@ -1155,8 +1156,8 @@ union /*01012*/ AA_Array aa_array[MAX_PP_AA_ARRAY]; // [300] 3600 bytes - AAs 12 bytes each /*04612*/ uint32 skill_count; // Seen 100 /*04616*/ uint32 skills[MAX_PP_SKILL]; // [100] 400 bytes - List of skills -/*05016*/ uint32 unknown15_count; // Seen 25 -/*05020*/ uint32 unknown_rof15[25]; // Most are 255 or 0 +/*05016*/ uint32 InnateSkills_count; // Seen 25 +/*05020*/ uint32 InnateSkills[MAX_PP_INNATE_SKILL]; // Most are 255 or 0 /*05120*/ uint32 discipline_count; // Seen 200 /*05124*/ Disciplines_Struct disciplines; // [200] 800 bytes Known disciplines /*05924*/ uint32 timestamp_count; // Seen 20 @@ -1826,6 +1827,20 @@ struct MoveItem_Struct /*0028*/ }; +struct MultiMoveItemSub_Struct +{ +/*0000*/ InventorySlot_Struct from_slot; +/*0012*/ InventorySlot_Struct to_slot; +/*0024*/ uint32 number_in_stack; +/*0028*/ uint8 unknown[8]; +}; + +struct MultiMoveItem_Struct +{ +/*0000*/ uint32 count; +/*0004*/ MultiMoveItemSub_Struct moves[0]; +}; + // // from_slot/to_slot // -1 - destroy diff --git a/common/patches/rof_structs.h b/common/patches/rof_structs.h index 24439d7f0..8de4fb9ab 100644 --- a/common/patches/rof_structs.h +++ b/common/patches/rof_structs.h @@ -131,6 +131,7 @@ static const uint32 MAX_PP_LANGUAGE = 32; // was 25 static const uint32 MAX_PP_SPELLBOOK = 720; // was 480 static const uint32 MAX_PP_MEMSPELL = 16; // was 12 static const uint32 MAX_PP_SKILL = PACKET_SKILL_ARRAY_SIZE; // 100 - actual skills buffer size +static const uint32 MAX_PP_INNATE_SKILL = 25; static const uint32 MAX_PP_AA_ARRAY = 300; static const uint32 MAX_PP_DISCIPLINES = 200; // was 100 static const uint32 MAX_GROUP_MEMBERS = 6; @@ -1096,8 +1097,8 @@ union /*01012*/ AA_Array aa_array[MAX_PP_AA_ARRAY]; // [300] 3600 bytes - AAs 12 bytes each /*04612*/ uint32 skill_count; // Seen 100 /*04616*/ uint32 skills[MAX_PP_SKILL]; // [100] 400 bytes - List of skills -/*05016*/ uint32 unknown15_count; // Seen 25 -/*05020*/ uint32 unknown_rof15[25]; // Most are 255 or 0 +/*05016*/ uint32 InnateSkills_count; // Seen 25 +/*05020*/ uint32 InnateSkills[MAX_PP_INNATE_SKILL]; // Most are 255 or 0 /*05120*/ uint32 discipline_count; // Seen 200 /*05124*/ Disciplines_Struct disciplines; // [200] 800 bytes Known disciplines /*05924*/ uint32 timestamp_count; // Seen 20 diff --git a/common/patches/sod.cpp b/common/patches/sod.cpp index 276f939e5..a77feedbc 100644 --- a/common/patches/sod.cpp +++ b/common/patches/sod.cpp @@ -1605,6 +1605,7 @@ namespace SoD OUT(copper_cursor); OUT_array(skills, structs::MAX_PP_SKILL); // 1:1 direct copy (100 dword) + OUT_array(InnateSkills, structs::MAX_PP_INNATE_SKILL); // 1:1 direct copy (25 dword) // OUT(unknown04760[236]); OUT(toxicity); diff --git a/common/patches/sod_structs.h b/common/patches/sod_structs.h index af449f758..88ccd7788 100644 --- a/common/patches/sod_structs.h +++ b/common/patches/sod_structs.h @@ -818,6 +818,7 @@ static const uint32 MAX_PP_LANGUAGE = 25; // static const uint32 MAX_PP_SPELLBOOK = 480; // Confirmed 60 pages on Live now static const uint32 MAX_PP_MEMSPELL = 10; //was 9 now 10 on Live static const uint32 MAX_PP_SKILL = PACKET_SKILL_ARRAY_SIZE; // 100 - actual skills buffer size +static const uint32 MAX_PP_INNATE_SKILL = 25; static const uint32 MAX_PP_AA_ARRAY = 300; //was 299 static const uint32 MAX_GROUP_MEMBERS = 6; static const uint32 MAX_RECAST_TYPES = 20; @@ -923,7 +924,8 @@ struct PlayerProfile_Struct /*06488*/ uint32 silver_cursor; // Silver Pieces on cursor /*06492*/ uint32 copper_cursor; // Copper Pieces on cursor /*06496*/ uint32 skills[MAX_PP_SKILL]; // [400] List of skills // 100 dword buffer -/*06896*/ uint8 unknown04760[136]; +/*06896*/ uint32 InnateSkills[MAX_PP_SKILL]; +/*06996*/ uint8 unknown04760[36]; /*07032*/ uint32 toxicity; // Potion Toxicity (15=too toxic, each potion adds 3) /*07036*/ uint32 thirst_level; // Drink (ticks till next drink) /*07040*/ uint32 hunger_level; // Food (ticks till next eat) diff --git a/common/patches/sof.cpp b/common/patches/sof.cpp index 77d65ce16..9df6ca1df 100644 --- a/common/patches/sof.cpp +++ b/common/patches/sof.cpp @@ -1276,6 +1276,7 @@ namespace SoF OUT(copper_cursor); OUT_array(skills, structs::MAX_PP_SKILL); // 1:1 direct copy (100 dword) + OUT_array(InnateSkills, structs::MAX_PP_INNATE_SKILL); // 1:1 direct copy (25 dword) // OUT(unknown04760[236]); OUT(toxicity); diff --git a/common/patches/sof_structs.h b/common/patches/sof_structs.h index 00fcd4ecf..9d87bd83a 100644 --- a/common/patches/sof_structs.h +++ b/common/patches/sof_structs.h @@ -52,6 +52,25 @@ struct EnterWorld_Struct { struct WorldObjectsSent_Struct { }; +// yep, even SoF had a version of the new inventory system, used by OP_MoveMultipleItems +struct InventorySlot_Struct +{ +/*000*/ int32 Type; // Worn and Normal inventory = 0, Bank = 1, Shared Bank = 2, Trade = 3, World = 4, Limbo = 5 +/*004*/ int32 Slot; +/*008*/ int32 SubIndex; +/*012*/ int32 AugIndex; +/*016*/ int32 Unknown01; +}; + +// unsure if they have a version of this, completeness though +struct TypelessInventorySlot_Struct +{ +/*000*/ int32 Slot; +/*004*/ int32 SubIndex; +/*008*/ int32 AugIndex; +/*012*/ int32 Unknown01; +}; + /* Name Approval Struct */ /* Len: */ /* Opcode: 0x8B20*/ @@ -799,6 +818,7 @@ static const uint32 MAX_PP_LANGUAGE = 25; // static const uint32 MAX_PP_SPELLBOOK = 480; // Confirmed 60 pages on Live now static const uint32 MAX_PP_MEMSPELL = 10; //was 9 now 10 on Live static const uint32 MAX_PP_SKILL = PACKET_SKILL_ARRAY_SIZE; // 100 - actual skills buffer size +static const uint32 MAX_PP_INNATE_SKILL = 25; static const uint32 MAX_PP_AA_ARRAY = 300; //was 299 static const uint32 MAX_GROUP_MEMBERS = 6; static const uint32 MAX_RECAST_TYPES = 20; @@ -903,7 +923,8 @@ struct PlayerProfile_Struct //23576 Octets /*06488*/ uint32 silver_cursor; // Silver Pieces on cursor /*06492*/ uint32 copper_cursor; // Copper Pieces on cursor /*06496*/ uint32 skills[MAX_PP_SKILL]; // [400] List of skills // 100 dword buffer -/*06896*/ uint8 unknown04760[136]; +/*06896*/ uint32 InnateSkills[MAX_PP_INNATE_SKILL]; +/*06996*/ uint8 unknown04760[36]; /*07032*/ uint32 toxicity; // Potion Toxicity (15=too toxic, each potion adds 3) /*07036*/ uint32 thirst_level; // Drink (ticks till next drink) /*07040*/ uint32 hunger_level; // Food (ticks till next eat) @@ -1555,6 +1576,19 @@ struct MoveItem_Struct /*0012*/ }; +struct MultiMoveItemSub_Struct +{ +/*0000*/ InventorySlot_Struct from_slot; +/*0020*/ uint32 number_in_stack; // so the amount we are moving from the source +/*0024*/ InventorySlot_Struct to_slot; +}; + +struct MultiMoveItem_Struct +{ +/*0000*/ uint32 count; +/*0004*/ MultiMoveItemSub_Struct moves[0]; +}; + // // from_slot/to_slot // -1 - destroy diff --git a/common/patches/titanium.cpp b/common/patches/titanium.cpp index e9679dc06..45f267223 100644 --- a/common/patches/titanium.cpp +++ b/common/patches/titanium.cpp @@ -1020,6 +1020,7 @@ namespace Titanium OUT(copper_cursor); OUT_array(skills, structs::MAX_PP_SKILL); // 1:1 direct copy (100 dword) + OUT_array(InnateSkills, structs::MAX_PP_INNATE_SKILL); // 1:1 direct copy (25 dword) // OUT(unknown04760[236]); OUT(toxicity); diff --git a/common/patches/titanium_structs.h b/common/patches/titanium_structs.h index 69988dd7c..e52b7628c 100644 --- a/common/patches/titanium_structs.h +++ b/common/patches/titanium_structs.h @@ -48,6 +48,23 @@ struct EnterWorld_Struct { /*068*/ uint32 return_home; // 01 on "Return Home", 00 if not }; +// yep, even tit had a version of the new inventory system, used by OP_MoveMultipleItems +struct InventorySlot_Struct +{ +/*000*/ int32 Type; // Worn and Normal inventory = 0, Bank = 1, Shared Bank = 2, Trade = 3, World = 4, Limbo = 5 +/*004*/ int32 Slot; +/*008*/ int32 SubIndex; // no aug index in Tit +/*012*/ int32 Unknown01; +}; + +// unsure if they have a version of this, completeness though +struct TypelessInventorySlot_Struct +{ +/*000*/ int32 Slot; +/*004*/ int32 SubIndex; // no aug index in Tit +/*008*/ int32 Unknown01; +}; + /* Name Approval Struct */ /* Len: */ /* Opcode: 0x8B20*/ @@ -740,6 +757,7 @@ static const uint32 MAX_PP_LANGUAGE = 28; static const uint32 MAX_PP_SPELLBOOK = 400; static const uint32 MAX_PP_MEMSPELL = 9; static const uint32 MAX_PP_SKILL = PACKET_SKILL_ARRAY_SIZE; // 100 - actual skills buffer size +static const uint32 MAX_PP_INNATE_SKILL = 25; static const uint32 MAX_PP_AA_ARRAY = 240; static const uint32 MAX_GROUP_MEMBERS = 6; static const uint32 MAX_RECAST_TYPES = 20; @@ -844,7 +862,8 @@ struct PlayerProfile_Struct /*04452*/ uint32 silver_cursor; // Silver Pieces on cursor /*04456*/ uint32 copper_cursor; // Copper Pieces on cursor /*04460*/ uint32 skills[MAX_PP_SKILL]; // [400] List of skills // 100 dword buffer -/*04860*/ uint8 unknown04760[136]; +/*04860*/ uint32 InnateSkills[MAX_PP_INNATE_SKILL]; +/*04960*/ uint8 unknown04760[36]; /*04996*/ uint32 toxicity; // Potion Toxicity (15=too toxic, each potion adds 3) /*05000*/ uint32 thirst_level; // Drink (ticks till next drink) /*05004*/ uint32 hunger_level; // Food (ticks till next eat) @@ -1327,6 +1346,19 @@ struct MoveItem_Struct /*0012*/ }; +struct MultiMoveItemSub_Struct +{ +/*0000*/ InventorySlot_Struct from_slot; +/*0016*/ uint32 number_in_stack; // so the amount we are moving from the source +/*0020*/ InventorySlot_Struct to_slot; +}; + +struct MultiMoveItem_Struct +{ +/*0000*/ uint32 count; +/*0004*/ MultiMoveItemSub_Struct moves[0]; +}; + // // from_slot/to_slot // -1 - destroy diff --git a/common/patches/uf.cpp b/common/patches/uf.cpp index 8b0c86cc5..cf0c17176 100644 --- a/common/patches/uf.cpp +++ b/common/patches/uf.cpp @@ -391,7 +391,7 @@ namespace UF { SETUP_VAR_ENCODE(BuffIcon_Struct); - uint32 sz = 12 + (17 * emu->count); + uint32 sz = 12 + (17 * emu->count) + emu->name_lengths; // 17 includes nullterm __packet->size = sz; __packet->pBuffer = new unsigned char[sz]; memset(__packet->pBuffer, 0, sz); @@ -407,7 +407,7 @@ namespace UF __packet->WriteUInt32(emu->entries[i].spell_id); __packet->WriteUInt32(emu->entries[i].tics_remaining); __packet->WriteUInt32(emu->entries[i].num_hits); - __packet->WriteString(""); + __packet->WriteString(emu->entries[i].caster); } __packet->WriteUInt8(emu->type); @@ -1850,6 +1850,7 @@ namespace UF OUT(copper_cursor); OUT_array(skills, structs::MAX_PP_SKILL); // 1:1 direct copy (100 dword) + OUT_array(InnateSkills, structs::MAX_PP_INNATE_SKILL); // 1:1 direct copy (25 dword) // OUT(unknown04760[236]); OUT(toxicity); diff --git a/common/patches/uf_structs.h b/common/patches/uf_structs.h index b187f9f82..cc5564e11 100644 --- a/common/patches/uf_structs.h +++ b/common/patches/uf_structs.h @@ -848,6 +848,7 @@ static const uint32 MAX_PP_LANGUAGE = 25; // static const uint32 MAX_PP_SPELLBOOK = 720; // Confirmed 60 pages on Underfoot now static const uint32 MAX_PP_MEMSPELL = 12; //was 9 now 10 on Underfoot static const uint32 MAX_PP_SKILL = PACKET_SKILL_ARRAY_SIZE; // 100 - actual skills buffer size +static const uint32 MAX_PP_INNATE_SKILL = 25; static const uint32 MAX_PP_AA_ARRAY = 300; //was 299 static const uint32 MAX_GROUP_MEMBERS = 6; static const uint32 MAX_RECAST_TYPES = 20; @@ -954,7 +955,8 @@ struct PlayerProfile_Struct /*07336*/ uint32 silver_cursor; // Silver Pieces on cursor /*07340*/ uint32 copper_cursor; // Copper Pieces on cursor /*07344*/ uint32 skills[MAX_PP_SKILL]; // [400] List of skills // 100 dword buffer -/*07744*/ uint8 unknown07644[136]; +/*07744*/ uint32 InnateSkills[MAX_PP_INNATE_SKILL]; +/*07844*/ uint8 unknown07644[36]; /*07880*/ uint32 toxicity; // Potion Toxicity (15=too toxic, each potion adds 3) /*07884*/ uint32 thirst_level; // Drink (ticks till next drink) /*07888*/ uint32 hunger_level; // Food (ticks till next eat) diff --git a/common/ruletypes.h b/common/ruletypes.h index a51a224b0..0d7c45edd 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -44,6 +44,7 @@ RULE_INT(Character, DeathExpLossMaxLevel, 255) // Any level greater than this wi RULE_INT(Character, DeathItemLossLevel, 10) RULE_INT(Character, DeathExpLossMultiplier, 3) //Adjust how much exp is lost RULE_BOOL(Character, UseDeathExpLossMult, false) //Adjust to use the above multiplier or to use code default. +RULE_BOOL(Character, UseOldRaceRezEffects, false) // older clients had ID 757 for races with high starting STR, but it doesn't seem used anymore RULE_INT(Character, CorpseDecayTimeMS, 10800000) RULE_INT(Character, CorpseResTimeMS, 10800000) // time before cant res corpse(3 hours) RULE_BOOL(Character, LeaveCorpses, true) @@ -66,11 +67,12 @@ RULE_INT(Character, AutosaveIntervalS, 300) //0=disabled RULE_INT(Character, HPRegenMultiplier, 100) RULE_INT(Character, ManaRegenMultiplier, 100) RULE_INT(Character, EnduranceRegenMultiplier, 100) +RULE_BOOL(Character, OldMinMana, false) // this is used for servers that want to follow older skill cap formulas so they can still have some regen w/o mediate RULE_INT(Character, ConsumptionMultiplier, 100) //item's hunger restored = this value * item's food level, 100 = normal, 50 = people eat 2x as fast, 200 = people eat 2x as slow RULE_BOOL(Character, HealOnLevel, false) RULE_BOOL(Character, FeignKillsPet, false) RULE_INT(Character, ItemManaRegenCap, 15) -RULE_INT(Character, ItemHealthRegenCap, 35) +RULE_INT(Character, ItemHealthRegenCap, 30) RULE_INT(Character, ItemDamageShieldCap, 30) RULE_INT(Character, ItemAccuracyCap, 150) RULE_INT(Character, ItemAvoidanceCap, 100) @@ -91,10 +93,12 @@ RULE_INT(Character, HasteCap, 100) // Haste cap for non-v3(overhaste) haste. RULE_INT(Character, SkillUpModifier, 100) //skill ups are at 100% RULE_BOOL(Character, SharedBankPlat, false) //off by default to prevent duping for now RULE_BOOL(Character, BindAnywhere, false) -RULE_INT(Character, RestRegenPercent, 0) // Set to >0 to enable rest state bonus HP and mana regen. +RULE_BOOL(Character, RestRegenEnabled, true) // Enable OOC Regen +RULE_INT(Character, RestRegenHP, 180) // seconds until full from 0. this is actually zone setable, but most or all zones are 180 +RULE_INT(Character, RestRegenMana, 180) // seconds until full from 0. this is actually zone setable, but most or all zones are 180 +RULE_INT(Character, RestRegenEnd, 180) // seconds until full from 0. this is actually zone setable, but most or all zones are 180 RULE_INT(Character, RestRegenTimeToActivate, 30) // Time in seconds for rest state regen to kick in. RULE_INT(Character, RestRegenRaidTimeToActivate, 300) // Time in seconds for rest state regen to kick in with a raid target. -RULE_BOOL(Character, RestRegenEndurance, false) // Whether rest regen will work for endurance or not. RULE_INT(Character, KillsPerGroupLeadershipAA, 250) // Number of dark blues or above per Group Leadership AA RULE_INT(Character, KillsPerRaidLeadershipAA, 250) // Number of dark blues or above per Raid Leadership AA RULE_INT(Character, MaxFearDurationForPlayerCharacter, 4) //4 tics, each tic calculates every 6 seconds. @@ -117,7 +121,8 @@ RULE_BOOL(Character, EnableDiscoveredItems, true) // If enabled, it enables EVEN RULE_BOOL(Character, EnableXTargetting, true) // Enable Extended Targetting Window, for users with UF and later clients. RULE_BOOL(Character, EnableAggroMeter, true) // Enable Aggro Meter, for users with RoF and later clients. RULE_BOOL(Character, KeepLevelOverMax, false) // Don't delevel a character that has somehow gone over the level cap -RULE_INT(Character, FoodLossPerUpdate, 35) // How much food/water you lose per stamina update +RULE_INT(Character, FoodLossPerUpdate, 32) // How much food/water you lose per stamina update +RULE_BOOL(Character, EnableHungerPenalties, false) // being hungry/thirsty has negative effects -- it does appear normal live servers do not have penalties RULE_INT(Character, BaseInstrumentSoftCap, 36) // Softcap for instrument mods, 36 commonly referred to as "3.6" as well. RULE_BOOL(Character, UseSpellFileSongCap, true) // When they removed the AA that increased the cap they removed the above and just use the spell field RULE_INT(Character, BaseRunSpeedCap, 158) // Base Run Speed Cap, on live it's 158% which will give you a runspeed of 1.580 hard capped to 225. @@ -186,12 +191,14 @@ RULE_INT(Skills, MaxTrainSpecializations, 50) // Max level a GM trainer will tra RULE_INT(Skills, SwimmingStartValue, 100) RULE_BOOL(Skills, TrainSenseHeading, false) RULE_INT(Skills, SenseHeadingStartValue, 200) +RULE_BOOL(Skills, SelfLanguageLearning, true) RULE_CATEGORY_END() RULE_CATEGORY(Pets) RULE_REAL(Pets, AttackCommandRange, 150) RULE_BOOL(Pets, UnTargetableSwarmPet, false) RULE_REAL(Pets, PetPowerLevelCap, 10) // Max number of levels your pet can go up with pet power +RULE_BOOL(Pets, CanTakeNoDrop, false) // Can everyone trade nodrop gear to pets RULE_CATEGORY_END() RULE_CATEGORY(GM) @@ -502,6 +509,7 @@ RULE_INT(Combat, NPCAssistCapTimer, 6000) // Time in milliseconds a NPC will tak RULE_BOOL(Combat, UseRevampHandToHand, false) // use h2h revamped dmg/delays I believe this was implemented during SoF RULE_BOOL(Combat, ClassicMasterWu, false) // classic master wu uses a random special, modern doesn't RULE_INT(Combat, LevelToStopDamageCaps, 0) // 1 will effectively disable them, 20 should give basically same results as old incorrect system +RULE_BOOL(Combat, ClassicNPCBackstab, false) // true disables npc facestab - npcs get normal attack if not behind RULE_CATEGORY_END() RULE_CATEGORY(NPC) @@ -563,6 +571,8 @@ RULE_INT(Range, DamageMessages, 50) RULE_INT(Range, SpellMessages, 75) RULE_INT(Range, SongMessages, 75) RULE_INT(Range, MobPositionUpdates, 600) +RULE_INT(Range, ClientPositionUpdates, 300) +RULE_INT(Range, ClientForceSpawnUpdateRange, 1000) RULE_INT(Range, CriticalDamage, 80) RULE_INT(Range, ClientNPCScan, 300) RULE_CATEGORY_END() diff --git a/common/shareddb.cpp b/common/shareddb.cpp index 331826ef2..09c936425 100644 --- a/common/shareddb.cpp +++ b/common/shareddb.cpp @@ -1817,8 +1817,8 @@ void SharedDatabase::LoadBaseData(void *data, int max_level) { bd->base_hp = atof(row[2]); bd->base_mana = atof(row[3]); bd->base_end = atof(row[4]); - bd->unk1 = atof(row[5]); - bd->unk2 = atof(row[6]); + bd->hp_regen = atof(row[5]); + bd->end_regen = atof(row[6]); bd->hp_factor = atof(row[7]); bd->mana_factor = atof(row[8]); bd->endurance_factor = atof(row[9]); diff --git a/common/spdat.h b/common/spdat.h index 605e9a6cf..0f9e7a47a 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -131,6 +131,11 @@ enum SpellAffectIndex { SAI_NPC_Special_80 = 80, SAI_Trap_Lock = 88 }; + +enum class GlobalGroup { + Lich = 46, +}; + enum RESISTTYPE { RESIST_NONE = 0, diff --git a/common/version.h b/common/version.h index 52af87872..108ce784b 100644 --- a/common/version.h +++ b/common/version.h @@ -30,7 +30,7 @@ Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt */ -#define CURRENT_BINARY_DATABASE_VERSION 9114 +#define CURRENT_BINARY_DATABASE_VERSION 9115 #ifdef BOTS #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9017 #else diff --git a/libs/format/fmt/format.h b/libs/format/fmt/format.h index 47a8b74d8..9c2827d46 100644 --- a/libs/format/fmt/format.h +++ b/libs/format/fmt/format.h @@ -1719,6 +1719,8 @@ FMT_DEFINE_INT_FORMATTERS(unsigned long) FMT_DEFINE_INT_FORMATTERS(LongLong) FMT_DEFINE_INT_FORMATTERS(ULongLong) +#define CHAR_WIDTH 1 + /** \rst Returns a string formatter that pads the formatted argument with the fill @@ -1823,7 +1825,7 @@ class ArgFormatterBase : public ArgVisitor { typedef typename BasicWriter::CharPtr CharPtr; Char fill = internal::CharTraits::cast(spec_.fill()); CharPtr out = CharPtr(); - const unsigned CHAR_WIDTH = 1; + if (spec_.width_ > CHAR_WIDTH) { out = writer_.grow_buffer(spec_.width_); if (spec_.align_ == ALIGN_RIGHT) { diff --git a/utils/patches/patch_RoF.conf b/utils/patches/patch_RoF.conf index 339bfd800..074d5751d 100644 --- a/utils/patches/patch_RoF.conf +++ b/utils/patches/patch_RoF.conf @@ -248,6 +248,7 @@ OP_AutoAttack=0x0d14 OP_AutoAttack2=0x3912 OP_Consume=0x4692 OP_MoveItem=0x62a2 +OP_MoveMultipleItems=0x55ef OP_DeleteItem=0x3eb5 OP_DeleteCharge=0x2d5b OP_ItemPacket=0x5e0e @@ -341,6 +342,7 @@ OP_MobUpdate=0x6b5a OP_NPCMoveUpdate=0x5bd9 OP_CameraEffect=0x5712 OP_SpellEffect=0x72b6 +OP_AddNimbusEffect=0x2954 OP_RemoveNimbusEffect=0x3ba7 OP_AltCurrency=0x8fcb OP_AltCurrencyMerchantRequest=0x7e3e diff --git a/utils/patches/patch_RoF2.conf b/utils/patches/patch_RoF2.conf index e63e5e9e9..dd58b3c11 100644 --- a/utils/patches/patch_RoF2.conf +++ b/utils/patches/patch_RoF2.conf @@ -247,6 +247,7 @@ OP_AutoAttack=0x109d OP_AutoAttack2=0x3526 OP_Consume=0x4b70 OP_MoveItem=0x32ee +OP_MoveMultipleItems=0x5623 OP_DeleteItem=0x18ad OP_DeleteCharge=0x01b8 OP_ItemPacket=0x368e @@ -340,6 +341,7 @@ OP_MobUpdate=0x2c84 OP_NPCMoveUpdate=0x5892 OP_CameraEffect=0x127f OP_SpellEffect=0x5936 +OP_AddNimbusEffect=0xc693 OP_RemoveNimbusEffect=0x7b1e OP_AltCurrency=0x6b6d OP_AltCurrencyMerchantRequest=0x5409 diff --git a/utils/patches/patch_SoD.conf b/utils/patches/patch_SoD.conf index ed7a19715..5913670ae 100644 --- a/utils/patches/patch_SoD.conf +++ b/utils/patches/patch_SoD.conf @@ -241,6 +241,7 @@ OP_AutoAttack=0x3d86 # C OP_AutoAttack2=0x4ca1 # C OP_Consume=0x7ce4 # C OP_MoveItem=0x7f56 # C +OP_MoveMultipleItems=0x4572 OP_DeleteItem=0x36f8 # C OP_DeleteCharge=0x1df9 # C OP_ItemPacket=0x34f8 # C @@ -345,6 +346,7 @@ OP_AltCurrencySell=0x7a21 OP_AltCurrencySellSelection=0x26d9 OP_AltCurrencyReclaim=0x712c OP_ShroudProgress=0x0296 +OP_AddNimbusEffect=0x6840 OP_RemoveNimbusEffect=0x5272 # C OP_Untargetable=0x5ea1 # 0x301d on UF? OP_IncreaseStats=0x71eb diff --git a/utils/patches/patch_SoF.conf b/utils/patches/patch_SoF.conf index 7ed41ff4a..6360617a2 100644 --- a/utils/patches/patch_SoF.conf +++ b/utils/patches/patch_SoF.conf @@ -237,6 +237,7 @@ OP_AutoAttack=0x3427 #Trevius 01/20/09 OP_AutoAttack2=0x6017 #Trevius 01/20/09 OP_Consume=0x729a #Trevius 02/08/09 OP_MoveItem=0x14B3 #Trevius 02/08/09 +OP_MoveMultipleItems=0x2d3e OP_DeleteItem=0x7DD4 #Xinu 03/08/09 0x41EE 0x018E 0x070C OP_DeleteCharge=0x32e2 #Trevius 03/23/09 OP_ItemPacket=0x78Cd #Trevius 02/08/09 @@ -329,6 +330,7 @@ OP_AltCurrencyPurchase=0x3994 OP_AltCurrencySell=0x2ac3 OP_AltCurrencySellSelection=0x7d00 OP_AltCurrencyReclaim=0x1996 +OP_AddNimbusEffect=0x45e2 OP_RemoveNimbusEffect=0x5872 # C OP_InspectMessageUpdate=0x67e9 # C OP_OpenInventory=0x66c8 diff --git a/utils/patches/patch_Titanium.conf b/utils/patches/patch_Titanium.conf index 23f07109f..27aa18376 100644 --- a/utils/patches/patch_Titanium.conf +++ b/utils/patches/patch_Titanium.conf @@ -198,6 +198,7 @@ OP_Split=0x4848 # ShowEQ 10/27/05 OP_Surname=0x4668 # ShowEQ 10/27/05 OP_ClearSurname=0x6cdb OP_MoveItem=0x420f # ShowEQ 10/27/05 +OP_MoveMultipleItems=0x463b OP_FaceChange=0x0f8e # ShowEQ 10/27/05 OP_ItemPacket=0x3397 # ShowEQ 10/27/05 OP_ItemLinkResponse=0x667c # ShowEQ 10/27/05 diff --git a/utils/patches/patch_UF.conf b/utils/patches/patch_UF.conf index af698d52a..a96b4208a 100644 --- a/utils/patches/patch_UF.conf +++ b/utils/patches/patch_UF.conf @@ -251,6 +251,7 @@ OP_AutoAttack=0x1df9 # C OP_AutoAttack2=0x517b # C OP_Consume=0x24c5 # V OP_MoveItem=0x2641 # C +OP_MoveMultipleItems=0x40e8 OP_DeleteItem=0x66e0 # C OP_DeleteCharge=0x4ca1 # C OP_ItemPacket=0x7b6e # C @@ -346,6 +347,7 @@ OP_MobUpdate=0x4656 # Same as OP_SpawnPositionUpdate OP_NPCMoveUpdate=0x0f3e # OP_CameraEffect=0x6b0e # V OP_SpellEffect=0x57a3 # V +OP_AddNimbusEffect=0x6361 OP_RemoveNimbusEffect=0x2c77 # C OP_AltCurrency=0x659e OP_AltCurrencyMerchantRequest=0x214C diff --git a/utils/sql/db_update_manifest.txt b/utils/sql/db_update_manifest.txt index faccf9e48..11dd1b315 100644 --- a/utils/sql/db_update_manifest.txt +++ b/utils/sql/db_update_manifest.txt @@ -357,7 +357,7 @@ 9101|2016_12_01_pcnpc_only.sql|SHOW COLUMNS FROM `spells_new` LIKE 'pcnpc_only_flag'|empty| 9102|2017_01_10_book_languages.sql|SHOW COLUMNS FROM `books` LIKE 'language'|empty| 9103|2017_01_30_book_languages_fix.sql|SELECT `language` from `books` WHERE `language` IS NULL|not_empty| -9104|2017_02_09_npc_spells_entries_type_update.sql|SHOW COLUMNS IN `npc_spells_entries` LIKE `type`|contains|smallint(5) unsigned +9104|2017_02_09_npc_spells_entries_type_update.sql|SHOW COLUMNS IN `npc_spells_entries` LIKE 'type'|contains|smallint(5) unsigned 9105|2017_02_15_bot_spells_entries.sql|SELECT `id` FROM `npc_spells_entries` WHERE `npc_spells_id` >= 701 AND `npc_spells_id` <= 712|not_empty| 9106|2017_02_26_npc_spells_update_for_bots.sql|SELECT * FROM `npc_spells` WHERE `id` = '701' AND `name` = 'Cleric Bot'|not_empty| 9107|2017_03_09_inventory_version.sql|SHOW TABLES LIKE 'inventory_version'|empty| @@ -368,6 +368,8 @@ 9112|2017_06_24_rule_values_expand.sql|SHOW COLUMNS FROM rule_values WHERE Field = 'rule_value' and Type = 'varchar(30)'|empty| 9113|2017_07_19_show_name.sql|SHOW COLUMNS FROM `npc_types` LIKE 'show_name'|empty| 9114|2017_07_22_aura.sql|SHOW TABLES LIKE 'auras'|empty| +9115|2017_10_28_traps.sql|SHOW COLUMNS FROM `traps` LIKE 'triggered_number'|empty| +9116|2017_12_16_GroundSpawn_Respawn_Timer.sql|SHOW COLUMNS FROM `ground_spawns` WHERE Field = 'respawn_timer' AND Type = 'int(11) unsigned'|empty| # Upgrade conditions: # This won't be needed after this system is implemented, but it is used database that are not diff --git a/utils/sql/git/optional/2017_10_07_new_default_rule_values.sql b/utils/sql/git/optional/2017_10_07_new_default_rule_values.sql new file mode 100644 index 000000000..47ebcf913 --- /dev/null +++ b/utils/sql/git/optional/2017_10_07_new_default_rule_values.sql @@ -0,0 +1,3 @@ +UPDATE `rule_values` SET `rule_value` = '32' WHERE `rule_name` = 'Character:FoodLossPerUpdate'; +UPDATE `rule_values` SET `rule_value` = '30' WHERE `rule_name` = 'Character:ItemHealthRegenCap'; + diff --git a/utils/sql/git/required/2017_10_28_traps.sql b/utils/sql/git/required/2017_10_28_traps.sql new file mode 100644 index 000000000..dddc00d1b --- /dev/null +++ b/utils/sql/git/required/2017_10_28_traps.sql @@ -0,0 +1,4 @@ +alter table `traps` add column `triggered_number` tinyint(4) not null default 0; +alter table `traps` add column `group` tinyint(4) not null default 0; +alter table `traps` add column `despawn_when_triggered` tinyint(4) not null default 0; +alter table `traps` add column `undetectable` tinyint(4) not null default 0; diff --git a/utils/sql/git/required/2017_12_16_GroundSpawn_Respawn_Timer.sql b/utils/sql/git/required/2017_12_16_GroundSpawn_Respawn_Timer.sql new file mode 100644 index 000000000..f7c8b6a31 --- /dev/null +++ b/utils/sql/git/required/2017_12_16_GroundSpawn_Respawn_Timer.sql @@ -0,0 +1,2 @@ +ALTER TABLE `ground_spawns` MODIFY `respawn_timer` int(11) unsigned NOT NULL default 300; +UPDATE `ground_spawns` SET `respawn_timer` = `respawn_timer` / 1000; diff --git a/world/adventure.cpp b/world/adventure.cpp index cbea9079c..c984fa831 100644 --- a/world/adventure.cpp +++ b/world/adventure.cpp @@ -380,7 +380,7 @@ void Adventure::MoveCorpsesToGraveyard() std::list dbid_list; std::list charid_list; - std::string query = StringFormat("SELECT id, charid FROM character_corpses WHERE instanceid=%d", GetInstanceID()); + std::string query = StringFormat("SELECT id, charid FROM character_corpses WHERE instance_id=%d", GetInstanceID()); auto results = database.QueryDatabase(query); if(!results.Success()) @@ -395,8 +395,8 @@ void Adventure::MoveCorpsesToGraveyard() float z = GetTemplate()->graveyard_z; query = StringFormat("UPDATE character_corpses " - "SET zoneid = %d, instanceid = 0, " - "x = %f, y = %f, z = %f WHERE instanceid = %d", + "SET zone_id = %d, instance_id = 0, " + "x = %f, y = %f, z = %f WHERE instance_id = %d", GetTemplate()->graveyard_zone_id, x, y, z, GetInstanceID()); database.QueryDatabase(query); diff --git a/world/net.cpp b/world/net.cpp index 4f9b7b64b..367f3e5f8 100644 --- a/world/net.cpp +++ b/world/net.cpp @@ -23,6 +23,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include #include +#include "../common/string_util.h" #include "../common/eqemu_logsys.h" #include "../common/queue.h" #include "../common/timer.h" @@ -105,6 +106,12 @@ WebInterfaceList web_interface; void CatchSignal(int sig_num); void CheckForServerScript(bool force_download = false); +inline void UpdateWindowTitle(std::string new_title) { +#ifdef _WINDOWS + SetConsoleTitle(new_title.c_str()); +#endif +} + int main(int argc, char** argv) { RegisterExecutablePlatform(ExePlatformWorld); LogSys.LoadLogSettingsDefaults(); @@ -539,8 +546,7 @@ int main(int argc, char** argv) { database.PurgeExpiredInstances(); } - if (EQTimeTimer.Check()) - { + if (EQTimeTimer.Check()) { TimeOfDay_Struct tod; zoneserver_list.worldclock.GetCurrentEQTimeOfDay(time(0), &tod); if (!database.SaveTime(tod.minute, tod.hour, tod.day, tod.month, tod.year)) @@ -557,6 +563,9 @@ int main(int argc, char** argv) { if (InterserverTimer.Check()) { InterserverTimer.Start(); database.ping(); + + std::string window_title = StringFormat("World: %s Clients: %i", Config->LongName.c_str(), client_list.GetClientCount()); + UpdateWindowTitle(window_title); } EQ::EventLoop::Get().Process(); diff --git a/world/net.h b/world/net.h index 5af1ef96a..06e918a5c 100644 --- a/world/net.h +++ b/world/net.h @@ -31,7 +31,6 @@ #endif void CatchSignal(int sig_num); -void UpdateWindowTitle(char* iNewTitle); #define EQ_WORLD_PORT 9000 //mandated by the client #define LOGIN_PORT 5997 diff --git a/zone/aggro.cpp b/zone/aggro.cpp index 06c78c999..340493578 100644 --- a/zone/aggro.cpp +++ b/zone/aggro.cpp @@ -1274,6 +1274,11 @@ void Mob::ClearFeignMemory() { AI_feign_remember_timer->Disable(); } +bool Mob::IsOnFeignMemory(Client *attacker) const +{ + return feign_memory_list.find(attacker->CharacterID()) != feign_memory_list.end(); +} + bool Mob::PassCharismaCheck(Mob* caster, uint16 spell_id) { /* diff --git a/zone/attack.cpp b/zone/attack.cpp index 4b480f580..f9edb46ac 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -852,16 +852,56 @@ int Mob::ACSum() return ac; } +int Mob::GetBestMeleeSkill() + { + int bestSkill=0; + EQEmu::skills::SkillType meleeSkills[]= + { EQEmu::skills::Skill1HBlunt, + EQEmu::skills::Skill1HSlashing, + EQEmu::skills::Skill2HBlunt, + EQEmu::skills::Skill2HSlashing, + EQEmu::skills::SkillHandtoHand, + EQEmu::skills::Skill1HPiercing, + EQEmu::skills::Skill2HPiercing, + EQEmu::skills::SkillCount + }; + int i; + + for (i=0; meleeSkills[i] != EQEmu::skills::SkillCount; ++i) { + int value; + value = GetSkill(meleeSkills[i]); + bestSkill = std::max(value, bestSkill); + } + + return bestSkill; + } + int Mob::offense(EQEmu::skills::SkillType skill) { int offense = GetSkill(skill); - int stat_bonus = 0; - if (skill == EQEmu::skills::SkillArchery || skill == EQEmu::skills::SkillThrowing) - stat_bonus = GetDEX(); - else - stat_bonus = GetSTR(); + int stat_bonus = GetSTR(); + + switch (skill) { + case EQEmu::skills::SkillArchery: + case EQEmu::skills::SkillThrowing: + stat_bonus = GetDEX(); + break; + + // Mobs with no weapons default to H2H. + // Since H2H is capped at 100 for many many classes, + // lets not handicap mobs based on not spawning with a + // weapon. + // + // Maybe we tweak this if Disarm is actually implemented. + + case EQEmu::skills::SkillHandtoHand: + offense = GetBestMeleeSkill(); + break; + } + if (stat_bonus >= 75) offense += (2 * stat_bonus - 150) / 3; + offense += GetATK(); return offense; } @@ -1262,6 +1302,7 @@ int Client::DoDamageCaps(int base_damage) return std::min(cap, base_damage); } +// other is the defender, this is the attacker void Mob::DoAttack(Mob *other, DamageHitInfo &hit, ExtraAttackOptions *opts) { if (!other) @@ -1288,6 +1329,20 @@ void Mob::DoAttack(Mob *other, DamageHitInfo &hit, ExtraAttackOptions *opts) if (hit.damage_done >= 0) { if (other->CheckHitChance(this, hit)) { + if (IsNPC() && other->IsClient() && other->animation > 0 && GetLevel() >= 5 && BehindMob(other, GetX(), GetY())) { + // ~ 12% chance + if (zone->random.Roll(12)) { + int stun_resist2 = other->spellbonuses.FrontalStunResist + other->itembonuses.FrontalStunResist + other->aabonuses.FrontalStunResist; + int stun_resist = other->spellbonuses.StunResist + other->itembonuses.StunResist + other->aabonuses.StunResist; + if (zone->random.Roll(stun_resist2)) { + other->Message_StringID(MT_Stun, AVOID_STUNNING_BLOW); + } else if (zone->random.Roll(stun_resist)) { + other->Message_StringID(MT_Stun, SHAKE_OFF_STUN); + } else { + other->Stun(3000); // yuck -- 3 seconds + } + } + } other->MeleeMitigation(this, hit, opts); if (hit.damage_done > 0) { ApplyDamageTable(hit); @@ -1673,6 +1728,15 @@ bool Client::Death(Mob* killerMob, int32 damage, uint16 spell, EQEmu::skills::Sk if (!RuleB(Character, UseDeathExpLossMult)) { exploss = (int)(GetLevel() * (GetLevel() / 18.0) * 12000); } + + if (RuleB(Zone, LevelBasedEXPMods)) { + // Death in levels with xp_mod (such as hell levels) was resulting + // in losing more that appropriate since the loss was the same but + // getting it back would take way longer. This makes the death the + // same amount of time to recover. Will also lose more if level is + // granting a bonus. + exploss *= zone->level_exp_mod[GetLevel()].ExpMod; + } if ((GetLevel() < RuleI(Character, DeathExpLossLevel)) || (GetLevel() > RuleI(Character, DeathExpLossMaxLevel)) || IsBecomeNPC()) { @@ -2518,6 +2582,9 @@ void Mob::AddToHateList(Mob* other, uint32 hate /*= 0*/, int32 damage /*= 0*/, b if (other == this) return; + if (other->IsTrap()) + return; + if (damage < 0) { hate = 1; } @@ -2617,7 +2684,7 @@ void Mob::AddToHateList(Mob* other, uint32 hate /*= 0*/, int32 damage /*= 0*/, b hate_list.AddEntToHateList(other, hate, damage, bFrenzy, !iBuffTic); - if (other->IsClient() && !on_hatelist) + if (other->IsClient() && !on_hatelist && !IsOnFeignMemory(other->CastToClient())) other->CastToClient()->AddAutoXTarget(this); #ifdef BOTS @@ -2658,9 +2725,9 @@ void Mob::AddToHateList(Mob* other, uint32 hate /*= 0*/, int32 damage /*= 0*/, b // owner must get on list, but he's not actually gained any hate yet if (!owner->GetSpecialAbility(IMMUNE_AGGRO)) { - hate_list.AddEntToHateList(owner, 0, 0, false, !iBuffTic); if (owner->IsClient() && !CheckAggro(owner)) owner->CastToClient()->AddAutoXTarget(this); + hate_list.AddEntToHateList(owner, 0, 0, false, !iBuffTic); } } } @@ -3349,7 +3416,7 @@ void Mob::CommonDamage(Mob* attacker, int &damage, const uint16 spell_id, const // 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 && !pet->IsFamiliar() && !pet->GetSpecialAbility(IMMUNE_AGGRO) && !pet->IsEngaged() && attacker && attacker != this && !attacker->IsCorpse() && !pet->IsGHeld() && !attacker->IsTrap()) { if (!pet->IsHeld()) { Log(Logs::Detail, Logs::Aggro, "Sending pet %s into battle due to attack.", pet->GetName()); @@ -3907,10 +3974,10 @@ void Mob::TryWeaponProc(const EQEmu::ItemInstance *inst, const EQEmu::ItemData * float WPC = ProcChance * (100.0f + // Proc chance for this weapon static_cast(weapon->ProcRate)) / 100.0f; if (zone->random.Roll(WPC)) { // 255 dex = 0.084 chance of proc. No idea what this number should be really. - if (weapon->Proc.Level > ourlevel) { + if (weapon->Proc.Level2 > ourlevel) { Log(Logs::Detail, Logs::Combat, "Tried to proc (%s), but our level (%d) is lower than required (%d)", - weapon->Name, ourlevel, weapon->Proc.Level); + weapon->Name, ourlevel, weapon->Proc.Level2); if (IsPet()) { Mob *own = GetOwner(); if (own) @@ -3947,7 +4014,7 @@ void Mob::TryWeaponProc(const EQEmu::ItemInstance *inst, const EQEmu::ItemData * float APC = ProcChance * (100.0f + // Proc chance for this aug static_cast(aug->ProcRate)) / 100.0f; if (zone->random.Roll(APC)) { - if (aug->Proc.Level > ourlevel) { + if (aug->Proc.Level2 > ourlevel) { if (IsPet()) { Mob *own = GetOwner(); if (own) @@ -5232,19 +5299,30 @@ void Client::DoAttackRounds(Mob *target, int hand, bool IsFromSpell) // extra off hand non-sense, can only double with skill of 150 or above // or you have any amount of GiveDoubleAttack if (candouble && hand == EQEmu::inventory::slotSecondary) - candouble = GetSkill(EQEmu::skills::SkillDoubleAttack) > 149 || (aabonuses.GiveDoubleAttack + spellbonuses.GiveDoubleAttack + itembonuses.GiveDoubleAttack) > 0; + candouble = + GetSkill(EQEmu::skills::SkillDoubleAttack) > 149 || + (aabonuses.GiveDoubleAttack + spellbonuses.GiveDoubleAttack + itembonuses.GiveDoubleAttack) > 0; if (candouble) { CheckIncreaseSkill(EQEmu::skills::SkillDoubleAttack, target, -10); if (CheckDoubleAttack()) { Attack(target, hand, false, false, IsFromSpell); + + // Modern AA description: Increases your chance of ... performing one additional hit with a 2-handed weapon when double attacking by 2%. + if (hand == EQEmu::inventory::slotPrimary) { + auto extraattackchance = aabonuses.ExtraAttackChance + spellbonuses.ExtraAttackChance + + itembonuses.ExtraAttackChance; + if (extraattackchance && HasTwoHanderEquipped() && zone->random.Roll(extraattackchance)) + Attack(target, hand, false, false, IsFromSpell); + } + // you can only triple from the main hand if (hand == EQEmu::inventory::slotPrimary && CanThisClassTripleAttack()) { CheckIncreaseSkill(EQEmu::skills::SkillTripleAttack, target, -10); if (CheckTripleAttack()) { Attack(target, hand, false, false, IsFromSpell); auto flurrychance = aabonuses.FlurryChance + spellbonuses.FlurryChance + - itembonuses.FlurryChance; + itembonuses.FlurryChance; if (flurrychance && zone->random.Roll(flurrychance)) { Attack(target, hand, false, false, IsFromSpell); if (zone->random.Roll(flurrychance)) @@ -5255,12 +5333,6 @@ void Client::DoAttackRounds(Mob *target, int hand, bool IsFromSpell) } } } - - if (hand == EQEmu::inventory::slotPrimary) { - auto extraattackchance = aabonuses.ExtraAttackChance + spellbonuses.ExtraAttackChance + itembonuses.ExtraAttackChance; - if (extraattackchance && HasTwoHanderEquipped() && zone->random.Roll(extraattackchance)) - Attack(target, hand, false, false, IsFromSpell); - } } bool Mob::CheckDualWield() diff --git a/zone/bonuses.cpp b/zone/bonuses.cpp index 4406508fe..c444ff9dd 100644 --- a/zone/bonuses.cpp +++ b/zone/bonuses.cpp @@ -120,6 +120,13 @@ void Client::CalcBonuses() if (GetMaxXTargets() != 5 + aabonuses.extra_xtargets) SetMaxXTargets(5 + aabonuses.extra_xtargets); + + // hmm maybe a better way to do this + int metabolism = spellbonuses.Metabolism + itembonuses.Metabolism + aabonuses.Metabolism; + int timer = GetClass() == MONK ? CONSUMPTION_MNK_TIMER : CONSUMPTION_TIMER; + timer = timer * (100 + metabolism) / 100; + if (timer != consume_food_timer.GetTimerTime()) + consume_food_timer.SetTimer(timer); } int Client::CalcRecommendedLevelBonus(uint8 level, uint8 reclevel, int basestat) @@ -908,7 +915,7 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) newbon->GivePetGroupTarget = true; break; case SE_ItemHPRegenCapIncrease: - newbon->ItemHPRegenCap = +base1; + newbon->ItemHPRegenCap += base1; break; case SE_Ambidexterity: newbon->Ambidexterity += base1; @@ -1003,6 +1010,15 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) case SE_RiposteChance: newbon->RiposteChance += base1; break; + case SE_DodgeChance: + newbon->DodgeChance += base1; + break; + case SE_ParryChance: + newbon->ParryChance += base1; + break; + case SE_IncreaseBlockChance: + newbon->IncreaseBlockChance += base1; + break; case SE_Flurry: newbon->FlurryChance += base1; break; @@ -2494,8 +2510,17 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne new_bonus->MagicWeapon = true; break; + case SE_Hunger: + new_bonus->hunger = true; + break; + case SE_IncreaseBlockChance: - new_bonus->IncreaseBlockChance += effect_value; + if (AdditiveWornBonus) + new_bonus->IncreaseBlockChance += effect_value; + else if (effect_value < 0 && new_bonus->IncreaseBlockChance > effect_value) + new_bonus->IncreaseBlockChance = effect_value; + else if (new_bonus->IncreaseBlockChance < effect_value) + new_bonus->IncreaseBlockChance = effect_value; break; case SE_PersistantCasting: diff --git a/zone/bot.cpp b/zone/bot.cpp index c78c41d62..6a1521d6b 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -2841,6 +2841,8 @@ void Bot::Spawn(Client* botCharacterOwner) { FaceTarget(botCharacterOwner); UpdateEquipmentLight(); UpdateActiveLight(); + + this->m_targetable = true; entity_list.AddBot(this, true, true); // Load pet LoadPet(); @@ -6725,7 +6727,7 @@ int32 Bot::CalcATK() { } void Bot::CalcRestState() { - if(!RuleI(Character, RestRegenPercent)) + if(!RuleB(Character, RestRegenEnabled)) return; RestRegenHP = RestRegenMana = RestRegenEndurance = 0; @@ -6741,10 +6743,9 @@ void Bot::CalcRestState() { } } - RestRegenHP = (GetMaxHP() * RuleI(Character, RestRegenPercent) / 100); - RestRegenMana = (GetMaxMana() * RuleI(Character, RestRegenPercent) / 100); - if(RuleB(Character, RestRegenEndurance)) - RestRegenEndurance = (GetMaxEndurance() * RuleI(Character, RestRegenPercent) / 100); + RestRegenHP = 6 * (GetMaxHP() / RuleI(Character, RestRegenHP)); + RestRegenMana = 6 * (GetMaxMana() / RuleI(Character, RestRegenMana)); + RestRegenEndurance = 6 * (GetMaxEndurance() / RuleI(Character, RestRegenEnd)); } int32 Bot::LevelRegen() { diff --git a/zone/client.cpp b/zone/client.cpp index 4866013a3..319752e36 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -123,7 +123,7 @@ Client::Client(EQStreamInterface* ieqs) hpupdate_timer(2000), camp_timer(29000), process_timer(100), - stamina_timer(40000), + consume_food_timer(CONSUMPTION_TIMER), zoneinpacket_timer(1000), linkdead_timer(RuleI(Zone,ClientLinkdeadMS)), dead_timer(2000), @@ -160,7 +160,8 @@ Client::Client(EQStreamInterface* ieqs) npc_close_scan_timer(6000), hp_self_update_throttle_timer(300), hp_other_update_throttle_timer(500), - position_update_timer(10000) + position_update_timer(10000), + tmSitting(0) { for (int client_filter = 0; client_filter < _FilterCount; client_filter++) @@ -270,9 +271,10 @@ Client::Client(EQStreamInterface* ieqs) m_ClientVersion = EQEmu::versions::ClientVersion::Unknown; m_ClientVersionBit = 0; AggroCount = 0; - RestRegenHP = 0; - RestRegenMana = 0; - RestRegenEndurance = 0; + ooc_regen = false; + AreaHPRegen = 1.0f; + AreaManaRegen = 1.0f; + AreaEndRegen = 1.0f; XPRate = 100; current_endurance = 0; @@ -329,6 +331,11 @@ Client::Client(EQStreamInterface* ieqs) interrogateinv_flag = false; + trapid = 0; + + for (int i = 0; i < InnateSkillMax; ++i) + m_pp.InnateSkills[i] = InnateDisabled; + AI_Init(); } @@ -658,13 +665,18 @@ bool Client::Save(uint8 iCommitNow) { m_pp.tribute_time_remaining = 0xFFFFFFFF; m_pp.tribute_active = 0; } + if (m_pp.hunger_level < 0) + m_pp.hunger_level = 0; + + if (m_pp.thirst_level < 0) + m_pp.thirst_level = 0; + p_timers.Store(&database); database.SaveCharacterTribute(this->CharacterID(), &m_pp); SaveTaskState(); /* Save Character Task */ - m_pp.hunger_level = EQEmu::Clamp(m_pp.hunger_level, 0, 50000); - m_pp.thirst_level = EQEmu::Clamp(m_pp.thirst_level, 0, 50000); + Log(Logs::General, Logs::Food, "Client::Save - hunger_level: %i thirst_level: %i", m_pp.hunger_level, m_pp.thirst_level); // perform snapshot before SaveCharacterData() so that m_epp will contain the updated time if (RuleB(Character, ActiveInvSnapshots) && time(nullptr) >= GetNextInvSnapshotTime()) { @@ -1211,18 +1223,18 @@ void Client::ChannelMessageSend(const char* from, const char* to, uint8 chan_num EffSkill = 100; cm->skill_in_language = EffSkill; - // Garble the message based on listener skill - if (ListenerSkill < 100) { - GarbleMessage(buffer, (100 - ListenerSkill)); - } - cm->chan_num = chan_num; strcpy(&cm->message[0], buffer); QueuePacket(&app); - if ((chan_num == 2) && (ListenerSkill < 100)) { // group message in unmastered language, check for skill up - if (m_pp.languages[language] <= lang_skill) - CheckLanguageSkillIncrease(language, lang_skill); + bool senderCanTrainSelf = RuleB(Client, SelfLanguageLearning); + bool weAreNotSender = strcmp(this->GetCleanName(), cm->sender); + + if (senderCanTrainSelf || weAreNotSender) { + if ((chan_num == 2) && (ListenerSkill < 100)) { // group message in unmastered language, check for skill up + if (m_pp.languages[language] <= lang_skill) + CheckLanguageSkillIncrease(language, lang_skill); + } } } @@ -4588,7 +4600,7 @@ void Client::IncrementAggroCount() { // AggroCount++; - if(!RuleI(Character, RestRegenPercent)) + if(!RuleB(Character, RestRegenEnabled)) return; // If we already had aggro before this method was called, the combat indicator should already be up for SoF clients, @@ -4625,7 +4637,7 @@ void Client::DecrementAggroCount() { AggroCount--; - if(!RuleI(Character, RestRegenPercent)) + if(!RuleB(Character, RestRegenEnabled)) return; // Something else is still aggro on us, can't rest yet. @@ -6712,7 +6724,7 @@ void Client::SendStatsWindow(Client* client, bool use_window) cap_regen_field = itoa(CalcHPRegenCap()); spell_regen_field = itoa(spellbonuses.HPRegen); aa_regen_field = itoa(aabonuses.HPRegen); - total_regen_field = itoa(CalcHPRegen()); + total_regen_field = itoa(CalcHPRegen(true)); break; } case 1: { @@ -6725,7 +6737,7 @@ void Client::SendStatsWindow(Client* client, bool use_window) cap_regen_field = itoa(CalcManaRegenCap()); spell_regen_field = itoa(spellbonuses.ManaRegen); aa_regen_field = itoa(aabonuses.ManaRegen); - total_regen_field = itoa(CalcManaRegen()); + total_regen_field = itoa(CalcManaRegen(true)); } else { continue; } break; @@ -6739,7 +6751,7 @@ void Client::SendStatsWindow(Client* client, bool use_window) cap_regen_field = itoa(CalcEnduranceRegenCap()); spell_regen_field = itoa(spellbonuses.EnduranceRegen); aa_regen_field = itoa(aabonuses.EnduranceRegen); - total_regen_field = itoa(CalcEnduranceRegen()); + total_regen_field = itoa(CalcEnduranceRegen(true)); break; } default: { break; } @@ -8585,51 +8597,52 @@ void Client::SetConsumption(int32 in_hunger, int32 in_thirst) void Client::Consume(const EQEmu::ItemData *item, uint8 type, int16 slot, bool auto_consume) { - if(!item) { return; } + if (!item) + return; - uint32 cons_mod = 180; + int increase = item->CastTime_ * 100; + if (!auto_consume) // force feeding is half as effective + increase /= 2; - int32 metabolism_bonus = spellbonuses.Metabolism + itembonuses.Metabolism + aabonuses.Metabolism; + if (increase < 0) // wasn't food? oh well + return; - if (metabolism_bonus) - cons_mod = cons_mod * metabolism_bonus * RuleI(Character, ConsumptionMultiplier) / 10000; - else - cons_mod = cons_mod * RuleI(Character, ConsumptionMultiplier) / 100; + if (type == EQEmu::item::ItemTypeFood) { + increase = mod_food_value(item, increase); - if (type == EQEmu::item::ItemTypeFood) - { - int hchange = item->CastTime_ * cons_mod; - hchange = mod_food_value(item, hchange); + if (increase < 0) + return; - if(hchange < 0) { return; } + m_pp.hunger_level += increase; - m_pp.hunger_level += hchange; - DeleteItemInInventory(slot, 1, false); + Log(Logs::General, Logs::Food, "Consuming food, points added to hunger_level: %i - current_hunger: %i", + increase, m_pp.hunger_level); - if(!auto_consume) //no message if the client consumed for us - entity_list.MessageClose_StringID(this, true, 50, 0, EATING_MESSAGE, GetName(), item->Name); - -#if EQDEBUG >= 5 - Log(Logs::General, Logs::None, "Eating from slot:%i", (int)slot); -#endif - } - else - { - int tchange = item->CastTime_ * cons_mod; - tchange = mod_drink_value(item, tchange); - - if(tchange < 0) { return; } - - m_pp.thirst_level += tchange; DeleteItemInInventory(slot, 1, false); - if(!auto_consume) //no message if the client consumed for us + if (!auto_consume) // no message if the client consumed for us + entity_list.MessageClose_StringID(this, true, 50, 0, EATING_MESSAGE, GetName(), item->Name); + + Log(Logs::General, Logs::Food, "Eating from slot: %i", (int)slot); + + } else { + increase = mod_drink_value(item, increase); + + if (increase < 0) + return; + + m_pp.thirst_level += increase; + + DeleteItemInInventory(slot, 1, false); + + Log(Logs::General, Logs::Food, "Consuming drink, points added to thirst_level: %i current_thirst: %i", + increase, m_pp.thirst_level); + + if (!auto_consume) // no message if the client consumed for us entity_list.MessageClose_StringID(this, true, 50, 0, DRINKING_MESSAGE, GetName(), item->Name); -#if EQDEBUG >= 5 - Log(Logs::General, Logs::None, "Drinking from slot:%i", (int)slot); -#endif - } + Log(Logs::General, Logs::Food, "Drinking from slot: %i", (int)slot); + } } void Client::SendMarqueeMessage(uint32 type, uint32 priority, uint32 fade_in, uint32 fade_out, uint32 duration, std::string msg) @@ -9055,3 +9068,193 @@ void Client::SetPetCommandState(int button, int state) FastQueuePacket(&app); } +bool Client::CanMedOnHorse() +{ + // no horse is false + if (GetHorseId() == 0) + return false; + + // can't med while attacking + if (auto_attack) + return false; + + return animation == 0 && m_Delta.x == 0.0f && m_Delta.y == 0.0f; // TODO: animation is SpeedRun +} + +void Client::EnableAreaHPRegen(int value) +{ + AreaHPRegen = value * 0.001f; + SendAppearancePacket(AT_AreaHPRegen, value, false); +} + +void Client::DisableAreaHPRegen() +{ + AreaHPRegen = 1.0f; + SendAppearancePacket(AT_AreaHPRegen, 1000, false); +} + +void Client::EnableAreaManaRegen(int value) +{ + AreaManaRegen = value * 0.001f; + SendAppearancePacket(AT_AreaManaRegen, value, false); +} + +void Client::DisableAreaManaRegen() +{ + AreaManaRegen = 1.0f; + SendAppearancePacket(AT_AreaManaRegen, 1000, false); +} + +void Client::EnableAreaEndRegen(int value) +{ + AreaEndRegen = value * 0.001f; + SendAppearancePacket(AT_AreaEndRegen, value, false); +} + +void Client::DisableAreaEndRegen() +{ + AreaEndRegen = 1.0f; + SendAppearancePacket(AT_AreaEndRegen, 1000, false); +} + +void Client::EnableAreaRegens(int value) +{ + EnableAreaHPRegen(value); + EnableAreaManaRegen(value); + EnableAreaEndRegen(value); +} + +void Client::DisableAreaRegens() +{ + DisableAreaHPRegen(); + DisableAreaManaRegen(); + DisableAreaEndRegen(); +} + +void Client::InitInnates() +{ + // this function on the client also inits the level one innate skills (like swimming, hide, etc) + // we won't do that here, lets just do the InnateSkills for now. Basically translation of what the client is doing + // A lot of these we could probably have ignored because they have no known use or are 100% client side + // but I figured just in case we'll do them all out + // + // The client calls this in a few places. When you remove a vision buff and in SetHeights, which is called in + // illusions, mounts, and a bunch of other cases. All of the calls to InitInnates are wrapped in restoring regen + // besides the call initializing the first time + auto race = GetRace(); + auto class_ = GetClass(); + + for (int i = 0; i < InnateSkillMax; ++i) + m_pp.InnateSkills[i] = InnateDisabled; + + m_pp.InnateSkills[InnateInspect] = InnateEnabled; + m_pp.InnateSkills[InnateOpen] = InnateEnabled; + if (race >= RT_FROGLOK_3) { + if (race == RT_SKELETON_2 || race == RT_FROGLOK_3) + m_pp.InnateSkills[InnateUltraVision] = InnateEnabled; + else + m_pp.InnateSkills[InnateInfravision] = InnateEnabled; + } + switch (race) { + case RT_BARBARIAN: + case RT_BARBARIAN_2: + m_pp.InnateSkills[InnateSlam] = InnateEnabled; + break; + case RT_ERUDITE: + case RT_ERUDITE_2: + m_pp.InnateSkills[InnateLore] = InnateEnabled; + break; + case RT_WOOD_ELF: + case RT_GUARD_3: + m_pp.InnateSkills[InnateInfravision] = InnateEnabled; + break; + case RT_HIGH_ELF: + case RT_GUARD_2: + m_pp.InnateSkills[InnateInfravision] = InnateEnabled; + m_pp.InnateSkills[InnateLore] = InnateEnabled; + break; + case RT_DARK_ELF: + case RT_DARK_ELF_2: + case RT_VAMPIRE_2: + m_pp.InnateSkills[InnateUltraVision] = InnateEnabled; + break; + case RT_TROLL: + case RT_TROLL_2: + m_pp.InnateSkills[InnateRegen] = InnateEnabled; + m_pp.InnateSkills[InnateSlam] = InnateEnabled; + m_pp.InnateSkills[InnateInfravision] = InnateEnabled; + break; + case RT_DWARF: + case RT_DWARF_2: + m_pp.InnateSkills[InnateInfravision] = InnateEnabled; + break; + case RT_OGRE: + case RT_OGRE_2: + m_pp.InnateSkills[InnateInfravision] = InnateEnabled; + m_pp.InnateSkills[InnateSlam] = InnateEnabled; + m_pp.InnateSkills[InnateNoBash] = InnateEnabled; + m_pp.InnateSkills[InnateBashDoor] = InnateEnabled; + break; + case RT_HALFLING: + case RT_HALFLING_2: + m_pp.InnateSkills[InnateInfravision] = InnateEnabled; + break; + case RT_GNOME: + m_pp.InnateSkills[InnateInfravision] = InnateEnabled; + m_pp.InnateSkills[InnateLore] = InnateEnabled; + break; + case RT_IKSAR: + m_pp.InnateSkills[InnateRegen] = InnateEnabled; + m_pp.InnateSkills[InnateInfravision] = InnateEnabled; + break; + case RT_VAH_SHIR: + m_pp.InnateSkills[InnateInfravision] = InnateEnabled; + break; + case RT_FROGLOK_2: + case RT_GHOST: + case RT_GHOUL: + case RT_SKELETON: + case RT_VAMPIRE: + case RT_WILL_O_WISP: + case RT_ZOMBIE: + case RT_SPECTRE: + case RT_GHOST_2: + case RT_GHOST_3: + case RT_DRAGON_2: + case RT_INNORUUK: + m_pp.InnateSkills[InnateUltraVision] = InnateEnabled; + break; + case RT_HUMAN: + case RT_GUARD: + case RT_BEGGAR: + case RT_HUMAN_2: + case RT_HUMAN_3: + case RT_FROGLOK_3: // client does froglok weird, but this should work out fine + break; + default: + m_pp.InnateSkills[InnateInfravision] = InnateEnabled; + break; + } + + switch (class_) { + case DRUID: + m_pp.InnateSkills[InnateHarmony] = InnateEnabled; + break; + case BARD: + m_pp.InnateSkills[InnateReveal] = InnateEnabled; + break; + case ROGUE: + m_pp.InnateSkills[InnateSurprise] = InnateEnabled; + m_pp.InnateSkills[InnateReveal] = InnateEnabled; + break; + case RANGER: + m_pp.InnateSkills[InnateAwareness] = InnateEnabled; + break; + case MONK: + m_pp.InnateSkills[InnateSurprise] = InnateEnabled; + m_pp.InnateSkills[InnateAwareness] = InnateEnabled; + default: + break; + } +} + diff --git a/zone/client.h b/zone/client.h index 952c98333..88622fcff 100644 --- a/zone/client.h +++ b/zone/client.h @@ -199,6 +199,27 @@ struct RespawnOption float heading; }; +// do not ask what all these mean because I have no idea! +// named from the client's CEverQuest::GetInnateDesc, they're missing some +enum eInnateSkill { + InnateEnabled = 0, + InnateAwareness = 1, + InnateBashDoor = 2, + InnateBreathFire = 3, + InnateHarmony = 4, + InnateInfravision = 6, + InnateLore = 8, + InnateNoBash = 9, + InnateRegen = 10, + InnateSlam = 11, + InnateSurprise = 12, + InnateUltraVision = 13, + InnateInspect = 14, + InnateOpen = 15, + InnateReveal = 16, + InnateSkillMax = 25, // size of array in client + InnateDisabled = 255 +}; const uint32 POPUPID_UPDATE_SHOWSTATSWINDOW = 1000000; @@ -406,6 +427,16 @@ public: const int32& SetMana(int32 amount); int32 CalcManaRegenCap(); + // guild pool regen shit. Sends a SpawnAppearance with a value that regens to value * 0.001 + void EnableAreaHPRegen(int value); + void DisableAreaHPRegen(); + void EnableAreaManaRegen(int value); + void DisableAreaManaRegen(); + void EnableAreaEndRegen(int value); + void DisableAreaEndRegen(); + void EnableAreaRegens(int value); + void DisableAreaRegens(); + void ServerFilter(SetServerFilter_Struct* filter); void BulkSendTraderInventory(uint32 char_id); void SendSingleTraderItem(uint32 char_id, int uniqueid); @@ -540,7 +571,7 @@ public: /*Endurance and such*/ void CalcMaxEndurance(); //This calculates the maximum endurance we can have int32 CalcBaseEndurance(); //Calculates Base End - int32 CalcEnduranceRegen(); //Calculates endurance regen used in DoEnduranceRegen() + int32 CalcEnduranceRegen(bool bCombat = false); //Calculates endurance regen used in DoEnduranceRegen() int32 GetEndurance() const {return current_endurance;} //This gets our current endurance int32 GetMaxEndurance() const {return max_end;} //This gets our endurance from the last CalcMaxEndurance() call int32 CalcEnduranceRegenCap(); @@ -719,6 +750,7 @@ public: void SendTradeskillDetails(uint32 recipe_id); bool TradeskillExecute(DBTradeskillRecipe_Struct *spec); void CheckIncreaseTradeskill(int16 bonusstat, int16 stat_modifier, float skillup_modifier, uint16 success_modifier, EQEmu::skills::SkillType tradeskill); + void InitInnates(); void GMKill(); inline bool IsMedding() const {return medding;} @@ -760,6 +792,9 @@ public: void SummonHorse(uint16 spell_id); void SetHorseId(uint16 horseid_in); uint16 GetHorseId() const { return horseId; } + bool CanMedOnHorse(); + + bool CanFastRegen() const { return ooc_regen; } void NPCSpawn(NPC *target_npc, const char *identifier, uint32 extra = 0); @@ -862,6 +897,7 @@ public: void SetHunger(int32 in_hunger); void SetThirst(int32 in_thirst); void SetConsumption(int32 in_hunger, int32 in_thirst); + bool IsStarved() const { if (GetGM() || !RuleB(Character, EnableHungerPenalties)) return false; return m_pp.hunger_level == 0 || m_pp.thirst_level == 0; } bool CheckTradeLoreConflict(Client* other); bool CheckTradeNonDroppable(); @@ -1266,6 +1302,8 @@ public: int32 CalcATK(); + uint32 trapid; //ID of trap player has triggered. This is cleared when the player leaves the trap's radius, or it despawns. + protected: friend class Mob; void CalcItemBonuses(StatBonuses* newbon); @@ -1345,13 +1383,13 @@ private: int32 CalcCorrup(); int32 CalcMaxHP(); int32 CalcBaseHP(); - int32 CalcHPRegen(); - int32 CalcManaRegen(); + int32 CalcHPRegen(bool bCombat = false); + int32 CalcManaRegen(bool bCombat = false); int32 CalcBaseManaRegen(); uint32 GetClassHPFactor(); void DoHPRegen(); void DoManaRegen(); - void DoStaminaUpdate(); + void DoStaminaHungerUpdate(); void CalcRestState(); uint32 pLastUpdate; @@ -1410,6 +1448,7 @@ private: std::string BuyerWelcomeMessage; bool AbilityTimer; int Haste; //precalced value + uint32 tmSitting; // time stamp started sitting, used for HP regen bonus added on MAY 5, 2004 int32 max_end; int32 current_endurance; @@ -1458,7 +1497,7 @@ private: Timer hpupdate_timer; Timer camp_timer; Timer process_timer; - Timer stamina_timer; + Timer consume_food_timer; Timer zoneinpacket_timer; Timer linkdead_timer; Timer dead_timer; @@ -1489,7 +1528,9 @@ private: Timer hp_self_update_throttle_timer; /* This is to prevent excessive packet sending under trains/fast combat */ Timer hp_other_update_throttle_timer; /* This is to keep clients from DOSing the server with macros that change client targets constantly */ Timer position_update_timer; /* Timer used when client hasn't updated within a 10 second window */ - glm::vec3 m_Proximity; + + glm::vec3 m_Proximity; + glm::vec4 last_major_update_position; void BulkSendInventoryItems(); @@ -1511,9 +1552,10 @@ private: unsigned int AggroCount; // How many mobs are aggro on us. - unsigned int RestRegenHP; - unsigned int RestRegenMana; - unsigned int RestRegenEndurance; + bool ooc_regen; + float AreaHPRegen; + float AreaManaRegen; + float AreaEndRegen; bool EngagedRaidTarget; uint32 SavedRaidRestTimer; diff --git a/zone/client_mods.cpp b/zone/client_mods.cpp index 6db0437c5..696214113 100644 --- a/zone/client_mods.cpp +++ b/zone/client_mods.cpp @@ -22,6 +22,8 @@ #include "../common/rulesys.h" #include "../common/spdat.h" +#include "../common/data_verification.h" + #include "client.h" #include "mob.h" @@ -231,16 +233,81 @@ int32 Client::LevelRegen() return hp; } -int32 Client::CalcHPRegen() +int32 Client::CalcHPRegen(bool bCombat) { - int32 regen = LevelRegen() + itembonuses.HPRegen + spellbonuses.HPRegen; - regen += aabonuses.HPRegen + GroupLeadershipAAHealthRegeneration(); + int item_regen = itembonuses.HPRegen; // worn spells and +regen, already capped + item_regen += GetHeroicSTA() / 20; + + item_regen += aabonuses.HPRegen; + + int base = 0; + auto base_data = database.GetBaseData(GetLevel(), GetClass()); + if (base_data) + base = static_cast(base_data->hp_regen); + + auto level = GetLevel(); + bool skip_innate = false; + + if (IsSitting()) { + if (level >= 50) { + base++; + if (level >= 65) + base++; + } + + if ((Timer::GetCurrentTime() - tmSitting) > 60000) { + if (!IsAffectedByBuffByGlobalGroup(GlobalGroup::Lich)) { + auto tic_diff = std::min((Timer::GetCurrentTime() - tmSitting) / 60000, static_cast(9)); + if (tic_diff != 1) { // starts at 2 mins + int tic_bonus = tic_diff * 1.5 * base; + if (m_pp.InnateSkills[InnateRegen] != InnateDisabled) + tic_bonus = tic_bonus * 1.2; + base = tic_bonus; + skip_innate = true; + } else if (m_pp.InnateSkills[InnateRegen] == InnateDisabled) { // no innate regen gets first tick + int tic_bonus = base * 1.5; + base = tic_bonus; + } + } + } + } + + if (!skip_innate && m_pp.InnateSkills[InnateRegen] != InnateDisabled) { + if (level >= 50) { + ++base; + if (level >= 55) + ++base; + } + base *= 2; + } + + if (IsStarved()) + base = 0; + + base += GroupLeadershipAAHealthRegeneration(); + // some IsKnockedOut that sets to -1 + base = base * 100.0f * AreaHPRegen * 0.01f + 0.5f; + // another check for IsClient && !(base + item_regen) && Cur_HP <= 0 do --base; do later + + if (!bCombat && CanFastRegen() && (IsSitting() || CanMedOnHorse())) { + auto fast_mod = RuleI(Character, RestRegenHP); // TODO: this is actually zone based + auto max_hp = GetMaxHP(); + int fast_regen = 6 * (max_hp / fast_mod); + if (base < fast_regen) // weird, but what the client is doing + base = fast_regen; + } + + int regen = base + item_regen + spellbonuses.HPRegen; // TODO: client does this in buff tick return (regen * RuleI(Character, HPRegenMultiplier) / 100); } int32 Client::CalcHPRegenCap() { - int cap = RuleI(Character, ItemHealthRegenCap) + itembonuses.HeroicSTA / 25; + int cap = RuleI(Character, ItemHealthRegenCap); + if (GetLevel() > 60) + cap = std::max(cap, GetLevel() - 30); // if the rule is set greater than normal I guess + if (GetLevel() > 65) + cap += GetLevel() - 65; cap += aabonuses.ItemHPRegenCap + spellbonuses.ItemHPRegenCap + itembonuses.ItemHPRegenCap; return (cap * RuleI(Character, HPRegenMultiplier) / 100); } @@ -1169,43 +1236,80 @@ int32 Client::CalcBaseManaRegen() return regen; } -int32 Client::CalcManaRegen() +int32 Client::CalcManaRegen(bool bCombat) { - uint8 clevel = GetLevel(); - int32 regen = 0; - //this should be changed so we dont med while camping, etc... - if (IsSitting() || (GetHorseId() != 0)) { - BuffFadeBySitModifier(); - if (HasSkill(EQEmu::skills::SkillMeditate)) { - this->medding = true; - regen = (((GetSkill(EQEmu::skills::SkillMeditate) / 10) + (clevel - (clevel / 4))) / 4) + 4; - regen += spellbonuses.ManaRegen + itembonuses.ManaRegen; - CheckIncreaseSkill(EQEmu::skills::SkillMeditate, nullptr, -5); - } - else { - regen = 2 + spellbonuses.ManaRegen + itembonuses.ManaRegen; + int regen = 0; + auto level = GetLevel(); + // so the new formulas break down with older skill caps where you don't have the skill until 4 or 8 + // so for servers that want to use the old skill progression they can set this rule so they + // will get at least 1 for standing and 2 for sitting. + bool old = RuleB(Character, OldMinMana); + if (!IsStarved()) { + // client does some base regen for shrouds here + if (IsSitting() || CanMedOnHorse()) { + // kind of weird to do it here w/e + // client does some base medding regen for shrouds here + if (GetClass() != BARD) { + auto skill = GetSkill(EQEmu::skills::SkillMeditate); + if (skill > 0) { + regen++; + if (skill > 1) + regen++; + if (skill >= 15) + regen += skill / 15; + } + } + if (old) + regen = std::max(regen, 2); + } else if (old) { + regen = std::max(regen, 1); } } - else { - this->medding = false; - regen = 2 + spellbonuses.ManaRegen + itembonuses.ManaRegen; + + if (level > 61) { + regen++; + if (level > 63) + regen++; } - //AAs + regen += aabonuses.ManaRegen; + // add in + 1 bonus for SE_CompleteHeal, but we don't do anything for it yet? + + int item_bonus = itembonuses.ManaRegen; // this is capped already + int heroic_bonus = 0; + + switch (GetCasterClass()) { + case 'W': + heroic_bonus = GetHeroicWIS(); + break; + default: + heroic_bonus = GetHeroicINT(); + break; + } + + item_bonus += heroic_bonus / 25; + regen += item_bonus; + + if (level <= 70 && regen > 65) + regen = 65; + + regen = regen * 100.0f * AreaManaRegen * 0.01f + 0.5f; + + if (!bCombat && CanFastRegen() && (IsSitting() || CanMedOnHorse())) { + auto fast_mod = RuleI(Character, RestRegenMana); // TODO: this is actually zone based + auto max_mana = GetMaxMana(); + int fast_regen = 6 * (max_mana / fast_mod); + if (regen < fast_regen) // weird, but what the client is doing + regen = fast_regen; + } + + regen += spellbonuses.ManaRegen; // TODO: live does this in buff tick return (regen * RuleI(Character, ManaRegenMultiplier) / 100); } int32 Client::CalcManaRegenCap() { int32 cap = RuleI(Character, ItemManaRegenCap) + aabonuses.ItemManaRegenCap; - switch (GetCasterClass()) { - case 'I': - cap += (itembonuses.HeroicINT / 25); - break; - case 'W': - cap += (itembonuses.HeroicWIS / 25); - break; - } return (cap * RuleI(Character, ManaRegenMultiplier) / 100); } @@ -2091,16 +2195,91 @@ int32 Client::CalcBaseEndurance() return base_end; } -int32 Client::CalcEnduranceRegen() +int32 Client::CalcEnduranceRegen(bool bCombat) { - int32 regen = int32(GetLevel() * 4 / 10) + 2; - regen += aabonuses.EnduranceRegen + spellbonuses.EnduranceRegen + itembonuses.EnduranceRegen; + int base = 0; + if (!IsStarved()) { + auto base_data = database.GetBaseData(GetLevel(), GetClass()); + if (base_data) { + base = static_cast(base_data->end_regen); + if (!auto_attack && base > 0) + base += base / 2; + } + } + + // so when we are mounted, our local client SpeedRun is always 0, so this is always false, but the packets we process it to our own shit :P + bool is_running = runmode && animation != 0 && GetHorseId() == 0; // TODO: animation is really what MQ2 calls SpeedRun + + int weight_limit = GetSTR(); + auto level = GetLevel(); + if (GetClass() == MONK) { + if (level > 99) + weight_limit = 58; + else if (level > 94) + weight_limit = 57; + else if (level > 89) + weight_limit = 56; + else if (level > 84) + weight_limit = 55; + else if (level > 79) + weight_limit = 54; + else if (level > 64) + weight_limit = 53; + else if (level > 63) + weight_limit = 50; + else if (level > 61) + weight_limit = 47; + else if (level > 59) + weight_limit = 45; + else if (level > 54) + weight_limit = 40; + else if (level > 50) + weight_limit = 38; + else if (level > 44) + weight_limit = 36; + else if (level > 29) + weight_limit = 34; + else if (level > 14) + weight_limit = 32; + else + weight_limit = 30; + } + + bool encumbered = (CalcCurrentWeight() / 10) >= weight_limit; + + if (is_running) + base += level / -15; + + if (encumbered) + base += level / -15; + + auto item_bonus = GetHeroicAGI() + GetHeroicDEX() + GetHeroicSTA() + GetHeroicSTR(); + item_bonus = item_bonus / 4 / 50; + item_bonus += itembonuses.EnduranceRegen; // this is capped already + base += item_bonus; + + base = base * AreaEndRegen + 0.5f; + + auto aa_regen = aabonuses.EnduranceRegen; + + int regen = base; + if (!bCombat && CanFastRegen() && (IsSitting() || CanMedOnHorse())) { + auto fast_mod = RuleI(Character, RestRegenEnd); // TODO: this is actually zone based + auto max_end = GetMaxEndurance(); + int fast_regen = 6 * (max_end / fast_mod); + if (aa_regen < fast_regen) // weird, but what the client is doing + aa_regen = fast_regen; + } + + regen += aa_regen; + regen += spellbonuses.EnduranceRegen; // TODO: client does this in buff tick + return (regen * RuleI(Character, EnduranceRegenMultiplier) / 100); } int32 Client::CalcEnduranceRegenCap() { - int cap = (RuleI(Character, ItemEnduranceRegenCap) + itembonuses.HeroicSTR / 25 + itembonuses.HeroicDEX / 25 + itembonuses.HeroicAGI / 25 + itembonuses.HeroicSTA / 25); + int cap = RuleI(Character, ItemEnduranceRegenCap); return (cap * RuleI(Character, EnduranceRegenMultiplier) / 100); } diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 95c9013b2..d336f7f0d 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -291,6 +291,7 @@ void MapOpcodes() ConnectedOpcodes[OP_MercenaryTimerRequest] = &Client::Handle_OP_MercenaryTimerRequest; ConnectedOpcodes[OP_MoveCoin] = &Client::Handle_OP_MoveCoin; ConnectedOpcodes[OP_MoveItem] = &Client::Handle_OP_MoveItem; + ConnectedOpcodes[OP_MoveMultipleItems] = &Client::Handle_OP_MoveMultipleItems; ConnectedOpcodes[OP_OpenContainer] = &Client::Handle_OP_OpenContainer; ConnectedOpcodes[OP_OpenGuildTributeMaster] = &Client::Handle_OP_OpenGuildTributeMaster; ConnectedOpcodes[OP_OpenInventory] = &Client::Handle_OP_OpenInventory; @@ -1410,6 +1411,12 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app) drakkin_tattoo = m_pp.drakkin_tattoo; drakkin_details = m_pp.drakkin_details; + // we know our class now, so we might have to fix our consume timer! + if (class_ == MONK) + consume_food_timer.SetTimer(CONSUMPTION_MNK_TIMER); + + InitInnates(); + /* If GM not set in DB, and does not meet min status to be GM, reset */ if (m_pp.gm && admin < minStatusToBeGM) m_pp.gm = 0; @@ -2139,8 +2146,8 @@ void Client::Handle_OP_AdventureMerchantRequest(const EQApplicationPacket *app) ss << item->ID << "|"; ss << item->LDoNPrice << "|"; ss << theme << "|"; - ss << "0|"; - ss << "1|"; + ss << (item->Stackable ? 1 : 0) << "|"; + ss << (item->LoreFlag ? 1 : 0) << "|"; ss << item->Races << "|"; ss << item->Classes; count++; @@ -4634,7 +4641,19 @@ void Client::Handle_OP_ClientUpdate(const EQApplicationPacket *app) entity_list.QueueClientsStatus(this, outapp, true, Admin(), 250); } else { - entity_list.QueueCloseClients(this, outapp, true, 300, nullptr, true); + entity_list.QueueCloseClients(this, outapp, true, RuleI(Range, ClientPositionUpdates), nullptr, true); + } + + + /* Always send position updates to group - send when beyond normal ClientPositionUpdate range */ + Group *group = this->GetGroup(); + Raid *raid = this->GetRaid(); + + if (raid) { + raid->QueueClients(this, outapp, true, true, (RuleI(Range, ClientPositionUpdates) * -1)); + } + else if (group) { + group->QueueClients(this, outapp, true, true, (RuleI(Range, ClientPositionUpdates) * -1)); } safe_delete(outapp); @@ -5311,31 +5330,44 @@ void Client::Handle_OP_DisarmTraps(const EQApplicationPacket *app) p_timers.Start(pTimerDisarmTraps, reuse - 1); - Trap* trap = entity_list.FindNearbyTrap(this, 60); + uint8 success = SKILLUP_FAILURE; + float curdist = 0; + Trap* trap = entity_list.FindNearbyTrap(this, 250, curdist, true); if (trap && trap->detected) { - int uskill = GetSkill(EQEmu::skills::SkillDisarmTraps); - if ((zone->random.Int(0, 49) + uskill) >= (zone->random.Int(0, 49) + trap->skill)) + float max_radius = (trap->radius * 2) * (trap->radius * 2); // radius is used to trigger trap, so disarm radius should be a bit bigger. + Log(Logs::General, Logs::Traps, "%s is attempting to disarm trap %d. Curdist is %0.2f maxdist is %0.2f", GetName(), trap->trap_id, curdist, max_radius); + if (curdist <= max_radius) { - Message(MT_Skills, "You disarm a trap."); - trap->disarmed = true; - trap->chkarea_timer.Disable(); - trap->respawn_timer.Start((trap->respawn_time + zone->random.Int(0, trap->respawn_var)) * 1000); + int uskill = GetSkill(EQEmu::skills::SkillDisarmTraps); + if ((zone->random.Int(0, 49) + uskill) >= (zone->random.Int(0, 49) + trap->skill)) + { + success = SKILLUP_SUCCESS; + Message_StringID(MT_Skills, DISARMED_TRAP); + trap->disarmed = true; + Log(Logs::General, Logs::Traps, "Trap %d is disarmed.", trap->trap_id); + trap->UpdateTrap(); + } + else + { + Message_StringID(MT_Skills, FAIL_DISARM_DETECTED_TRAP); + if (zone->random.Int(0, 99) < 25) { + trap->Trigger(this); + } + } + CheckIncreaseSkill(EQEmu::skills::SkillDisarmTraps, nullptr); + return; } else { - if (zone->random.Int(0, 99) < 25) { - Message(MT_Skills, "You set off the trap while trying to disarm it!"); - trap->Trigger(this); - } - else { - Message(MT_Skills, "You failed to disarm a trap."); - } + Message_StringID(MT_Skills, TRAP_TOO_FAR); } - CheckIncreaseSkill(EQEmu::skills::SkillDisarmTraps, nullptr); - return; } - Message(MT_Skills, "You did not find any traps close enough to disarm."); + else + { + Message_StringID(MT_Skills, LDON_SENSE_TRAP2); + } + return; } @@ -8690,6 +8722,8 @@ void Client::Handle_OP_ItemVerifyRequest(const EQApplicationPacket *app) } else { + + /* //This is food/drink - consume it if (item->ItemType == EQEmu::item::ItemTypeFood && m_pp.hunger_level < 5000) { @@ -8711,19 +8745,21 @@ void Client::Handle_OP_ItemVerifyRequest(const EQApplicationPacket *app) //CheckIncreaseSkill(ALCOHOL_TOLERANCE, nullptr, 25); } - if (m_pp.hunger_level > 6000) - m_pp.hunger_level = 6000; - if (m_pp.thirst_level > 6000) - m_pp.thirst_level = 6000; - EQApplicationPacket *outapp2 = nullptr; outapp2 = new EQApplicationPacket(OP_Stamina, sizeof(Stamina_Struct)); Stamina_Struct* sta = (Stamina_Struct*)outapp2->pBuffer; + + if (m_pp.hunger_level > 6000) + sta->food = 6000; + if (m_pp.thirst_level > 6000) + sta->water = 6000; + sta->food = m_pp.hunger_level; sta->water = m_pp.thirst_level; QueuePacket(outapp2); safe_delete(outapp2); + */ } } @@ -9820,6 +9856,11 @@ void Client::Handle_OP_MoveItem(const EQApplicationPacket *app) return; } +void Client::Handle_OP_MoveMultipleItems(const EQApplicationPacket *app) +{ + Kick(); // TODO: lets not desync though +} + void Client::Handle_OP_OpenContainer(const EQApplicationPacket *app) { // Does not exist in Ti client @@ -10948,592 +10989,614 @@ void Client::Handle_OP_RaidCommand(const EQApplicationPacket *app) return; } - RaidGeneral_Struct *ri = (RaidGeneral_Struct*)app->pBuffer; - switch (ri->action) + RaidGeneral_Struct *raid_command_packet = (RaidGeneral_Struct*)app->pBuffer; + switch (raid_command_packet->action) { - case RaidCommandInviteIntoExisting: - case RaidCommandInvite: { - Client *i = entity_list.GetClientByName(ri->player_name); - if (!i) - break; - Group *g = i->GetGroup(); - // These two messages should be generated by the client I think, just do this for now - if (i->HasRaid()) { - Message(13, "%s is already in a raid.", i->GetName()); + case RaidCommandInviteIntoExisting: + case RaidCommandInvite: { + + Client *player_to_invite = entity_list.GetClientByName(raid_command_packet->player_name); + + if (!player_to_invite) + break; + + Group *player_to_invite_group = player_to_invite->GetGroup(); + + if (player_to_invite->HasRaid()) { + Message(13, "%s is already in a raid.", player_to_invite->GetName()); + break; + } + + if (player_to_invite_group && !player_to_invite_group->IsLeader(player_to_invite)) { + Message(13, "You can only invite an ungrouped player or group leader to join your raid."); + break; + } + + /* Send out invite to the client */ + auto outapp = new EQApplicationPacket(OP_RaidUpdate, sizeof(RaidGeneral_Struct)); + RaidGeneral_Struct *raid_command = (RaidGeneral_Struct*)outapp->pBuffer; + + strn0cpy(raid_command->leader_name, raid_command_packet->leader_name, 64); + strn0cpy(raid_command->player_name, raid_command_packet->player_name, 64); + + raid_command->parameter = 0; + raid_command->action = 20; + + player_to_invite->QueuePacket(outapp); + + safe_delete(outapp); + break; } - if (g && !g->IsLeader(i)) { - Message(13, "You can only invite an ungrouped player or group leader to join your raid."); - break; - } - //This sends an "invite" to the client in question. - auto outapp = new EQApplicationPacket(OP_RaidUpdate, sizeof(RaidGeneral_Struct)); - RaidGeneral_Struct *rg = (RaidGeneral_Struct*)outapp->pBuffer; - strn0cpy(rg->leader_name, ri->leader_name, 64); - strn0cpy(rg->player_name, ri->player_name, 64); - - rg->parameter = 0; - rg->action = 20; - i->QueuePacket(outapp); - safe_delete(outapp); - break; - } - case RaidCommandAcceptInvite: { - Client *i = entity_list.GetClientByName(ri->player_name); - if (i) { - if (IsRaidGrouped()) { - i->Message_StringID(0, ALREADY_IN_RAID, GetName()); //group failed, must invite members not in raid... - return; - } - Raid *r = entity_list.GetRaidByClient(i); - if (r) { - r->VerifyRaid(); - Group *g = GetGroup(); - if (g) { - if (g->GroupCount() + r->RaidCount() > MAX_RAID_MEMBERS) - { - i->Message(13, "Invite failed, group invite would create a raid larger than the maximum number of members allowed."); - return; - } + case RaidCommandAcceptInvite: { + Client *player_accepting_invite = entity_list.GetClientByName(raid_command_packet->player_name); + if (player_accepting_invite) { + if (IsRaidGrouped()) { + player_accepting_invite->Message_StringID(0, ALREADY_IN_RAID, GetName()); //group failed, must invite members not in raid... + return; } - else { - if (1 + r->RaidCount() > MAX_RAID_MEMBERS) - { - i->Message(13, "Invite failed, member invite would create a raid larger than the maximum number of members allowed."); - return; - } - } - if (g) {//add us all - uint32 freeGroup = r->GetFreeGroup(); - Client *addClient = nullptr; - for (int x = 0; x < 6; x++) - { - if (g->members[x]) { - Client *c = nullptr; - if (g->members[x]->IsClient()) - c = g->members[x]->CastToClient(); - else - continue; - - if (!addClient) - { - addClient = c; - r->SetGroupLeader(addClient->GetName()); - } - - r->SendRaidCreate(c); - r->SendMakeLeaderPacketTo(r->leadername, c); - if (g->IsLeader(g->members[x])) - r->AddMember(c, freeGroup, false, true); - else - r->AddMember(c, freeGroup); - r->SendBulkRaid(c); - if (r->IsLocked()) { - r->SendRaidLockTo(c); - } + Raid *raid = entity_list.GetRaidByClient(player_accepting_invite); + if (raid) { + raid->VerifyRaid(); + Group *group = GetGroup(); + if (group) { + if (group->GroupCount() + raid->RaidCount() > MAX_RAID_MEMBERS) { + player_accepting_invite->Message(13, "Invite failed, group invite would create a raid larger than the maximum number of members allowed."); + return; } } - g->JoinRaidXTarget(r); - g->DisbandGroup(true); - r->GroupUpdate(freeGroup); - } - else { - r->SendRaidCreate(this); - r->SendMakeLeaderPacketTo(r->leadername, this); - r->AddMember(this); - r->SendBulkRaid(this); - if (r->IsLocked()) { - r->SendRaidLockTo(this); - } - } - } - else - { - Group *ig = i->GetGroup(); - Group *g = GetGroup(); - if (g) //if our target has a group - { - r = new Raid(i); - entity_list.AddRaid(r); - r->SetRaidDetails(); - - uint32 groupFree = r->GetFreeGroup(); //get a free group - if (ig) { //if we already have a group then cycle through adding us... - Client *addClientig = nullptr; - for (int x = 0; x < 6; x++) - { - if (ig->members[x]) { - if (!addClientig) { - if (ig->members[x]->IsClient()) { - addClientig = ig->members[x]->CastToClient(); - r->SetGroupLeader(addClientig->GetName()); - } - } - if (ig->IsLeader(ig->members[x])) { - Client *c = nullptr; - if (ig->members[x]->IsClient()) - c = ig->members[x]->CastToClient(); - else - continue; - r->SendRaidCreate(c); - r->SendMakeLeaderPacketTo(r->leadername, c); - r->AddMember(c, groupFree, true, true, true); - r->SendBulkRaid(c); - if (r->IsLocked()) { - r->SendRaidLockTo(c); - } - } - else { - Client *c = nullptr; - if (ig->members[x]->IsClient()) - c = ig->members[x]->CastToClient(); - else - continue; - r->SendRaidCreate(c); - r->SendMakeLeaderPacketTo(r->leadername, c); - r->AddMember(c, groupFree); - r->SendBulkRaid(c); - if (r->IsLocked()) { - r->SendRaidLockTo(c); - } - } - } + else { + if (1 + raid->RaidCount() > MAX_RAID_MEMBERS) { + player_accepting_invite->Message(13, "Invite failed, member invite would create a raid larger than the maximum number of members allowed."); + return; } - ig->JoinRaidXTarget(r, true); - ig->DisbandGroup(true); - r->GroupUpdate(groupFree); - groupFree = r->GetFreeGroup(); } - else { //else just add the inviter - r->SendRaidCreate(i); - r->AddMember(i, 0xFFFFFFFF, true, false, true); - } - - Client *addClient = nullptr; - //now add the existing group - for (int x = 0; x < 6; x++) - { - if (g->members[x]) { - if (!addClient) - { - if (g->members[x]->IsClient()) { - addClient = g->members[x]->CastToClient(); - r->SetGroupLeader(addClient->GetName()); - } - } - if (g->IsLeader(g->members[x])) - { + if (group) {//add us all + uint32 free_group_id = raid->GetFreeGroup(); + Client *addClient = nullptr; + for (int x = 0; x < 6; x++) { + if (group->members[x]) { Client *c = nullptr; - if (g->members[x]->IsClient()) - c = g->members[x]->CastToClient(); + if (group->members[x]->IsClient()) + c = group->members[x]->CastToClient(); else continue; - r->SendRaidCreate(c); - r->SendMakeLeaderPacketTo(r->leadername, c); - r->AddMember(c, groupFree, false, true); - r->SendBulkRaid(c); - if (r->IsLocked()) { - r->SendRaidLockTo(c); - } - } - else - { - Client *c = nullptr; - if (g->members[x]->IsClient()) - c = g->members[x]->CastToClient(); - else - continue; - r->SendRaidCreate(c); - r->SendMakeLeaderPacketTo(r->leadername, c); - r->AddMember(c, groupFree); - r->SendBulkRaid(c); - if (r->IsLocked()) { - r->SendRaidLockTo(c); - } - } - } - } - g->JoinRaidXTarget(r); - g->DisbandGroup(true); - r->GroupUpdate(groupFree); - } - else // target does not have a group - { - if (ig) { - r = new Raid(i); - entity_list.AddRaid(r); - r->SetRaidDetails(); - Client *addClientig = nullptr; - for (int x = 0; x < 6; x++) - { - if (ig->members[x]) - { - if (!addClientig) { - if (ig->members[x]->IsClient()) { - addClientig = ig->members[x]->CastToClient(); - r->SetGroupLeader(addClientig->GetName()); - } - } - if (ig->IsLeader(ig->members[x])) + + if (!addClient) { + addClient = c; + raid->SetGroupLeader(addClient->GetName()); + } + + raid->SendRaidCreate(c); + raid->SendMakeLeaderPacketTo(raid->leadername, c); + if (group->IsLeader(group->members[x])) + raid->AddMember(c, free_group_id, false, true); + else + raid->AddMember(c, free_group_id); + raid->SendBulkRaid(c); + if (raid->IsLocked()) { + raid->SendRaidLockTo(c); + } + } + } + group->JoinRaidXTarget(raid); + group->DisbandGroup(true); + raid->GroupUpdate(free_group_id); + } + else { + raid->SendRaidCreate(this); + raid->SendMakeLeaderPacketTo(raid->leadername, this); + raid->AddMember(this); + raid->SendBulkRaid(this); + if (raid->IsLocked()) { + raid->SendRaidLockTo(this); + } + } + } + else + { + Group *player_invited_group = player_accepting_invite->GetGroup(); + Group *group = GetGroup(); + if (group) //if our target has a group + { + raid = new Raid(player_accepting_invite); + entity_list.AddRaid(raid); + raid->SetRaidDetails(); + + uint32 raid_free_group_id = raid->GetFreeGroup(); + + /* If we already have a group then cycle through adding us... */ + if (player_invited_group) { + Client *client_to_be_leader = nullptr; + for (int x = 0; x < 6; x++) { + if (player_invited_group->members[x]) { + if (!client_to_be_leader) { + if (player_invited_group->members[x]->IsClient()) { + client_to_be_leader = player_invited_group->members[x]->CastToClient(); + raid->SetGroupLeader(client_to_be_leader->GetName()); + } + } + if (player_invited_group->IsLeader(player_invited_group->members[x])) { + Client *c = nullptr; + + if (player_invited_group->members[x]->IsClient()) + c = player_invited_group->members[x]->CastToClient(); + else + continue; + + raid->SendRaidCreate(c); + raid->SendMakeLeaderPacketTo(raid->leadername, c); + raid->AddMember(c, raid_free_group_id, true, true, true); + raid->SendBulkRaid(c); + + if (raid->IsLocked()) { + raid->SendRaidLockTo(c); + } + } + else { + Client *c = nullptr; + + if (player_invited_group->members[x]->IsClient()) + c = player_invited_group->members[x]->CastToClient(); + else + continue; + + raid->SendRaidCreate(c); + raid->SendMakeLeaderPacketTo(raid->leadername, c); + raid->AddMember(c, raid_free_group_id); + raid->SendBulkRaid(c); + + if (raid->IsLocked()) { + raid->SendRaidLockTo(c); + } + } + } + } + player_invited_group->JoinRaidXTarget(raid, true); + player_invited_group->DisbandGroup(true); + raid->GroupUpdate(raid_free_group_id); + raid_free_group_id = raid->GetFreeGroup(); + } + else { + raid->SendRaidCreate(player_accepting_invite); + raid->AddMember(player_accepting_invite, 0xFFFFFFFF, true, false, true); + } + + Client *client_to_add = nullptr; + /* Add client to an existing group */ + for (int x = 0; x < 6; x++) { + if (group->members[x]) { + if (!client_to_add) { + if (group->members[x]->IsClient()) { + client_to_add = group->members[x]->CastToClient(); + raid->SetGroupLeader(client_to_add->GetName()); + } + } + if (group->IsLeader(group->members[x])) { Client *c = nullptr; - if (ig->members[x]->IsClient()) - c = ig->members[x]->CastToClient(); + + if (group->members[x]->IsClient()) + c = group->members[x]->CastToClient(); else continue; - - r->SendRaidCreate(c); - r->SendMakeLeaderPacketTo(r->leadername, c); - r->AddMember(c, 0, true, true, true); - r->SendBulkRaid(c); - if (r->IsLocked()) { - r->SendRaidLockTo(c); + + raid->SendRaidCreate(c); + raid->SendMakeLeaderPacketTo(raid->leadername, c); + raid->AddMember(c, raid_free_group_id, false, true); + raid->SendBulkRaid(c); + + if (raid->IsLocked()) { + raid->SendRaidLockTo(c); } } else { Client *c = nullptr; - if (ig->members[x]->IsClient()) - c = ig->members[x]->CastToClient(); + + if (group->members[x]->IsClient()) + c = group->members[x]->CastToClient(); else continue; - - r->SendRaidCreate(c); - r->SendMakeLeaderPacketTo(r->leadername, c); - r->AddMember(c, 0); - r->SendBulkRaid(c); - if (r->IsLocked()) { - r->SendRaidLockTo(c); + + raid->SendRaidCreate(c); + raid->SendMakeLeaderPacketTo(raid->leadername, c); + raid->AddMember(c, raid_free_group_id); + raid->SendBulkRaid(c); + + if (raid->IsLocked()) { + raid->SendRaidLockTo(c); } } } } - r->SendRaidCreate(this); - r->SendMakeLeaderPacketTo(r->leadername, this); - r->SendBulkRaid(this); - ig->JoinRaidXTarget(r, true); - r->AddMember(this); - ig->DisbandGroup(true); - r->GroupUpdate(0); - if (r->IsLocked()) { - r->SendRaidLockTo(this); - } + group->JoinRaidXTarget(raid); + group->DisbandGroup(true); + + raid->GroupUpdate(raid_free_group_id); } - else { // neither has a group - r = new Raid(i); - entity_list.AddRaid(r); - r->SetRaidDetails(); - r->SendRaidCreate(i); - r->SendRaidCreate(this); - r->SendMakeLeaderPacketTo(r->leadername, this); - r->AddMember(i, 0xFFFFFFFF, true, false, true); - r->SendBulkRaid(this); - r->AddMember(this); - if (r->IsLocked()) { - r->SendRaidLockTo(this); + /* Target does not have a group */ + else { + if (player_invited_group) { + + raid = new Raid(player_accepting_invite); + + entity_list.AddRaid(raid); + raid->SetRaidDetails(); + Client *addClientig = nullptr; + for (int x = 0; x < 6; x++) { + if (player_invited_group->members[x]) { + if (!addClientig) { + if (player_invited_group->members[x]->IsClient()) { + addClientig = player_invited_group->members[x]->CastToClient(); + raid->SetGroupLeader(addClientig->GetName()); + } + } + if (player_invited_group->IsLeader(player_invited_group->members[x])) { + Client *c = nullptr; + + if (player_invited_group->members[x]->IsClient()) + c = player_invited_group->members[x]->CastToClient(); + else + continue; + + raid->SendRaidCreate(c); + raid->SendMakeLeaderPacketTo(raid->leadername, c); + raid->AddMember(c, 0, true, true, true); + raid->SendBulkRaid(c); + + if (raid->IsLocked()) { + raid->SendRaidLockTo(c); + } + } + else + { + Client *c = nullptr; + if (player_invited_group->members[x]->IsClient()) + c = player_invited_group->members[x]->CastToClient(); + else + continue; + + raid->SendRaidCreate(c); + raid->SendMakeLeaderPacketTo(raid->leadername, c); + raid->AddMember(c, 0); + raid->SendBulkRaid(c); + if (raid->IsLocked()) { + raid->SendRaidLockTo(c); + } + } + } + } + raid->SendRaidCreate(this); + raid->SendMakeLeaderPacketTo(raid->leadername, this); + raid->SendBulkRaid(this); + player_invited_group->JoinRaidXTarget(raid, true); + raid->AddMember(this); + player_invited_group->DisbandGroup(true); + raid->GroupUpdate(0); + if (raid->IsLocked()) { + raid->SendRaidLockTo(this); + } + } + else { // neither has a group + raid = new Raid(player_accepting_invite); + entity_list.AddRaid(raid); + raid->SetRaidDetails(); + raid->SendRaidCreate(player_accepting_invite); + raid->SendRaidCreate(this); + raid->SendMakeLeaderPacketTo(raid->leadername, this); + raid->AddMember(player_accepting_invite, 0xFFFFFFFF, true, false, true); + raid->SendBulkRaid(this); + raid->AddMember(this); + if (raid->IsLocked()) { + raid->SendRaidLockTo(this); + } } } } } + break; } - break; - } - case RaidCommandDisband: { - Raid *r = entity_list.GetRaidByClient(this); - if (r) { - //if(this == r->GetLeader()){ - uint32 grp = r->GetGroup(ri->leader_name); + case RaidCommandDisband: { + Raid *raid = entity_list.GetRaidByClient(this); + if (raid) { + uint32 group = raid->GetGroup(raid_command_packet->leader_name); - if (grp < 12) { - uint32 i = r->GetPlayerIndex(ri->leader_name); - if (r->members[i].IsGroupLeader) { //assign group leader to someone else - for (int x = 0; x < MAX_RAID_MEMBERS; x++) { - if (strlen(r->members[x].membername) > 0 && i != x) { - if (r->members[x].GroupNumber == grp) { - r->SetGroupLeader(ri->leader_name, false); - r->SetGroupLeader(r->members[x].membername); - r->UpdateGroupAAs(grp); + if (group < 12) { + uint32 i = raid->GetPlayerIndex(raid_command_packet->leader_name); + if (raid->members[i].IsGroupLeader) { //assign group leader to someone else + for (int x = 0; x < MAX_RAID_MEMBERS; x++) { + if (strlen(raid->members[x].membername) > 0 && i != x) { + if (raid->members[x].GroupNumber == group) { + raid->SetGroupLeader(raid_command_packet->leader_name, false); + raid->SetGroupLeader(raid->members[x].membername); + raid->UpdateGroupAAs(group); + break; + } + } + } + + } + if (raid->members[i].IsRaidLeader) { + for (int x = 0; x < MAX_RAID_MEMBERS; x++) { + if (strlen(raid->members[x].membername) > 0 && strcmp(raid->members[x].membername, raid->members[i].membername) != 0) + { + raid->SetRaidLeader(raid->members[i].membername, raid->members[x].membername); + raid->UpdateRaidAAs(); + raid->SendAllRaidLeadershipAA(); break; } } } - } - if (r->members[i].IsRaidLeader) { - for (int x = 0; x < MAX_RAID_MEMBERS; x++) { - if (strlen(r->members[x].membername) > 0 && strcmp(r->members[x].membername, r->members[i].membername) != 0) - { - r->SetRaidLeader(r->members[i].membername, r->members[x].membername); - r->UpdateRaidAAs(); - r->SendAllRaidLeadershipAA(); - break; - } - } - } - } - r->RemoveMember(ri->leader_name); - Client *c = entity_list.GetClientByName(ri->leader_name); - if (c) - r->SendGroupDisband(c); - else { - auto pack = - new ServerPacket(ServerOP_RaidGroupDisband, sizeof(ServerRaidGeneralAction_Struct)); - ServerRaidGeneralAction_Struct* rga = (ServerRaidGeneralAction_Struct*)pack->pBuffer; - rga->rid = GetID(); - rga->zoneid = zone->GetZoneID(); - rga->instance_id = zone->GetInstanceID(); - strn0cpy(rga->playername, ri->leader_name, 64); - worldserver.SendPacket(pack); - safe_delete(pack); + raid->RemoveMember(raid_command_packet->leader_name); + Client *c = entity_list.GetClientByName(raid_command_packet->leader_name); + if (c) + raid->SendGroupDisband(c); + else { + auto pack = + new ServerPacket(ServerOP_RaidGroupDisband, sizeof(ServerRaidGeneralAction_Struct)); + ServerRaidGeneralAction_Struct* rga = (ServerRaidGeneralAction_Struct*)pack->pBuffer; + rga->rid = GetID(); + rga->zoneid = zone->GetZoneID(); + rga->instance_id = zone->GetInstanceID(); + strn0cpy(rga->playername, raid_command_packet->leader_name, 64); + worldserver.SendPacket(pack); + safe_delete(pack); + } + //r->SendRaidGroupRemove(ri->leader_name, grp); + raid->GroupUpdate(group);// break + //} } - //r->SendRaidGroupRemove(ri->leader_name, grp); - r->GroupUpdate(grp);// break - //} + break; } - break; - } - case RaidCommandMoveGroup: - { - Raid *r = entity_list.GetRaidByClient(this); - if (r) + case RaidCommandMoveGroup: { - if (ri->parameter < 12) //moving to a group - { - uint8 grpcount = r->GroupCount(ri->parameter); + Raid *raid = entity_list.GetRaidByClient(this); + if (raid) { + /* Moving to group */ + if (raid_command_packet->parameter < 12) { + uint8 group_count = raid->GroupCount(raid_command_packet->parameter); - if (grpcount < 6) - { - Client *c = entity_list.GetClientByName(ri->leader_name); - uint32 oldgrp = r->GetGroup(ri->leader_name); - if (ri->parameter == oldgrp) //don't rejoin grp if we order to join same group. - break; + if (group_count < 6) { + Client *c = entity_list.GetClientByName(raid_command_packet->leader_name); + uint32 old_group = raid->GetGroup(raid_command_packet->leader_name); + if (raid_command_packet->parameter == old_group) //don't rejoin grp if we order to join same group. + break; - if (r->members[r->GetPlayerIndex(ri->leader_name)].IsGroupLeader) - { - r->SetGroupLeader(ri->leader_name, false); - if (oldgrp < 12) { //we were the leader of our old grp - for (int x = 0; x < MAX_RAID_MEMBERS; x++) //assign a new grp leader if we can - { - if (r->members[x].GroupNumber == oldgrp) - { - if (strcmp(ri->leader_name, r->members[x].membername) != 0 && strlen(ri->leader_name) > 0) - { - r->SetGroupLeader(r->members[x].membername); - r->UpdateGroupAAs(oldgrp); - Client *cgl = entity_list.GetClientByName(r->members[x].membername); - if (cgl) { - r->SendRaidRemove(r->members[x].membername, cgl); - r->SendRaidCreate(cgl); - r->SendMakeLeaderPacketTo(r->leadername, cgl); - r->SendRaidAdd(r->members[x].membername, cgl); - r->SendBulkRaid(cgl); - if (r->IsLocked()) { - r->SendRaidLockTo(cgl); + if (raid->members[raid->GetPlayerIndex(raid_command_packet->leader_name)].IsGroupLeader) { + raid->SetGroupLeader(raid_command_packet->leader_name, false); + + /* We were the leader of our old group */ + if (old_group < 12) { + /* Assign new group leader if we can */ + for (int x = 0; x < MAX_RAID_MEMBERS; x++) { + if (raid->members[x].GroupNumber == old_group) { + if (strcmp(raid_command_packet->leader_name, raid->members[x].membername) != 0 && strlen(raid_command_packet->leader_name) > 0) { + raid->SetGroupLeader(raid->members[x].membername); + raid->UpdateGroupAAs(old_group); + + Client *client_to_update = entity_list.GetClientByName(raid->members[x].membername); + if (client_to_update) { + raid->SendRaidRemove(raid->members[x].membername, client_to_update); + raid->SendRaidCreate(client_to_update); + raid->SendMakeLeaderPacketTo(raid->leadername, client_to_update); + raid->SendRaidAdd(raid->members[x].membername, client_to_update); + raid->SendBulkRaid(client_to_update); + if (raid->IsLocked()) { + raid->SendRaidLockTo(client_to_update); + } } + else { + auto pack = new ServerPacket(ServerOP_RaidChangeGroup, sizeof(ServerRaidGeneralAction_Struct)); + ServerRaidGeneralAction_Struct *raid_command_packet = (ServerRaidGeneralAction_Struct*)pack->pBuffer; + + raid_command_packet->rid = raid->GetID(); + raid_command_packet->zoneid = zone->GetZoneID(); + raid_command_packet->instance_id = zone->GetInstanceID(); + strn0cpy(raid_command_packet->playername, raid->members[x].membername, 64); + + worldserver.SendPacket(pack); + + safe_delete(pack); + } + break; } - else { - auto pack = new ServerPacket( - ServerOP_RaidChangeGroup, - sizeof( - ServerRaidGeneralAction_Struct)); - ServerRaidGeneralAction_Struct *rga = (ServerRaidGeneralAction_Struct*)pack->pBuffer; - rga->rid = r->GetID(); - strn0cpy(rga->playername, r->members[x].membername, 64); - rga->zoneid = zone->GetZoneID(); - rga->instance_id = zone->GetInstanceID(); - worldserver.SendPacket(pack); - safe_delete(pack); - } - break; } } } } - } - if (grpcount == 0) { - r->SetGroupLeader(ri->leader_name); - r->UpdateGroupAAs(ri->parameter); - } + if (group_count == 0) { + raid->SetGroupLeader(raid_command_packet->leader_name); + raid->UpdateGroupAAs(raid_command_packet->parameter); + } - r->MoveMember(ri->leader_name, ri->parameter); - if (c) { - r->SendGroupDisband(c); - } - else { - auto pack = new ServerPacket(ServerOP_RaidGroupDisband, - sizeof(ServerRaidGeneralAction_Struct)); - ServerRaidGeneralAction_Struct* rga = (ServerRaidGeneralAction_Struct*)pack->pBuffer; - rga->rid = r->GetID(); - rga->zoneid = zone->GetZoneID(); - rga->instance_id = zone->GetInstanceID(); - strn0cpy(rga->playername, ri->leader_name, 64); - worldserver.SendPacket(pack); - safe_delete(pack); - } - //r->SendRaidGroupAdd(ri->leader_name, ri->parameter); - //r->SendRaidGroupRemove(ri->leader_name, oldgrp); - //r->SendGroupUpdate(c); - //break - r->GroupUpdate(ri->parameter); //send group update to our new group - if (oldgrp < 12) //if our old was a group send update there too - r->GroupUpdate(oldgrp); + raid->MoveMember(raid_command_packet->leader_name, raid_command_packet->parameter); + if (c) { + raid->SendGroupDisband(c); + } + else { + auto pack = new ServerPacket(ServerOP_RaidGroupDisband, sizeof(ServerRaidGeneralAction_Struct)); + ServerRaidGeneralAction_Struct* raid_command = (ServerRaidGeneralAction_Struct*)pack->pBuffer; + raid_command->rid = raid->GetID(); + raid_command->zoneid = zone->GetZoneID(); + raid_command->instance_id = zone->GetInstanceID(); + strn0cpy(raid_command->playername, raid_command_packet->leader_name, 64); + worldserver.SendPacket(pack); + safe_delete(pack); + } - //r->SendMakeGroupLeaderPacketAll(); + /* Send group update to our new group */ + raid->GroupUpdate(raid_command_packet->parameter); + + /* If our old was a group send update there too */ + if (old_group < 12) + raid->GroupUpdate(old_group); + + } } - } - else //moving to ungrouped - { - Client *c = entity_list.GetClientByName(ri->leader_name); - uint32 oldgrp = r->GetGroup(ri->leader_name); - if (r->members[r->GetPlayerIndex(ri->leader_name)].IsGroupLeader) { - r->SetGroupLeader(ri->leader_name, false); - for (int x = 0; x < MAX_RAID_MEMBERS; x++) - { - if (r->members[x].GroupNumber == oldgrp && strlen(r->members[x].membername) > 0 && strcmp(r->members[x].membername, ri->leader_name) != 0) - { - r->SetGroupLeader(r->members[x].membername); - r->UpdateGroupAAs(oldgrp); - Client *cgl = entity_list.GetClientByName(r->members[x].membername); - if (cgl) { - r->SendRaidRemove(r->members[x].membername, cgl); - r->SendRaidCreate(cgl); - r->SendMakeLeaderPacketTo(r->leadername, cgl); - r->SendRaidAdd(r->members[x].membername, cgl); - r->SendBulkRaid(cgl); - if (r->IsLocked()) { - r->SendRaidLockTo(cgl); + /* Move player to ungrouped bank */ + else { + Client *c = entity_list.GetClientByName(raid_command_packet->leader_name); + uint32 oldgrp = raid->GetGroup(raid_command_packet->leader_name); + if (raid->members[raid->GetPlayerIndex(raid_command_packet->leader_name)].IsGroupLeader) { + raid->SetGroupLeader(raid_command_packet->leader_name, false); + for (int x = 0; x < MAX_RAID_MEMBERS; x++) { + if (raid->members[x].GroupNumber == oldgrp && strlen(raid->members[x].membername) > 0 && strcmp(raid->members[x].membername, raid_command_packet->leader_name) != 0){ + + raid->SetGroupLeader(raid->members[x].membername); + raid->UpdateGroupAAs(oldgrp); + + Client *client_leaving_group = entity_list.GetClientByName(raid->members[x].membername); + if (client_leaving_group) { + raid->SendRaidRemove(raid->members[x].membername, client_leaving_group); + raid->SendRaidCreate(client_leaving_group); + raid->SendMakeLeaderPacketTo(raid->leadername, client_leaving_group); + raid->SendRaidAdd(raid->members[x].membername, client_leaving_group); + raid->SendBulkRaid(client_leaving_group); + if (raid->IsLocked()) { + raid->SendRaidLockTo(client_leaving_group); + } } + else { + auto pack = new ServerPacket( ServerOP_RaidChangeGroup, sizeof(ServerRaidGeneralAction_Struct)); + ServerRaidGeneralAction_Struct *raid_command = (ServerRaidGeneralAction_Struct*)pack->pBuffer; + + raid_command->rid = raid->GetID(); + strn0cpy(raid_command->playername, raid->members[x].membername, 64); + raid_command->zoneid = zone->GetZoneID(); + raid_command->instance_id = zone->GetInstanceID(); + + worldserver.SendPacket(pack); + safe_delete(pack); + } + break; } - else { - auto pack = new ServerPacket( - ServerOP_RaidChangeGroup, - sizeof(ServerRaidGeneralAction_Struct)); - ServerRaidGeneralAction_Struct *rga = (ServerRaidGeneralAction_Struct*)pack->pBuffer; - rga->rid = r->GetID(); - strn0cpy(rga->playername, r->members[x].membername, 64); - rga->zoneid = zone->GetZoneID(); - rga->instance_id = zone->GetInstanceID(); - worldserver.SendPacket(pack); - safe_delete(pack); - } - break; } } + raid->MoveMember(raid_command_packet->leader_name, 0xFFFFFFFF); + if (c) { + raid->SendGroupDisband(c); + } + else { + auto pack = new ServerPacket(ServerOP_RaidGroupDisband, sizeof(ServerRaidGeneralAction_Struct)); + ServerRaidGeneralAction_Struct* raid_command = (ServerRaidGeneralAction_Struct*)pack->pBuffer; + + raid_command->rid = raid->GetID(); + raid_command->zoneid = zone->GetZoneID(); + raid_command->instance_id = zone->GetInstanceID(); + strn0cpy(raid_command->playername, raid_command_packet->leader_name, 64); + + worldserver.SendPacket(pack); + + safe_delete(pack); + } + + raid->GroupUpdate(oldgrp); } - r->MoveMember(ri->leader_name, 0xFFFFFFFF); - if (c) { - r->SendGroupDisband(c); - } - else { - auto pack = new ServerPacket(ServerOP_RaidGroupDisband, - sizeof(ServerRaidGeneralAction_Struct)); - ServerRaidGeneralAction_Struct* rga = (ServerRaidGeneralAction_Struct*)pack->pBuffer; - rga->rid = r->GetID(); - rga->zoneid = zone->GetZoneID(); - rga->instance_id = zone->GetInstanceID(); - strn0cpy(rga->playername, ri->leader_name, 64); - worldserver.SendPacket(pack); - safe_delete(pack); - } - //r->SendRaidGroupRemove(ri->leader_name, oldgrp); - r->GroupUpdate(oldgrp); - //r->SendMakeGroupLeaderPacketAll(); } - } - break; - } - case RaidCommandRaidLock: - { - Raid *r = entity_list.GetRaidByClient(this); - if (r) - { - if (!r->IsLocked()) - r->LockRaid(true); - else - r->SendRaidLockTo(this); - } - break; - } - case RaidCommandRaidUnlock: - { - Raid *r = entity_list.GetRaidByClient(this); - if (r) - { - if (r->IsLocked()) - r->LockRaid(false); - else - r->SendRaidUnlockTo(this); - } - break; - } - case RaidCommandLootType2: - case RaidCommandLootType: - { - Raid *r = entity_list.GetRaidByClient(this); - if (r) - { - Message(15, "Loot type changed to: %d.", ri->parameter); - r->ChangeLootType(ri->parameter); - } - break; - } - case RaidCommandAddLooter2: - case RaidCommandAddLooter: - { - Raid *r = entity_list.GetRaidByClient(this); - if (r) - { - Message(15, "Adding %s as a raid looter.", ri->leader_name); - r->AddRaidLooter(ri->leader_name); - } - break; - } + Client *client_moved = entity_list.GetClientByName(raid_command_packet->leader_name); - case RaidCommandRemoveLooter2: - case RaidCommandRemoveLooter: - { - Raid *r = entity_list.GetRaidByClient(this); - if (r) - { - Message(15, "Removing %s as a raid looter.", ri->leader_name); - r->RemoveRaidLooter(ri->leader_name); - } - break; - } + if (client_moved && client_moved->GetRaid()) { + client_moved->GetRaid()->SendHPManaEndPacketsTo(client_moved); + client_moved->GetRaid()->SendHPManaEndPacketsFrom(client_moved); - case RaidCommandMakeLeader: - { - Raid *r = entity_list.GetRaidByClient(this); - if (r) - { - if (strcmp(r->leadername, GetName()) == 0) { - r->SetRaidLeader(GetName(), ri->leader_name); - r->UpdateRaidAAs(); - r->SendAllRaidLeadershipAA(); + Log(Logs::General, Logs::HP_Update, + "Client::Handle_OP_RaidCommand :: %s sending and recieving HP/Mana/End updates", + client_moved->GetCleanName() + ); } - } - break; - } - case RaidCommandSetMotd: - { - Raid *r = entity_list.GetRaidByClient(this); - if (!r) break; - // we don't use the RaidGeneral here! - RaidMOTD_Struct *motd = (RaidMOTD_Struct *)app->pBuffer; - r->SetRaidMOTD(std::string(motd->motd)); - r->SaveRaidMOTD(); - r->SendRaidMOTDToWorld(); - break; - } + } + case RaidCommandRaidLock: + { + Raid *raid = entity_list.GetRaidByClient(this); + if (raid) { + if (!raid->IsLocked()) + raid->LockRaid(true); + else + raid->SendRaidLockTo(this); + } + break; + } + case RaidCommandRaidUnlock: + { + Raid *raid = entity_list.GetRaidByClient(this); + if (raid) + { + if (raid->IsLocked()) + raid->LockRaid(false); + else + raid->SendRaidUnlockTo(this); + } + break; + } + case RaidCommandLootType2: + case RaidCommandLootType: + { + Raid *raid = entity_list.GetRaidByClient(this); + if (raid) { + Message(15, "Loot type changed to: %d.", raid_command_packet->parameter); + raid->ChangeLootType(raid_command_packet->parameter); + } + break; + } - default: { - Message(13, "Raid command (%d) NYI", ri->action); - break; - } + case RaidCommandAddLooter2: + case RaidCommandAddLooter: + { + Raid *raid = entity_list.GetRaidByClient(this); + if (raid) { + Message(15, "Adding %s as a raid looter.", raid_command_packet->leader_name); + raid->AddRaidLooter(raid_command_packet->leader_name); + } + break; + } + + case RaidCommandRemoveLooter2: + case RaidCommandRemoveLooter: + { + Raid *raid = entity_list.GetRaidByClient(this); + if (raid) { + Message(15, "Removing %s as a raid looter.", raid_command_packet->leader_name); + raid->RemoveRaidLooter(raid_command_packet->leader_name); + } + break; + } + + case RaidCommandMakeLeader: + { + Raid *raid = entity_list.GetRaidByClient(this); + if (raid) { + if (strcmp(raid->leadername, GetName()) == 0) { + raid->SetRaidLeader(GetName(), raid_command_packet->leader_name); + raid->UpdateRaidAAs(); + raid->SendAllRaidLeadershipAA(); + } + } + break; + } + + case RaidCommandSetMotd: + { + Raid *raid = entity_list.GetRaidByClient(this); + if (!raid) + break; + // we don't use the RaidGeneral here! + RaidMOTD_Struct *motd = (RaidMOTD_Struct *)app->pBuffer; + raid->SetRaidMOTD(std::string(motd->motd)); + raid->SaveRaidMOTD(); + raid->SendRaidMOTDToWorld(); + break; + } + + default: { + Message(13, "Raid command (%d) NYI", raid_command_packet->action); + break; + } } } @@ -12041,15 +12104,6 @@ void Client::Handle_OP_SenseHeading(const EQApplicationPacket *app) int chancemod = 0; - // The client seems to limit sense heading packets based on skill - // level. So if we're really low, we don't hit this routine very often. - // I think it's the GUI deciding when to skill you up. - // So, I'm adding a mod here which is larger at lower levels so - // very low levels get a much better chance to skill up when the GUI - // eventually sends a message. - if (GetLevel() <= 8) - chancemod += (9 - level) * 10; - CheckIncreaseSkill(EQEmu::skills::SkillSenseHeading, nullptr, chancemod); return; @@ -12072,7 +12126,8 @@ void Client::Handle_OP_SenseTraps(const EQApplicationPacket *app) p_timers.Start(pTimerSenseTraps, reuse - 1); - Trap* trap = entity_list.FindNearbyTrap(this, 800); + float trap_curdist = 0; + Trap* trap = entity_list.FindNearbyTrap(this, 800, trap_curdist); CheckIncreaseSkill(EQEmu::skills::SkillSenseTraps, nullptr); @@ -13012,6 +13067,8 @@ void Client::Handle_OP_SpawnAppearance(const EQApplicationPacket *app) InterruptSpell(); SetFeigned(false); BindWound(this, false, true); + tmSitting = Timer::GetCurrentTime(); + BuffFadeBySitModifier(); } else if (sa->parameter == ANIM_CROUCH) { if (!UseBardSpellLogic()) @@ -13368,10 +13425,9 @@ void Client::Handle_OP_TargetCommand(const EQApplicationPacket *app) } QueuePacket(app); - EQApplicationPacket hp_app; + GetTarget()->IsTargeted(1); - GetTarget()->CreateHPPacket(&hp_app); - QueuePacket(&hp_app, false); + SendHPUpdate(); } else { diff --git a/zone/client_packet.h b/zone/client_packet.h index c63a57825..1e7981ed9 100644 --- a/zone/client_packet.h +++ b/zone/client_packet.h @@ -204,6 +204,7 @@ void Handle_OP_MercenaryTimerRequest(const EQApplicationPacket *app); void Handle_OP_MoveCoin(const EQApplicationPacket *app); void Handle_OP_MoveItem(const EQApplicationPacket *app); + void Handle_OP_MoveMultipleItems(const EQApplicationPacket *app); void Handle_OP_OpenContainer(const EQApplicationPacket *app); void Handle_OP_OpenGuildTributeMaster(const EQApplicationPacket *app); void Handle_OP_OpenInventory(const EQApplicationPacket *app); diff --git a/zone/client_process.cpp b/zone/client_process.cpp index 3e08c1fba..804bc32ca 100644 --- a/zone/client_process.cpp +++ b/zone/client_process.cpp @@ -38,6 +38,7 @@ #include #endif +#include "../common/data_verification.h" #include "../common/rulesys.h" #include "../common/skills.h" #include "../common/spdat.h" @@ -243,24 +244,42 @@ bool Client::Process() { /* Build a close range list of NPC's */ if (npc_close_scan_timer.Check()) { - close_mobs.clear(); - auto &mob_list = entity_list.GetMobList(); - float scan_range = (RuleI(Range, ClientNPCScan) * RuleI(Range, ClientNPCScan)); - float client_update_range = (RuleI(Range, MobPositionUpdates) * RuleI(Range, MobPositionUpdates)); + /* Force spawn updates when traveled far */ + bool force_spawn_updates = false; + float client_update_range = (RuleI(Range, ClientForceSpawnUpdateRange) * RuleI(Range, ClientForceSpawnUpdateRange)); + if (DistanceSquared(last_major_update_position, m_Position) >= client_update_range) { + last_major_update_position = m_Position; + force_spawn_updates = true; + } + float scan_range = (RuleI(Range, ClientNPCScan) * RuleI(Range, ClientNPCScan)); + auto &mob_list = entity_list.GetMobList(); for (auto itr = mob_list.begin(); itr != mob_list.end(); ++itr) { Mob* mob = itr->second; + float distance = DistanceSquared(m_Position, mob->GetPosition()); if (mob->IsNPC()) { if (distance <= scan_range) { close_mobs.insert(std::pair(mob, distance)); } - else if (mob->GetAggroRange() > scan_range) { + else if ((mob->GetAggroRange() * mob->GetAggroRange()) > scan_range) { close_mobs.insert(std::pair(mob, distance)); } } + + if (force_spawn_updates && mob != this) { + + if (mob->is_distance_roamer) { + mob->SendPositionUpdateToClient(this); + continue; + } + + if (distance <= client_update_range) + mob->SendPositionUpdateToClient(this); + } + } } @@ -513,6 +532,10 @@ bool Client::Process() { DoEnduranceUpkeep(); } + // this is independent of the tick timer + if (consume_food_timer.Check()) + DoStaminaHungerUpdate(); + if (tic_timer.Check() && !dead) { CalcMaxHP(); CalcMaxMana(); @@ -523,7 +546,6 @@ bool Client::Process() { DoManaRegen(); DoEnduranceRegen(); BuffProcess(); - DoStaminaUpdate(); if (tribute_timer.Check()) { ToggleTribute(true); //re-activate the tribute. @@ -1041,7 +1063,8 @@ void Client::OPRezzAnswer(uint32 Action, uint32 SpellID, uint16 ZoneID, uint16 I SetMana(0); SetHP(GetMaxHP()/5); int rez_eff = 756; - if (GetRace() == BARBARIAN || GetRace() == DWARF || GetRace() == TROLL || GetRace() == OGRE) + if (RuleB(Character, UseOldRaceRezEffects) && + (GetRace() == BARBARIAN || GetRace() == DWARF || GetRace() == TROLL || GetRace() == OGRE)) rez_eff = 757; SpellOnTarget(rez_eff, this); // Rezz effects } @@ -1806,7 +1829,7 @@ void Client::OPGMSummon(const EQApplicationPacket *app) } void Client::DoHPRegen() { - SetHP(GetHP() + CalcHPRegen() + RestRegenHP); + SetHP(GetHP() + CalcHPRegen()); SendHPUpdate(); } @@ -1814,41 +1837,55 @@ void Client::DoManaRegen() { if (GetMana() >= max_mana && spellbonuses.ManaRegen >= 0) return; - SetMana(GetMana() + CalcManaRegen() + RestRegenMana); + if (GetMana() < max_mana && (IsSitting() || CanMedOnHorse()) && HasSkill(EQEmu::skills::SkillMeditate)) + CheckIncreaseSkill(EQEmu::skills::SkillMeditate, nullptr, -5); + + SetMana(GetMana() + CalcManaRegen()); CheckManaEndUpdate(); } - -void Client::DoStaminaUpdate() { - if(!stamina_timer.Check()) - return; - +void Client::DoStaminaHungerUpdate() +{ auto outapp = new EQApplicationPacket(OP_Stamina, sizeof(Stamina_Struct)); - Stamina_Struct* sta = (Stamina_Struct*)outapp->pBuffer; + Stamina_Struct *sta = (Stamina_Struct *)outapp->pBuffer; - if(zone->GetZoneID() != 151) { + Log(Logs::General, Logs::Food, "Client::DoStaminaHungerUpdate() hunger_level: %i thirst_level: %i before loss", + m_pp.hunger_level, m_pp.thirst_level); + + if (zone->GetZoneID() != 151 && !GetGM()) { int loss = RuleI(Character, FoodLossPerUpdate); - if (m_pp.hunger_level > 0) - m_pp.hunger_level-=loss; - if (m_pp.thirst_level > 0) - m_pp.thirst_level-=loss; - sta->food = m_pp.hunger_level > 6000 ? 6000 : m_pp.hunger_level; - sta->water = m_pp.thirst_level> 6000 ? 6000 : m_pp.thirst_level; - } - else { + if (GetHorseId() != 0) + loss *= 3; + + m_pp.hunger_level = EQEmu::Clamp(m_pp.hunger_level - loss, 0, 6000); + m_pp.thirst_level = EQEmu::Clamp(m_pp.thirst_level - loss, 0, 6000); + if (spellbonuses.hunger) { + m_pp.hunger_level = EQEmu::ClampLower(m_pp.hunger_level, 3500); + m_pp.thirst_level = EQEmu::ClampLower(m_pp.thirst_level, 3500); + } + sta->food = m_pp.hunger_level; + sta->water = m_pp.thirst_level; + } else { // No auto food/drink consumption in the Bazaar sta->food = 6000; sta->water = 6000; } + + Log(Logs::General, Logs::Food, + "Client::DoStaminaHungerUpdate() Current hunger_level: %i = (%i minutes left) thirst_level: %i = (%i " + "minutes left) - after loss", + m_pp.hunger_level, m_pp.hunger_level, m_pp.thirst_level, m_pp.thirst_level); + FastQueuePacket(&outapp); } void Client::DoEnduranceRegen() { - if(GetEndurance() >= GetMaxEndurance()) - return; + // endurance has some negative mods that could result in a negative regen when starved + int regen = CalcEnduranceRegen(); - SetEndurance(GetEndurance() + CalcEnduranceRegen() + RestRegenEndurance); + if (regen < 0 || (regen > 0 && GetEndurance() < GetMaxEndurance())) + SetEndurance(GetEndurance() + regen); } void Client::DoEnduranceUpkeep() { @@ -1897,12 +1934,12 @@ void Client::CalcRestState() { // The client must have been out of combat for RuleI(Character, RestRegenTimeToActivate) seconds, // must be sitting down, and must not have any detrimental spells affecting them. // - if(!RuleI(Character, RestRegenPercent)) + if(!RuleB(Character, RestRegenEnabled)) return; - RestRegenHP = RestRegenMana = RestRegenEndurance = 0; + ooc_regen = false; - if(AggroCount || !IsSitting()) + if(AggroCount || !(IsSitting() || CanMedOnHorse())) return; if(!rest_timer.Check(false)) @@ -1917,12 +1954,8 @@ void Client::CalcRestState() { } } - RestRegenHP = (GetMaxHP() * RuleI(Character, RestRegenPercent) / 100); + ooc_regen = true; - RestRegenMana = (GetMaxMana() * RuleI(Character, RestRegenPercent) / 100); - - if(RuleB(Character, RestRegenEndurance)) - RestRegenEndurance = (GetMaxEndurance() * RuleI(Character, RestRegenPercent) / 100); } void Client::DoTracking() diff --git a/zone/command.cpp b/zone/command.cpp index 829588ca7..db24d3185 100644 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -321,6 +321,7 @@ int command_init(void) command_add("reloadqst", " - Clear quest cache (any argument causes it to also stop all timers)", 150, command_reloadqst) || command_add("reloadrulesworld", "Executes a reload of all rules in world specifically.", 80, command_reloadworldrules) || command_add("reloadstatic", "- Reload Static Zone Data", 150, command_reloadstatic) || + command_add("reloadtraps", "- Repops all traps in the current zone.", 80, command_reloadtraps) || command_add("reloadtitles", "- Reload player titles from the database", 150, command_reloadtitles) || command_add("reloadworld", "[0|1] - Clear quest cache (0 - no repop, 1 - repop)", 255, command_reloadworld) || command_add("reloadzps", "- Reload zone points from database", 150, command_reloadzps) || @@ -375,6 +376,7 @@ int command_init(void) command_add("task", "(subcommand) - Task system commands", 150, command_task) || command_add("tattoo", "- Change the tattoo of your target (Drakkin Only)", 80, command_tattoo) || command_add("tempname", "[newname] - Temporarily renames your target. Leave name blank to restore the original name.", 100, command_tempname) || + command_add("petname", "[newname] - Temporarily renames your pet. Leave name blank to restore the original name.", 100, command_petname) || command_add("texture", "[texture] [helmtexture] - Change your or your target's appearance, use 255 to show equipment", 10, command_texture) || command_add("time", "[HH] [MM] - Set EQ time", 90, command_time) || command_add("timers", "- Display persistent timers for target", 200, command_timers) || @@ -382,6 +384,7 @@ int command_init(void) command_add("title", "[text] [1 = create title table row] - Set your or your player target's title", 50, command_title) || command_add("titlesuffix", "[text] [1 = create title table row] - Set your or your player target's title suffix", 50, command_titlesuffix) || command_add("traindisc", "[level] - Trains all the disciplines usable by the target, up to level specified. (may freeze client for a few seconds)", 150, command_traindisc) || + command_add("trapinfo", "- Gets infomation about the traps currently spawned in the zone.", 81, command_trapinfo) || command_add("tune", "Calculate ideal statical values related to combat.", 100, command_tune) || command_add("undyeme", "- Remove dye from all of your armor slots", 0, command_undyeme) || command_add("unfreeze", "- Unfreeze your target", 80, command_unfreeze) || @@ -4157,6 +4160,26 @@ void command_tempname(Client *c, const Seperator *sep) } } +void command_petname(Client *c, const Seperator *sep) +{ + Mob *target; + target = c->GetTarget(); + + if(!target) + c->Message(0, "Usage: #petname newname (requires a target)"); + else if(target->IsPet() && (target->GetOwnerID() == c->GetID()) && strlen(sep->arg[1]) > 0) + { + char *oldname = strdup(target->GetName()); + target->TempName(sep->arg[1]); + c->Message(0, "Renamed %s to %s", oldname, sep->arg[1]); + free(oldname); + } + else { + target->TempName(); + c->Message(0, "Restored the original name"); + } +} + void command_npcspecialattk(Client *c, const Seperator *sep) { if (c->GetTarget()==0 || c->GetTarget()->IsClient() || strlen(sep->arg[1]) <= 0 || strlen(sep->arg[2]) <= 0) @@ -7257,7 +7280,7 @@ void command_bestz(Client *c, const Seperator *sep) { float best_z = zone->zonemap->FindBestZ(me, &hit); - if (best_z != -999999) + if (best_z != BEST_Z_INVALID) { c->Message(0, "Z is %.3f at (%.3f, %.3f).", best_z, me.x, me.y); } @@ -10851,6 +10874,16 @@ void command_reloadperlexportsettings(Client *c, const Seperator *sep) } } +void command_trapinfo(Client *c, const Seperator *sep) +{ + entity_list.GetTrapInfo(c); +} + +void command_reloadtraps(Client *c, const Seperator *sep) +{ + entity_list.UpdateAllTraps(true, true); + c->Message(CC_Default, "Traps reloaded for %s.", zone->GetShortName()); +} // All new code added to command.cpp should be BEFORE this comment line. Do no append code to this file below the BOTS code block. #ifdef BOTS diff --git a/zone/command.h b/zone/command.h index 0a850fbca..a27f173fc 100644 --- a/zone/command.h +++ b/zone/command.h @@ -228,6 +228,7 @@ void command_reloadperlexportsettings(Client *c, const Seperator *sep); void command_reloadqst(Client *c, const Seperator *sep); void command_reloadstatic(Client *c, const Seperator *sep); void command_reloadtitles(Client *c, const Seperator *sep); +void command_reloadtraps(Client* c, const Seperator *sep); void command_reloadworld(Client *c, const Seperator *sep); void command_reloadworldrules(Client *c, const Seperator *sep); void command_reloadzps(Client *c, const Seperator *sep); @@ -286,6 +287,7 @@ void command_synctod(Client *c, const Seperator *sep); void command_task(Client *c, const Seperator *sep); void command_tattoo(Client *c, const Seperator *sep); void command_tempname(Client *c, const Seperator *sep); +void command_petname(Client *c, const Seperator *sep); void command_testspawn(Client *c, const Seperator *sep); void command_testspawnkill(Client *c, const Seperator *sep); void command_texture(Client *c, const Seperator *sep); @@ -295,6 +297,7 @@ void command_timezone(Client *c, const Seperator *sep); void command_title(Client *c, const Seperator *sep); void command_titlesuffix(Client *c, const Seperator *sep); void command_traindisc(Client *c, const Seperator *sep); +void command_trapinfo(Client* c, const Seperator *sep); void command_tune(Client *c, const Seperator *sep); void command_undye(Client *c, const Seperator *sep); void command_undyeme(Client *c, const Seperator *sep); diff --git a/zone/common.h b/zone/common.h index 7b6382a20..f7b157115 100644 --- a/zone/common.h +++ b/zone/common.h @@ -550,6 +550,7 @@ struct StatBonuses { int FeignedMinionChance; // SPA 281 base1 = chance, just like normal FD int aura_slots; int trap_slots; + bool hunger; // Song of Sustenance -- min caps to 3500 }; typedef struct @@ -603,6 +604,11 @@ enum { //type arguments to DoAnim }; +enum { + SKILLUP_UNKNOWN = 0, + SKILLUP_SUCCESS = 1, + SKILLUP_FAILURE = 2 +}; typedef enum { petFamiliar, //only listens to /pet get lost diff --git a/zone/effects.cpp b/zone/effects.cpp index adc7b16b2..6b0c2a2ad 100644 --- a/zone/effects.cpp +++ b/zone/effects.cpp @@ -611,9 +611,13 @@ bool Client::UseDiscipline(uint32 spell_id, uint32 target) { return false; } + // the client does this check before calling CastSpell, should prevent discs being eaten + if (spell.buffdurationformula != 0 && spell.targettype == ST_Self && HasDiscBuff()) + return false; + //Check the disc timer pTimerType DiscTimer = pTimerDisciplineReuseStart + spell.EndurTimerIndex; - if(!p_timers.Expired(&database, DiscTimer)) { + if(!p_timers.Expired(&database, DiscTimer, false)) { // lets not set the reuse timer in case CastSpell fails (or we would have to turn off the timer, but CastSpell will set it as well) /*char val1[20]={0};*/ //unused /*char val2[20]={0};*/ //unused uint32 remain = p_timers.GetRemainingTime(DiscTimer); diff --git a/zone/embperl.cpp b/zone/embperl.cpp index 71338c05a..bc20bd4ef 100644 --- a/zone/embperl.cpp +++ b/zone/embperl.cpp @@ -119,7 +119,7 @@ void Embperl::DoInit() { perl_run(my_perl); //a little routine we use a lot. - eval_pv("sub my_eval {eval $_[0];}", TRUE); //dies on error + eval_pv("sub my_eval { eval $_[0];}", TRUE); //dies on error //ruin the perl exit and command: eval_pv("sub my_exit {}",TRUE); @@ -149,7 +149,7 @@ void Embperl::DoInit() { //make a tieable class to capture IO and pass it into EQEMuLog eval_pv( "package EQEmuIO; " - "sub TIEHANDLE { my $me = bless {}, $_[0]; $me->PRINT('Creating '.$me); return($me); } " + "sub TIEHANDLE { my $me = bless {}, $_[0]; $me->PRINT('Creating '. $me); return($me); } " "sub WRITE { } " //dunno why I need to shift off fmt here, but it dosent like without it "sub PRINTF { my $me = shift; my $fmt = shift; $me->PRINT(sprintf($fmt, @_)); } " @@ -237,6 +237,7 @@ void Embperl::init_eval_file(void) { eval_pv( "our %Cache;" + "no warnings;" "use Symbol qw(delete_package);" "sub eval_file {" "my($package, $filename) = @_;" @@ -246,8 +247,9 @@ void Embperl::init_eval_file(void) "if(defined $Cache{$package}{mtime}&&$Cache{$package}{mtime} <= $mtime && !($package eq 'plugin')){" " return;" "} else {" - //we 'my' $filename,$mtime,$package,$sub to prevent them from changing our state up here. - " eval(\"package $package; my(\\$filename,\\$mtime,\\$package,\\$sub); \\$isloaded = 1; require '$filename'; \");" + // we 'my' $filename,$mtime,$package,$sub to prevent them from changing our state up here. + " eval(\"package $package; my(\\$filename,\\$mtime,\\$package,\\$sub); \\$isloaded = 1; require './$filename'; \");" + // " print $@ if $@;" /* "local *FH;open FH, $filename or die \"open '$filename' $!\";" "local($/) = undef;my $sub = ;close FH;" "my $eval = qq{package $package; sub handler { $sub; }};" diff --git a/zone/embxs.cpp b/zone/embxs.cpp index fe4a12aee..3816850db 100644 --- a/zone/embxs.cpp +++ b/zone/embxs.cpp @@ -99,6 +99,10 @@ XS(XS_EQEmuIO_PRINT) /* Strip newlines from log message 'str' */ *std::remove(str, str + strlen(str), '\n') = '\0'; + std::string log_string = str; + if (log_string.find("did not return a true") != std::string::npos) + return;; + int i; int pos = 0; int len = 0; diff --git a/zone/entity.cpp b/zone/entity.cpp index 03b4ccf5a..4b0599541 100644 --- a/zone/entity.cpp +++ b/zone/entity.cpp @@ -648,7 +648,7 @@ void EntityList::AddNPC(NPC *npc, bool SendSpawnPacket, bool dontqueue) parse->EventNPC(EVENT_SPAWN, npc, nullptr, "", 0); - npc->FixZ(); + npc->FixZ(1); uint16 emoteid = npc->GetEmoteID(); if (emoteid != 0) @@ -1419,10 +1419,10 @@ void EntityList::RemoveFromTargets(Mob *mob, bool RemoveFromXTargets) continue; if (RemoveFromXTargets) { - if (m->IsClient() && mob->CheckAggro(m)) + if (m->IsClient() && (mob->CheckAggro(m) || mob->IsOnFeignMemory(m->CastToClient()))) m->CastToClient()->RemoveXTarget(mob, false); // FadingMemories calls this function passing the client. - else if (mob->IsClient() && m->CheckAggro(mob)) + else if (mob->IsClient() && (m->CheckAggro(mob) || m->IsOnFeignMemory(mob->CastToClient()))) mob->CastToClient()->RemoveXTarget(m, false); } @@ -1461,7 +1461,7 @@ void EntityList::RefreshAutoXTargets(Client *c) if (!m || m->GetHP() <= 0) continue; - if (m->CheckAggro(c) && !c->IsXTarget(m)) { + if ((m->CheckAggro(c) || m->IsOnFeignMemory(c)) && !c->IsXTarget(m)) { c->AddAutoXTarget(m, false); // we only call this before a bulk, so lets not send right away break; } @@ -2617,12 +2617,13 @@ void EntityList::RemoveFromHateLists(Mob *mob, bool settoone) auto it = npc_list.begin(); while (it != npc_list.end()) { if (it->second->CheckAggro(mob)) { - if (!settoone) + if (!settoone) { it->second->RemoveFromHateList(mob); - else + if (mob->IsClient()) + mob->CastToClient()->RemoveXTarget(it->second, false); // gotta do book keeping + } else { it->second->SetHateAmountOnEnt(mob, 1); - if (mob->IsClient()) - mob->CastToClient()->RemoveXTarget(it->second, false); // gotta do book keeping + } } ++it; } @@ -3079,7 +3080,10 @@ void EntityList::ClearAggro(Mob* targ) c->RemoveXTarget(it->second, false); it->second->RemoveFromHateList(targ); } - it->second->RemoveFromFeignMemory(targ->CastToClient()); //just in case we feigned + if (c && it->second->IsOnFeignMemory(c)) { + it->second->RemoveFromFeignMemory(c); //just in case we feigned + c->RemoveXTarget(it->second, false); + } ++it; } } @@ -3088,7 +3092,8 @@ void EntityList::ClearFeignAggro(Mob *targ) { auto it = npc_list.begin(); while (it != npc_list.end()) { - if (it->second->CheckAggro(targ)) { + // add Feign Memory check because sometimes weird stuff happens + if (it->second->CheckAggro(targ) || (targ->IsClient() && it->second->IsOnFeignMemory(targ->CastToClient()))) { if (it->second->GetSpecialAbility(IMMUNE_FEIGN_DEATH)) { ++it; continue; @@ -3252,7 +3257,7 @@ void EntityList::AddHealAggro(Mob *target, Mob *caster, uint16 hate) for (auto &e : npc_list) { auto &npc = e.second; - if (!npc->CheckAggro(target) || npc->IsFeared()) + if (!npc->CheckAggro(target) || npc->IsFeared() || npc->IsPet()) continue; if (zone->random.Roll(50)) // witness check -- place holder diff --git a/zone/entity.h b/zone/entity.h index 29075d9fe..dccbb45b3 100644 --- a/zone/entity.h +++ b/zone/entity.h @@ -365,7 +365,7 @@ public: //trap stuff Mob* GetTrapTrigger(Trap* trap); void SendAlarm(Trap* trap, Mob* currenttarget, uint8 kos); - Trap* FindNearbyTrap(Mob* searcher, float max_dist); + Trap* FindNearbyTrap(Mob* searcher, float max_dist, float &curdist, bool detected = false); void AddHealAggro(Mob* target, Mob* caster, uint16 hate); Mob* FindDefenseNPC(uint32 npcid); @@ -473,6 +473,10 @@ public: void RefreshClientXTargets(Client *c); void SendAlternateAdvancementStats(); + void GetTrapInfo(Client* client); + bool IsTrapGroupSpawned(uint32 trap_id, uint8 group); + void UpdateAllTraps(bool respawn, bool repopnow = false); + void ClearTrapPointers(); protected: friend class Zone; void Depop(bool StartSpawnTimer = false); diff --git a/zone/exp.cpp b/zone/exp.cpp index 7d9a02fa8..1fd8ea0a3 100644 --- a/zone/exp.cpp +++ b/zone/exp.cpp @@ -327,8 +327,7 @@ void Client::AddEXP(uint32 in_add_exp, uint8 conlevel, bool resexp) { aatotalmod *= zone->newzone_data.zone_exp_multiplier; } - - + // Shouldn't race not affect AA XP? if(RuleB(Character,UseRaceClassExpBonuses)) { if(GetBaseRace() == HALFLING){ @@ -340,6 +339,12 @@ void Client::AddEXP(uint32 in_add_exp, uint8 conlevel, bool resexp) { } } + // why wasn't this here? Where should it be? + if(zone->IsHotzone()) + { + aatotalmod += RuleR(Zone, HotZoneBonus); + } + if(RuleB(Zone, LevelBasedEXPMods)){ if(zone->level_exp_mod[GetLevel()].ExpMod){ add_exp *= zone->level_exp_mod[GetLevel()].ExpMod; diff --git a/zone/fearpath.cpp b/zone/fearpath.cpp index c2ce95fbe..6f3580406 100644 --- a/zone/fearpath.cpp +++ b/zone/fearpath.cpp @@ -163,7 +163,7 @@ void Mob::CalculateNewFearpoint() ranx = GetX()+zone->random.Int(0, ran-1)-zone->random.Int(0, ran-1); rany = GetY()+zone->random.Int(0, ran-1)-zone->random.Int(0, ran-1); ranz = FindGroundZ(ranx,rany); - if (ranz == -999999) + if (ranz == BEST_Z_INVALID) continue; float fdist = ranz - GetZ(); if (fdist >= -12 && fdist <= 12 && CheckCoordLosNoZLeaps(GetX(),GetY(),GetZ(),ranx,rany,ranz)) diff --git a/zone/groups.cpp b/zone/groups.cpp index c6fa6a978..a7e05bf49 100644 --- a/zone/groups.cpp +++ b/zone/groups.cpp @@ -2464,3 +2464,33 @@ bool Group::HasRole(Mob *m, uint8 Role) return false; } +void Group::QueueClients(Mob *sender, const EQApplicationPacket *app, bool ack_required /*= true*/, bool ignore_sender /*= true*/, float distance /*= 0*/) { + if (sender && sender->IsClient()) { + for (uint32 i = 0; i < MAX_GROUP_MEMBERS; i++) { + + if (!members[i]) + continue; + + if (!members[i]->IsClient()) + continue; + + if (ignore_sender && members[i] == sender) + continue; + + /* If we don't have a distance requirement - send to all members */ + if (distance == 0) { + members[i]->CastToClient()->QueuePacket(app, ack_required); + } + else { + /* If negative distance - we check if current distance is greater than X */ + if (distance <= 0 && DistanceSquared(sender->GetPosition(), members[i]->GetPosition()) >= (distance * distance)) { + members[i]->CastToClient()->QueuePacket(app, ack_required); + } + /* If positive distance - we check if current distance is less than X */ + else if (distance >= 0 && DistanceSquared(sender->GetPosition(), members[i]->GetPosition()) <= (distance * distance)) { + members[i]->CastToClient()->QueuePacket(app, ack_required); + } + } + } + } +} \ No newline at end of file diff --git a/zone/groups.h b/zone/groups.h index 79fb0d8b6..913a6c169 100644 --- a/zone/groups.h +++ b/zone/groups.h @@ -140,6 +140,7 @@ public: inline int GetLeadershipAA(int AAID) { return LeaderAbilities.ranks[AAID]; } void ClearAllNPCMarks(); void QueueHPPacketsForNPCHealthAA(Mob* sender, const EQApplicationPacket* app); + void QueueClients(Mob *sender, const EQApplicationPacket *app, bool ack_required = true, bool ignore_sender = true, float distance = 0); void ChangeLeader(Mob* newleader); const char *GetClientNameByIndex(uint8 index); void UpdateXTargetMarkedNPC(uint32 Number, Mob *m); diff --git a/zone/horse.cpp b/zone/horse.cpp index efe4fe898..fe5762d5a 100644 --- a/zone/horse.cpp +++ b/zone/horse.cpp @@ -151,6 +151,7 @@ void Client::SummonHorse(uint16 spell_id) { uint16 tmpID = horse->GetID(); SetHorseId(tmpID); + BuffFadeBySitModifier(); } diff --git a/zone/loottables.cpp b/zone/loottables.cpp index 08d16e412..d9fa86257 100644 --- a/zone/loottables.cpp +++ b/zone/loottables.cpp @@ -336,7 +336,8 @@ void NPC::AddLootDrop(const EQEmu::ItemData *item2, ItemList* itemlist, int16 ch eslot = EQEmu::textures::weaponPrimary; if (item2->Damage > 0) { SendAddPlayerState(PlayerState::PrimaryWeaponEquipped); - SetFacestab(true); + if (!RuleB(Combat, ClassicNPCBackstab)) + SetFacestab(true); } if (item2->IsType2HWeapon()) SetTwoHanderEquipped(true); diff --git a/zone/lua_client.cpp b/zone/lua_client.cpp index 64bf2321c..45ea8472e 100644 --- a/zone/lua_client.cpp +++ b/zone/lua_client.cpp @@ -1440,6 +1440,54 @@ void Lua_Client::FilteredMessage(Mob *sender, uint32 type, int filter, const cha self->FilteredMessage(sender, type, (eqFilterType)filter, message); } +void Lua_Client::EnableAreaHPRegen(int value) +{ + Lua_Safe_Call_Void(); + self->EnableAreaHPRegen(value); +} + +void Lua_Client::DisableAreaHPRegen() +{ + Lua_Safe_Call_Void(); + self->DisableAreaHPRegen(); +} + +void Lua_Client::EnableAreaManaRegen(int value) +{ + Lua_Safe_Call_Void(); + self->EnableAreaManaRegen(value); +} + +void Lua_Client::DisableAreaManaRegen() +{ + Lua_Safe_Call_Void(); + self->DisableAreaManaRegen(); +} + +void Lua_Client::EnableAreaEndRegen(int value) +{ + Lua_Safe_Call_Void(); + self->EnableAreaEndRegen(value); +} + +void Lua_Client::DisableAreaEndRegen() +{ + Lua_Safe_Call_Void(); + self->DisableAreaEndRegen(); +} + +void Lua_Client::EnableAreaRegens(int value) +{ + Lua_Safe_Call_Void(); + self->EnableAreaRegens(value); +} + +void Lua_Client::DisableAreaRegens() +{ + Lua_Safe_Call_Void(); + self->DisableAreaRegens(); +} + luabind::scope lua_register_client() { return luabind::class_("Client") .def(luabind::constructor<>()) @@ -1712,7 +1760,15 @@ luabind::scope lua_register_client() { .def("IsDead", &Lua_Client::IsDead) .def("CalcCurrentWeight", &Lua_Client::CalcCurrentWeight) .def("CalcATK", &Lua_Client::CalcATK) - .def("FilteredMessage", &Lua_Client::FilteredMessage); + .def("FilteredMessage", &Lua_Client::FilteredMessage) + .def("EnableAreaHPRegen", &Lua_Client::EnableAreaHPRegen) + .def("DisableAreaHPRegen", &Lua_Client::DisableAreaHPRegen) + .def("EnableAreaManaRegen", &Lua_Client::EnableAreaManaRegen) + .def("DisableAreaManaRegen", &Lua_Client::DisableAreaManaRegen) + .def("EnableAreaEndRegen", &Lua_Client::EnableAreaEndRegen) + .def("DisableAreaEndRegen", &Lua_Client::DisableAreaEndRegen) + .def("EnableAreaRegens", &Lua_Client::EnableAreaRegens) + .def("DisableAreaRegens", &Lua_Client::DisableAreaRegens); } luabind::scope lua_register_inventory_where() { diff --git a/zone/lua_client.h b/zone/lua_client.h index f29e3b46f..10e766b5c 100644 --- a/zone/lua_client.h +++ b/zone/lua_client.h @@ -301,6 +301,14 @@ public: int CalcCurrentWeight(); int CalcATK(); void FilteredMessage(Mob *sender, uint32 type, int filter, const char* message); + void EnableAreaHPRegen(int value); + void DisableAreaHPRegen(); + void EnableAreaManaRegen(int value); + void DisableAreaManaRegen(); + void EnableAreaEndRegen(int value); + void DisableAreaEndRegen(); + void EnableAreaRegens(int value); + void DisableAreaRegens(); }; #endif diff --git a/zone/lua_mob.cpp b/zone/lua_mob.cpp index 18651edea..8b88ba12f 100644 --- a/zone/lua_mob.cpp +++ b/zone/lua_mob.cpp @@ -1686,6 +1686,11 @@ void Lua_Mob::DoKnockback(Lua_Mob caster, uint32 pushback, uint32 pushup) { self->DoKnockback(caster, pushback, pushup); } +void Lua_Mob::AddNimbusEffect(int effect_id) { + Lua_Safe_Call_Void(); + self->AddNimbusEffect(effect_id); +} + void Lua_Mob::RemoveNimbusEffect(int effect_id) { Lua_Safe_Call_Void(); self->RemoveNimbusEffect(effect_id); @@ -2367,6 +2372,7 @@ luabind::scope lua_register_mob() { .def("SetSlotTint", (void(Lua_Mob::*)(int,int,int,int))&Lua_Mob::SetSlotTint) .def("WearChange", (void(Lua_Mob::*)(int,int,uint32))&Lua_Mob::WearChange) .def("DoKnockback", (void(Lua_Mob::*)(Lua_Mob,uint32,uint32))&Lua_Mob::DoKnockback) + .def("AddNimbusEffect", (void(Lua_Mob::*)(int))&Lua_Mob::AddNimbusEffect) .def("RemoveNimbusEffect", (void(Lua_Mob::*)(int))&Lua_Mob::RemoveNimbusEffect) .def("IsFeared", (bool(Lua_Mob::*)(void))&Lua_Mob::IsFeared) .def("IsBlind", (bool(Lua_Mob::*)(void))&Lua_Mob::IsBlind) diff --git a/zone/lua_mob.h b/zone/lua_mob.h index c54deb6bc..493f5e1e8 100644 --- a/zone/lua_mob.h +++ b/zone/lua_mob.h @@ -323,6 +323,7 @@ public: void SetSlotTint(int material_slot, int red_tint, int green_tint, int blue_tint); void WearChange(int material_slot, int texture, uint32 color); void DoKnockback(Lua_Mob caster, uint32 pushback, uint32 pushup); + void AddNimbusEffect(int effect_id); void RemoveNimbusEffect(int effect_id); bool IsRunning(); void SetRunning(bool running); diff --git a/zone/merc.cpp b/zone/merc.cpp index a3ff051f2..03e618881 100644 --- a/zone/merc.cpp +++ b/zone/merc.cpp @@ -1154,7 +1154,7 @@ void Merc::CalcRestState() { // The bot must have been out of combat for RuleI(Character, RestRegenTimeToActivate) seconds, // must be sitting down, and must not have any detrimental spells affecting them. // - if(!RuleI(Character, RestRegenPercent)) + if(!RuleB(Character, RestRegenEnabled)) return; RestRegenHP = RestRegenMana = RestRegenEndurance = 0; @@ -1174,12 +1174,11 @@ void Merc::CalcRestState() { } } - RestRegenHP = (GetMaxHP() * RuleI(Character, RestRegenPercent) / 100); + RestRegenHP = 6 * (GetMaxHP() / RuleI(Character, RestRegenHP)); - RestRegenMana = (GetMaxMana() * RuleI(Character, RestRegenPercent) / 100); + RestRegenMana = 6 * (GetMaxMana() / RuleI(Character, RestRegenMana)); - if(RuleB(Character, RestRegenEndurance)) - RestRegenEndurance = (GetMaxEndurance() * RuleI(Character, RestRegenPercent) / 100); + RestRegenEndurance = 6 * (GetMaxEndurance() / RuleI(Character, RestRegenEnd)); } bool Merc::HasSkill(EQEmu::skills::SkillType skill_id) const { diff --git a/zone/mob.cpp b/zone/mob.cpp index 33863081a..322bc7658 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -75,7 +75,6 @@ Mob::Mob(const char* in_name, uint32 in_drakkin_tattoo, uint32 in_drakkin_details, EQEmu::TintProfile in_armor_tint, - uint8 in_aa_title, uint8 in_see_invis, // see through invis/ivu uint8 in_see_invis_undead, @@ -91,24 +90,24 @@ Mob::Mob(const char* in_name, uint8 in_handtexture, uint8 in_legtexture, uint8 in_feettexture - ) : + ) : attack_timer(2000), attack_dw_timer(2000), ranged_timer(2000), tic_timer(6000), mana_timer(2000), spellend_timer(0), - rewind_timer(30000), //Timer used for determining amount of time between actual player position updates for /rewind. + rewind_timer(30000), bindwound_timer(10000), stunned_timer(0), spun_timer(0), bardsong_timer(6000), gravity_timer(1000), viral_timer(0), - m_FearWalkTarget(-999999.0f,-999999.0f,-999999.0f), + m_FearWalkTarget(-999999.0f, -999999.0f, -999999.0f), m_TargetLocation(glm::vec3()), m_TargetV(glm::vec3()), - flee_timer(FLEE_CHECK_TIMER), + flee_timer(FLEE_CHECK_TIMER), m_Position(position), tmHidden(-1), mitigation_ac(0), @@ -119,47 +118,46 @@ Mob::Mob(const char* in_name, position_update_melee_push_timer(1000) { targeted = 0; - tar_ndx=0; - tar_vector=0; + tar_ndx = 0; + tar_vector = 0; currently_fleeing = false; - last_z = 0; - last_major_update_position = m_Position; + is_distance_roamer = false; AI_Init(); SetMoving(false); - moved=false; + moved = false; m_RewindLocation = glm::vec3(); _egnode = nullptr; - name[0]=0; - orig_name[0]=0; - clean_name[0]=0; - lastname[0]=0; - if(in_name) { - strn0cpy(name,in_name,64); - strn0cpy(orig_name,in_name,64); + name[0] = 0; + orig_name[0] = 0; + clean_name[0] = 0; + lastname[0] = 0; + if (in_name) { + strn0cpy(name, in_name, 64); + strn0cpy(orig_name, in_name, 64); } - if(in_lastname) - strn0cpy(lastname,in_lastname,64); - cur_hp = in_cur_hp; - max_hp = in_max_hp; - base_hp = in_max_hp; - gender = in_gender; - race = in_race; - base_gender = in_gender; - base_race = in_race; - class_ = in_class; - bodytype = in_bodytype; + if (in_lastname) + strn0cpy(lastname, in_lastname, 64); + cur_hp = in_cur_hp; + max_hp = in_max_hp; + base_hp = in_max_hp; + gender = in_gender; + race = in_race; + base_gender = in_gender; + base_race = in_race; + class_ = in_class; + bodytype = in_bodytype; orig_bodytype = in_bodytype; - deity = in_deity; - level = in_level; + deity = in_deity; + level = in_level; orig_level = in_level; - npctype_id = in_npctype_id; - size = in_size; - base_size = size; - runspeed = in_runspeed; + npctype_id = in_npctype_id; + size = in_size; + base_size = size; + runspeed = in_runspeed; // neotokyo: sanity check if (runspeed < 0 || runspeed > 20) runspeed = 1.25f; @@ -172,7 +170,8 @@ Mob::Mob(const char* in_name, fearspeed = 0.625f; base_fearspeed = 25; // npcs - } else { + } + else { base_walkspeed = base_runspeed * 100 / 265; walkspeed = ((float)base_walkspeed) * 0.025f; base_fearspeed = base_runspeed * 100 / 127; @@ -184,7 +183,7 @@ Mob::Mob(const char* in_name, current_speed = base_runspeed; - m_PlayerState = 0; + m_PlayerState = 0; // sanity check @@ -196,8 +195,8 @@ Mob::Mob(const char* in_name, m_Light.Type[EQEmu::lightsource::LightActive] = m_Light.Type[EQEmu::lightsource::LightInnate]; m_Light.Level[EQEmu::lightsource::LightActive] = m_Light.Level[EQEmu::lightsource::LightInnate]; - texture = in_texture; - helmtexture = in_helmtexture; + texture = in_texture; + helmtexture = in_helmtexture; armtexture = in_armtexture; bracertexture = in_bracertexture; handtexture = in_handtexture; @@ -205,21 +204,21 @@ Mob::Mob(const char* in_name, feettexture = in_feettexture; multitexture = (armtexture || bracertexture || handtexture || legtexture || feettexture); - haircolor = in_haircolor; - beardcolor = in_beardcolor; - eyecolor1 = in_eyecolor1; - eyecolor2 = in_eyecolor2; - hairstyle = in_hairstyle; - luclinface = in_luclinface; - beard = in_beard; - drakkin_heritage = in_drakkin_heritage; - drakkin_tattoo = in_drakkin_tattoo; - drakkin_details = in_drakkin_details; + haircolor = in_haircolor; + beardcolor = in_beardcolor; + eyecolor1 = in_eyecolor1; + eyecolor2 = in_eyecolor2; + hairstyle = in_hairstyle; + luclinface = in_luclinface; + beard = in_beard; + drakkin_heritage = in_drakkin_heritage; + drakkin_tattoo = in_drakkin_tattoo; + drakkin_details = in_drakkin_details; attack_speed = 0; attack_delay = 0; slow_mitigation = 0; - findable = false; - trackable = true; + findable = false; + trackable = true; has_shieldequiped = false; has_twohandbluntequiped = false; has_twohanderequipped = false; @@ -230,19 +229,19 @@ Mob::Mob(const char* in_name, SpellPowerDistanceMod = 0; last_los_check = false; - if(in_aa_title>0) - aa_title = in_aa_title; + if (in_aa_title > 0) + aa_title = in_aa_title; else - aa_title =0xFF; - AC = in_ac; - ATK = in_atk; - STR = in_str; - STA = in_sta; - DEX = in_dex; - AGI = in_agi; - INT = in_int; - WIS = in_wis; - CHA = in_cha; + aa_title = 0xFF; + AC = in_ac; + ATK = in_atk; + STR = in_str; + STA = in_sta; + DEX = in_dex; + AGI = in_agi; + INT = in_int; + WIS = in_wis; + CHA = in_cha; MR = CR = FR = DR = PR = Corrup = 0; ExtraHaste = 0; @@ -263,8 +262,8 @@ Mob::Mob(const char* in_name, hidden = false; improved_hidden = false; invulnerable = false; - IsFullHP = (cur_hp == max_hp); - qglobal=0; + IsFullHP = (cur_hp == max_hp); + qglobal = 0; spawned = false; InitializeBuffSlots(); @@ -305,7 +304,7 @@ Mob::Mob(const char* in_name, logging_enabled = false; isgrouped = false; israidgrouped = false; - + IsHorse = false; entity_id_being_looted = 0; @@ -376,13 +375,13 @@ Mob::Mob(const char* in_name, } destructibleobject = false; - wandertype=0; - pausetype=0; + wandertype = 0; + pausetype = 0; cur_wp = 0; m_CurrentWayPoint = glm::vec4(); cur_wp_pause = 0; - patrol=0; - follow=0; + patrol = 0; + follow = 0; follow_dist = 100; // Default Distance for Follow no_target_hotkey = false; flee_mode = false; @@ -396,7 +395,7 @@ Mob::Mob(const char* in_name, rooted = false; charmed = false; has_virus = false; - for (i=0; iSendHPPacketsFrom(this); + raid->SendHPManaEndPacketsFrom(this); } /* Pet - Update master - group and raid if exists */ @@ -1396,7 +1395,7 @@ void Mob::SendHPUpdate(bool skip_self /*= false*/, bool force_update_all /*= fal Raid *raid = entity_list.GetRaidByClient(GetOwner()->CastToClient()); if(raid) - raid->SendHPPacketsFrom(this); + raid->SendHPManaEndPacketsFrom(this); } /* Send to pet */ @@ -1447,6 +1446,7 @@ void Mob::SendPosition() { if (DistanceSquared(last_major_update_position, m_Position) >= (100 * 100)) { entity_list.QueueClients(this, app, true, true); last_major_update_position = m_Position; + is_distance_roamer = true; } else { entity_list.QueueCloseClients(this, app, true, RuleI(Range, MobPositionUpdates), nullptr, false); @@ -1455,6 +1455,20 @@ void Mob::SendPosition() { safe_delete(app); } +void Mob::SendPositionUpdateToClient(Client *client) { + auto app = new EQApplicationPacket(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct)); + PlayerPositionUpdateServer_Struct* spawn_update = (PlayerPositionUpdateServer_Struct*)app->pBuffer; + + if(this->IsMoving()) + MakeSpawnUpdate(spawn_update); + else + MakeSpawnUpdateNoDelta(spawn_update); + + client->QueuePacket(app, false); + + safe_delete(app); +} + /* Position updates for mobs on the move */ void Mob::SendPositionUpdate(uint8 iSendToSelf) { auto app = new EQApplicationPacket(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct)); @@ -1466,6 +1480,11 @@ void Mob::SendPositionUpdate(uint8 iSendToSelf) { CastToClient()->FastQueuePacket(&app, false); } } + else if (DistanceSquared(last_major_update_position, m_Position) >= (100 * 100)) { + entity_list.QueueClients(this, app, true, true); + last_major_update_position = m_Position; + is_distance_roamer = true; + } else { entity_list.QueueCloseClients(this, app, (iSendToSelf == 0), RuleI(Range, MobPositionUpdates), nullptr, false); } @@ -3383,13 +3402,18 @@ int Mob::GetHaste() else // 1-25 h += itembonuses.haste > 10 ? 10 : itembonuses.haste; - // 60+ 100, 51-59 85, 1-50 level+25 - if (level > 59) // 60+ - cap = RuleI(Character, HasteCap); - else if (level > 50) // 51-59 - cap = 85; - else // 1-50 - cap = level + 25; + // mobs are different! + Mob *owner = nullptr; + if (IsPet()) + owner = GetOwner(); + else if (IsNPC() && CastToNPC()->GetSwarmOwner()) + owner = entity_list.GetMobID(CastToNPC()->GetSwarmOwner()); + if (owner) { + cap = 10 + level; + cap += std::max(0, owner->GetLevel() - 39) + std::max(0, owner->GetLevel() - 60); + } else { + cap = 150; + } if(h > cap) h = cap; @@ -3425,9 +3449,22 @@ void Mob::SetTarget(Mob* mob) { this->GetTarget()->SendHPUpdate(false, true); } +// For when we want a Ground Z at a location we are not at yet +// Like MoveTo. +float Mob::FindDestGroundZ(glm::vec3 dest, float z_offset) +{ + float best_z = BEST_Z_INVALID; + if (zone->zonemap != nullptr) + { + dest.z += z_offset; + best_z = zone->zonemap->FindBestZ(dest, nullptr); + } + return best_z; +} + float Mob::FindGroundZ(float new_x, float new_y, float z_offset) { - float ret = -999999; + float ret = BEST_Z_INVALID; if (zone->zonemap != nullptr) { glm::vec3 me; @@ -3436,7 +3473,7 @@ float Mob::FindGroundZ(float new_x, float new_y, float z_offset) me.z = m_Position.z + z_offset; glm::vec3 hit; float best_z = zone->zonemap->FindBestZ(me, &hit); - if (best_z != -999999) + if (best_z != BEST_Z_INVALID) { ret = best_z; } @@ -3447,7 +3484,7 @@ float Mob::FindGroundZ(float new_x, float new_y, float z_offset) // Copy of above function that isn't protected to be exported to Perl::Mob float Mob::GetGroundZ(float new_x, float new_y, float z_offset) { - float ret = -999999; + float ret = BEST_Z_INVALID; if (zone->zonemap != 0) { glm::vec3 me; @@ -3456,7 +3493,7 @@ float Mob::GetGroundZ(float new_x, float new_y, float z_offset) me.z = m_Position.z+z_offset; glm::vec3 hit; float best_z = zone->zonemap->FindBestZ(me, &hit); - if (best_z != -999999) + if (best_z != BEST_Z_INVALID) { ret = best_z; } @@ -3761,7 +3798,7 @@ void Mob::TryTriggerOnValueAmount(bool IsHP, bool IsMana, bool IsEndur, bool IsP if ((base2 >= 500 && base2 <= 520) && GetHPRatio() < (base2 - 500)*5) use_spell = true; - else if (base2 = 1004 && GetHPRatio() < 80) + else if (base2 == 1004 && GetHPRatio() < 80) use_spell = true; } @@ -3769,12 +3806,12 @@ void Mob::TryTriggerOnValueAmount(bool IsHP, bool IsMana, bool IsEndur, bool IsP if ( (base2 = 521 && GetManaRatio() < 20) || (base2 = 523 && GetManaRatio() < 40)) use_spell = true; - else if (base2 = 38311 && GetManaRatio() < 10) + else if (base2 == 38311 && GetManaRatio() < 10) use_spell = true; } else if (IsEndur){ - if (base2 = 522 && GetEndurancePercent() < 40){ + if (base2 == 522 && GetEndurancePercent() < 40){ use_spell = true; } } @@ -3935,10 +3972,17 @@ int16 Mob::GetHealRate(uint16 spell_id, Mob* caster) { bool Mob::TryFadeEffect(int slot) { + if (!buffs[slot].spellid) + return false; + if(IsValidSpell(buffs[slot].spellid)) { for(int i = 0; i < EFFECT_COUNT; i++) { + + if (!spells[buffs[slot].spellid].effectid[i]) + continue; + if (spells[buffs[slot].spellid].effectid[i] == SE_CastOnFadeEffectAlways || spells[buffs[slot].spellid].effectid[i] == SE_CastOnRuneFadeEffect) { @@ -4980,6 +5024,18 @@ void Mob::SpreadVirus(uint16 spell_id, uint16 casterID) } } +void Mob::AddNimbusEffect(int effectid) +{ + SetNimbusEffect(effectid); + + auto outapp = new EQApplicationPacket(OP_AddNimbusEffect, sizeof(RemoveNimbusEffect_Struct)); + auto ane = (RemoveNimbusEffect_Struct *)outapp->pBuffer; + ane->spawnid = GetID(); + ane->nimbus_effect = effectid; + entity_list.QueueClients(this, outapp); + safe_delete(outapp); +} + void Mob::RemoveNimbusEffect(int effectid) { if (effectid == nimbus_effect1) diff --git a/zone/mob.h b/zone/mob.h index bdc1e7c3a..deb24f640 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -162,6 +162,8 @@ public: inline virtual bool IsMob() const { return true; } inline virtual bool InZone() const { return true; } + bool is_distance_roamer; + //Somewhat sorted: needs documenting! //Attack @@ -199,6 +201,7 @@ public: void ApplyMeleeDamageMods(uint16 skill, int &damage, Mob * defender = nullptr, ExtraAttackOptions *opts = nullptr); int ACSum(); int offense(EQEmu::skills::SkillType skill); + int GetBestMeleeSkill(); void CalcAC() { mitigation_ac = ACSum(); } int GetACSoftcap(); double GetSoftcapReturns(); @@ -278,6 +281,7 @@ public: float ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use_resist_override = false, int resist_override = 0, bool CharismaCheck = false, bool CharmTick = false, bool IsRoot = false, int level_override = -1); + int GetResist(uint8 resist_type); int ResistPhysical(int level_diff, uint8 caster_level); int ResistElementalWeaponDmg(const EQEmu::ItemInstance *item); int CheckBaneDamage(const EQEmu::ItemInstance *item); @@ -336,6 +340,7 @@ public: void BuffFadeDetrimentalByCaster(Mob *caster); void BuffFadeBySitModifier(); bool IsAffectedByBuff(uint16 spell_id); + bool IsAffectedByBuffByGlobalGroup(GlobalGroup group); void BuffModifyDurationBySpellID(uint16 spell_id, int32 newDuration); int AddBuff(Mob *caster, const uint16 spell_id, int duration = 0, int32 level_override = -1); int CanBuffStack(uint16 spellid, uint8 caster_level, bool iFailIfOverwrite = false); @@ -348,6 +353,7 @@ public: virtual int GetMaxSongSlots() const { return 0; } virtual int GetMaxDiscSlots() const { return 0; } virtual int GetMaxTotalSlots() const { return 0; } + bool HasDiscBuff(); virtual uint32 GetFirstBuffSlot(bool disc, bool song); virtual uint32 GetLastBuffSlot(bool disc, bool song); virtual void InitializeBuffSlots() { buffs = nullptr; current_buff_count = 0; } @@ -376,6 +382,7 @@ public: inline virtual uint32 GetNimbusEffect1() const { return nimbus_effect1; } inline virtual uint32 GetNimbusEffect2() const { return nimbus_effect2; } inline virtual uint32 GetNimbusEffect3() const { return nimbus_effect3; } + void AddNimbusEffect(int effectid); void RemoveNimbusEffect(int effectid); inline const glm::vec3& GetTargetRingLocation() const { return m_TargetRing; } inline float GetTargetRingX() const { return m_TargetRing.x; } @@ -543,6 +550,7 @@ public: virtual void GMMove(float x, float y, float z, float heading = 0.01, bool SendUpdate = true); void SetDelta(const glm::vec4& delta); void SetTargetDestSteps(uint8 target_steps) { tar_ndx = target_steps; } + void SendPositionUpdateToClient(Client *client); void SendPositionUpdate(uint8 iSendToSelf = 0); void MakeSpawnUpdateNoDelta(PlayerPositionUpdateServer_Struct* spu); void MakeSpawnUpdate(PlayerPositionUpdateServer_Struct* spu); @@ -585,6 +593,7 @@ public: void AddFeignMemory(Client* attacker); void RemoveFromFeignMemory(Client* attacker); void ClearFeignMemory(); + bool IsOnFeignMemory(Client *attacker) const; void PrintHateListToClient(Client *who) { hate_list.PrintHateListToClient(who); } std::list& GetHateList() { return hate_list.GetHateList(); } bool CheckLosFN(Mob* other); @@ -949,7 +958,9 @@ public: float GetGroundZ(float new_x, float new_y, float z_offset=0.0); void SendTo(float new_x, float new_y, float new_z); void SendToFixZ(float new_x, float new_y, float new_z); - void FixZ(); + float GetZOffset() const; + void FixZ(int32 z_find_offset = 5); + float GetFixedZ(glm::vec3 position, int32 z_find_offset = 5); void NPCSpecialAttacks(const char* parse, int permtag, bool reset = true, bool remove = false); inline uint32 DontHealMeBefore() const { return pDontHealMeBefore; } inline uint32 DontBuffMeBefore() const { return pDontBuffMeBefore; } @@ -1103,8 +1114,6 @@ public: int GetWeaponDamage(Mob *against, const EQEmu::ItemData *weapon_item); int GetWeaponDamage(Mob *against, const EQEmu::ItemInstance *weapon_item, uint32 *hate = nullptr); - float last_z; - // Bots HealRotation methods #ifdef BOTS bool IsHealRotationTarget() { return (m_target_of_heal_rotation.use_count() && m_target_of_heal_rotation.get()); } @@ -1220,8 +1229,9 @@ protected: uint32 npctype_id; glm::vec4 m_Position; /* Used to determine when an NPC has traversed so many units - to send a zone wide pos update */ - glm::vec4 last_major_update_position; - uint16 animation; + glm::vec4 last_major_update_position; + + int animation; // this is really what MQ2 calls SpeedRun just packed like (int)(SpeedRun * 40.0f) float base_size; float size; float runspeed; @@ -1262,6 +1272,7 @@ protected: virtual int16 GetFocusEffect(focusType type, uint16 spell_id) { return 0; } void CalculateNewFearpoint(); float FindGroundZ(float new_x, float new_y, float z_offset=0.0); + float FindDestGroundZ(glm::vec3 dest, float z_offset=0.0); glm::vec3 UpdatePath(float ToX, float ToY, float ToZ, float Speed, bool &WaypointChange, bool &NodeReached); void PrintRoute(); diff --git a/zone/mob_ai.cpp b/zone/mob_ai.cpp index 14e9337ac..d331ee7a0 100644 --- a/zone/mob_ai.cpp +++ b/zone/mob_ai.cpp @@ -57,7 +57,11 @@ bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) { if(AI_HasSpells() == false) return false; - if (iChance < 100) { + // Rooted mobs were just standing around when tar out of range. + // Any sane mob would cast if they can. + bool cast_only_option = (IsRooted() && !CombatRange(tar)); + + if (!cast_only_option && iChance < 100) { if (zone->random.Int(0, 100) >= iChance) return false; } @@ -123,7 +127,7 @@ bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) { } case SpellType_Root: { Mob *rootee = GetHateRandom(); - if (rootee && !rootee->IsRooted() && zone->random.Roll(50) + if (rootee && !rootee->IsRooted() && !rootee->IsFeared() && zone->random.Roll(50) && rootee->DontRootMeBefore() < Timer::GetCurrentTime() && rootee->CanBuffStack(AIspells[i].spellid, GetLevel(), true) >= 0 ) { @@ -499,7 +503,7 @@ void NPC::AI_Start(uint32 iMoveDelay) { AIautocastspell_timer->Disable(); } else { AIautocastspell_timer = std::unique_ptr(new Timer(750)); - AIautocastspell_timer->Start(RandomTimer(0, 15000), false); + AIautocastspell_timer->Start(RandomTimer(0, 300), false); } if (NPCTypedata) { @@ -1001,10 +1005,9 @@ void Mob::AI_Process() { if (this->GetTarget()) { /* If we are engaged, moving and following client, let's look for best Z more often */ float target_distance = DistanceNoZ(this->GetPosition(), this->GetTarget()->GetPosition()); - if (target_distance >= 25) { - this->FixZ(); - } - else if (!this->CheckLosFN(this->GetTarget())) { + this->FixZ(); + + if (target_distance <= 15 && !this->CheckLosFN(this->GetTarget())) { Mob* target = this->GetTarget(); m_Position.x = target->GetX(); @@ -1295,7 +1298,7 @@ void Mob::AI_Process() { if (AI_PursueCastCheck()) { //we did something, so do not process movement. } - else if (AI_movement_timer->Check()) + else if (AI_movement_timer->Check() && target) { if (!IsRooted()) { Log(Logs::Detail, Logs::AI, "Pursuing %s while engaged.", target->GetName()); @@ -1388,19 +1391,33 @@ void Mob::AI_Process() { //if(owner->IsClient()) // printf("Pet start pos: (%f, %f, %f)\n", GetX(), GetY(), GetZ()); - float dist = DistanceSquared(m_Position, owner->GetPosition()); - if (dist >= 400) + glm::vec4 ownerPos = owner->GetPosition(); + float dist = DistanceSquared(m_Position, ownerPos); + float distz = ownerPos.z - m_Position.z; + + if (dist >= 400 || distz > 100) { int speed = GetWalkspeed(); if (dist >= 5625) speed = GetRunspeed(); - CalculateNewPosition2(owner->GetX(), owner->GetY(), owner->GetZ(), speed); + if (distz > 100) + { + m_Position = ownerPos; + SendPositionUpdate(); + moved = true; + } + else + { + CalculateNewPosition2(owner->GetX(), + owner->GetY(), owner->GetZ(), speed); + } } else { if(moved) { + this->FixZ(); SetCurrentSpeed(0); moved = false; } @@ -1532,16 +1549,17 @@ void NPC::AI_DoMovement() { roambox_movingto_x = zone->random.Real(roambox_min_x+1,roambox_max_x-1); if (roambox_movingto_y > roambox_max_y || roambox_movingto_y < roambox_min_y) roambox_movingto_y = zone->random.Real(roambox_min_y+1,roambox_max_y-1); + Log(Logs::Detail, Logs::AI, + "Roam Box: d=%.3f (%.3f->%.3f,%.3f->%.3f): Go To (%.3f,%.3f)", + roambox_distance, roambox_min_x, roambox_max_x, roambox_min_y, + roambox_max_y, roambox_movingto_x, roambox_movingto_y); } - Log(Logs::Detail, Logs::AI, "Roam Box: d=%.3f (%.3f->%.3f,%.3f->%.3f): Go To (%.3f,%.3f)", - roambox_distance, roambox_min_x, roambox_max_x, roambox_min_y, roambox_max_y, roambox_movingto_x, roambox_movingto_y); - - float new_z = this->FindGroundZ(m_Position.x, m_Position.y, 5); - new_z += (this->GetSize() / 1.55); - - if (!CalculateNewPosition2(roambox_movingto_x, roambox_movingto_y, new_z, walksp, true)) + // Keep calling with updates, using wherever we are in Z. + if (!MakeNewPositionAndSendUpdate(roambox_movingto_x, + roambox_movingto_y, m_Position.z, walksp)) { + this->FixZ(); // FixZ on final arrival point. roambox_movingto_x = roambox_max_x + 1; // force update pLastFightingDelayMoving = Timer::GetCurrentTime() + RandomTimer(roambox_min_delay, roambox_delay); SetMoving(false); @@ -1578,18 +1596,22 @@ void NPC::AI_DoMovement() { } this->FixZ(); - SendPosition(); //kick off event_waypoint arrive char temp[16]; sprintf(temp, "%d", cur_wp); parse->EventNPC(EVENT_WAYPOINT_ARRIVE, CastToNPC(), nullptr, temp, 0); - // start moving directly to next waypoint if we're at a 0 pause waypoint and we didn't get quest halted. - if (!AI_walking_timer->Enabled()) + // No need to move as we are there. Next loop will + // take care of normal grids, even at pause 0. + // We do need to call and setup a wp if we're cur_wp=-2 + // as that is where roamer is unset and we don't want + // the next trip through to move again based on grid stuff. + doMove = false; + if (cur_wp == -2) { AI_SetupNextWaypoint(); - else - doMove = false; + } + // wipe feign memory since we reached our first waypoint if(cur_wp == 1) ClearFeignMemory(); @@ -2589,7 +2611,7 @@ void NPC::AddSpellToNPCList(int16 iPriority, int16 iSpellID, uint32 iType, // If we're going from an empty list, we need to start the timer if (AIspells.size() == 1) - AIautocastspell_timer->Start(RandomTimer(0, 15000), false); + AIautocastspell_timer->Start(RandomTimer(0, 300), false); } void NPC::RemoveSpellFromNPCList(int16 spell_id) diff --git a/zone/net.cpp b/zone/net.cpp index eafdde807..80411af7b 100644 --- a/zone/net.cpp +++ b/zone/net.cpp @@ -542,8 +542,10 @@ int main(int argc, char** argv) { process_timer.Stop(); process_timer.Start(1000, true); - uint32 shutdown_timer = database.getZoneShutDownDelay(zone->GetZoneID(), zone->GetInstanceVersion()); - zone->StartShutdownTimer(shutdown_timer); + if (zone && zone->GetZoneID() && zone->GetInstanceVersion()) { + uint32 shutdown_timer = database.getZoneShutDownDelay(zone->GetZoneID(), zone->GetInstanceVersion()); + zone->StartShutdownTimer(shutdown_timer); + } } else if (!previous_loaded && current_loaded) { process_timer.Stop(); diff --git a/zone/npc.cpp b/zone/npc.cpp index a4e6c4214..c13841358 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -436,6 +436,26 @@ void NPC::SetTarget(Mob* mob) { //attack_timer.Disable(); attack_dw_timer.Disable(); } + + // either normal pet and owner is client or charmed pet and owner is client + Mob *owner = nullptr; + if (IsPet() && IsPetOwnerClient()) { + owner = GetOwner(); + } else if (IsCharmed()) { + owner = GetOwner(); + if (owner && !owner->IsClient()) + owner = nullptr; + } + + if (owner) { + auto client = owner->CastToClient(); + if (client->ClientVersionBit() & EQEmu::versions::bit_UFAndLater) { + auto app = new EQApplicationPacket(OP_PetHoTT, sizeof(ClientTarget_Struct)); + auto ct = (ClientTarget_Struct *)app->pBuffer; + ct->new_target = mob ? mob->GetID() : 0; + client->FastQueuePacket(&app); + } + } Mob::SetTarget(mob); } diff --git a/zone/object.cpp b/zone/object.cpp index d8db2f1d8..0ba5f9fa3 100644 --- a/zone/object.cpp +++ b/zone/object.cpp @@ -70,7 +70,7 @@ Object::Object(uint32 id, uint32 type, uint32 icon, const Object_Struct& object, //creating a re-ocurring ground spawn. Object::Object(const EQEmu::ItemInstance* inst, char* name,float max_x,float min_x,float max_y,float min_y,float z,float heading,uint32 respawntimer) - : respawn_timer(respawntimer), decay_timer(300000) + : respawn_timer(respawntimer * 1000), decay_timer(300000) { user = nullptr; diff --git a/zone/pets.cpp b/zone/pets.cpp index 0c150f743..bbc4f8eea 100644 --- a/zone/pets.cpp +++ b/zone/pets.cpp @@ -475,22 +475,6 @@ Pet::Pet(NPCType *type_data, Mob *owner, PetType type, uint16 spell_id, int16 po // Class should use npc constructor to set light properties } -void Pet::SetTarget(Mob *mob) -{ - if (mob == GetTarget()) - return; - - auto owner = GetOwner(); - if (owner && owner->IsClient() && owner->CastToClient()->ClientVersionBit() & EQEmu::versions::bit_UFAndLater) { - auto app = new EQApplicationPacket(OP_PetHoTT, sizeof(ClientTarget_Struct)); - auto ct = (ClientTarget_Struct *)app->pBuffer; - ct->new_target = mob ? mob->GetID() : 0; - owner->CastToClient()->QueuePacket(app); - safe_delete(app); - } - NPC::SetTarget(mob); -} - bool ZoneDatabase::GetPetEntry(const char *pet_type, PetRecord *into) { return GetPoweredPetEntry(pet_type, 0, into); } @@ -671,10 +655,15 @@ void NPC::SetPetState(SpellBuff_Struct *pet_buffs, uint32 *items) { continue; const EQEmu::ItemData* item2 = database.GetItem(items[i]); - if (item2 && item2->NoDrop != 0) { - //dont bother saving item charges for now, NPCs never use them - //and nobody should be able to get them off the corpse..? - AddLootDrop(item2, &itemlist, 0, 1, 255, true, true); + + if (item2) { + bool noDrop=(item2->NoDrop == 0); // Field is reverse logic + bool petCanHaveNoDrop = (RuleB(Pets, CanTakeNoDrop) && + _CLIENTPET(this) && GetPetType() <= petOther); + + if (!noDrop || petCanHaveNoDrop) { + AddLootDrop(item2, &itemlist, 0, 1, 255, true, true); + } } } } diff --git a/zone/pets.h b/zone/pets.h index edb6dbe95..1b9811149 100644 --- a/zone/pets.h +++ b/zone/pets.h @@ -7,7 +7,6 @@ struct NPCType; class Pet : public NPC { public: Pet(NPCType *type_data, Mob *owner, PetType type, uint16 spell_id, int16 power); - virtual void SetTarget(Mob *mob); virtual bool CheckSpellLevelRestriction(uint16 spell_id); }; diff --git a/zone/raids.cpp b/zone/raids.cpp index 26845910c..2bbd83064 100644 --- a/zone/raids.cpp +++ b/zone/raids.cpp @@ -149,7 +149,7 @@ void Raid::AddMember(Client *c, uint32 group, bool rleader, bool groupleader, bo raid_update = c->GetRaid(); if (raid_update) { raid_update->SendHPManaEndPacketsTo(c); - raid_update->SendHPPacketsFrom(c); + raid_update->SendHPManaEndPacketsFrom(c); } auto pack = new ServerPacket(ServerOP_RaidAdd, sizeof(ServerRaidGeneralAction_Struct)); @@ -323,6 +323,10 @@ void Raid::SaveRaidLeaderAA() void Raid::UpdateGroupAAs(uint32 gid) { + + if (gid < 0 || gid > MAX_RAID_GROUPS) + return; + Client *gl = GetGroupLeader(gid); if (gl) @@ -1591,7 +1595,7 @@ void Raid::SendHPManaEndPacketsTo(Client *client) } } -void Raid::SendHPPacketsFrom(Mob *mob) +void Raid::SendHPManaEndPacketsFrom(Mob *mob) { if(!mob) return; @@ -1779,3 +1783,42 @@ void Raid::SetDirtyAutoHaters() } +void Raid::QueueClients(Mob *sender, const EQApplicationPacket *app, bool ack_required /*= true*/, bool ignore_sender /*= true*/, float distance /*= 0*/, bool group_only /*= true*/) { + if (sender && sender->IsClient()) { + + uint32 group_id = this->GetGroup(sender->CastToClient()); + + /* If this is a group only packet and we're not in a group -- return */ + if (!group_id == 0xFFFFFFFF && group_only) + return; + + for (uint32 i = 0; i < MAX_RAID_MEMBERS; i++) { + if (!members[i].member) + continue; + + if (!members[i].member->IsClient()) + continue; + + if (ignore_sender && members[i].member == sender) + continue; + + if (group_only && members[i].GroupNumber != group_id) + continue; + + /* If we don't have a distance requirement - send to all members */ + if (distance == 0) { + members[i].member->CastToClient()->QueuePacket(app, ack_required); + } + else { + /* If negative distance - we check if current distance is greater than X */ + if (distance <= 0 && DistanceSquared(sender->GetPosition(), members[i].member->GetPosition()) >= (distance * distance)) { + members[i].member->CastToClient()->QueuePacket(app, ack_required); + } + /* If positive distance - we check if current distance is less than X */ + else if (distance >= 0 && DistanceSquared(sender->GetPosition(), members[i].member->GetPosition()) <= (distance * distance)) { + members[i].member->CastToClient()->QueuePacket(app, ack_required); + } + } + } + } +} \ No newline at end of file diff --git a/zone/raids.h b/zone/raids.h index df43d7755..9ed9bc11c 100644 --- a/zone/raids.h +++ b/zone/raids.h @@ -174,7 +174,7 @@ public: void VerifyRaid(); void MemberZoned(Client *c); void SendHPManaEndPacketsTo(Client *c); - void SendHPPacketsFrom(Mob *mob); + void SendHPManaEndPacketsFrom(Mob *mob); void SendManaPacketFrom(Mob *mob); void SendEndurancePacketFrom(Mob *mob); void RaidSay(const char *msg, Client *c); @@ -237,6 +237,8 @@ public: void SetDirtyAutoHaters(); inline XTargetAutoHaters *GetXTargetAutoMgr() { return &m_autohatermgr; } + void QueueClients(Mob *sender, const EQApplicationPacket *app, bool ack_required = true, bool ignore_sender = true, float distance = 0, bool group_only = true); + RaidMember members[MAX_RAID_MEMBERS]; char leadername[64]; protected: diff --git a/zone/special_attacks.cpp b/zone/special_attacks.cpp index 57ea1c781..03b144b5a 100644 --- a/zone/special_attacks.cpp +++ b/zone/special_attacks.cpp @@ -37,6 +37,7 @@ int Mob::GetBaseSkillDamage(EQEmu::skills::SkillType skill, Mob *target) case EQEmu::skills::SkillDragonPunch: case EQEmu::skills::SkillEagleStrike: case EQEmu::skills::SkillTigerClaw: + case EQEmu::skills::SkillRoundKick: if (skill_level >= 25) base++; if (skill_level >= 75) diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index 6a50ab142..67104a234 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -60,6 +60,9 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove const SPDat_Spell_Struct &spell = spells[spell_id]; + if (spell.disallow_sit && IsBuffSpell(spell_id) && IsClient() && (CastToClient()->IsSitting() || CastToClient()->GetHorseId() != 0)) + return false; + bool c_override = false; if (caster && caster->IsClient() && GetCastedSpellInvSlot() > 0) { const EQEmu::ItemInstance *inst = caster->CastToClient()->GetInv().GetItem(GetCastedSpellInvSlot()); @@ -134,6 +137,15 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove buffs[buffslot].magic_rune = 0; buffs[buffslot].numhits = 0; + if (spells[spell_id].numhits > 0) { + + int numhit = spells[spell_id].numhits; + + numhit += numhit * caster->GetFocusEffect(focusFcLimitUse, spell_id) / 100; + numhit += caster->GetFocusEffect(focusIncreaseNumHits, spell_id); + buffs[buffslot].numhits = numhit; + } + if (spells[spell_id].EndurUpkeep > 0) SetEndurUpkeep(true); @@ -181,14 +193,6 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove } } - if(spells[spell_id].numhits > 0 && buffslot >= 0){ - - int numhit = spells[spell_id].numhits; - - numhit += numhit*caster->GetFocusEffect(focusFcLimitUse, spell_id)/100; - numhit += caster->GetFocusEffect(focusIncreaseNumHits, spell_id); - buffs[buffslot].numhits = numhit; - } if (!IsPowerDistModSpell(spell_id)) SetSpellPowerDistanceMod(0); @@ -277,8 +281,9 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove caster->SetMana(0); } else if (spell_id == 2755 && caster) //Lifeburn { - dmg = -1 * caster->GetHP(); // just your current HP or should it be Max HP? + dmg = caster->GetHP(); // just your current HP caster->SetHP(dmg / 4); // 2003 patch notes say ~ 1/4 HP. Should this be 1/4 your current HP or do 3/4 max HP dmg? Can it kill you? + dmg = -dmg; } //do any AAs apply to these spells? @@ -1791,8 +1796,12 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove Message_StringID(4, CORPSE_CANT_SENSE); } } - else if (caster) - caster->Message_StringID(MT_SpellFailure, SPELL_LEVEL_REQ); + else if (caster) { + char level[4]; + ConvertArray(effect_value, level); + caster->Message_StringID(MT_SpellFailure, + SPELL_LEVEL_REQ, level); + } } else { Message_StringID(4, TARGET_NOT_FOUND); @@ -3655,16 +3664,6 @@ void Mob::DoBuffTic(const Buffs_Struct &buff, int slot, Mob *caster) break; } - case SE_Hunger: { - // this procedure gets called 7 times for every once that the stamina update occurs so we add - // 1/7 of the subtraction. - // It's far from perfect, but works without any unnecessary buff checks to bog down the server. - if (IsClient()) { - CastToClient()->m_pp.hunger_level += 5; - CastToClient()->m_pp.thirst_level += 5; - } - break; - } case SE_Invisibility: case SE_InvisVsAnimals: case SE_InvisVsUndead: { diff --git a/zone/spells.cpp b/zone/spells.cpp index 8e7d6721a..8117a8018 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -1427,8 +1427,10 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, CastingSlot slo ZeroCastingVars(); // set the rapid recast timer for next time around + // Why do we have this? It mostly just causes issues when things are working correctly + // It also needs to be IsClient()) - strcpy(buffs[emptyslot].caster_name, caster->GetName()); + if (caster && !caster->IsAura()) // maybe some other things we don't want to ... + strcpy(buffs[emptyslot].caster_name, caster->GetCleanName()); else memset(buffs[emptyslot].caster_name, 0, 64); buffs[emptyslot].casterid = caster ? caster->GetID() : 0; @@ -4232,6 +4240,19 @@ bool Mob::IsAffectedByBuff(uint16 spell_id) return false; } +bool Mob::IsAffectedByBuffByGlobalGroup(GlobalGroup group) +{ + int buff_count = GetMaxTotalSlots(); + for (int i = 0; i < buff_count; ++i) { + if (buffs[i].spellid == SPELL_UNKNOWN) + continue; + if (spells[buffs[i].spellid].spell_category == static_cast(group)) + return true; + } + + return false; +} + // checks if 'this' can be affected by spell_id from caster // returns true if the spell should fail, false otherwise bool Mob::IsImmuneToSpell(uint16 spell_id, Mob *caster) @@ -4416,6 +4437,36 @@ bool Mob::IsImmuneToSpell(uint16 spell_id, Mob *caster) return false; } +int Mob::GetResist(uint8 resist_type) +{ + switch(resist_type) + { + case RESIST_FIRE: + return GetFR(); + case RESIST_COLD: + return GetCR(); + case RESIST_MAGIC: + return GetMR(); + case RESIST_DISEASE: + return GetDR(); + case RESIST_POISON: + return GetPR(); + case RESIST_CORRUPTION: + return GetCorrup(); + case RESIST_PRISMATIC: + return (GetFR() + GetCR() + GetMR() + GetDR() + GetPR()) / 5; + case RESIST_CHROMATIC: + return std::min({GetFR(), GetCR(), GetMR(), GetDR(), GetPR()}); + case RESIST_PHYSICAL: + if (IsNPC()) + return GetPhR(); + else + return 0; + default: + return 0; + } +} + // // Spell resists: // returns an effectiveness index from 0 to 100. for most spells, 100 means @@ -4499,68 +4550,16 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use return 100; } - int target_resist; - switch(resist_type) - { - case RESIST_FIRE: - target_resist = GetFR(); - break; - case RESIST_COLD: - target_resist = GetCR(); - break; - case RESIST_MAGIC: - target_resist = GetMR(); - break; - case RESIST_DISEASE: - target_resist = GetDR(); - break; - case RESIST_POISON: - target_resist = GetPR(); - break; - case RESIST_CORRUPTION: - target_resist = GetCorrup(); - break; - case RESIST_PRISMATIC: - target_resist = (GetFR() + GetCR() + GetMR() + GetDR() + GetPR()) / 5; - break; - case RESIST_CHROMATIC: - { - target_resist = GetFR(); - int temp = GetCR(); - if(temp < target_resist) - { - target_resist = temp; - } + int target_resist = GetResist(resist_type); - temp = GetMR(); - if(temp < target_resist) - { - target_resist = temp; - } - - temp = GetDR(); - if(temp < target_resist) - { - target_resist = temp; - } - - temp = GetPR(); - if(temp < target_resist) - { - target_resist = temp; - } + // JULY 24, 2002 changes + int level = GetLevel(); + if (IsPetOwnerClient() && caster->IsNPC() && !caster->IsPetOwnerClient()) { + auto owner = GetOwner(); + if (owner != nullptr) { + target_resist = std::max(target_resist, owner->GetResist(resist_type)); + level = owner->GetLevel(); } - break; - case RESIST_PHYSICAL: - { - if (IsNPC()) - target_resist = GetPhR(); - else - target_resist = 0; - } - default: - - target_resist = 0; } //Setup our base resist chance. @@ -4569,7 +4568,7 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use //Adjust our resist chance based on level modifiers uint8 caster_level = level_override > 0 ? level_override : caster->GetLevel(); - int temp_level_diff = GetLevel() - caster_level; + int temp_level_diff = level - caster_level; //Physical Resists are calclated using their own formula derived from extensive parsing. if (resist_type == RESIST_PHYSICAL) { @@ -4578,7 +4577,7 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use else { - if(IsNPC() && GetLevel() >= RuleI(Casting,ResistFalloff)) + if(IsNPC() && level >= RuleI(Casting,ResistFalloff)) { int a = (RuleI(Casting,ResistFalloff)-1) - caster_level; if(a > 0) @@ -4591,7 +4590,7 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use } } - if(IsClient() && GetLevel() >= 21 && temp_level_diff > 15) + if(IsClient() && level >= 21 && temp_level_diff > 15) { temp_level_diff = 15; } @@ -4607,16 +4606,16 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use level_mod = -level_mod; } - if(IsNPC() && (caster_level - GetLevel()) < -20) + if(IsNPC() && (caster_level - level) < -20) { level_mod = 1000; } //Even more level stuff this time dealing with damage spells - if(IsNPC() && IsDamageSpell(spell_id) && GetLevel() >= 17) + if(IsNPC() && IsDamageSpell(spell_id) && level >= 17) { int level_diff; - if(GetLevel() >= RuleI(Casting,ResistFalloff)) + if(level >= RuleI(Casting,ResistFalloff)) { level_diff = (RuleI(Casting,ResistFalloff)-1) - caster_level; if(level_diff < 0) @@ -4626,7 +4625,7 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use } else { - level_diff = GetLevel() - caster_level; + level_diff = level - caster_level; } level_mod += (2 * level_diff); } @@ -4737,17 +4736,17 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use if(IsNPC()) { - if(GetLevel() > caster_level && GetLevel() >= 17 && caster_level <= 50) + if(level > caster_level && level >= 17 && caster_level <= 50) { partial_modifier += 5; } - if(GetLevel() >= 30 && caster_level < 50) + if(level >= 30 && caster_level < 50) { partial_modifier += (caster_level - 25); } - if(GetLevel() < 15) + if(level < 15) { partial_modifier -= 5; } @@ -4755,9 +4754,9 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use if(caster->IsNPC()) { - if((GetLevel() - caster_level) >= 20) + if((level - caster_level) >= 20) { - partial_modifier += (GetLevel() - caster_level) * 1.5; + partial_modifier += (level - caster_level) * 1.5; } } @@ -5533,6 +5532,8 @@ void Client::SendBuffNumHitPacket(Buffs_Struct &buff, int slot) bi->entries[0].spell_id = buff.spellid; bi->entries[0].tics_remaining = buff.ticsremaining; bi->entries[0].num_hits = buff.numhits; + strn0cpy(bi->entries[0].caster, buff.caster_name, 64); + bi->name_lengths = strlen(bi->entries[0].caster); FastQueuePacket(&outapp); } @@ -5618,6 +5619,7 @@ EQApplicationPacket *Mob::MakeBuffsPacket(bool for_target) else buff->type = 0; + buff->name_lengths = 0; // hacky shit uint32 index = 0; for(int i = 0; i < buff_count; ++i) { @@ -5627,6 +5629,8 @@ EQApplicationPacket *Mob::MakeBuffsPacket(bool for_target) buff->entries[index].spell_id = buffs[i].spellid; buff->entries[index].tics_remaining = buffs[i].ticsremaining; buff->entries[index].num_hits = buffs[i].numhits; + strn0cpy(buff->entries[index].caster, buffs[i].caster_name, 64); + buff->name_lengths += strlen(buff->entries[index].caster); ++index; } } diff --git a/zone/string_ids.h b/zone/string_ids.h index c8175c8b6..997858bed 100644 --- a/zone/string_ids.h +++ b/zone/string_ids.h @@ -19,6 +19,7 @@ #define PROC_TOOLOW 126 //Your will is not sufficient to command this weapon. #define PROC_PETTOOLOW 127 //Your pet's will is not sufficient to command its weapon. #define YOU_FLURRY 128 //You unleash a flurry of attacks. +#define FAILED_DISARM_TRAP 129 //You failed to disarm the trap. #define DOORS_LOCKED 130 //It's locked and you're not holding the key. #define DOORS_CANT_PICK 131 //This lock cannot be picked. #define DOORS_INSUFFICIENT_SKILL 132 //You are not sufficiently skilled to pick this lock. @@ -98,6 +99,7 @@ #define DUP_LORE 290 //Duplicate lore items are not allowed. #define TGB_ON 293 //Target other group buff is *ON*. #define TGB_OFF 294 //Target other group buff is *OFF*. +#define DISARMED_TRAP 305 //You have disarmed the trap. #define LDON_SENSE_TRAP1 306 //You do not Sense any traps. #define TRADESKILL_NOCOMBINE 334 //You cannot combine these items in this container type! #define TRADESKILL_FAILED 336 //You lacked the skills to fashion the items together. @@ -114,6 +116,8 @@ #define MEND_WORSEN 351 //You have worsened your wounds! #define MEND_FAIL 352 //You have failed to mend your wounds. #define LDON_SENSE_TRAP2 367 //You have not detected any traps. +#define TRAP_TOO_FAR 368 //You are too far away from that trap to affect it. +#define FAIL_DISARM_DETECTED_TRAP 370 //You fail to disarm the detected trap. #define LOOT_LORE_ERROR 371 //You cannot loot this Lore Item. You already have one. #define PICK_LORE 379 //You cannot pick up a lore item you already possess. #define CONSENT_DENIED 390 //You do not have consent to summon that corpse. @@ -421,6 +425,7 @@ #define SENSE_ANIMAL 12472 //You sense an animal in this direction. #define SENSE_SUMMONED 12473 //You sense a summoned being in this direction. #define SENSE_NOTHING 12474 //You don't sense anything. +#define SENSE_TRAP 12475 //You sense a trap in this direction. #define LDON_SENSE_TRAP3 12476 //You don't sense any traps. #define INTERRUPT_SPELL_OTHER 12478 //%1's casting is interrupted! #define YOU_HIT_NONMELEE 12481 //You were hit by non-melee for %1 damage. diff --git a/zone/trading.cpp b/zone/trading.cpp index 5c069cacd..70d5c85c1 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -523,7 +523,6 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st else qs_audit->char1_count += detail->charges; - //for (uint8 sub_slot = SUB_BEGIN; ((sub_slot < inst->GetItem()->BagSlots) && (sub_slot < EmuConstants::ITEM_CONTAINER_SIZE)); ++sub_slot) { for (uint8 sub_slot = EQEmu::inventory::containerBegin; (sub_slot < EQEmu::inventory::ContainerCount); ++sub_slot) { // this is to catch ALL items const EQEmu::ItemInstance* bag_inst = inst->GetItem(sub_slot); @@ -743,7 +742,6 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st qs_audit->char1_count += detail->charges; // 'step 3' should never really see containers..but, just in case... - //for (uint8 sub_slot = SUB_BEGIN; ((sub_slot < inst->GetItem()->BagSlots) && (sub_slot < EmuConstants::ITEM_CONTAINER_SIZE)); ++sub_slot) { for (uint8 sub_slot = EQEmu::inventory::containerBegin; (sub_slot < EQEmu::inventory::ContainerCount); ++sub_slot) { // this is to catch ALL items const EQEmu::ItemInstance* bag_inst = inst->GetItem(sub_slot); @@ -888,8 +886,12 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st const EQEmu::ItemData* item = inst->GetItem(); if(item && quest_npc == false) { + bool isPetAndCanHaveNoDrop = (RuleB(Pets, CanTakeNoDrop) && + _CLIENTPET(tradingWith) && + tradingWith->GetPetType()<=petOther); // if it was not a NO DROP or Attuned item (or if a GM is trading), let the NPC have it - if(GetGM() || (item->NoDrop != 0 && inst->IsAttuned() == false)) { + if(GetGM() || (inst->IsAttuned() == false && + (item->NoDrop != 0 || isPetAndCanHaveNoDrop))) { // pets need to look inside bags and try to equip items found there if (item->IsClassBag() && item->BagSlots > 0) { for (int16 bslot = EQEmu::inventory::containerBegin; bslot < item->BagSlots; bslot++) { diff --git a/zone/trap.cpp b/zone/trap.cpp index 87000f940..d5bcfbeb8 100644 --- a/zone/trap.cpp +++ b/zone/trap.cpp @@ -52,10 +52,12 @@ CREATE TABLE traps ( Trap::Trap() : Entity(), respawn_timer(600000), - chkarea_timer(500), + chkarea_timer(1000), + reset_timer(5000), m_Position(glm::vec3()) { trap_id = 0; + db_id = 0; maxzdiff = 0; radius = 0; effect = 0; @@ -64,12 +66,20 @@ Trap::Trap() : skill = 0; level = 0; respawn_timer.Disable(); + reset_timer.Disable(); detected = false; disarmed = false; respawn_time = 0; respawn_var = 0; hiddenTrigger = nullptr; ownHiddenTrigger = false; + chance = 0; + triggered_number = 0; + times_triggered = 0; + group = 0; + despawn_when_triggered = false; + charid = 0; + undetectable = false; } Trap::~Trap() @@ -80,8 +90,7 @@ Trap::~Trap() bool Trap::Process() { - if (chkarea_timer.Enabled() && chkarea_timer.Check() - /*&& zone->GetClientCount() > 0*/ ) + if (chkarea_timer.Enabled() && chkarea_timer.Check() && !reset_timer.Enabled()) { Mob* trigger = entity_list.GetTrapTrigger(this); if (trigger && !(trigger->IsClient() && trigger->CastToClient()->GetGM())) @@ -89,6 +98,13 @@ bool Trap::Process() Trigger(trigger); } } + else if (reset_timer.Enabled() && reset_timer.Check()) + { + Log(Logs::General, Logs::Traps, "Reset timer disabled in Reset Check Process for trap %d.", trap_id); + reset_timer.Disable(); + charid = 0; + } + if (respawn_timer.Enabled() && respawn_timer.Check()) { detected = false; @@ -96,11 +112,15 @@ bool Trap::Process() chkarea_timer.Enable(); respawn_timer.Disable(); } + + return true; } void Trap::Trigger(Mob* trigger) { + Log(Logs::General, Logs::Traps, "Trap %d triggered by %s for the %d time!", trap_id, trigger->GetName(), times_triggered + 1); + int i = 0; const NPCType* tmp = 0; switch (effect) @@ -128,7 +148,7 @@ void Trap::Trigger(Mob* trigger) entity_list.MessageClose(trigger,false,effectvalue,13,"%s",message.c_str()); } - entity_list.SendAlarm(this,trigger,effectvalue); + entity_list.SendAlarm(this,trigger, effectvalue2); break; case trapTypeMysticSpawn: if (message.empty()) @@ -201,12 +221,41 @@ void Trap::Trigger(Mob* trigger) safe_delete(outapp); } } - respawn_timer.Start((respawn_time + zone->random.Int(0, respawn_var)) * 1000); - chkarea_timer.Disable(); - disarmed = true; + + if (trigger && trigger->IsClient()) + { + trigger->CastToClient()->trapid = trap_id; + charid = trigger->CastToClient()->CharacterID(); + } + + bool update = false; + if (despawn_when_triggered) + { + Log(Logs::General, Logs::Traps, "Trap %d is despawning after being triggered.", trap_id); + update = true; + } + else + { + reset_timer.Start(5000); + } + + if (triggered_number > 0) + ++times_triggered; + + if (triggered_number > 0 && triggered_number <= times_triggered) + { + Log(Logs::General, Logs::Traps, "Triggered number for trap %d reached. %d/%d", trap_id, times_triggered, triggered_number); + update = true; + } + + if (update) + { + UpdateTrap(); + } } -Trap* EntityList::FindNearbyTrap(Mob* searcher, float max_dist) { +Trap* EntityList::FindNearbyTrap(Mob* searcher, float max_dist, float &trap_curdist, bool detected) +{ float dist = 999999; Trap* current_trap = nullptr; @@ -215,61 +264,161 @@ Trap* EntityList::FindNearbyTrap(Mob* searcher, float max_dist) { for (auto it = trap_list.begin(); it != trap_list.end(); ++it) { cur = it->second; - if(cur->disarmed) + if(cur->disarmed || (detected && !cur->detected) || cur->undetectable) continue; auto diff = glm::vec3(searcher->GetPosition()) - cur->m_Position; - float curdist = diff.x * diff.x + diff.y * diff.y + diff.z * diff.z; + float curdist = diff.x*diff.x + diff.y*diff.y; + diff.z = std::abs(diff.z); - if (curdist < max_dist2 && curdist < dist) + if (curdist < max_dist2 && curdist < dist && diff.z <= cur->maxzdiff) { + Log(Logs::General, Logs::Traps, "Trap %d is curdist %0.1f", cur->db_id, curdist); dist = curdist; current_trap = cur; } } + if (current_trap != nullptr) + { + Log(Logs::General, Logs::Traps, "Trap %d is the closest trap.", current_trap->db_id); + trap_curdist = dist; + } + else + trap_curdist = INVALID_INDEX; + return current_trap; } -Mob* EntityList::GetTrapTrigger(Trap* trap) { - Mob* savemob = 0; +Mob* EntityList::GetTrapTrigger(Trap* trap) +{ float maxdist = trap->radius * trap->radius; - - for (auto it = client_list.begin(); it != client_list.end(); ++it) { + for (auto it = client_list.begin(); it != client_list.end(); ++it) + { Client* cur = it->second; auto diff = glm::vec3(cur->GetPosition()) - trap->m_Position; diff.z = std::abs(diff.z); if ((diff.x*diff.x + diff.y*diff.y) <= maxdist - && diff.z < trap->maxzdiff) + && diff.z <= trap->maxzdiff) { - if (zone->random.Roll(trap->chance)) - return(cur); - else - savemob = cur; - } + //This prevents the trap from triggering on players while zoning. + if (strcmp(cur->GetName(), "No name") == 0) + continue; + if (cur->trapid == 0 && !cur->GetGM() && (trap->chance == 0 || zone->random.Roll(trap->chance))) + { + Log(Logs::General, Logs::Traps, "%s is about to trigger trap %d of chance %d. diff: %0.2f maxdist: %0.2f zdiff: %0.2f maxzdiff: %0.2f", cur->GetName(), trap->trap_id, trap->chance, (diff.x*diff.x + diff.y*diff.y), maxdist, diff.z, trap->maxzdiff); + return cur; + } + } + else + { + if (cur->trapid == trap->trap_id) + { + Log(Logs::General, Logs::Traps, "%s is clearing trapid for trap %d", cur->GetName(), trap->trap_id); + cur->trapid = 0; + } + } } - return savemob; + return nullptr; } -//todo: rewrite this to not need direct access to trap members. +bool EntityList::IsTrapGroupSpawned(uint32 trap_id, uint8 group) +{ + auto it = trap_list.begin(); + while (it != trap_list.end()) + { + Trap* cur = it->second; + if (cur->IsTrap() && cur->group == group && cur->trap_id != trap_id) + { + return true; + } + ++it; + } + + return false; +} + +void EntityList::UpdateAllTraps(bool respawn, bool repopnow) +{ + auto it = trap_list.begin(); + while (it != trap_list.end()) + { + Trap* cur = it->second; + if (cur->IsTrap()) + { + cur->UpdateTrap(respawn, repopnow); + } + ++it; + } + + Log(Logs::General, Logs::Traps, "All traps updated."); +} + +void EntityList::GetTrapInfo(Client* client) +{ + uint8 count = 0; + auto it = trap_list.begin(); + while (it != trap_list.end()) + { + Trap* cur = it->second; + if (cur->IsTrap()) + { + bool isset = (cur->chkarea_timer.Enabled() && !cur->reset_timer.Enabled()); + client->Message(CC_Default, " Trap: (%d) found at %0.2f,%0.2f,%0.2f. Times Triggered: %d Is Active: %d Group: %d Message: %s", cur->trap_id, cur->m_Position.x, cur->m_Position.y, cur->m_Position.z, cur->times_triggered, isset, cur->group, cur->message.c_str()); + ++count; + } + ++it; + } + + client->Message(CC_Default, "%d traps found.", count); +} + +void EntityList::ClearTrapPointers() +{ + auto it = trap_list.begin(); + while (it != trap_list.end()) + { + Trap* cur = it->second; + if (cur->IsTrap()) + { + cur->DestroyHiddenTrigger(); + } + ++it; + } +} + + bool ZoneDatabase::LoadTraps(const char* zonename, int16 version) { std::string query = StringFormat("SELECT id, x, y, z, effect, effectvalue, effectvalue2, skill, " - "maxzdiff, radius, chance, message, respawn_time, respawn_var, level " - "FROM traps WHERE zone='%s' AND version=%u", zonename, version); + "maxzdiff, radius, chance, message, respawn_time, respawn_var, level, " + "`group`, triggered_number, despawn_when_triggered, undetectable FROM traps WHERE zone='%s' AND version=%u", zonename, version); + auto results = QueryDatabase(query); if (!results.Success()) { return false; } for (auto row = results.begin(); row != results.end(); ++row) { + uint32 tid = atoi(row[0]); + uint8 grp = atoi(row[15]); + + if (grp > 0) + { + // If a member of our group is already spawned skip loading this trap. + if (entity_list.IsTrapGroupSpawned(tid, grp)) + { + continue; + } + } auto trap = new Trap(); - trap->trap_id = atoi(row[0]); + trap->trap_id = tid; + trap->db_id = tid; trap->m_Position = glm::vec3(atof(row[1]), atof(row[2]), atof(row[3])); trap->effect = atoi(row[4]); trap->effectvalue = atoi(row[5]); @@ -282,8 +431,13 @@ bool ZoneDatabase::LoadTraps(const char* zonename, int16 version) { trap->respawn_time = atoi(row[12]); trap->respawn_var = atoi(row[13]); trap->level = atoi(row[14]); + trap->group = grp; + trap->triggered_number = atoi(row[16]); + trap->despawn_when_triggered = atobool(row[17]); + trap->undetectable = atobool(row[18]); entity_list.AddTrap(trap); trap->CreateHiddenTrigger(); + Log(Logs::General, Logs::Traps, "Trap %d successfully loaded.", trap->trap_id); } return true; @@ -318,3 +472,87 @@ void Trap::CreateHiddenTrigger() hiddenTrigger = npca; ownHiddenTrigger = true; } +bool ZoneDatabase::SetTrapData(Trap* trap, bool repopnow) { + + uint32 dbid = trap->db_id; + std::string query; + + if (trap->group > 0) + { + query = StringFormat("SELECT id, x, y, z, effect, effectvalue, effectvalue2, skill, " + "maxzdiff, radius, chance, message, respawn_time, respawn_var, level, " + "triggered_number, despawn_when_triggered, undetectable FROM traps WHERE zone='%s' AND `group`=%d AND id != %d ORDER BY RAND() LIMIT 1", zone->GetShortName(), trap->group, dbid); + } + else + { + // We could just use the existing data here, but querying the DB is not expensive, and allows content developers to change traps without rebooting. + query = StringFormat("SELECT id, x, y, z, effect, effectvalue, effectvalue2, skill, " + "maxzdiff, radius, chance, message, respawn_time, respawn_var, level, " + "triggered_number, despawn_when_triggered, undetectable FROM traps WHERE zone='%s' AND id = %d", zone->GetShortName(), dbid); + } + + auto results = QueryDatabase(query); + if (!results.Success()) { + return false; + } + + for (auto row = results.begin(); row != results.end(); ++row) { + + trap->db_id = atoi(row[0]); + trap->m_Position = glm::vec3(atof(row[1]), atof(row[2]), atof(row[3])); + trap->effect = atoi(row[4]); + trap->effectvalue = atoi(row[5]); + trap->effectvalue2 = atoi(row[6]); + trap->skill = atoi(row[7]); + trap->maxzdiff = atof(row[8]); + trap->radius = atof(row[9]); + trap->chance = atoi(row[10]); + trap->message = row[11]; + trap->respawn_time = atoi(row[12]); + trap->respawn_var = atoi(row[13]); + trap->level = atoi(row[14]); + trap->triggered_number = atoi(row[15]); + trap->despawn_when_triggered = atobool(row[16]); + trap->undetectable = atobool(row[17]); + trap->CreateHiddenTrigger(); + + if (repopnow) + { + trap->chkarea_timer.Enable(); + } + else + { + trap->respawn_timer.Start((trap->respawn_time + zone->random.Int(0, trap->respawn_var)) * 1000); + } + + if (trap->trap_id != trap->db_id) + Log(Logs::General, Logs::Traps, "Trap (%d) DBID has changed from %d to %d", trap->trap_id, dbid, trap->db_id); + + return true; + } + + return false; +} + +void Trap::UpdateTrap(bool respawn, bool repopnow) +{ + respawn_timer.Disable(); + chkarea_timer.Disable(); + reset_timer.Disable(); + if (hiddenTrigger) + { + hiddenTrigger->Depop(); + hiddenTrigger = nullptr; + } + times_triggered = 0; + Client* trigger = entity_list.GetClientByCharID(charid); + if (trigger) + { + trigger->trapid = 0; + } + charid = 0; + if (respawn) + { + database.SetTrapData(this, repopnow); + } +} \ No newline at end of file diff --git a/zone/trap.h b/zone/trap.h index 4adab320a..e20e2314f 100644 --- a/zone/trap.h +++ b/zone/trap.h @@ -49,11 +49,14 @@ public: NPC * GetHiddenTrigger() { return hiddenTrigger; } void SetHiddenTrigger(NPC* n) { hiddenTrigger = n; } void CreateHiddenTrigger(); - + void DestroyHiddenTrigger() { hiddenTrigger = nullptr; } + void UpdateTrap(bool respawn = true, bool repopnow = false); //Trap data, leave this unprotected Timer respawn_timer; //Respawn Time when Trap's been disarmed Timer chkarea_timer; - uint32 trap_id; //Database ID of trap + Timer reset_timer; //How long a trap takes to reset before triggering again. + uint32 trap_id; //Original ID of the trap from DB. This value never changes. + uint32 db_id; //The DB ID of the trap that currently is spawned. glm::vec3 m_Position; float maxzdiff; //maximum z diff to be triggerable float radius; //radius around trap to be triggerable @@ -67,6 +70,12 @@ public: bool disarmed; uint32 respawn_time; uint32 respawn_var; + uint8 triggered_number; + uint8 times_triggered; + uint8 group; + bool despawn_when_triggered; + uint32 charid; //ID of character that triggered trap. This is cleared when the trap despawns are resets. + bool undetectable; std::string message; protected: diff --git a/zone/waypoints.cpp b/zone/waypoints.cpp index c1997af59..9231ccae2 100644 --- a/zone/waypoints.cpp +++ b/zone/waypoints.cpp @@ -176,9 +176,15 @@ void NPC::MoveTo(const glm::vec4& position, bool saveguardspot) cur_wp = -2; // flag as quest controlled w/no grid Log(Logs::Detail, Logs::AI, "MoveTo %s without a grid.", to_string(static_cast(position)).c_str()); } + + glm::vec3 dest(position); + + m_CurrentWayPoint = position; + m_CurrentWayPoint.z = GetFixedZ(dest); + if (saveguardspot) { - m_GuardPoint = position; + m_GuardPoint = m_CurrentWayPoint; if (m_GuardPoint.w == 0) m_GuardPoint.w = 0.0001; //hack to make IsGuarding simpler @@ -189,7 +195,6 @@ void NPC::MoveTo(const glm::vec4& position, bool saveguardspot) Log(Logs::Detail, Logs::AI, "Setting guard position to %s", to_string(static_cast(m_GuardPoint)).c_str()); } - m_CurrentWayPoint = position; cur_wp_pause = 0; pLastFightingDelayMoving = 0; if (AI_walking_timer->Enabled()) @@ -838,52 +843,172 @@ void Mob::SendToFixZ(float new_x, float new_y, float new_z) { } } -void Mob::FixZ() { - +float Mob::GetFixedZ(glm::vec3 dest, int32 z_find_offset) +{ BenchTimer timer; timer.reset(); + float new_z = dest.z; - if (zone->HasMap() && RuleB(Map, FixZWhenMoving) && (flymode != 1 && flymode != 2)) + if (zone->HasMap() && RuleB(Map, FixZWhenMoving) && + (flymode != 1 && flymode != 2)) { - if (!RuleB(Watermap, CheckForWaterWhenMoving) || !zone->HasWaterMap() || - (zone->HasWaterMap() && !zone->watermap->InWater(glm::vec3(m_Position)))) + if (!RuleB(Watermap, CheckForWaterWhenMoving) || !zone->HasWaterMap() + || (zone->HasWaterMap() && + !zone->watermap->InWater(glm::vec3(m_Position)))) { /* Any more than 5 in the offset makes NPC's hop/snap to ceiling in small corridors */ - float new_z = this->FindGroundZ(m_Position.x, m_Position.y, 5); - new_z += (this->GetSize() / 1.55); + new_z = this->FindDestGroundZ(dest, z_find_offset); + if (new_z != BEST_Z_INVALID) + { + new_z += this->GetZOffset(); - auto duration = timer.elapsed(); - - Log( - Logs::Moderate, - Logs::FixZ, - "Mob::FixZ() (%s) returned %4.3f at %4.3f, %4.3f, %4.3f - Took %lf", - this->GetCleanName(), - new_z, - m_Position.x, - m_Position.y, - m_Position.z, - duration - ); - - if ((new_z > -2000) && new_z != -999999) { - if (RuleB(Map, MobZVisualDebug)) - this->SendAppearanceEffect(78, 0, 0, 0, 0); - - m_Position.z = new_z; + // If bad new Z restore old one + if (new_z < -2000) { + new_z = m_Position.z; + } } - else { - if (RuleB(Map, MobZVisualDebug)) - this->SendAppearanceEffect(103, 0, 0, 0, 0); + } - Log(Logs::General, Logs::FixZ, "%s is failing to find Z %f", this->GetCleanName(), std::abs(m_Position.z - new_z)); - } + auto duration = timer.elapsed(); - last_z = m_Position.z; + Log(Logs::Moderate, Logs::FixZ, + "Mob::GetFixedZ() (%s) returned %4.3f at %4.3f, %4.3f, %4.3f - Took %lf", + this->GetCleanName(), new_z, dest.x, dest.y, dest.z, duration); + } + + return new_z; +} + +void Mob::FixZ(int32 z_find_offset /*= 5*/) +{ + glm::vec3 current_loc(m_Position); + float new_z = GetFixedZ(current_loc, z_find_offset); + + if (!IsClient() && new_z != m_Position.z) + { + if ((new_z > -2000) && new_z != BEST_Z_INVALID) { + if (RuleB(Map, MobZVisualDebug)) + this->SendAppearanceEffect(78, 0, 0, 0, 0); + + m_Position.z = new_z; + } + else { + if (RuleB(Map, MobZVisualDebug)) + this->SendAppearanceEffect(103, 0, 0, 0, 0); + + Log(Logs::General, Logs::FixZ, "%s is failing to find Z %f", + this->GetCleanName(), std::abs(m_Position.z - new_z)); } } } +float Mob::GetZOffset() const { + float offset = 3.125f; + + switch (race) { + case 436: + offset = 0.577f; + break; + case 430: + offset = 0.5f; + break; + case 432: + offset = 1.9f; + break; + case 435: + offset = 0.93f; + break; + case 450: + offset = 0.938f; + break; + case 479: + offset = 0.8f; + break; + case 451: + offset = 0.816f; + break; + case 437: + offset = 0.527f; + break; + case 439: + offset = 1.536f; + break; + case 415: + offset = 1.0f; + break; + case 438: + offset = 0.776f; + break; + case 452: + offset = 0.776f; + break; + case 441: + offset = 0.816f; + break; + case 440: + offset = 0.938f; + break; + case 468: + offset = 1.0f; + break; + case 459: + offset = 1.0f; + break; + case 462: + offset = 1.5f; + break; + case 530: + offset = 1.2f; + break; + case 549: + offset = 0.5f; + break; + case 548: + offset = 0.5f; + break; + case 547: + offset = 0.5f; + break; + case 604: + offset = 1.2f; + break; + case 653: + offset = 5.9f; + break; + case 658: + offset = 4.0f; + break; + case 323: + offset = 5.0f; + break; + case 663: + offset = 5.0f; + break; + case 664: + offset = 4.0f; + break; + case 703: + offset = 9.0f; + break; + case 688: + offset = 5.0f; + break; + case 669: + offset = 7.0f; + break; + case 687: + offset = 2.0f; + break; + case 686: + offset = 2.0f; + break; + default: + offset = 3.125f; + } + + return 0.2 * GetSize() * offset; +} + int ZoneDatabase::GetHighestGrid(uint32 zoneid) { std::string query = StringFormat("SELECT COALESCE(MAX(id), 0) FROM grid WHERE zoneid = %i", zoneid); diff --git a/zone/zone.cpp b/zone/zone.cpp index 8441f7d82..0e6a50092 100644 --- a/zone/zone.cpp +++ b/zone/zone.cpp @@ -1428,7 +1428,8 @@ void Zone::StartShutdownTimer(uint32 set_time) { bool Zone::Depop(bool StartSpawnTimer) { std::map::iterator itr; entity_list.Depop(StartSpawnTimer); - + entity_list.ClearTrapPointers(); + entity_list.UpdateAllTraps(false); /* Refresh npctable (cache), getting current info from database. */ while(!npctable.empty()) { itr = npctable.begin(); @@ -1496,6 +1497,8 @@ void Zone::Repop(uint32 delay) { iterator.RemoveCurrent(); } + entity_list.ClearTrapPointers(); + quest_manager.ClearAllTimers(); if (!database.PopulateZoneSpawnList(zoneid, spawn2_list, GetInstanceVersion(), delay)) @@ -1503,6 +1506,8 @@ void Zone::Repop(uint32 delay) { initgrids_timer.Start(); + entity_list.UpdateAllTraps(true, true); + //MODDING HOOK FOR REPOP mod_repop(); } @@ -1510,7 +1515,7 @@ void Zone::Repop(uint32 delay) { void Zone::GetTimeSync() { if (worldserver.Connected() && !zone_has_current_time) { - auto pack = new ServerPacket(ServerOP_GetWorldTime, 0); + auto pack = new ServerPacket(ServerOP_GetWorldTime, 1); worldserver.SendPacket(pack); safe_delete(pack); } diff --git a/zone/zonedb.cpp b/zone/zonedb.cpp index bd28bcba3..e23d534c5 100644 --- a/zone/zonedb.cpp +++ b/zone/zonedb.cpp @@ -1433,6 +1433,11 @@ bool ZoneDatabase::SaveCharacterInventorySnapshot(uint32 character_id){ } bool ZoneDatabase::SaveCharacterData(uint32 character_id, uint32 account_id, PlayerProfile_Struct* pp, ExtendedProfile_Struct* m_epp){ + + /* If this is ever zero - the client hasn't fully loaded and potentially crashed during zone */ + if (account_id <= 0) + return false; + clock_t t = std::clock(); /* Function timer start */ std::string query = StringFormat( "REPLACE INTO `character_data` (" @@ -2953,9 +2958,11 @@ uint32 ZoneDatabase::GetKarma(uint32 acct_id) if (!results.Success()) return 0; - auto row = results.begin(); + for (auto row = results.begin(); row != results.end(); ++row) { + return atoi(row[0]); + } - return atoi(row[0]); + return 0; } void ZoneDatabase::UpdateKarma(uint32 acct_id, uint32 amount) diff --git a/zone/zonedb.h b/zone/zonedb.h index a8ab3659f..c134dade3 100644 --- a/zone/zonedb.h +++ b/zone/zonedb.h @@ -16,6 +16,7 @@ class NPC; class Petition; class Spawn2; class SpawnGroupList; +class Trap; struct CharacterEventLog_Struct; struct Door; struct ExtendedProfile_Struct; @@ -478,7 +479,7 @@ public: /* Traps */ bool LoadTraps(const char* zonename, int16 version); - char* GetTrapMessage(uint32 trap_id); + bool SetTrapData(Trap* trap, bool repopnow = false); /* Time */ uint32 GetZoneTZ(uint32 zoneid, uint32 version);