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 f99b17975..baac6618e 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,23 @@ 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](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_oplist.h b/common/emu_oplist.h index 29a20cb14..319517499 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), 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..10d9e460c 100644 --- a/common/eqemu_logsys.h +++ b/common/eqemu_logsys.h @@ -88,6 +88,7 @@ enum LogCategory { Headless_Client, HP_Update, FixZ, + Food, MaxCategoryID /* Don't Remove this*/ }; @@ -140,7 +141,8 @@ static const char* LogCategoryName[LogCategory::MaxCategoryID] = { "Client Login", "Headless Client", "HP Update", - "FixZ" + "FixZ", + "Food" }; } 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 2e8d8458b..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 @@ -1883,35 +1883,34 @@ namespace RoF2 eq->FogDensity = emu->fog_density; /*fill in some unknowns with observed values, hopefully it will help */ - eq->unknown569 = 0; + eq->ZoneTimeZone = 0; eq->unknown571 = 0; - eq->unknown572 = 4; - eq->unknown576 = 2; - eq->unknown580 = 0; + eq->WaterMidi = 4; + eq->DayMidi = 2; + eq->NightMidi = 0; - eq->unknown800 = -1; - eq->unknown844 = 600; - eq->unknown848 = 2008; // Guild Lobby observed value - eq->unknown880 = 50; - eq->unknown884 = 10; - eq->unknown888 = 1; - eq->unknown889 = 0; - eq->unknown890 = 1; - eq->unknown891 = 0; - eq->unknown892 = 0; - eq->unknown893 = 0; + eq->SkyRelated2 = -1; + eq->NPCAggroMaxDist = 600; + eq->FilterID = 2008; // Guild Lobby observed value + eq->LavaDamage = 50; + eq->MinLavaDamage = 10; + eq->bDisallowManaStone = 1; + eq->bNoBind = 0; + eq->bNoAttack = 0; + eq->bNoCallOfHero = 0; + eq->bNoFlux = 0; + eq->bNoFear = 0; eq->fall_damage = 0; // 0 = Fall Damage on, 1 = Fall Damage off eq->unknown895 = 0; - eq->unknown896 = 180; - eq->unknown900 = 180; - eq->unknown904 = 180; - eq->unknown908 = 2; - eq->unknown912 = 2; - eq->unknown932 = -1; // Set from PoK Example - eq->unknown936 = -1; // Set from PoK Example - eq->unknown944 = 1.0; // Set from PoK Example - eq->unknown948 = 0; // New on Live as of Dec 15 2014 - eq->unknown952 = 100; // New on Live as of Dec 15 2014 + eq->FastRegenHP = 180; + eq->FastRegenMana = 180; + eq->FastRegenEndurance = 180; + eq->CanPlaceCampsite = 2; + eq->CanPlaceGuildBanner = 2; + eq->FishingRelated = -1; // Set from PoK Example + eq->ForageRelated = -1; // Set from PoK Example + eq->bNoLevitate = 0; + eq->Blooming = 1.0; // Set from PoK Example FINISH_ENCODE(); } @@ -2262,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_structs.h b/common/patches/rof2_structs.h index 1b3c41794..28905dd4a 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; @@ -561,74 +562,100 @@ struct ServerZoneEntry_Struct //Adjusted from SEQ Everquest.h Struct //New Zone Struct - Size: 948 struct NewZone_Struct { /*0000*/ char char_name[64]; // Character Name - /*0064*/ char zone_short_name[32]; // Zone Short Name - /*0096*/ char unknown0096[96]; - /*0192*/ char zone_long_name[278]; // Zone Long Name - /*0470*/ uint8 ztype; // Zone type (usually FF) - /*0471*/ uint8 fog_red[4]; // Zone fog (red) - /*0475*/ uint8 fog_green[4]; // Zone fog (green) - /*0479*/ uint8 fog_blue[4]; // Zone fog (blue) - /*0483*/ uint8 unknown323; - /*0484*/ float fog_minclip[4]; + /*0064*/ char zone_short_name[128]; // Zone Short Name + /*0192*/ char zone_long_name[128]; // Zone Long Name + /*0320*/ char zone_desc[5][30]; // mostly just empty strings + /*0470*/ uint8 ztype; // Zone type (usually FF) FogOnOff + /*0471*/ uint8 fog_red[4]; // Zone fog (red) ARGBCOLOR + /*0475*/ uint8 fog_green[4]; // Zone fog (green) ARGBCOLOR + /*0479*/ uint8 fog_blue[4]; // Zone fog (blue) ARGBCOLO + /*0483*/ uint8 unknown323; // padding? + /*0484*/ float fog_minclip[4]; // MQ2 has this starting at this offset, must be padding above /*0500*/ float fog_maxclip[4]; /*0516*/ float gravity; - /*0520*/ uint8 time_type; + /*0520*/ uint8 time_type; // OutDoor flag 0 = IndoorDungeon, 1 = OUtdoor, 2 = OutdoorCity, 3 = DungeonCity, 4 = IndoorCity, 5 = OutdoorDungeon /*0521*/ uint8 rain_chance[4]; /*0525*/ uint8 rain_duration[4]; /*0529*/ uint8 snow_chance[4]; /*0533*/ uint8 snow_duration[4]; - /*0537*/ uint8 unknown537[32]; // Seen all 0xff - /*0569*/ uint8 unknown569; // Unknown - Seen 0 + /*0537*/ uint8 unknown537[32]; // this is removed on live, specialdates and specialcodes probably macro'd out + /*0569*/ uint8 ZoneTimeZone; // MQ2 "in hours from worldserver, can be negative" /*0570*/ uint8 sky; // Sky Type - /*0571*/ uint8 unknown571; // Unknown - Seen 0 - /*0572*/ uint32 unknown572; // Unknown - Seen 4 in Guild Lobby - /*0576*/ uint32 unknown576; // Unknown - Seen 2 in Guild Lobby - /*0580*/ uint32 unknown580; // Unknown - Seen 0 in Guild Lobby + /*0571*/ uint8 unknown571; // Padding I think + /*0572*/ uint32 WaterMidi; // Unknown - Seen 4 in Guild Lobby + /*0576*/ uint32 DayMidi; // Unknown - Seen 2 in Guild Lobby + /*0580*/ uint32 NightMidi; // Unknown - Seen 0 in Guild Lobby /*0584*/ float zone_exp_multiplier; // Experience Multiplier /*0588*/ float safe_y; // Zone Safe Y /*0592*/ float safe_x; // Zone Safe X /*0596*/ float safe_z; // Zone Safe Z - /*0600*/ float min_z; // Guessed - NEW - Seen 0 - /*0604*/ float max_z; // Guessed - /*0608*/ float underworld; // Underworld, min z (Not Sure?) + /*0600*/ float min_z; // This isn't safe heading like live -- could be default heading rather than succor heading + /*0604*/ float max_z; // Ceiling + /*0608*/ float underworld; // Underworld, min z (Not Sure?) Floor /*0612*/ float minclip; // Minimum View Distance /*0616*/ float maxclip; // Maximum View DIstance - /*0620*/ uint8 unknown620[84]; // ***Placeholder - /*0704*/ char zone_short_name2[96]; //zone file name? excludes instance number which can be in previous version. - /*0800*/ int32 unknown800; //seen -1 - /*0804*/ char unknown804[40]; // - /*0844*/ int32 unknown844; //seen 600 - /*0848*/ int32 unknown848; //seen 2008 - /*0852*/ uint16 zone_id; + /*0620*/ uint32 ForageLow; // Forage loot table ID? + /*0624*/ uint32 ForageMedium; // Forage loot table ID? + /*0628*/ uint32 ForageHigh; // Forage loot table ID? + /*0632*/ uint32 FishingLow; // Fishing loot table ID? + /*0636*/ uint32 FishingMedium; // Fishing loot table ID? + /*0640*/ uint32 FishingHigh; // Fishing loot table ID? + /*0644*/ uint32 sky_lock; // MQ2 skyrelated + /*0648*/ uint32 graveyard_timer; // minutes until corpse pop to graveyard + /*0652*/ uint32 scriptIDHour; // These are IDs of scripts + /*0656*/ uint32 scriptIDMinute; // These are IDs of scripts + /*0660*/ uint32 scriptIDTick; // These are IDs of scripts + /*0664*/ uint32 scriptIDOnPlayerDeath; // These are IDs of scripts + /*0668*/ uint32 scriptIDOnNPCDeath; // These are IDs of scripts + /*0672*/ uint32 scriptIDPlayerEnteringZone; // These are IDs of scripts + /*0676*/ uint32 scriptIDOnZonePop; // These are IDs of scripts + /*0680*/ uint32 scriptIDNPCLoot; // These are IDs of scripts + /*0684*/ uint32 scriptIDAdventureFailed; // These are IDs of scripts + /*0688*/ uint32 CanExploreTasks; + /*0692*/ uint32 UnknownFlag; // not sure, neither is MQ2! + /*0696*/ uint32 scriptIDOnFishing; // THese are IDs of scripts + /*0700*/ uint32 scriptIDOnForage; // THese are IDs of scripts + /*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 -- maybe some default sky time? + /*0804*/ char WeatherString2[32]; // + /*0836*/ float WeatherChangeTime; // not sure :P + /*0840*/ uint32 Climate; + /*0844*/ int32 NPCAggroMaxDist; //seen 600 + /*0848*/ int32 FilterID; //seen 2008 -- maybe zone guide related? + /*0852*/ uint16 zone_id; // this might just be instance ID got 1736 for time /*0854*/ uint16 zone_instance; - /*0856*/ char unknown856[20]; - /*0876*/ uint32 SuspendBuffs; - /*0880*/ uint32 unknown880; // Seen 50 - /*0884*/ uint32 unknown884; // Seen 10 - /*0888*/ uint8 unknown888; // Seen 1 - /*0889*/ uint8 unknown889; // Seen 0 (POK) or 1 (rujj) - /*0890*/ uint8 unknown890; // Seen 1 - /*0891*/ uint8 unknown891; // Seen 0 - /*0892*/ uint8 unknown892; // Seen 0 - /*0893*/ uint8 unknown893; // Seen 0 - 00 - /*0894*/ uint8 fall_damage; // 0 = Fall Damage on, 1 = Fall Damage off - /*0895*/ uint8 unknown895; // Seen 0 - 00 - /*0896*/ uint32 unknown896; // Seen 180 - /*0900*/ uint32 unknown900; // Seen 180 - /*0904*/ uint32 unknown904; // Seen 180 - /*0908*/ uint32 unknown908; // Seen 2 - /*0912*/ uint32 unknown912; // Seen 2 - /*0916*/ float FogDensity; // Most zones have this set to 0.33 Blightfire had 0.16 - /*0920*/ uint32 unknown920; // Seen 0 - /*0924*/ uint32 unknown924; // Seen 0 - /*0928*/ uint32 unknown928; // Seen 0 - /*0932*/ int32 unknown932; // Seen -1 - /*0936*/ int32 unknown936; // Seen -1 - /*0940*/ uint32 unknown940; // Seen 0 - /*0944*/ float unknown944; // Seen 1.0 in PoK, and 0.25 in Guild Lobby - /*0948*/ uint32 unknown948; // Seen 0 - New on Live as of Dec 15 2014 - /*0952*/ uint32 unknown952; // Seen 100 - New on Live as of Dec 15 2014 - /*0956*/ + /*0856*/ uint32 scriptNPCReceivedanItem; + /*0860*/ uint32 bCheck; // padded bool + /*0864*/ uint32 scriptIDSomething; + /*0868*/ uint32 scriptIDSomething2; + /*0872*/ uint32 scriptIDSomething3; + /*0876*/ uint32 SuspendBuffs; // padded bool + /*0880*/ uint32 LavaDamage; // LavaDamage value + /*0884*/ uint32 MinLavaDamage; // min cap after resist calcs + /*0888*/ uint8 bDisallowManaStone; // can't use manastone in this zone + /*0889*/ uint8 bNoBind; // can't bind even if outdoor says we can! + /*0890*/ uint8 bNoAttack; // non-attack zone + /*0891*/ uint8 bNoCallOfHero; // coth line disabled + /*0892*/ uint8 bNoFlux; // gflux no worky + /*0893*/ uint8 bNoFear; // fear spells no worky + /*0894*/ uint8 fall_damage; // 0 = Fall Damage on, 1 = Fall Damage off MQ2 calls bNoEncumber + /*0895*/ uint8 unknown895; // padding + /*0896*/ uint32 FastRegenHP; // percentage I think? + /*0900*/ uint32 FastRegenMana; // percentage I think? + /*0904*/ uint32 FastRegenEndurance; // percentage I think? + /*0908*/ uint32 CanPlaceCampsite; // 0 = no, 1 = can place, 2 = place and goto + /*0912*/ uint32 CanPlaceGuildBanner; // ^ + /*0916*/ float FogDensity; // Most zones have this set to 0.33 Blightfire had 0.16 + /*0920*/ uint32 bAdjustGamma; // padded bool + /*0924*/ uint32 TimeStringID; // Seen 0 + /*0928*/ uint32 bNoMercenaries; // padded bool + /*0932*/ int32 FishingRelated; // Seen -1 idk + /*0936*/ int32 ForageRelated; // Seen -1 idk + /*0940*/ uint32 bNoLevitate; // padded bool + /*0944*/ float Blooming; // Seen 1.0 in PoK, and 0.25 in Guild Lobby + /*0948*/ }; /* @@ -1129,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 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..2a1eab3f0 100644 --- a/common/patches/sof_structs.h +++ b/common/patches/sof_structs.h @@ -799,6 +799,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 +904,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) 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..56213854a 100644 --- a/common/patches/titanium_structs.h +++ b/common/patches/titanium_structs.h @@ -740,6 +740,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 +845,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) 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..c3a8fd5dc 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -66,11 +66,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 +92,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 +120,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,6 +190,7 @@ 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) @@ -563,6 +568,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/utils/patches/patch_RoF.conf b/utils/patches/patch_RoF.conf index 339bfd800..f3d4249c0 100644 --- a/utils/patches/patch_RoF.conf +++ b/utils/patches/patch_RoF.conf @@ -341,6 +341,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..420fcec70 100644 --- a/utils/patches/patch_RoF2.conf +++ b/utils/patches/patch_RoF2.conf @@ -340,6 +340,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..0e8e36e02 100644 --- a/utils/patches/patch_SoD.conf +++ b/utils/patches/patch_SoD.conf @@ -345,6 +345,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..cae177bfc 100644 --- a/utils/patches/patch_SoF.conf +++ b/utils/patches/patch_SoF.conf @@ -329,6 +329,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_UF.conf b/utils/patches/patch_UF.conf index af698d52a..6d8064029 100644 --- a/utils/patches/patch_UF.conf +++ b/utils/patches/patch_UF.conf @@ -346,6 +346,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..e385b9f9e 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| 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/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 599f01c8f..b6e7babcc 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" @@ -104,6 +105,12 @@ WebInterfaceList web_interface; void CatchSignal(int sig_num); +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(); @@ -527,8 +534,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)) @@ -545,6 +551,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(); @@ -563,17 +572,4 @@ int main(int argc, char** argv) { void CatchSignal(int sig_num) { Log(Logs::General, Logs::World_Server, "Caught signal %d", sig_num); RunLoops = false; -} - -void UpdateWindowTitle(char* iNewTitle) { -#ifdef _WINDOWS - char tmp[500]; - if (iNewTitle) { - snprintf(tmp, sizeof(tmp), "World: %s", iNewTitle); - } - else { - snprintf(tmp, sizeof(tmp), "World"); - } - SetConsoleTitle(tmp); -#endif -} +} \ No newline at end of file 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..a6baffc8d 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -1262,6 +1262,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 +1289,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); @@ -3907,10 +3922,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 +3962,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) 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..ce084747a 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,9 @@ Client::Client(EQStreamInterface* ieqs) interrogateinv_flag = false; + for (int i = 0; i < InnateSkillMax; ++i) + m_pp.InnateSkills[i] = InnateDisabled; + AI_Init(); } @@ -658,13 +663,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()) { @@ -1220,9 +1230,14 @@ void Client::ChannelMessageSend(const char* from, const char* to, uint8 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 +4603,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 +4640,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 +6727,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 +6740,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 +6754,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 +8600,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 +9071,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..e334f8fd9 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(); @@ -1345,13 +1381,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 +1446,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 +1495,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 +1526,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 +1550,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..6710fdfec 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -1410,6 +1410,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 +2145,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 +4640,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); @@ -8690,6 +8708,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 +8731,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); + */ } } @@ -10948,592 +10970,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 +12085,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; @@ -13012,6 +13047,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 +13405,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_process.cpp b/zone/client_process.cpp index 3e08c1fba..a0bdeead2 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,15 +244,21 @@ 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) { @@ -261,6 +268,10 @@ bool Client::Process() { close_mobs.insert(std::pair(mob, distance)); } } + + if (force_spawn_updates && mob != this && distance <= client_update_range) + mob->SendPositionUpdateToClient(this); + } } @@ -513,6 +524,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 +538,6 @@ bool Client::Process() { DoManaRegen(); DoEnduranceRegen(); BuffProcess(); - DoStaminaUpdate(); if (tribute_timer.Check()) { ToggleTribute(true); //re-activate the tribute. @@ -1806,7 +1820,7 @@ void Client::OPGMSummon(const EQApplicationPacket *app) } void Client::DoHPRegen() { - SetHP(GetHP() + CalcHPRegen() + RestRegenHP); + SetHP(GetHP() + CalcHPRegen()); SendHPUpdate(); } @@ -1814,41 +1828,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 +1925,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 +1945,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 cbcdf6bb3..15b09b9dc 100644 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -3895,6 +3895,8 @@ void command_depopzone(Client *c, const Seperator *sep) void command_repop(Client *c, const Seperator *sep) { int timearg = 1; + int delay = 0; + if (sep->arg[1] && strcasecmp(sep->arg[1], "force") == 0) { timearg++; @@ -3913,13 +3915,19 @@ void command_repop(Client *c, const Seperator *sep) } if (!sep->IsNumber(timearg)) { - c->Message(0, "Zone depoped. Repoping now."); + c->Message(0, "Zone depopped - repopping now."); + zone->Repop(); + + /* Force a spawn2 timer trigger so we don't delay actually spawning the NPC's */ + zone->spawn2_timer.Trigger(); return; } c->Message(0, "Zone depoped. Repop in %i seconds", atoi(sep->arg[timearg])); - zone->Repop(atoi(sep->arg[timearg])*1000); + zone->Repop(atoi(sep->arg[timearg]) * 1000); + + zone->spawn2_timer.Trigger(); } void command_repopclose(Client *c, const Seperator *sep) @@ -7249,7 +7257,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); } diff --git a/zone/common.h b/zone/common.h index 7b6382a20..bcc9ff305 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 diff --git a/zone/effects.cpp b/zone/effects.cpp index adc7b16b2..2741a924b 100644 --- a/zone/effects.cpp +++ b/zone/effects.cpp @@ -613,7 +613,7 @@ bool Client::UseDiscipline(uint32 spell_id, uint32 target) { //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 344e79a04..0ab10982a 100644 --- a/zone/entity.cpp +++ b/zone/entity.cpp @@ -648,6 +648,8 @@ void EntityList::AddNPC(NPC *npc, bool SendSpawnPacket, bool dontqueue) parse->EventNPC(EVENT_SPAWN, npc, nullptr, "", 0); + npc->FixZ(1); + uint16 emoteid = npc->GetEmoteID(); if (emoteid != 0) npc->DoNPCEmote(ONSPAWN, emoteid); @@ -1417,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); } @@ -1459,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; } @@ -2615,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; } @@ -3077,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; } } @@ -3086,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; 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/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..d08804410 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -1383,7 +1383,7 @@ void Mob::SendHPUpdate(bool skip_self /*= false*/, bool force_update_all /*= fal if(IsClient()){ Raid *raid = entity_list.GetRaidByClient(CastToClient()); if (raid) - raid->SendHPPacketsFrom(this); + raid->SendHPManaEndPacketsFrom(this); } /* Pet - Update master - group and raid if exists */ @@ -1396,7 +1396,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 */ @@ -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)); @@ -3427,7 +3441,7 @@ void Mob::SetTarget(Mob* mob) { 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 +3450,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 +3461,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 +3470,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 +3775,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 +3783,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 +3949,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 +5001,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..058774c54 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -336,6 +336,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); @@ -376,6 +377,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 +545,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 +588,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 +953,8 @@ 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); 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; } @@ -1221,7 +1226,7 @@ protected: 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; + int animation; // this is really what MQ2 calls SpeedRun just packed like (int)(SpeedRun * 40.0f) float base_size; float size; float runspeed; diff --git a/zone/mob_ai.cpp b/zone/mob_ai.cpp index 14e9337ac..fb8faebf0 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; } @@ -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()); @@ -1532,16 +1535,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); 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/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..9f78cb35d 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? @@ -3655,16 +3660,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..c5a5a0b48 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 +4234,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) @@ -5533,6 +5548,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 +5635,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 +5645,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/waypoints.cpp b/zone/waypoints.cpp index c1997af59..ca0d24175 100644 --- a/zone/waypoints.cpp +++ b/zone/waypoints.cpp @@ -838,7 +838,8 @@ void Mob::SendToFixZ(float new_x, float new_y, float new_z) { } } -void Mob::FixZ() { +void Mob::FixZ(int32 z_find_offset /*= 5*/) +{ BenchTimer timer; timer.reset(); @@ -849,8 +850,8 @@ void Mob::FixZ() { (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); + float new_z = this->FindGroundZ(m_Position.x, m_Position.y, z_find_offset); + new_z += this->GetZOffset(); auto duration = timer.elapsed(); @@ -866,7 +867,7 @@ void Mob::FixZ() { duration ); - if ((new_z > -2000) && new_z != -999999) { + if ((new_z > -2000) && new_z != BEST_Z_INVALID) { if (RuleB(Map, MobZVisualDebug)) this->SendAppearanceEffect(78, 0, 0, 0, 0); @@ -884,6 +885,113 @@ void Mob::FixZ() { } } +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 6a0754dea..8441f7d82 100644 --- a/zone/zone.cpp +++ b/zone/zone.cpp @@ -1168,10 +1168,13 @@ bool Zone::Process() { spawn_conditions.Process(); if(spawn2_timer.Check()) { + LinkedListIterator iterator(spawn2_list); EQEmu::InventoryProfile::CleanDirty(); + Log(Logs::Detail, Logs::Spawns, "Running Zone::Process -> Spawn2::Process"); + iterator.Reset(); while (iterator.MoreElements()) { if (iterator.GetData()->Process()) { @@ -1181,10 +1184,10 @@ bool Zone::Process() { iterator.RemoveCurrent(); } } + if(adv_data && !did_adventure_actions) - { DoAdventureActions(); - } + } if(initgrids_timer.Check()) { //delayed grid loading stuff. diff --git a/zone/zone.h b/zone/zone.h index a3e9f59b4..b3ea9fbf1 100644 --- a/zone/zone.h +++ b/zone/zone.h @@ -106,6 +106,7 @@ public: inline const uint8 GetZoneType() const { return zone_type; } inline Timer* GetInstanceTimer() { return Instance_Timer; } + Timer spawn2_timer; inline glm::vec3 GetSafePoint() { return m_SafePoint; } inline const uint32& graveyard_zoneid() { return pgraveyard_zoneid; } @@ -336,7 +337,6 @@ private: Timer autoshutdown_timer; Timer clientauth_timer; - Timer spawn2_timer; Timer qglobal_purge_timer; Timer* Weather_Timer; Timer* Instance_Timer; 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)