Merge fixes

This commit is contained in:
Akkadius 2018-01-13 16:41:10 -06:00
commit c4432bcd7e
92 changed files with 2787 additions and 1448 deletions

339
GPL.txt
View File

@ -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.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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.
<signature of Ty Coon>, 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.

165
LICENSE Normal file
View File

@ -0,0 +1,165 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
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.

View File

@ -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

View File

@ -53,7 +53,24 @@ forum, although pull requests will be much quicker and easier on all parties.
- **User Discord Channel**: `#general` - **User Discord Channel**: `#general`
- **Developer Discord Channel**: `#eqemucoders` - **Developer Discord Channel**: `#eqemucoders`
Resources ## Resources
---
- [EQEmulator Forums](http://www.eqemulator.org/forums) - [EQEmulator Forums](http://www.eqemulator.org/forums)
- [EQEmulator Wiki](https://github.com/EQEmu/Server/wiki) - [EQEmulator Wiki](https://github.com/EQEmu/Server/wiki)
- [EQEmulator Wiki](http://wiki.eqemulator.org/i?M=Wiki)
## Related Repositories
* [ProjectEQ Quests](https://github.com/ProjectEQ/projecteqquests)
* [Maps](https://github.com/Akkadius/EQEmuMaps)
* [Installer Resources](https://github.com/Akkadius/EQEmuInstall)
* [Zone Utilities](https://github.com/EQEmu/zone-utilities) - Various utilities and libraries for parsing, rendering and manipulating EQ Zone files.
## Other License Info
* The server code and utilities are released under **GPLv3**
* We also include some small libraries for convienence that may be under different licensing
* SocketLib - GPL LibXML
* zlib - zlib license
* MariaDB/MySQL - GPL
* GPL Perl - GPL / ActiveState (under the assumption that this is a free project)
* CPPUnit - GLP StringUtilities - Apache
* LUA - MIT

View File

@ -1,5 +1,32 @@
EQEMu Changelog (Started on Sept 24, 2003 15:50) 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 == == 7/14/2017 ==
Akkadius: HP Update tuning - HP Updates are now forced when a client is targeted Akkadius: HP Update tuning - HP Updates are now forced when a client is targeted

View File

@ -24,8 +24,8 @@ struct BaseDataStruct
double base_hp; double base_hp;
double base_mana; double base_mana;
double base_end; double base_end;
double unk1; double hp_regen;
double unk2; double end_regen;
double hp_factor; double hp_factor;
double mana_factor; double mana_factor;
double endurance_factor; double endurance_factor;

View File

@ -78,6 +78,8 @@ namespace EQEmu
SLOT_CURSOR_BAG_END = 340, SLOT_CURSOR_BAG_END = 340,
SLOT_TRIBUTE_BEGIN = 400, SLOT_TRIBUTE_BEGIN = 400,
SLOT_TRIBUTE_END = 404, SLOT_TRIBUTE_END = 404,
SLOT_GUILD_TRIBUTE_BEGIN = 450,
SLOT_GUILD_TRIBUTE_END = 451,
SLOT_BANK_BEGIN = 2000, SLOT_BANK_BEGIN = 2000,
SLOT_BANK_END = 2023, SLOT_BANK_END = 2023,
SLOT_BANK_BAGS_BEGIN = 2031, SLOT_BANK_BAGS_BEGIN = 2031,

View File

@ -9,6 +9,7 @@ N(OP_AcceptNewTask),
N(OP_AckPacket), N(OP_AckPacket),
N(OP_Action), N(OP_Action),
N(OP_Action2), N(OP_Action2),
N(OP_AddNimbusEffect),
N(OP_AdventureData), N(OP_AdventureData),
N(OP_AdventureDetails), N(OP_AdventureDetails),
N(OP_AdventureFinish), N(OP_AdventureFinish),
@ -339,6 +340,7 @@ N(OP_MOTD),
N(OP_MoveCoin), N(OP_MoveCoin),
N(OP_MoveDoor), N(OP_MoveDoor),
N(OP_MoveItem), N(OP_MoveItem),
N(OP_MoveMultipleItems),
N(OP_MoveLogDisregard), N(OP_MoveLogDisregard),
N(OP_MoveLogRequest), N(OP_MoveLogRequest),
N(OP_MultiLineMsg), N(OP_MultiLineMsg),

View File

@ -551,6 +551,7 @@ struct BlockedBuffs_Struct
/*86*/ uint16 Flags; /*86*/ uint16 Flags;
}; };
// same for adding
struct RemoveNimbusEffect_Struct struct RemoveNimbusEffect_Struct
{ {
/*00*/ uint32 spawnid; // Spawn ID /*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_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_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_PP_AA_ARRAY = 240;
static const uint32 MAX_GROUP_MEMBERS = 6; static const uint32 MAX_GROUP_MEMBERS = 6;
static const uint32 MAX_RECAST_TYPES = 20; static const uint32 MAX_RECAST_TYPES = 20;
@ -993,7 +995,8 @@ struct PlayerProfile_Struct
/*4768*/ int32 platinum_shared; // Platinum shared between characters /*4768*/ int32 platinum_shared; // Platinum shared between characters
/*4772*/ uint8 unknown4808[24]; /*4772*/ uint8 unknown4808[24];
/*4796*/ uint32 skills[MAX_PP_SKILL]; // [400] List of skills // 100 dword buffer /*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; // /*5380*/ uint32 pvp2; //
/*5384*/ uint32 unknown5420; // /*5384*/ uint32 unknown5420; //
/*5388*/ uint32 pvptype; // /*5388*/ uint32 pvptype; //
@ -4764,6 +4767,7 @@ struct BuffIconEntry_Struct
uint32 spell_id; uint32 spell_id;
int32 tics_remaining; int32 tics_remaining;
uint32 num_hits; uint32 num_hits;
char caster[64];
}; };
struct BuffIcon_Struct struct BuffIcon_Struct
@ -4773,6 +4777,7 @@ struct BuffIcon_Struct
uint16 count; uint16 count;
uint8 type; // 0 = self buff window, 1 = self target window, 4 = group, 5 = PC, 7 = NPC uint8 type; // 0 = self buff window, 1 = self target window, 4 = group, 5 = PC, 7 = NPC
int32 tic_timer; 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]; BuffIconEntry_Struct entries[0];
}; };

View File

@ -88,6 +88,8 @@ enum LogCategory {
Headless_Client, Headless_Client,
HP_Update, HP_Update,
FixZ, FixZ,
Food,
Traps,
MaxCategoryID /* Don't Remove this*/ MaxCategoryID /* Don't Remove this*/
}; };
@ -140,7 +142,9 @@ static const char* LogCategoryName[LogCategory::MaxCategoryID] = {
"Client Login", "Client Login",
"Headless Client", "Headless Client",
"HP Update", "HP Update",
"FixZ" "FixZ",
"Food",
"Traps"
}; };
} }

View File

@ -276,6 +276,11 @@ enum {
#define SAYLINK_ITEM_ID 0xFFFFF #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 Developer configuration

View File

@ -460,7 +460,7 @@ namespace RoF
{ {
SETUP_VAR_ENCODE(BuffIcon_Struct); 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->size = sz;
__packet->pBuffer = new unsigned char[sz]; __packet->pBuffer = new unsigned char[sz];
memset(__packet->pBuffer, 0, sz); memset(__packet->pBuffer, 0, sz);
@ -476,7 +476,7 @@ namespace RoF
__packet->WriteUInt32(emu->entries[i].spell_id); __packet->WriteUInt32(emu->entries[i].spell_id);
__packet->WriteUInt32(emu->entries[i].tics_remaining); __packet->WriteUInt32(emu->entries[i].tics_remaining);
__packet->WriteUInt32(emu->entries[i].num_hits); // Unknown __packet->WriteUInt32(emu->entries[i].num_hits); // Unknown
__packet->WriteString(""); __packet->WriteString(emu->entries[i].caster);
} }
__packet->WriteUInt8(emu->type); // Unknown __packet->WriteUInt8(emu->type); // Unknown
@ -2184,11 +2184,11 @@ namespace RoF
outapp->WriteUInt32(emu->skills[r]); 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 outapp->WriteUInt32(structs::MAX_PP_DISCIPLINES); // Discipline count

View File

@ -528,7 +528,7 @@ namespace RoF2
{ {
SETUP_VAR_ENCODE(BuffIcon_Struct); 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->size = sz;
__packet->pBuffer = new unsigned char[sz]; __packet->pBuffer = new unsigned char[sz];
memset(__packet->pBuffer, 0, sz); memset(__packet->pBuffer, 0, sz);
@ -544,7 +544,7 @@ namespace RoF2
__packet->WriteUInt32(emu->entries[i].spell_id); __packet->WriteUInt32(emu->entries[i].spell_id);
__packet->WriteUInt32(emu->entries[i].tics_remaining); __packet->WriteUInt32(emu->entries[i].tics_remaining);
__packet->WriteUInt32(emu->entries[i].num_hits); // Unknown __packet->WriteUInt32(emu->entries[i].num_hits); // Unknown
__packet->WriteString(""); __packet->WriteString(emu->entries[i].caster);
} }
__packet->WriteUInt8(emu->type); // Unknown __packet->WriteUInt8(emu->type); // Unknown
@ -2261,11 +2261,11 @@ namespace RoF2
outapp->WriteUInt32(emu->skills[r]); 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 outapp->WriteUInt32(structs::MAX_PP_DISCIPLINES); // Discipline count

View File

@ -164,7 +164,7 @@ namespace RoF2
ItemPacket11 = 111, ItemPacket11 = 111,
ItemPacket12 = 112, ItemPacket12 = 112,
ItemPacketRecovery = 113, ItemPacketRecovery = 113,
ItemPacket14 = 115 ItemPacket14 = 115 // Parcel? adds to merchant window too
}; };
} /*item*/ } /*item*/

View File

@ -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_SPELLBOOK = 720; // was 480
static const uint32 MAX_PP_MEMSPELL = 16; // was 12 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_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_AA_ARRAY = 300;
static const uint32 MAX_PP_DISCIPLINES = 300; // was 200 static const uint32 MAX_PP_DISCIPLINES = 300; // was 200
static const uint32 MAX_GROUP_MEMBERS = 6; static const uint32 MAX_GROUP_MEMBERS = 6;
@ -617,7 +618,7 @@ struct NewZone_Struct {
/*0704*/ char zone_short_name2[32]; //zone file name? excludes instance number which can be in previous version. /*0704*/ char zone_short_name2[32]; //zone file name? excludes instance number which can be in previous version.
/*0736*/ char WeatherString[32]; /*0736*/ char WeatherString[32];
/*0768*/ char SkyString2[32]; /*0768*/ char SkyString2[32];
/*0800*/ int32 SkyRelated2; //seen -1 /*0800*/ int32 SkyRelated2; //seen -1 -- maybe some default sky time?
/*0804*/ char WeatherString2[32]; // /*0804*/ char WeatherString2[32]; //
/*0836*/ float WeatherChangeTime; // not sure :P /*0836*/ float WeatherChangeTime; // not sure :P
/*0840*/ uint32 Climate; /*0840*/ uint32 Climate;
@ -1155,8 +1156,8 @@ union
/*01012*/ AA_Array aa_array[MAX_PP_AA_ARRAY]; // [300] 3600 bytes - AAs 12 bytes each /*01012*/ AA_Array aa_array[MAX_PP_AA_ARRAY]; // [300] 3600 bytes - AAs 12 bytes each
/*04612*/ uint32 skill_count; // Seen 100 /*04612*/ uint32 skill_count; // Seen 100
/*04616*/ uint32 skills[MAX_PP_SKILL]; // [100] 400 bytes - List of skills /*04616*/ uint32 skills[MAX_PP_SKILL]; // [100] 400 bytes - List of skills
/*05016*/ uint32 unknown15_count; // Seen 25 /*05016*/ uint32 InnateSkills_count; // Seen 25
/*05020*/ uint32 unknown_rof15[25]; // Most are 255 or 0 /*05020*/ uint32 InnateSkills[MAX_PP_INNATE_SKILL]; // Most are 255 or 0
/*05120*/ uint32 discipline_count; // Seen 200 /*05120*/ uint32 discipline_count; // Seen 200
/*05124*/ Disciplines_Struct disciplines; // [200] 800 bytes Known disciplines /*05124*/ Disciplines_Struct disciplines; // [200] 800 bytes Known disciplines
/*05924*/ uint32 timestamp_count; // Seen 20 /*05924*/ uint32 timestamp_count; // Seen 20
@ -1826,6 +1827,20 @@ struct MoveItem_Struct
/*0028*/ /*0028*/
}; };
struct MultiMoveItemSub_Struct
{
/*0000*/ InventorySlot_Struct from_slot;
/*0012*/ InventorySlot_Struct to_slot;
/*0024*/ uint32 number_in_stack;
/*0028*/ uint8 unknown[8];
};
struct MultiMoveItem_Struct
{
/*0000*/ uint32 count;
/*0004*/ MultiMoveItemSub_Struct moves[0];
};
// //
// from_slot/to_slot // from_slot/to_slot
// -1 - destroy // -1 - destroy

View File

@ -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_SPELLBOOK = 720; // was 480
static const uint32 MAX_PP_MEMSPELL = 16; // was 12 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_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_AA_ARRAY = 300;
static const uint32 MAX_PP_DISCIPLINES = 200; // was 100 static const uint32 MAX_PP_DISCIPLINES = 200; // was 100
static const uint32 MAX_GROUP_MEMBERS = 6; 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 /*01012*/ AA_Array aa_array[MAX_PP_AA_ARRAY]; // [300] 3600 bytes - AAs 12 bytes each
/*04612*/ uint32 skill_count; // Seen 100 /*04612*/ uint32 skill_count; // Seen 100
/*04616*/ uint32 skills[MAX_PP_SKILL]; // [100] 400 bytes - List of skills /*04616*/ uint32 skills[MAX_PP_SKILL]; // [100] 400 bytes - List of skills
/*05016*/ uint32 unknown15_count; // Seen 25 /*05016*/ uint32 InnateSkills_count; // Seen 25
/*05020*/ uint32 unknown_rof15[25]; // Most are 255 or 0 /*05020*/ uint32 InnateSkills[MAX_PP_INNATE_SKILL]; // Most are 255 or 0
/*05120*/ uint32 discipline_count; // Seen 200 /*05120*/ uint32 discipline_count; // Seen 200
/*05124*/ Disciplines_Struct disciplines; // [200] 800 bytes Known disciplines /*05124*/ Disciplines_Struct disciplines; // [200] 800 bytes Known disciplines
/*05924*/ uint32 timestamp_count; // Seen 20 /*05924*/ uint32 timestamp_count; // Seen 20

View File

@ -1605,6 +1605,7 @@ namespace SoD
OUT(copper_cursor); OUT(copper_cursor);
OUT_array(skills, structs::MAX_PP_SKILL); // 1:1 direct copy (100 dword) 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(unknown04760[236]);
OUT(toxicity); OUT(toxicity);

View File

@ -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_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_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_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_PP_AA_ARRAY = 300; //was 299
static const uint32 MAX_GROUP_MEMBERS = 6; static const uint32 MAX_GROUP_MEMBERS = 6;
static const uint32 MAX_RECAST_TYPES = 20; static const uint32 MAX_RECAST_TYPES = 20;
@ -923,7 +924,8 @@ struct PlayerProfile_Struct
/*06488*/ uint32 silver_cursor; // Silver Pieces on cursor /*06488*/ uint32 silver_cursor; // Silver Pieces on cursor
/*06492*/ uint32 copper_cursor; // Copper Pieces on cursor /*06492*/ uint32 copper_cursor; // Copper Pieces on cursor
/*06496*/ uint32 skills[MAX_PP_SKILL]; // [400] List of skills // 100 dword buffer /*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) /*07032*/ uint32 toxicity; // Potion Toxicity (15=too toxic, each potion adds 3)
/*07036*/ uint32 thirst_level; // Drink (ticks till next drink) /*07036*/ uint32 thirst_level; // Drink (ticks till next drink)
/*07040*/ uint32 hunger_level; // Food (ticks till next eat) /*07040*/ uint32 hunger_level; // Food (ticks till next eat)

View File

@ -1276,6 +1276,7 @@ namespace SoF
OUT(copper_cursor); OUT(copper_cursor);
OUT_array(skills, structs::MAX_PP_SKILL); // 1:1 direct copy (100 dword) 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(unknown04760[236]);
OUT(toxicity); OUT(toxicity);

View File

@ -52,6 +52,25 @@ struct EnterWorld_Struct {
struct WorldObjectsSent_Struct { struct WorldObjectsSent_Struct {
}; };
// yep, even SoF had a version of the new inventory system, used by OP_MoveMultipleItems
struct InventorySlot_Struct
{
/*000*/ int32 Type; // Worn and Normal inventory = 0, Bank = 1, Shared Bank = 2, Trade = 3, World = 4, Limbo = 5
/*004*/ int32 Slot;
/*008*/ int32 SubIndex;
/*012*/ int32 AugIndex;
/*016*/ int32 Unknown01;
};
// unsure if they have a version of this, completeness though
struct TypelessInventorySlot_Struct
{
/*000*/ int32 Slot;
/*004*/ int32 SubIndex;
/*008*/ int32 AugIndex;
/*012*/ int32 Unknown01;
};
/* Name Approval Struct */ /* Name Approval Struct */
/* Len: */ /* Len: */
/* Opcode: 0x8B20*/ /* Opcode: 0x8B20*/
@ -799,6 +818,7 @@ static const uint32 MAX_PP_LANGUAGE = 25; //
static const uint32 MAX_PP_SPELLBOOK = 480; // Confirmed 60 pages on Live now static const uint32 MAX_PP_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_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_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_PP_AA_ARRAY = 300; //was 299
static const uint32 MAX_GROUP_MEMBERS = 6; static const uint32 MAX_GROUP_MEMBERS = 6;
static const uint32 MAX_RECAST_TYPES = 20; static const uint32 MAX_RECAST_TYPES = 20;
@ -903,7 +923,8 @@ struct PlayerProfile_Struct //23576 Octets
/*06488*/ uint32 silver_cursor; // Silver Pieces on cursor /*06488*/ uint32 silver_cursor; // Silver Pieces on cursor
/*06492*/ uint32 copper_cursor; // Copper Pieces on cursor /*06492*/ uint32 copper_cursor; // Copper Pieces on cursor
/*06496*/ uint32 skills[MAX_PP_SKILL]; // [400] List of skills // 100 dword buffer /*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) /*07032*/ uint32 toxicity; // Potion Toxicity (15=too toxic, each potion adds 3)
/*07036*/ uint32 thirst_level; // Drink (ticks till next drink) /*07036*/ uint32 thirst_level; // Drink (ticks till next drink)
/*07040*/ uint32 hunger_level; // Food (ticks till next eat) /*07040*/ uint32 hunger_level; // Food (ticks till next eat)
@ -1555,6 +1576,19 @@ struct MoveItem_Struct
/*0012*/ /*0012*/
}; };
struct MultiMoveItemSub_Struct
{
/*0000*/ InventorySlot_Struct from_slot;
/*0020*/ uint32 number_in_stack; // so the amount we are moving from the source
/*0024*/ InventorySlot_Struct to_slot;
};
struct MultiMoveItem_Struct
{
/*0000*/ uint32 count;
/*0004*/ MultiMoveItemSub_Struct moves[0];
};
// //
// from_slot/to_slot // from_slot/to_slot
// -1 - destroy // -1 - destroy

View File

@ -1020,6 +1020,7 @@ namespace Titanium
OUT(copper_cursor); OUT(copper_cursor);
OUT_array(skills, structs::MAX_PP_SKILL); // 1:1 direct copy (100 dword) 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(unknown04760[236]);
OUT(toxicity); OUT(toxicity);

View File

@ -48,6 +48,23 @@ struct EnterWorld_Struct {
/*068*/ uint32 return_home; // 01 on "Return Home", 00 if not /*068*/ uint32 return_home; // 01 on "Return Home", 00 if not
}; };
// yep, even tit had a version of the new inventory system, used by OP_MoveMultipleItems
struct InventorySlot_Struct
{
/*000*/ int32 Type; // Worn and Normal inventory = 0, Bank = 1, Shared Bank = 2, Trade = 3, World = 4, Limbo = 5
/*004*/ int32 Slot;
/*008*/ int32 SubIndex; // no aug index in Tit
/*012*/ int32 Unknown01;
};
// unsure if they have a version of this, completeness though
struct TypelessInventorySlot_Struct
{
/*000*/ int32 Slot;
/*004*/ int32 SubIndex; // no aug index in Tit
/*008*/ int32 Unknown01;
};
/* Name Approval Struct */ /* Name Approval Struct */
/* Len: */ /* Len: */
/* Opcode: 0x8B20*/ /* Opcode: 0x8B20*/
@ -740,6 +757,7 @@ static const uint32 MAX_PP_LANGUAGE = 28;
static const uint32 MAX_PP_SPELLBOOK = 400; static const uint32 MAX_PP_SPELLBOOK = 400;
static const uint32 MAX_PP_MEMSPELL = 9; 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_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_PP_AA_ARRAY = 240;
static const uint32 MAX_GROUP_MEMBERS = 6; static const uint32 MAX_GROUP_MEMBERS = 6;
static const uint32 MAX_RECAST_TYPES = 20; static const uint32 MAX_RECAST_TYPES = 20;
@ -844,7 +862,8 @@ struct PlayerProfile_Struct
/*04452*/ uint32 silver_cursor; // Silver Pieces on cursor /*04452*/ uint32 silver_cursor; // Silver Pieces on cursor
/*04456*/ uint32 copper_cursor; // Copper Pieces on cursor /*04456*/ uint32 copper_cursor; // Copper Pieces on cursor
/*04460*/ uint32 skills[MAX_PP_SKILL]; // [400] List of skills // 100 dword buffer /*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) /*04996*/ uint32 toxicity; // Potion Toxicity (15=too toxic, each potion adds 3)
/*05000*/ uint32 thirst_level; // Drink (ticks till next drink) /*05000*/ uint32 thirst_level; // Drink (ticks till next drink)
/*05004*/ uint32 hunger_level; // Food (ticks till next eat) /*05004*/ uint32 hunger_level; // Food (ticks till next eat)
@ -1327,6 +1346,19 @@ struct MoveItem_Struct
/*0012*/ /*0012*/
}; };
struct MultiMoveItemSub_Struct
{
/*0000*/ InventorySlot_Struct from_slot;
/*0016*/ uint32 number_in_stack; // so the amount we are moving from the source
/*0020*/ InventorySlot_Struct to_slot;
};
struct MultiMoveItem_Struct
{
/*0000*/ uint32 count;
/*0004*/ MultiMoveItemSub_Struct moves[0];
};
// //
// from_slot/to_slot // from_slot/to_slot
// -1 - destroy // -1 - destroy

View File

@ -391,7 +391,7 @@ namespace UF
{ {
SETUP_VAR_ENCODE(BuffIcon_Struct); 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->size = sz;
__packet->pBuffer = new unsigned char[sz]; __packet->pBuffer = new unsigned char[sz];
memset(__packet->pBuffer, 0, sz); memset(__packet->pBuffer, 0, sz);
@ -407,7 +407,7 @@ namespace UF
__packet->WriteUInt32(emu->entries[i].spell_id); __packet->WriteUInt32(emu->entries[i].spell_id);
__packet->WriteUInt32(emu->entries[i].tics_remaining); __packet->WriteUInt32(emu->entries[i].tics_remaining);
__packet->WriteUInt32(emu->entries[i].num_hits); __packet->WriteUInt32(emu->entries[i].num_hits);
__packet->WriteString(""); __packet->WriteString(emu->entries[i].caster);
} }
__packet->WriteUInt8(emu->type); __packet->WriteUInt8(emu->type);
@ -1850,6 +1850,7 @@ namespace UF
OUT(copper_cursor); OUT(copper_cursor);
OUT_array(skills, structs::MAX_PP_SKILL); // 1:1 direct copy (100 dword) 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(unknown04760[236]);
OUT(toxicity); OUT(toxicity);

View File

@ -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_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_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_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_PP_AA_ARRAY = 300; //was 299
static const uint32 MAX_GROUP_MEMBERS = 6; static const uint32 MAX_GROUP_MEMBERS = 6;
static const uint32 MAX_RECAST_TYPES = 20; static const uint32 MAX_RECAST_TYPES = 20;
@ -954,7 +955,8 @@ struct PlayerProfile_Struct
/*07336*/ uint32 silver_cursor; // Silver Pieces on cursor /*07336*/ uint32 silver_cursor; // Silver Pieces on cursor
/*07340*/ uint32 copper_cursor; // Copper Pieces on cursor /*07340*/ uint32 copper_cursor; // Copper Pieces on cursor
/*07344*/ uint32 skills[MAX_PP_SKILL]; // [400] List of skills // 100 dword buffer /*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) /*07880*/ uint32 toxicity; // Potion Toxicity (15=too toxic, each potion adds 3)
/*07884*/ uint32 thirst_level; // Drink (ticks till next drink) /*07884*/ uint32 thirst_level; // Drink (ticks till next drink)
/*07888*/ uint32 hunger_level; // Food (ticks till next eat) /*07888*/ uint32 hunger_level; // Food (ticks till next eat)

View File

@ -44,6 +44,7 @@ RULE_INT(Character, DeathExpLossMaxLevel, 255) // Any level greater than this wi
RULE_INT(Character, DeathItemLossLevel, 10) RULE_INT(Character, DeathItemLossLevel, 10)
RULE_INT(Character, DeathExpLossMultiplier, 3) //Adjust how much exp is lost RULE_INT(Character, DeathExpLossMultiplier, 3) //Adjust how much exp is lost
RULE_BOOL(Character, UseDeathExpLossMult, false) //Adjust to use the above multiplier or to use code default. RULE_BOOL(Character, UseDeathExpLossMult, false) //Adjust to use the above multiplier or to use code default.
RULE_BOOL(Character, UseOldRaceRezEffects, false) // older clients had ID 757 for races with high starting STR, but it doesn't seem used anymore
RULE_INT(Character, CorpseDecayTimeMS, 10800000) RULE_INT(Character, CorpseDecayTimeMS, 10800000)
RULE_INT(Character, CorpseResTimeMS, 10800000) // time before cant res corpse(3 hours) RULE_INT(Character, CorpseResTimeMS, 10800000) // time before cant res corpse(3 hours)
RULE_BOOL(Character, LeaveCorpses, true) RULE_BOOL(Character, LeaveCorpses, true)
@ -66,11 +67,12 @@ RULE_INT(Character, AutosaveIntervalS, 300) //0=disabled
RULE_INT(Character, HPRegenMultiplier, 100) RULE_INT(Character, HPRegenMultiplier, 100)
RULE_INT(Character, ManaRegenMultiplier, 100) RULE_INT(Character, ManaRegenMultiplier, 100)
RULE_INT(Character, EnduranceRegenMultiplier, 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_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, HealOnLevel, false)
RULE_BOOL(Character, FeignKillsPet, false) RULE_BOOL(Character, FeignKillsPet, false)
RULE_INT(Character, ItemManaRegenCap, 15) RULE_INT(Character, ItemManaRegenCap, 15)
RULE_INT(Character, ItemHealthRegenCap, 35) RULE_INT(Character, ItemHealthRegenCap, 30)
RULE_INT(Character, ItemDamageShieldCap, 30) RULE_INT(Character, ItemDamageShieldCap, 30)
RULE_INT(Character, ItemAccuracyCap, 150) RULE_INT(Character, ItemAccuracyCap, 150)
RULE_INT(Character, ItemAvoidanceCap, 100) RULE_INT(Character, ItemAvoidanceCap, 100)
@ -91,10 +93,12 @@ RULE_INT(Character, HasteCap, 100) // Haste cap for non-v3(overhaste) haste.
RULE_INT(Character, SkillUpModifier, 100) //skill ups are at 100% RULE_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, SharedBankPlat, false) //off by default to prevent duping for now
RULE_BOOL(Character, BindAnywhere, false) 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, 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_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, 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, 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. RULE_INT(Character, MaxFearDurationForPlayerCharacter, 4) //4 tics, each tic calculates every 6 seconds.
@ -117,7 +121,8 @@ RULE_BOOL(Character, EnableDiscoveredItems, true) // If enabled, it enables EVEN
RULE_BOOL(Character, EnableXTargetting, true) // Enable Extended Targetting Window, for users with UF and later clients. RULE_BOOL(Character, 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, 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_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_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_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. RULE_INT(Character, BaseRunSpeedCap, 158) // Base Run Speed Cap, on live it's 158% which will give you a runspeed of 1.580 hard capped to 225.
@ -186,12 +191,14 @@ RULE_INT(Skills, MaxTrainSpecializations, 50) // Max level a GM trainer will tra
RULE_INT(Skills, SwimmingStartValue, 100) RULE_INT(Skills, SwimmingStartValue, 100)
RULE_BOOL(Skills, TrainSenseHeading, false) RULE_BOOL(Skills, TrainSenseHeading, false)
RULE_INT(Skills, SenseHeadingStartValue, 200) RULE_INT(Skills, SenseHeadingStartValue, 200)
RULE_BOOL(Skills, SelfLanguageLearning, true)
RULE_CATEGORY_END() RULE_CATEGORY_END()
RULE_CATEGORY(Pets) RULE_CATEGORY(Pets)
RULE_REAL(Pets, AttackCommandRange, 150) RULE_REAL(Pets, AttackCommandRange, 150)
RULE_BOOL(Pets, UnTargetableSwarmPet, false) RULE_BOOL(Pets, UnTargetableSwarmPet, false)
RULE_REAL(Pets, PetPowerLevelCap, 10) // Max number of levels your pet can go up with pet power RULE_REAL(Pets, PetPowerLevelCap, 10) // Max number of levels your pet can go up with pet power
RULE_BOOL(Pets, CanTakeNoDrop, false) // Can everyone trade nodrop gear to pets
RULE_CATEGORY_END() RULE_CATEGORY_END()
RULE_CATEGORY(GM) RULE_CATEGORY(GM)
@ -502,6 +509,7 @@ RULE_INT(Combat, NPCAssistCapTimer, 6000) // Time in milliseconds a NPC will tak
RULE_BOOL(Combat, UseRevampHandToHand, false) // use h2h revamped dmg/delays I believe this was implemented during SoF RULE_BOOL(Combat, UseRevampHandToHand, false) // use h2h revamped dmg/delays I believe this was implemented during SoF
RULE_BOOL(Combat, ClassicMasterWu, false) // classic master wu uses a random special, modern doesn't RULE_BOOL(Combat, ClassicMasterWu, false) // classic master wu uses a random special, modern doesn't
RULE_INT(Combat, LevelToStopDamageCaps, 0) // 1 will effectively disable them, 20 should give basically same results as old incorrect system RULE_INT(Combat, LevelToStopDamageCaps, 0) // 1 will effectively disable them, 20 should give basically same results as old incorrect system
RULE_BOOL(Combat, ClassicNPCBackstab, false) // true disables npc facestab - npcs get normal attack if not behind
RULE_CATEGORY_END() RULE_CATEGORY_END()
RULE_CATEGORY(NPC) RULE_CATEGORY(NPC)
@ -563,6 +571,8 @@ RULE_INT(Range, DamageMessages, 50)
RULE_INT(Range, SpellMessages, 75) RULE_INT(Range, SpellMessages, 75)
RULE_INT(Range, SongMessages, 75) RULE_INT(Range, SongMessages, 75)
RULE_INT(Range, MobPositionUpdates, 600) RULE_INT(Range, MobPositionUpdates, 600)
RULE_INT(Range, ClientPositionUpdates, 300)
RULE_INT(Range, ClientForceSpawnUpdateRange, 1000)
RULE_INT(Range, CriticalDamage, 80) RULE_INT(Range, CriticalDamage, 80)
RULE_INT(Range, ClientNPCScan, 300) RULE_INT(Range, ClientNPCScan, 300)
RULE_CATEGORY_END() RULE_CATEGORY_END()

View File

@ -1817,8 +1817,8 @@ void SharedDatabase::LoadBaseData(void *data, int max_level) {
bd->base_hp = atof(row[2]); bd->base_hp = atof(row[2]);
bd->base_mana = atof(row[3]); bd->base_mana = atof(row[3]);
bd->base_end = atof(row[4]); bd->base_end = atof(row[4]);
bd->unk1 = atof(row[5]); bd->hp_regen = atof(row[5]);
bd->unk2 = atof(row[6]); bd->end_regen = atof(row[6]);
bd->hp_factor = atof(row[7]); bd->hp_factor = atof(row[7]);
bd->mana_factor = atof(row[8]); bd->mana_factor = atof(row[8]);
bd->endurance_factor = atof(row[9]); bd->endurance_factor = atof(row[9]);

View File

@ -131,6 +131,11 @@ enum SpellAffectIndex {
SAI_NPC_Special_80 = 80, SAI_NPC_Special_80 = 80,
SAI_Trap_Lock = 88 SAI_Trap_Lock = 88
}; };
enum class GlobalGroup {
Lich = 46,
};
enum RESISTTYPE enum RESISTTYPE
{ {
RESIST_NONE = 0, RESIST_NONE = 0,

View File

@ -30,7 +30,7 @@
Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt
*/ */
#define CURRENT_BINARY_DATABASE_VERSION 9114 #define CURRENT_BINARY_DATABASE_VERSION 9115
#ifdef BOTS #ifdef BOTS
#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9017 #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9017
#else #else

View File

@ -1719,6 +1719,8 @@ FMT_DEFINE_INT_FORMATTERS(unsigned long)
FMT_DEFINE_INT_FORMATTERS(LongLong) FMT_DEFINE_INT_FORMATTERS(LongLong)
FMT_DEFINE_INT_FORMATTERS(ULongLong) FMT_DEFINE_INT_FORMATTERS(ULongLong)
#define CHAR_WIDTH 1
/** /**
\rst \rst
Returns a string formatter that pads the formatted argument with the fill Returns a string formatter that pads the formatted argument with the fill
@ -1823,7 +1825,7 @@ class ArgFormatterBase : public ArgVisitor<Impl, void> {
typedef typename BasicWriter<Char>::CharPtr CharPtr; typedef typename BasicWriter<Char>::CharPtr CharPtr;
Char fill = internal::CharTraits<Char>::cast(spec_.fill()); Char fill = internal::CharTraits<Char>::cast(spec_.fill());
CharPtr out = CharPtr(); CharPtr out = CharPtr();
const unsigned CHAR_WIDTH = 1;
if (spec_.width_ > CHAR_WIDTH) { if (spec_.width_ > CHAR_WIDTH) {
out = writer_.grow_buffer(spec_.width_); out = writer_.grow_buffer(spec_.width_);
if (spec_.align_ == ALIGN_RIGHT) { if (spec_.align_ == ALIGN_RIGHT) {

View File

@ -248,6 +248,7 @@ OP_AutoAttack=0x0d14
OP_AutoAttack2=0x3912 OP_AutoAttack2=0x3912
OP_Consume=0x4692 OP_Consume=0x4692
OP_MoveItem=0x62a2 OP_MoveItem=0x62a2
OP_MoveMultipleItems=0x55ef
OP_DeleteItem=0x3eb5 OP_DeleteItem=0x3eb5
OP_DeleteCharge=0x2d5b OP_DeleteCharge=0x2d5b
OP_ItemPacket=0x5e0e OP_ItemPacket=0x5e0e
@ -341,6 +342,7 @@ OP_MobUpdate=0x6b5a
OP_NPCMoveUpdate=0x5bd9 OP_NPCMoveUpdate=0x5bd9
OP_CameraEffect=0x5712 OP_CameraEffect=0x5712
OP_SpellEffect=0x72b6 OP_SpellEffect=0x72b6
OP_AddNimbusEffect=0x2954
OP_RemoveNimbusEffect=0x3ba7 OP_RemoveNimbusEffect=0x3ba7
OP_AltCurrency=0x8fcb OP_AltCurrency=0x8fcb
OP_AltCurrencyMerchantRequest=0x7e3e OP_AltCurrencyMerchantRequest=0x7e3e

View File

@ -247,6 +247,7 @@ OP_AutoAttack=0x109d
OP_AutoAttack2=0x3526 OP_AutoAttack2=0x3526
OP_Consume=0x4b70 OP_Consume=0x4b70
OP_MoveItem=0x32ee OP_MoveItem=0x32ee
OP_MoveMultipleItems=0x5623
OP_DeleteItem=0x18ad OP_DeleteItem=0x18ad
OP_DeleteCharge=0x01b8 OP_DeleteCharge=0x01b8
OP_ItemPacket=0x368e OP_ItemPacket=0x368e
@ -340,6 +341,7 @@ OP_MobUpdate=0x2c84
OP_NPCMoveUpdate=0x5892 OP_NPCMoveUpdate=0x5892
OP_CameraEffect=0x127f OP_CameraEffect=0x127f
OP_SpellEffect=0x5936 OP_SpellEffect=0x5936
OP_AddNimbusEffect=0xc693
OP_RemoveNimbusEffect=0x7b1e OP_RemoveNimbusEffect=0x7b1e
OP_AltCurrency=0x6b6d OP_AltCurrency=0x6b6d
OP_AltCurrencyMerchantRequest=0x5409 OP_AltCurrencyMerchantRequest=0x5409

View File

@ -241,6 +241,7 @@ OP_AutoAttack=0x3d86 # C
OP_AutoAttack2=0x4ca1 # C OP_AutoAttack2=0x4ca1 # C
OP_Consume=0x7ce4 # C OP_Consume=0x7ce4 # C
OP_MoveItem=0x7f56 # C OP_MoveItem=0x7f56 # C
OP_MoveMultipleItems=0x4572
OP_DeleteItem=0x36f8 # C OP_DeleteItem=0x36f8 # C
OP_DeleteCharge=0x1df9 # C OP_DeleteCharge=0x1df9 # C
OP_ItemPacket=0x34f8 # C OP_ItemPacket=0x34f8 # C
@ -345,6 +346,7 @@ OP_AltCurrencySell=0x7a21
OP_AltCurrencySellSelection=0x26d9 OP_AltCurrencySellSelection=0x26d9
OP_AltCurrencyReclaim=0x712c OP_AltCurrencyReclaim=0x712c
OP_ShroudProgress=0x0296 OP_ShroudProgress=0x0296
OP_AddNimbusEffect=0x6840
OP_RemoveNimbusEffect=0x5272 # C OP_RemoveNimbusEffect=0x5272 # C
OP_Untargetable=0x5ea1 # 0x301d on UF? OP_Untargetable=0x5ea1 # 0x301d on UF?
OP_IncreaseStats=0x71eb OP_IncreaseStats=0x71eb

View File

@ -237,6 +237,7 @@ OP_AutoAttack=0x3427 #Trevius 01/20/09
OP_AutoAttack2=0x6017 #Trevius 01/20/09 OP_AutoAttack2=0x6017 #Trevius 01/20/09
OP_Consume=0x729a #Trevius 02/08/09 OP_Consume=0x729a #Trevius 02/08/09
OP_MoveItem=0x14B3 #Trevius 02/08/09 OP_MoveItem=0x14B3 #Trevius 02/08/09
OP_MoveMultipleItems=0x2d3e
OP_DeleteItem=0x7DD4 #Xinu 03/08/09 0x41EE 0x018E 0x070C OP_DeleteItem=0x7DD4 #Xinu 03/08/09 0x41EE 0x018E 0x070C
OP_DeleteCharge=0x32e2 #Trevius 03/23/09 OP_DeleteCharge=0x32e2 #Trevius 03/23/09
OP_ItemPacket=0x78Cd #Trevius 02/08/09 OP_ItemPacket=0x78Cd #Trevius 02/08/09
@ -329,6 +330,7 @@ OP_AltCurrencyPurchase=0x3994
OP_AltCurrencySell=0x2ac3 OP_AltCurrencySell=0x2ac3
OP_AltCurrencySellSelection=0x7d00 OP_AltCurrencySellSelection=0x7d00
OP_AltCurrencyReclaim=0x1996 OP_AltCurrencyReclaim=0x1996
OP_AddNimbusEffect=0x45e2
OP_RemoveNimbusEffect=0x5872 # C OP_RemoveNimbusEffect=0x5872 # C
OP_InspectMessageUpdate=0x67e9 # C OP_InspectMessageUpdate=0x67e9 # C
OP_OpenInventory=0x66c8 OP_OpenInventory=0x66c8

View File

@ -198,6 +198,7 @@ OP_Split=0x4848 # ShowEQ 10/27/05
OP_Surname=0x4668 # ShowEQ 10/27/05 OP_Surname=0x4668 # ShowEQ 10/27/05
OP_ClearSurname=0x6cdb OP_ClearSurname=0x6cdb
OP_MoveItem=0x420f # ShowEQ 10/27/05 OP_MoveItem=0x420f # ShowEQ 10/27/05
OP_MoveMultipleItems=0x463b
OP_FaceChange=0x0f8e # ShowEQ 10/27/05 OP_FaceChange=0x0f8e # ShowEQ 10/27/05
OP_ItemPacket=0x3397 # ShowEQ 10/27/05 OP_ItemPacket=0x3397 # ShowEQ 10/27/05
OP_ItemLinkResponse=0x667c # ShowEQ 10/27/05 OP_ItemLinkResponse=0x667c # ShowEQ 10/27/05

View File

@ -251,6 +251,7 @@ OP_AutoAttack=0x1df9 # C
OP_AutoAttack2=0x517b # C OP_AutoAttack2=0x517b # C
OP_Consume=0x24c5 # V OP_Consume=0x24c5 # V
OP_MoveItem=0x2641 # C OP_MoveItem=0x2641 # C
OP_MoveMultipleItems=0x40e8
OP_DeleteItem=0x66e0 # C OP_DeleteItem=0x66e0 # C
OP_DeleteCharge=0x4ca1 # C OP_DeleteCharge=0x4ca1 # C
OP_ItemPacket=0x7b6e # C OP_ItemPacket=0x7b6e # C
@ -346,6 +347,7 @@ OP_MobUpdate=0x4656 # Same as OP_SpawnPositionUpdate
OP_NPCMoveUpdate=0x0f3e # OP_NPCMoveUpdate=0x0f3e #
OP_CameraEffect=0x6b0e # V OP_CameraEffect=0x6b0e # V
OP_SpellEffect=0x57a3 # V OP_SpellEffect=0x57a3 # V
OP_AddNimbusEffect=0x6361
OP_RemoveNimbusEffect=0x2c77 # C OP_RemoveNimbusEffect=0x2c77 # C
OP_AltCurrency=0x659e OP_AltCurrency=0x659e
OP_AltCurrencyMerchantRequest=0x214C OP_AltCurrencyMerchantRequest=0x214C

View File

@ -357,7 +357,7 @@
9101|2016_12_01_pcnpc_only.sql|SHOW COLUMNS FROM `spells_new` LIKE 'pcnpc_only_flag'|empty| 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| 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| 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| 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| 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| 9107|2017_03_09_inventory_version.sql|SHOW TABLES LIKE 'inventory_version'|empty|
@ -368,6 +368,8 @@
9112|2017_06_24_rule_values_expand.sql|SHOW COLUMNS FROM rule_values WHERE Field = 'rule_value' and Type = 'varchar(30)'|empty| 9112|2017_06_24_rule_values_expand.sql|SHOW COLUMNS FROM rule_values WHERE Field = 'rule_value' and Type = 'varchar(30)'|empty|
9113|2017_07_19_show_name.sql|SHOW COLUMNS FROM `npc_types` LIKE 'show_name'|empty| 9113|2017_07_19_show_name.sql|SHOW COLUMNS FROM `npc_types` LIKE 'show_name'|empty|
9114|2017_07_22_aura.sql|SHOW TABLES LIKE 'auras'|empty| 9114|2017_07_22_aura.sql|SHOW TABLES LIKE 'auras'|empty|
9115|2017_10_28_traps.sql|SHOW COLUMNS FROM `traps` LIKE 'triggered_number'|empty|
9116|2017_12_16_GroundSpawn_Respawn_Timer.sql|SHOW COLUMNS FROM `ground_spawns` WHERE Field = 'respawn_timer' AND Type = 'int(11) unsigned'|empty|
# Upgrade conditions: # Upgrade conditions:
# This won't be needed after this system is implemented, but it is used database that are not # This won't be needed after this system is implemented, but it is used database that are not

View File

@ -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';

View File

@ -0,0 +1,4 @@
alter table `traps` add column `triggered_number` tinyint(4) not null default 0;
alter table `traps` add column `group` tinyint(4) not null default 0;
alter table `traps` add column `despawn_when_triggered` tinyint(4) not null default 0;
alter table `traps` add column `undetectable` tinyint(4) not null default 0;

View File

@ -0,0 +1,2 @@
ALTER TABLE `ground_spawns` MODIFY `respawn_timer` int(11) unsigned NOT NULL default 300;
UPDATE `ground_spawns` SET `respawn_timer` = `respawn_timer` / 1000;

View File

@ -380,7 +380,7 @@ void Adventure::MoveCorpsesToGraveyard()
std::list<uint32> dbid_list; std::list<uint32> dbid_list;
std::list<uint32> charid_list; std::list<uint32> 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); auto results = database.QueryDatabase(query);
if(!results.Success()) if(!results.Success())
@ -395,8 +395,8 @@ void Adventure::MoveCorpsesToGraveyard()
float z = GetTemplate()->graveyard_z; float z = GetTemplate()->graveyard_z;
query = StringFormat("UPDATE character_corpses " query = StringFormat("UPDATE character_corpses "
"SET zoneid = %d, instanceid = 0, " "SET zone_id = %d, instance_id = 0, "
"x = %f, y = %f, z = %f WHERE instanceid = %d", "x = %f, y = %f, z = %f WHERE instance_id = %d",
GetTemplate()->graveyard_zone_id, GetTemplate()->graveyard_zone_id,
x, y, z, GetInstanceID()); x, y, z, GetInstanceID());
database.QueryDatabase(query); database.QueryDatabase(query);

View File

@ -23,6 +23,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#include <stdlib.h> #include <stdlib.h>
#include <signal.h> #include <signal.h>
#include "../common/string_util.h"
#include "../common/eqemu_logsys.h" #include "../common/eqemu_logsys.h"
#include "../common/queue.h" #include "../common/queue.h"
#include "../common/timer.h" #include "../common/timer.h"
@ -105,6 +106,12 @@ WebInterfaceList web_interface;
void CatchSignal(int sig_num); void CatchSignal(int sig_num);
void CheckForServerScript(bool force_download = false); void CheckForServerScript(bool force_download = false);
inline void UpdateWindowTitle(std::string new_title) {
#ifdef _WINDOWS
SetConsoleTitle(new_title.c_str());
#endif
}
int main(int argc, char** argv) { int main(int argc, char** argv) {
RegisterExecutablePlatform(ExePlatformWorld); RegisterExecutablePlatform(ExePlatformWorld);
LogSys.LoadLogSettingsDefaults(); LogSys.LoadLogSettingsDefaults();
@ -539,8 +546,7 @@ int main(int argc, char** argv) {
database.PurgeExpiredInstances(); database.PurgeExpiredInstances();
} }
if (EQTimeTimer.Check()) if (EQTimeTimer.Check()) {
{
TimeOfDay_Struct tod; TimeOfDay_Struct tod;
zoneserver_list.worldclock.GetCurrentEQTimeOfDay(time(0), &tod); zoneserver_list.worldclock.GetCurrentEQTimeOfDay(time(0), &tod);
if (!database.SaveTime(tod.minute, tod.hour, tod.day, tod.month, tod.year)) if (!database.SaveTime(tod.minute, tod.hour, tod.day, tod.month, tod.year))
@ -557,6 +563,9 @@ int main(int argc, char** argv) {
if (InterserverTimer.Check()) { if (InterserverTimer.Check()) {
InterserverTimer.Start(); InterserverTimer.Start();
database.ping(); 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(); EQ::EventLoop::Get().Process();

View File

@ -31,7 +31,6 @@
#endif #endif
void CatchSignal(int sig_num); void CatchSignal(int sig_num);
void UpdateWindowTitle(char* iNewTitle);
#define EQ_WORLD_PORT 9000 //mandated by the client #define EQ_WORLD_PORT 9000 //mandated by the client
#define LOGIN_PORT 5997 #define LOGIN_PORT 5997

View File

@ -1274,6 +1274,11 @@ void Mob::ClearFeignMemory() {
AI_feign_remember_timer->Disable(); 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) { bool Mob::PassCharismaCheck(Mob* caster, uint16 spell_id) {
/* /*

View File

@ -852,16 +852,56 @@ int Mob::ACSum()
return ac; return ac;
} }
int Mob::GetBestMeleeSkill()
{
int bestSkill=0;
EQEmu::skills::SkillType meleeSkills[]=
{ EQEmu::skills::Skill1HBlunt,
EQEmu::skills::Skill1HSlashing,
EQEmu::skills::Skill2HBlunt,
EQEmu::skills::Skill2HSlashing,
EQEmu::skills::SkillHandtoHand,
EQEmu::skills::Skill1HPiercing,
EQEmu::skills::Skill2HPiercing,
EQEmu::skills::SkillCount
};
int i;
for (i=0; meleeSkills[i] != EQEmu::skills::SkillCount; ++i) {
int value;
value = GetSkill(meleeSkills[i]);
bestSkill = std::max(value, bestSkill);
}
return bestSkill;
}
int Mob::offense(EQEmu::skills::SkillType skill) int Mob::offense(EQEmu::skills::SkillType skill)
{ {
int offense = GetSkill(skill); int offense = GetSkill(skill);
int stat_bonus = 0; int stat_bonus = GetSTR();
if (skill == EQEmu::skills::SkillArchery || skill == EQEmu::skills::SkillThrowing)
switch (skill) {
case EQEmu::skills::SkillArchery:
case EQEmu::skills::SkillThrowing:
stat_bonus = GetDEX(); stat_bonus = GetDEX();
else break;
stat_bonus = GetSTR();
// Mobs with no weapons default to H2H.
// Since H2H is capped at 100 for many many classes,
// lets not handicap mobs based on not spawning with a
// weapon.
//
// Maybe we tweak this if Disarm is actually implemented.
case EQEmu::skills::SkillHandtoHand:
offense = GetBestMeleeSkill();
break;
}
if (stat_bonus >= 75) if (stat_bonus >= 75)
offense += (2 * stat_bonus - 150) / 3; offense += (2 * stat_bonus - 150) / 3;
offense += GetATK(); offense += GetATK();
return offense; return offense;
} }
@ -1262,6 +1302,7 @@ int Client::DoDamageCaps(int base_damage)
return std::min(cap, 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) void Mob::DoAttack(Mob *other, DamageHitInfo &hit, ExtraAttackOptions *opts)
{ {
if (!other) if (!other)
@ -1288,6 +1329,20 @@ void Mob::DoAttack(Mob *other, DamageHitInfo &hit, ExtraAttackOptions *opts)
if (hit.damage_done >= 0) { if (hit.damage_done >= 0) {
if (other->CheckHitChance(this, hit)) { 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); other->MeleeMitigation(this, hit, opts);
if (hit.damage_done > 0) { if (hit.damage_done > 0) {
ApplyDamageTable(hit); ApplyDamageTable(hit);
@ -1674,6 +1729,15 @@ bool Client::Death(Mob* killerMob, int32 damage, uint16 spell, EQEmu::skills::Sk
exploss = (int)(GetLevel() * (GetLevel() / 18.0) * 12000); exploss = (int)(GetLevel() * (GetLevel() / 18.0) * 12000);
} }
if (RuleB(Zone, LevelBasedEXPMods)) {
// Death in levels with xp_mod (such as hell levels) was resulting
// in losing more that appropriate since the loss was the same but
// getting it back would take way longer. This makes the death the
// same amount of time to recover. Will also lose more if level is
// granting a bonus.
exploss *= zone->level_exp_mod[GetLevel()].ExpMod;
}
if ((GetLevel() < RuleI(Character, DeathExpLossLevel)) || (GetLevel() > RuleI(Character, DeathExpLossMaxLevel)) || IsBecomeNPC()) if ((GetLevel() < RuleI(Character, DeathExpLossLevel)) || (GetLevel() > RuleI(Character, DeathExpLossMaxLevel)) || IsBecomeNPC())
{ {
exploss = 0; exploss = 0;
@ -2518,6 +2582,9 @@ void Mob::AddToHateList(Mob* other, uint32 hate /*= 0*/, int32 damage /*= 0*/, b
if (other == this) if (other == this)
return; return;
if (other->IsTrap())
return;
if (damage < 0) { if (damage < 0) {
hate = 1; hate = 1;
} }
@ -2617,7 +2684,7 @@ void Mob::AddToHateList(Mob* other, uint32 hate /*= 0*/, int32 damage /*= 0*/, b
hate_list.AddEntToHateList(other, hate, damage, bFrenzy, !iBuffTic); hate_list.AddEntToHateList(other, hate, damage, bFrenzy, !iBuffTic);
if (other->IsClient() && !on_hatelist) if (other->IsClient() && !on_hatelist && !IsOnFeignMemory(other->CastToClient()))
other->CastToClient()->AddAutoXTarget(this); other->CastToClient()->AddAutoXTarget(this);
#ifdef BOTS #ifdef BOTS
@ -2658,9 +2725,9 @@ void Mob::AddToHateList(Mob* other, uint32 hate /*= 0*/, int32 damage /*= 0*/, b
// owner must get on list, but he's not actually gained any hate yet // owner must get on list, but he's not actually gained any hate yet
if (!owner->GetSpecialAbility(IMMUNE_AGGRO)) if (!owner->GetSpecialAbility(IMMUNE_AGGRO))
{ {
hate_list.AddEntToHateList(owner, 0, 0, false, !iBuffTic);
if (owner->IsClient() && !CheckAggro(owner)) if (owner->IsClient() && !CheckAggro(owner))
owner->CastToClient()->AddAutoXTarget(this); owner->CastToClient()->AddAutoXTarget(this);
hate_list.AddEntToHateList(owner, 0, 0, false, !iBuffTic);
} }
} }
} }
@ -3349,7 +3416,7 @@ void Mob::CommonDamage(Mob* attacker, int &damage, const uint16 spell_id, const
// pets that have GHold will never automatically add NPCs // pets that have GHold will never automatically add NPCs
// pets that have Hold and no Focus will add NPCs if they're engaged // pets that have Hold and no Focus will add NPCs if they're engaged
// pets that have Hold and Focus will not add NPCs // pets that have Hold and Focus will not add NPCs
if (pet && !pet->IsFamiliar() && !pet->GetSpecialAbility(IMMUNE_AGGRO) && !pet->IsEngaged() && attacker && attacker != this && !attacker->IsCorpse() && !pet->IsGHeld()) if (pet && !pet->IsFamiliar() && !pet->GetSpecialAbility(IMMUNE_AGGRO) && !pet->IsEngaged() && attacker && attacker != this && !attacker->IsCorpse() && !pet->IsGHeld() && !attacker->IsTrap())
{ {
if (!pet->IsHeld()) { if (!pet->IsHeld()) {
Log(Logs::Detail, Logs::Aggro, "Sending pet %s into battle due to attack.", pet->GetName()); Log(Logs::Detail, Logs::Aggro, "Sending pet %s into battle due to attack.", pet->GetName());
@ -3907,10 +3974,10 @@ void Mob::TryWeaponProc(const EQEmu::ItemInstance *inst, const EQEmu::ItemData *
float WPC = ProcChance * (100.0f + // Proc chance for this weapon float WPC = ProcChance * (100.0f + // Proc chance for this weapon
static_cast<float>(weapon->ProcRate)) / 100.0f; static_cast<float>(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 (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, Log(Logs::Detail, Logs::Combat,
"Tried to proc (%s), but our level (%d) is lower than required (%d)", "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()) { if (IsPet()) {
Mob *own = GetOwner(); Mob *own = GetOwner();
if (own) if (own)
@ -3947,7 +4014,7 @@ void Mob::TryWeaponProc(const EQEmu::ItemInstance *inst, const EQEmu::ItemData *
float APC = ProcChance * (100.0f + // Proc chance for this aug float APC = ProcChance * (100.0f + // Proc chance for this aug
static_cast<float>(aug->ProcRate)) / 100.0f; static_cast<float>(aug->ProcRate)) / 100.0f;
if (zone->random.Roll(APC)) { if (zone->random.Roll(APC)) {
if (aug->Proc.Level > ourlevel) { if (aug->Proc.Level2 > ourlevel) {
if (IsPet()) { if (IsPet()) {
Mob *own = GetOwner(); Mob *own = GetOwner();
if (own) if (own)
@ -5232,12 +5299,23 @@ void Client::DoAttackRounds(Mob *target, int hand, bool IsFromSpell)
// extra off hand non-sense, can only double with skill of 150 or above // extra off hand non-sense, can only double with skill of 150 or above
// or you have any amount of GiveDoubleAttack // or you have any amount of GiveDoubleAttack
if (candouble && hand == EQEmu::inventory::slotSecondary) if (candouble && hand == EQEmu::inventory::slotSecondary)
candouble = GetSkill(EQEmu::skills::SkillDoubleAttack) > 149 || (aabonuses.GiveDoubleAttack + spellbonuses.GiveDoubleAttack + itembonuses.GiveDoubleAttack) > 0; candouble =
GetSkill(EQEmu::skills::SkillDoubleAttack) > 149 ||
(aabonuses.GiveDoubleAttack + spellbonuses.GiveDoubleAttack + itembonuses.GiveDoubleAttack) > 0;
if (candouble) { if (candouble) {
CheckIncreaseSkill(EQEmu::skills::SkillDoubleAttack, target, -10); CheckIncreaseSkill(EQEmu::skills::SkillDoubleAttack, target, -10);
if (CheckDoubleAttack()) { if (CheckDoubleAttack()) {
Attack(target, hand, false, false, IsFromSpell); Attack(target, hand, false, false, IsFromSpell);
// Modern AA description: Increases your chance of ... performing one additional hit with a 2-handed weapon when double attacking by 2%.
if (hand == EQEmu::inventory::slotPrimary) {
auto extraattackchance = aabonuses.ExtraAttackChance + spellbonuses.ExtraAttackChance +
itembonuses.ExtraAttackChance;
if (extraattackchance && HasTwoHanderEquipped() && zone->random.Roll(extraattackchance))
Attack(target, hand, false, false, IsFromSpell);
}
// you can only triple from the main hand // you can only triple from the main hand
if (hand == EQEmu::inventory::slotPrimary && CanThisClassTripleAttack()) { if (hand == EQEmu::inventory::slotPrimary && CanThisClassTripleAttack()) {
CheckIncreaseSkill(EQEmu::skills::SkillTripleAttack, target, -10); CheckIncreaseSkill(EQEmu::skills::SkillTripleAttack, target, -10);
@ -5255,12 +5333,6 @@ void Client::DoAttackRounds(Mob *target, int hand, bool IsFromSpell)
} }
} }
} }
if (hand == EQEmu::inventory::slotPrimary) {
auto extraattackchance = aabonuses.ExtraAttackChance + spellbonuses.ExtraAttackChance + itembonuses.ExtraAttackChance;
if (extraattackchance && HasTwoHanderEquipped() && zone->random.Roll(extraattackchance))
Attack(target, hand, false, false, IsFromSpell);
}
} }
bool Mob::CheckDualWield() bool Mob::CheckDualWield()

View File

@ -120,6 +120,13 @@ void Client::CalcBonuses()
if (GetMaxXTargets() != 5 + aabonuses.extra_xtargets) if (GetMaxXTargets() != 5 + aabonuses.extra_xtargets)
SetMaxXTargets(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) 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; newbon->GivePetGroupTarget = true;
break; break;
case SE_ItemHPRegenCapIncrease: case SE_ItemHPRegenCapIncrease:
newbon->ItemHPRegenCap = +base1; newbon->ItemHPRegenCap += base1;
break; break;
case SE_Ambidexterity: case SE_Ambidexterity:
newbon->Ambidexterity += base1; newbon->Ambidexterity += base1;
@ -1003,6 +1010,15 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon)
case SE_RiposteChance: case SE_RiposteChance:
newbon->RiposteChance += base1; newbon->RiposteChance += base1;
break; 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: case SE_Flurry:
newbon->FlurryChance += base1; newbon->FlurryChance += base1;
break; break;
@ -2494,8 +2510,17 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne
new_bonus->MagicWeapon = true; new_bonus->MagicWeapon = true;
break; break;
case SE_Hunger:
new_bonus->hunger = true;
break;
case SE_IncreaseBlockChance: case SE_IncreaseBlockChance:
if (AdditiveWornBonus)
new_bonus->IncreaseBlockChance += effect_value; 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; break;
case SE_PersistantCasting: case SE_PersistantCasting:

View File

@ -2841,6 +2841,8 @@ void Bot::Spawn(Client* botCharacterOwner) {
FaceTarget(botCharacterOwner); FaceTarget(botCharacterOwner);
UpdateEquipmentLight(); UpdateEquipmentLight();
UpdateActiveLight(); UpdateActiveLight();
this->m_targetable = true;
entity_list.AddBot(this, true, true); entity_list.AddBot(this, true, true);
// Load pet // Load pet
LoadPet(); LoadPet();
@ -6725,7 +6727,7 @@ int32 Bot::CalcATK() {
} }
void Bot::CalcRestState() { void Bot::CalcRestState() {
if(!RuleI(Character, RestRegenPercent)) if(!RuleB(Character, RestRegenEnabled))
return; return;
RestRegenHP = RestRegenMana = RestRegenEndurance = 0; RestRegenHP = RestRegenMana = RestRegenEndurance = 0;
@ -6741,10 +6743,9 @@ void Bot::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 = 6 * (GetMaxEndurance() / RuleI(Character, RestRegenEnd));
RestRegenEndurance = (GetMaxEndurance() * RuleI(Character, RestRegenPercent) / 100);
} }
int32 Bot::LevelRegen() { int32 Bot::LevelRegen() {

View File

@ -123,7 +123,7 @@ Client::Client(EQStreamInterface* ieqs)
hpupdate_timer(2000), hpupdate_timer(2000),
camp_timer(29000), camp_timer(29000),
process_timer(100), process_timer(100),
stamina_timer(40000), consume_food_timer(CONSUMPTION_TIMER),
zoneinpacket_timer(1000), zoneinpacket_timer(1000),
linkdead_timer(RuleI(Zone,ClientLinkdeadMS)), linkdead_timer(RuleI(Zone,ClientLinkdeadMS)),
dead_timer(2000), dead_timer(2000),
@ -160,7 +160,8 @@ Client::Client(EQStreamInterface* ieqs)
npc_close_scan_timer(6000), npc_close_scan_timer(6000),
hp_self_update_throttle_timer(300), hp_self_update_throttle_timer(300),
hp_other_update_throttle_timer(500), 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++) 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_ClientVersion = EQEmu::versions::ClientVersion::Unknown;
m_ClientVersionBit = 0; m_ClientVersionBit = 0;
AggroCount = 0; AggroCount = 0;
RestRegenHP = 0; ooc_regen = false;
RestRegenMana = 0; AreaHPRegen = 1.0f;
RestRegenEndurance = 0; AreaManaRegen = 1.0f;
AreaEndRegen = 1.0f;
XPRate = 100; XPRate = 100;
current_endurance = 0; current_endurance = 0;
@ -329,6 +331,11 @@ Client::Client(EQStreamInterface* ieqs)
interrogateinv_flag = false; interrogateinv_flag = false;
trapid = 0;
for (int i = 0; i < InnateSkillMax; ++i)
m_pp.InnateSkills[i] = InnateDisabled;
AI_Init(); AI_Init();
} }
@ -658,13 +665,18 @@ bool Client::Save(uint8 iCommitNow) {
m_pp.tribute_time_remaining = 0xFFFFFFFF; m_pp.tribute_active = 0; 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); p_timers.Store(&database);
database.SaveCharacterTribute(this->CharacterID(), &m_pp); database.SaveCharacterTribute(this->CharacterID(), &m_pp);
SaveTaskState(); /* Save Character Task */ SaveTaskState(); /* Save Character Task */
m_pp.hunger_level = EQEmu::Clamp(m_pp.hunger_level, 0, 50000); Log(Logs::General, Logs::Food, "Client::Save - hunger_level: %i thirst_level: %i", m_pp.hunger_level, m_pp.thirst_level);
m_pp.thirst_level = EQEmu::Clamp(m_pp.thirst_level, 0, 50000);
// perform snapshot before SaveCharacterData() so that m_epp will contain the updated time // perform snapshot before SaveCharacterData() so that m_epp will contain the updated time
if (RuleB(Character, ActiveInvSnapshots) && time(nullptr) >= GetNextInvSnapshotTime()) { if (RuleB(Character, ActiveInvSnapshots) && time(nullptr) >= GetNextInvSnapshotTime()) {
@ -1211,20 +1223,20 @@ void Client::ChannelMessageSend(const char* from, const char* to, uint8 chan_num
EffSkill = 100; EffSkill = 100;
cm->skill_in_language = EffSkill; cm->skill_in_language = EffSkill;
// Garble the message based on listener skill
if (ListenerSkill < 100) {
GarbleMessage(buffer, (100 - ListenerSkill));
}
cm->chan_num = chan_num; cm->chan_num = chan_num;
strcpy(&cm->message[0], buffer); strcpy(&cm->message[0], buffer);
QueuePacket(&app); QueuePacket(&app);
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 ((chan_num == 2) && (ListenerSkill < 100)) { // group message in unmastered language, check for skill up
if (m_pp.languages[language] <= lang_skill) if (m_pp.languages[language] <= lang_skill)
CheckLanguageSkillIncrease(language, lang_skill); CheckLanguageSkillIncrease(language, lang_skill);
} }
} }
}
void Client::Message(uint32 type, const char* message, ...) { void Client::Message(uint32 type, const char* message, ...) {
if (GetFilter(FilterSpellDamage) == FilterHide && type == MT_NonMelee) if (GetFilter(FilterSpellDamage) == FilterHide && type == MT_NonMelee)
@ -4588,7 +4600,7 @@ void Client::IncrementAggroCount() {
// //
AggroCount++; AggroCount++;
if(!RuleI(Character, RestRegenPercent)) if(!RuleB(Character, RestRegenEnabled))
return; return;
// If we already had aggro before this method was called, the combat indicator should already be up for SoF clients, // If we already had aggro before this method was called, the combat indicator should already be up for SoF clients,
@ -4625,7 +4637,7 @@ void Client::DecrementAggroCount() {
AggroCount--; AggroCount--;
if(!RuleI(Character, RestRegenPercent)) if(!RuleB(Character, RestRegenEnabled))
return; return;
// Something else is still aggro on us, can't rest yet. // Something else is still aggro on us, can't rest yet.
@ -6712,7 +6724,7 @@ void Client::SendStatsWindow(Client* client, bool use_window)
cap_regen_field = itoa(CalcHPRegenCap()); cap_regen_field = itoa(CalcHPRegenCap());
spell_regen_field = itoa(spellbonuses.HPRegen); spell_regen_field = itoa(spellbonuses.HPRegen);
aa_regen_field = itoa(aabonuses.HPRegen); aa_regen_field = itoa(aabonuses.HPRegen);
total_regen_field = itoa(CalcHPRegen()); total_regen_field = itoa(CalcHPRegen(true));
break; break;
} }
case 1: { case 1: {
@ -6725,7 +6737,7 @@ void Client::SendStatsWindow(Client* client, bool use_window)
cap_regen_field = itoa(CalcManaRegenCap()); cap_regen_field = itoa(CalcManaRegenCap());
spell_regen_field = itoa(spellbonuses.ManaRegen); spell_regen_field = itoa(spellbonuses.ManaRegen);
aa_regen_field = itoa(aabonuses.ManaRegen); aa_regen_field = itoa(aabonuses.ManaRegen);
total_regen_field = itoa(CalcManaRegen()); total_regen_field = itoa(CalcManaRegen(true));
} }
else { continue; } else { continue; }
break; break;
@ -6739,7 +6751,7 @@ void Client::SendStatsWindow(Client* client, bool use_window)
cap_regen_field = itoa(CalcEnduranceRegenCap()); cap_regen_field = itoa(CalcEnduranceRegenCap());
spell_regen_field = itoa(spellbonuses.EnduranceRegen); spell_regen_field = itoa(spellbonuses.EnduranceRegen);
aa_regen_field = itoa(aabonuses.EnduranceRegen); aa_regen_field = itoa(aabonuses.EnduranceRegen);
total_regen_field = itoa(CalcEnduranceRegen()); total_regen_field = itoa(CalcEnduranceRegen(true));
break; break;
} }
default: { break; } default: { break; }
@ -8585,50 +8597,51 @@ void Client::SetConsumption(int32 in_hunger, int32 in_thirst)
void Client::Consume(const EQEmu::ItemData *item, uint8 type, int16 slot, bool auto_consume) 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) if (type == EQEmu::item::ItemTypeFood) {
cons_mod = cons_mod * metabolism_bonus * RuleI(Character, ConsumptionMultiplier) / 10000; increase = mod_food_value(item, increase);
else
cons_mod = cons_mod * RuleI(Character, ConsumptionMultiplier) / 100;
if (type == EQEmu::item::ItemTypeFood) if (increase < 0)
{ return;
int hchange = item->CastTime_ * cons_mod;
hchange = mod_food_value(item, hchange);
if(hchange < 0) { return; } m_pp.hunger_level += increase;
Log(Logs::General, Logs::Food, "Consuming food, points added to hunger_level: %i - current_hunger: %i",
increase, m_pp.hunger_level);
m_pp.hunger_level += hchange;
DeleteItemInInventory(slot, 1, false); 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); entity_list.MessageClose_StringID(this, true, 50, 0, EATING_MESSAGE, GetName(), item->Name);
#if EQDEBUG >= 5 Log(Logs::General, Logs::Food, "Eating from slot: %i", (int)slot);
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; } } else {
increase = mod_drink_value(item, increase);
if (increase < 0)
return;
m_pp.thirst_level += increase;
m_pp.thirst_level += tchange;
DeleteItemInInventory(slot, 1, false); 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 if (!auto_consume) // no message if the client consumed for us
entity_list.MessageClose_StringID(this, true, 50, 0, DRINKING_MESSAGE, GetName(), item->Name); entity_list.MessageClose_StringID(this, true, 50, 0, DRINKING_MESSAGE, GetName(), item->Name);
#if EQDEBUG >= 5 Log(Logs::General, Logs::Food, "Drinking from slot: %i", (int)slot);
Log(Logs::General, Logs::None, "Drinking from slot:%i", (int)slot);
#endif
} }
} }
@ -9055,3 +9068,193 @@ void Client::SetPetCommandState(int button, int state)
FastQueuePacket(&app); 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;
}
}

View File

@ -199,6 +199,27 @@ struct RespawnOption
float heading; 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; const uint32 POPUPID_UPDATE_SHOWSTATSWINDOW = 1000000;
@ -406,6 +427,16 @@ public:
const int32& SetMana(int32 amount); const int32& SetMana(int32 amount);
int32 CalcManaRegenCap(); 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 ServerFilter(SetServerFilter_Struct* filter);
void BulkSendTraderInventory(uint32 char_id); void BulkSendTraderInventory(uint32 char_id);
void SendSingleTraderItem(uint32 char_id, int uniqueid); void SendSingleTraderItem(uint32 char_id, int uniqueid);
@ -540,7 +571,7 @@ public:
/*Endurance and such*/ /*Endurance and such*/
void CalcMaxEndurance(); //This calculates the maximum endurance we can have void CalcMaxEndurance(); //This calculates the maximum endurance we can have
int32 CalcBaseEndurance(); //Calculates Base End 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 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 GetMaxEndurance() const {return max_end;} //This gets our endurance from the last CalcMaxEndurance() call
int32 CalcEnduranceRegenCap(); int32 CalcEnduranceRegenCap();
@ -719,6 +750,7 @@ public:
void SendTradeskillDetails(uint32 recipe_id); void SendTradeskillDetails(uint32 recipe_id);
bool TradeskillExecute(DBTradeskillRecipe_Struct *spec); bool TradeskillExecute(DBTradeskillRecipe_Struct *spec);
void CheckIncreaseTradeskill(int16 bonusstat, int16 stat_modifier, float skillup_modifier, uint16 success_modifier, EQEmu::skills::SkillType tradeskill); void CheckIncreaseTradeskill(int16 bonusstat, int16 stat_modifier, float skillup_modifier, uint16 success_modifier, EQEmu::skills::SkillType tradeskill);
void InitInnates();
void GMKill(); void GMKill();
inline bool IsMedding() const {return medding;} inline bool IsMedding() const {return medding;}
@ -760,6 +792,9 @@ public:
void SummonHorse(uint16 spell_id); void SummonHorse(uint16 spell_id);
void SetHorseId(uint16 horseid_in); void SetHorseId(uint16 horseid_in);
uint16 GetHorseId() const { return horseId; } uint16 GetHorseId() const { return horseId; }
bool CanMedOnHorse();
bool CanFastRegen() const { return ooc_regen; }
void NPCSpawn(NPC *target_npc, const char *identifier, uint32 extra = 0); void NPCSpawn(NPC *target_npc, const char *identifier, uint32 extra = 0);
@ -862,6 +897,7 @@ public:
void SetHunger(int32 in_hunger); void SetHunger(int32 in_hunger);
void SetThirst(int32 in_thirst); void SetThirst(int32 in_thirst);
void SetConsumption(int32 in_hunger, 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 CheckTradeLoreConflict(Client* other);
bool CheckTradeNonDroppable(); bool CheckTradeNonDroppable();
@ -1266,6 +1302,8 @@ public:
int32 CalcATK(); int32 CalcATK();
uint32 trapid; //ID of trap player has triggered. This is cleared when the player leaves the trap's radius, or it despawns.
protected: protected:
friend class Mob; friend class Mob;
void CalcItemBonuses(StatBonuses* newbon); void CalcItemBonuses(StatBonuses* newbon);
@ -1345,13 +1383,13 @@ private:
int32 CalcCorrup(); int32 CalcCorrup();
int32 CalcMaxHP(); int32 CalcMaxHP();
int32 CalcBaseHP(); int32 CalcBaseHP();
int32 CalcHPRegen(); int32 CalcHPRegen(bool bCombat = false);
int32 CalcManaRegen(); int32 CalcManaRegen(bool bCombat = false);
int32 CalcBaseManaRegen(); int32 CalcBaseManaRegen();
uint32 GetClassHPFactor(); uint32 GetClassHPFactor();
void DoHPRegen(); void DoHPRegen();
void DoManaRegen(); void DoManaRegen();
void DoStaminaUpdate(); void DoStaminaHungerUpdate();
void CalcRestState(); void CalcRestState();
uint32 pLastUpdate; uint32 pLastUpdate;
@ -1410,6 +1448,7 @@ private:
std::string BuyerWelcomeMessage; std::string BuyerWelcomeMessage;
bool AbilityTimer; bool AbilityTimer;
int Haste; //precalced value int Haste; //precalced value
uint32 tmSitting; // time stamp started sitting, used for HP regen bonus added on MAY 5, 2004
int32 max_end; int32 max_end;
int32 current_endurance; int32 current_endurance;
@ -1458,7 +1497,7 @@ private:
Timer hpupdate_timer; Timer hpupdate_timer;
Timer camp_timer; Timer camp_timer;
Timer process_timer; Timer process_timer;
Timer stamina_timer; Timer consume_food_timer;
Timer zoneinpacket_timer; Timer zoneinpacket_timer;
Timer linkdead_timer; Timer linkdead_timer;
Timer dead_timer; Timer dead_timer;
@ -1489,7 +1528,9 @@ private:
Timer hp_self_update_throttle_timer; /* This is to prevent excessive packet sending under trains/fast combat */ Timer hp_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 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 */ 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(); void BulkSendInventoryItems();
@ -1511,9 +1552,10 @@ private:
unsigned int AggroCount; // How many mobs are aggro on us. unsigned int AggroCount; // How many mobs are aggro on us.
unsigned int RestRegenHP; bool ooc_regen;
unsigned int RestRegenMana; float AreaHPRegen;
unsigned int RestRegenEndurance; float AreaManaRegen;
float AreaEndRegen;
bool EngagedRaidTarget; bool EngagedRaidTarget;
uint32 SavedRaidRestTimer; uint32 SavedRaidRestTimer;

View File

@ -22,6 +22,8 @@
#include "../common/rulesys.h" #include "../common/rulesys.h"
#include "../common/spdat.h" #include "../common/spdat.h"
#include "../common/data_verification.h"
#include "client.h" #include "client.h"
#include "mob.h" #include "mob.h"
@ -231,16 +233,81 @@ int32 Client::LevelRegen()
return hp; return hp;
} }
int32 Client::CalcHPRegen() int32 Client::CalcHPRegen(bool bCombat)
{ {
int32 regen = LevelRegen() + itembonuses.HPRegen + spellbonuses.HPRegen; int item_regen = itembonuses.HPRegen; // worn spells and +regen, already capped
regen += aabonuses.HPRegen + GroupLeadershipAAHealthRegeneration(); item_regen += GetHeroicSTA() / 20;
item_regen += aabonuses.HPRegen;
int base = 0;
auto base_data = database.GetBaseData(GetLevel(), GetClass());
if (base_data)
base = static_cast<int>(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<uint32>(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); return (regen * RuleI(Character, HPRegenMultiplier) / 100);
} }
int32 Client::CalcHPRegenCap() 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; cap += aabonuses.ItemHPRegenCap + spellbonuses.ItemHPRegenCap + itembonuses.ItemHPRegenCap;
return (cap * RuleI(Character, HPRegenMultiplier) / 100); return (cap * RuleI(Character, HPRegenMultiplier) / 100);
} }
@ -1169,43 +1236,80 @@ int32 Client::CalcBaseManaRegen()
return regen; return regen;
} }
int32 Client::CalcManaRegen() int32 Client::CalcManaRegen(bool bCombat)
{ {
uint8 clevel = GetLevel(); int regen = 0;
int32 regen = 0; auto level = GetLevel();
//this should be changed so we dont med while camping, etc... // so the new formulas break down with older skill caps where you don't have the skill until 4 or 8
if (IsSitting() || (GetHorseId() != 0)) { // so for servers that want to use the old skill progression they can set this rule so they
BuffFadeBySitModifier(); // will get at least 1 for standing and 2 for sitting.
if (HasSkill(EQEmu::skills::SkillMeditate)) { bool old = RuleB(Character, OldMinMana);
this->medding = true; if (!IsStarved()) {
regen = (((GetSkill(EQEmu::skills::SkillMeditate) / 10) + (clevel - (clevel / 4))) / 4) + 4; // client does some base regen for shrouds here
regen += spellbonuses.ManaRegen + itembonuses.ManaRegen; if (IsSitting() || CanMedOnHorse()) {
CheckIncreaseSkill(EQEmu::skills::SkillMeditate, nullptr, -5); // kind of weird to do it here w/e
} // client does some base medding regen for shrouds here
else { if (GetClass() != BARD) {
regen = 2 + spellbonuses.ManaRegen + itembonuses.ManaRegen; auto skill = GetSkill(EQEmu::skills::SkillMeditate);
if (skill > 0) {
regen++;
if (skill > 1)
regen++;
if (skill >= 15)
regen += skill / 15;
} }
} }
else { if (old)
this->medding = false; regen = std::max(regen, 2);
regen = 2 + spellbonuses.ManaRegen + itembonuses.ManaRegen; } else if (old) {
regen = std::max(regen, 1);
} }
//AAs }
if (level > 61) {
regen++;
if (level > 63)
regen++;
}
regen += aabonuses.ManaRegen; 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); return (regen * RuleI(Character, ManaRegenMultiplier) / 100);
} }
int32 Client::CalcManaRegenCap() int32 Client::CalcManaRegenCap()
{ {
int32 cap = RuleI(Character, ItemManaRegenCap) + aabonuses.ItemManaRegenCap; 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); return (cap * RuleI(Character, ManaRegenMultiplier) / 100);
} }
@ -2091,16 +2195,91 @@ int32 Client::CalcBaseEndurance()
return base_end; return base_end;
} }
int32 Client::CalcEnduranceRegen() int32 Client::CalcEnduranceRegen(bool bCombat)
{ {
int32 regen = int32(GetLevel() * 4 / 10) + 2; int base = 0;
regen += aabonuses.EnduranceRegen + spellbonuses.EnduranceRegen + itembonuses.EnduranceRegen; if (!IsStarved()) {
auto base_data = database.GetBaseData(GetLevel(), GetClass());
if (base_data) {
base = static_cast<int>(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); return (regen * RuleI(Character, EnduranceRegenMultiplier) / 100);
} }
int32 Client::CalcEnduranceRegenCap() 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); return (cap * RuleI(Character, EnduranceRegenMultiplier) / 100);
} }

File diff suppressed because it is too large Load Diff

View File

@ -204,6 +204,7 @@
void Handle_OP_MercenaryTimerRequest(const EQApplicationPacket *app); void Handle_OP_MercenaryTimerRequest(const EQApplicationPacket *app);
void Handle_OP_MoveCoin(const EQApplicationPacket *app); void Handle_OP_MoveCoin(const EQApplicationPacket *app);
void Handle_OP_MoveItem(const EQApplicationPacket *app); void Handle_OP_MoveItem(const EQApplicationPacket *app);
void Handle_OP_MoveMultipleItems(const EQApplicationPacket *app);
void Handle_OP_OpenContainer(const EQApplicationPacket *app); void Handle_OP_OpenContainer(const EQApplicationPacket *app);
void Handle_OP_OpenGuildTributeMaster(const EQApplicationPacket *app); void Handle_OP_OpenGuildTributeMaster(const EQApplicationPacket *app);
void Handle_OP_OpenInventory(const EQApplicationPacket *app); void Handle_OP_OpenInventory(const EQApplicationPacket *app);

View File

@ -38,6 +38,7 @@
#include <unistd.h> #include <unistd.h>
#endif #endif
#include "../common/data_verification.h"
#include "../common/rulesys.h" #include "../common/rulesys.h"
#include "../common/skills.h" #include "../common/skills.h"
#include "../common/spdat.h" #include "../common/spdat.h"
@ -243,24 +244,42 @@ bool Client::Process() {
/* Build a close range list of NPC's */ /* Build a close range list of NPC's */
if (npc_close_scan_timer.Check()) { if (npc_close_scan_timer.Check()) {
close_mobs.clear(); close_mobs.clear();
auto &mob_list = entity_list.GetMobList(); /* Force spawn updates when traveled far */
float scan_range = (RuleI(Range, ClientNPCScan) * RuleI(Range, ClientNPCScan)); bool force_spawn_updates = false;
float client_update_range = (RuleI(Range, MobPositionUpdates) * RuleI(Range, MobPositionUpdates)); 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) { for (auto itr = mob_list.begin(); itr != mob_list.end(); ++itr) {
Mob* mob = itr->second; Mob* mob = itr->second;
float distance = DistanceSquared(m_Position, mob->GetPosition()); float distance = DistanceSquared(m_Position, mob->GetPosition());
if (mob->IsNPC()) { if (mob->IsNPC()) {
if (distance <= scan_range) { if (distance <= scan_range) {
close_mobs.insert(std::pair<Mob *, float>(mob, distance)); close_mobs.insert(std::pair<Mob *, float>(mob, distance));
} }
else if (mob->GetAggroRange() > scan_range) { else if ((mob->GetAggroRange() * mob->GetAggroRange()) > scan_range) {
close_mobs.insert(std::pair<Mob *, float>(mob, distance)); close_mobs.insert(std::pair<Mob *, float>(mob, distance));
} }
} }
if (force_spawn_updates && mob != this) {
if (mob->is_distance_roamer) {
mob->SendPositionUpdateToClient(this);
continue;
}
if (distance <= client_update_range)
mob->SendPositionUpdateToClient(this);
}
} }
} }
@ -513,6 +532,10 @@ bool Client::Process() {
DoEnduranceUpkeep(); DoEnduranceUpkeep();
} }
// this is independent of the tick timer
if (consume_food_timer.Check())
DoStaminaHungerUpdate();
if (tic_timer.Check() && !dead) { if (tic_timer.Check() && !dead) {
CalcMaxHP(); CalcMaxHP();
CalcMaxMana(); CalcMaxMana();
@ -523,7 +546,6 @@ bool Client::Process() {
DoManaRegen(); DoManaRegen();
DoEnduranceRegen(); DoEnduranceRegen();
BuffProcess(); BuffProcess();
DoStaminaUpdate();
if (tribute_timer.Check()) { if (tribute_timer.Check()) {
ToggleTribute(true); //re-activate the tribute. ToggleTribute(true); //re-activate the tribute.
@ -1041,7 +1063,8 @@ void Client::OPRezzAnswer(uint32 Action, uint32 SpellID, uint16 ZoneID, uint16 I
SetMana(0); SetMana(0);
SetHP(GetMaxHP()/5); SetHP(GetMaxHP()/5);
int rez_eff = 756; int rez_eff = 756;
if (GetRace() == BARBARIAN || GetRace() == DWARF || GetRace() == TROLL || GetRace() == OGRE) if (RuleB(Character, UseOldRaceRezEffects) &&
(GetRace() == BARBARIAN || GetRace() == DWARF || GetRace() == TROLL || GetRace() == OGRE))
rez_eff = 757; rez_eff = 757;
SpellOnTarget(rez_eff, this); // Rezz effects SpellOnTarget(rez_eff, this); // Rezz effects
} }
@ -1806,7 +1829,7 @@ void Client::OPGMSummon(const EQApplicationPacket *app)
} }
void Client::DoHPRegen() { void Client::DoHPRegen() {
SetHP(GetHP() + CalcHPRegen() + RestRegenHP); SetHP(GetHP() + CalcHPRegen());
SendHPUpdate(); SendHPUpdate();
} }
@ -1814,41 +1837,55 @@ void Client::DoManaRegen() {
if (GetMana() >= max_mana && spellbonuses.ManaRegen >= 0) if (GetMana() >= max_mana && spellbonuses.ManaRegen >= 0)
return; 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(); CheckManaEndUpdate();
} }
void Client::DoStaminaHungerUpdate()
void Client::DoStaminaUpdate() { {
if(!stamina_timer.Check())
return;
auto outapp = new EQApplicationPacket(OP_Stamina, sizeof(Stamina_Struct)); 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); int loss = RuleI(Character, FoodLossPerUpdate);
if (m_pp.hunger_level > 0) if (GetHorseId() != 0)
m_pp.hunger_level-=loss; loss *= 3;
if (m_pp.thirst_level > 0)
m_pp.thirst_level-=loss; m_pp.hunger_level = EQEmu::Clamp(m_pp.hunger_level - loss, 0, 6000);
sta->food = m_pp.hunger_level > 6000 ? 6000 : m_pp.hunger_level; m_pp.thirst_level = EQEmu::Clamp(m_pp.thirst_level - loss, 0, 6000);
sta->water = m_pp.thirst_level> 6000 ? 6000 : m_pp.thirst_level; 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);
} }
else { sta->food = m_pp.hunger_level;
sta->water = m_pp.thirst_level;
} else {
// No auto food/drink consumption in the Bazaar // No auto food/drink consumption in the Bazaar
sta->food = 6000; sta->food = 6000;
sta->water = 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); FastQueuePacket(&outapp);
} }
void Client::DoEnduranceRegen() void Client::DoEnduranceRegen()
{ {
if(GetEndurance() >= GetMaxEndurance()) // endurance has some negative mods that could result in a negative regen when starved
return; int regen = CalcEnduranceRegen();
SetEndurance(GetEndurance() + CalcEnduranceRegen() + RestRegenEndurance); if (regen < 0 || (regen > 0 && GetEndurance() < GetMaxEndurance()))
SetEndurance(GetEndurance() + regen);
} }
void Client::DoEnduranceUpkeep() { void Client::DoEnduranceUpkeep() {
@ -1897,12 +1934,12 @@ void Client::CalcRestState() {
// The client must have been out of combat for RuleI(Character, RestRegenTimeToActivate) seconds, // 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. // must be sitting down, and must not have any detrimental spells affecting them.
// //
if(!RuleI(Character, RestRegenPercent)) if(!RuleB(Character, RestRegenEnabled))
return; return;
RestRegenHP = RestRegenMana = RestRegenEndurance = 0; ooc_regen = false;
if(AggroCount || !IsSitting()) if(AggroCount || !(IsSitting() || CanMedOnHorse()))
return; return;
if(!rest_timer.Check(false)) if(!rest_timer.Check(false))
@ -1917,12 +1954,8 @@ void Client::CalcRestState() {
} }
} }
RestRegenHP = (GetMaxHP() * RuleI(Character, RestRegenPercent) / 100); ooc_regen = true;
RestRegenMana = (GetMaxMana() * RuleI(Character, RestRegenPercent) / 100);
if(RuleB(Character, RestRegenEndurance))
RestRegenEndurance = (GetMaxEndurance() * RuleI(Character, RestRegenPercent) / 100);
} }
void Client::DoTracking() void Client::DoTracking()

View File

@ -321,6 +321,7 @@ int command_init(void)
command_add("reloadqst", " - Clear quest cache (any argument causes it to also stop all timers)", 150, command_reloadqst) || command_add("reloadqst", " - Clear quest cache (any argument causes it to also stop all timers)", 150, command_reloadqst) ||
command_add("reloadrulesworld", "Executes a reload of all rules in world specifically.", 80, command_reloadworldrules) || command_add("reloadrulesworld", "Executes a reload of all rules in world specifically.", 80, command_reloadworldrules) ||
command_add("reloadstatic", "- Reload Static Zone Data", 150, command_reloadstatic) || command_add("reloadstatic", "- Reload Static Zone Data", 150, command_reloadstatic) ||
command_add("reloadtraps", "- Repops all traps in the current zone.", 80, command_reloadtraps) ||
command_add("reloadtitles", "- Reload player titles from the database", 150, command_reloadtitles) || command_add("reloadtitles", "- Reload player titles from the database", 150, command_reloadtitles) ||
command_add("reloadworld", "[0|1] - Clear quest cache (0 - no repop, 1 - repop)", 255, command_reloadworld) || command_add("reloadworld", "[0|1] - Clear quest cache (0 - no repop, 1 - repop)", 255, command_reloadworld) ||
command_add("reloadzps", "- Reload zone points from database", 150, command_reloadzps) || command_add("reloadzps", "- Reload zone points from database", 150, command_reloadzps) ||
@ -375,6 +376,7 @@ int command_init(void)
command_add("task", "(subcommand) - Task system commands", 150, command_task) || command_add("task", "(subcommand) - Task system commands", 150, command_task) ||
command_add("tattoo", "- Change the tattoo of your target (Drakkin Only)", 80, command_tattoo) || command_add("tattoo", "- Change the tattoo of your target (Drakkin Only)", 80, command_tattoo) ||
command_add("tempname", "[newname] - Temporarily renames your target. Leave name blank to restore the original name.", 100, command_tempname) || command_add("tempname", "[newname] - Temporarily renames your target. Leave name blank to restore the original name.", 100, command_tempname) ||
command_add("petname", "[newname] - Temporarily renames your pet. Leave name blank to restore the original name.", 100, command_petname) ||
command_add("texture", "[texture] [helmtexture] - Change your or your target's appearance, use 255 to show equipment", 10, command_texture) || command_add("texture", "[texture] [helmtexture] - Change your or your target's appearance, use 255 to show equipment", 10, command_texture) ||
command_add("time", "[HH] [MM] - Set EQ time", 90, command_time) || command_add("time", "[HH] [MM] - Set EQ time", 90, command_time) ||
command_add("timers", "- Display persistent timers for target", 200, command_timers) || command_add("timers", "- Display persistent timers for target", 200, command_timers) ||
@ -382,6 +384,7 @@ int command_init(void)
command_add("title", "[text] [1 = create title table row] - Set your or your player target's title", 50, command_title) || command_add("title", "[text] [1 = create title table row] - Set your or your player target's title", 50, command_title) ||
command_add("titlesuffix", "[text] [1 = create title table row] - Set your or your player target's title suffix", 50, command_titlesuffix) || command_add("titlesuffix", "[text] [1 = create title table row] - Set your or your player target's title suffix", 50, command_titlesuffix) ||
command_add("traindisc", "[level] - Trains all the disciplines usable by the target, up to level specified. (may freeze client for a few seconds)", 150, command_traindisc) || command_add("traindisc", "[level] - Trains all the disciplines usable by the target, up to level specified. (may freeze client for a few seconds)", 150, command_traindisc) ||
command_add("trapinfo", "- Gets infomation about the traps currently spawned in the zone.", 81, command_trapinfo) ||
command_add("tune", "Calculate ideal statical values related to combat.", 100, command_tune) || command_add("tune", "Calculate ideal statical values related to combat.", 100, command_tune) ||
command_add("undyeme", "- Remove dye from all of your armor slots", 0, command_undyeme) || command_add("undyeme", "- Remove dye from all of your armor slots", 0, command_undyeme) ||
command_add("unfreeze", "- Unfreeze your target", 80, command_unfreeze) || command_add("unfreeze", "- Unfreeze your target", 80, command_unfreeze) ||
@ -4157,6 +4160,26 @@ void command_tempname(Client *c, const Seperator *sep)
} }
} }
void command_petname(Client *c, const Seperator *sep)
{
Mob *target;
target = c->GetTarget();
if(!target)
c->Message(0, "Usage: #petname newname (requires a target)");
else if(target->IsPet() && (target->GetOwnerID() == c->GetID()) && strlen(sep->arg[1]) > 0)
{
char *oldname = strdup(target->GetName());
target->TempName(sep->arg[1]);
c->Message(0, "Renamed %s to %s", oldname, sep->arg[1]);
free(oldname);
}
else {
target->TempName();
c->Message(0, "Restored the original name");
}
}
void command_npcspecialattk(Client *c, const Seperator *sep) void command_npcspecialattk(Client *c, const Seperator *sep)
{ {
if (c->GetTarget()==0 || c->GetTarget()->IsClient() || strlen(sep->arg[1]) <= 0 || strlen(sep->arg[2]) <= 0) if (c->GetTarget()==0 || c->GetTarget()->IsClient() || strlen(sep->arg[1]) <= 0 || strlen(sep->arg[2]) <= 0)
@ -7257,7 +7280,7 @@ void command_bestz(Client *c, const Seperator *sep) {
float best_z = zone->zonemap->FindBestZ(me, &hit); 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); c->Message(0, "Z is %.3f at (%.3f, %.3f).", best_z, me.x, me.y);
} }
@ -10851,6 +10874,16 @@ void command_reloadperlexportsettings(Client *c, const Seperator *sep)
} }
} }
void command_trapinfo(Client *c, const Seperator *sep)
{
entity_list.GetTrapInfo(c);
}
void command_reloadtraps(Client *c, const Seperator *sep)
{
entity_list.UpdateAllTraps(true, true);
c->Message(CC_Default, "Traps reloaded for %s.", zone->GetShortName());
}
// All new code added to command.cpp should be BEFORE this comment line. Do no append code to this file below the BOTS code block. // All new code added to command.cpp should be BEFORE this comment line. Do no append code to this file below the BOTS code block.
#ifdef BOTS #ifdef BOTS

View File

@ -228,6 +228,7 @@ void command_reloadperlexportsettings(Client *c, const Seperator *sep);
void command_reloadqst(Client *c, const Seperator *sep); void command_reloadqst(Client *c, const Seperator *sep);
void command_reloadstatic(Client *c, const Seperator *sep); void command_reloadstatic(Client *c, const Seperator *sep);
void command_reloadtitles(Client *c, const Seperator *sep); void command_reloadtitles(Client *c, const Seperator *sep);
void command_reloadtraps(Client* c, const Seperator *sep);
void command_reloadworld(Client *c, const Seperator *sep); void command_reloadworld(Client *c, const Seperator *sep);
void command_reloadworldrules(Client *c, const Seperator *sep); void command_reloadworldrules(Client *c, const Seperator *sep);
void command_reloadzps(Client *c, const Seperator *sep); void command_reloadzps(Client *c, const Seperator *sep);
@ -286,6 +287,7 @@ void command_synctod(Client *c, const Seperator *sep);
void command_task(Client *c, const Seperator *sep); void command_task(Client *c, const Seperator *sep);
void command_tattoo(Client *c, const Seperator *sep); void command_tattoo(Client *c, const Seperator *sep);
void command_tempname(Client *c, const Seperator *sep); void command_tempname(Client *c, const Seperator *sep);
void command_petname(Client *c, const Seperator *sep);
void command_testspawn(Client *c, const Seperator *sep); void command_testspawn(Client *c, const Seperator *sep);
void command_testspawnkill(Client *c, const Seperator *sep); void command_testspawnkill(Client *c, const Seperator *sep);
void command_texture(Client *c, const Seperator *sep); void command_texture(Client *c, const Seperator *sep);
@ -295,6 +297,7 @@ void command_timezone(Client *c, const Seperator *sep);
void command_title(Client *c, const Seperator *sep); void command_title(Client *c, const Seperator *sep);
void command_titlesuffix(Client *c, const Seperator *sep); void command_titlesuffix(Client *c, const Seperator *sep);
void command_traindisc(Client *c, const Seperator *sep); void command_traindisc(Client *c, const Seperator *sep);
void command_trapinfo(Client* c, const Seperator *sep);
void command_tune(Client *c, const Seperator *sep); void command_tune(Client *c, const Seperator *sep);
void command_undye(Client *c, const Seperator *sep); void command_undye(Client *c, const Seperator *sep);
void command_undyeme(Client *c, const Seperator *sep); void command_undyeme(Client *c, const Seperator *sep);

View File

@ -550,6 +550,7 @@ struct StatBonuses {
int FeignedMinionChance; // SPA 281 base1 = chance, just like normal FD int FeignedMinionChance; // SPA 281 base1 = chance, just like normal FD
int aura_slots; int aura_slots;
int trap_slots; int trap_slots;
bool hunger; // Song of Sustenance -- min caps to 3500
}; };
typedef struct typedef struct
@ -603,6 +604,11 @@ enum { //type arguments to DoAnim
}; };
enum {
SKILLUP_UNKNOWN = 0,
SKILLUP_SUCCESS = 1,
SKILLUP_FAILURE = 2
};
typedef enum { typedef enum {
petFamiliar, //only listens to /pet get lost petFamiliar, //only listens to /pet get lost

View File

@ -611,9 +611,13 @@ bool Client::UseDiscipline(uint32 spell_id, uint32 target) {
return false; return false;
} }
// the client does this check before calling CastSpell, should prevent discs being eaten
if (spell.buffdurationformula != 0 && spell.targettype == ST_Self && HasDiscBuff())
return false;
//Check the disc timer //Check the disc timer
pTimerType DiscTimer = pTimerDisciplineReuseStart + spell.EndurTimerIndex; 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 val1[20]={0};*/ //unused
/*char val2[20]={0};*/ //unused /*char val2[20]={0};*/ //unused
uint32 remain = p_timers.GetRemainingTime(DiscTimer); uint32 remain = p_timers.GetRemainingTime(DiscTimer);

View File

@ -237,6 +237,7 @@ void Embperl::init_eval_file(void)
{ {
eval_pv( eval_pv(
"our %Cache;" "our %Cache;"
"no warnings;"
"use Symbol qw(delete_package);" "use Symbol qw(delete_package);"
"sub eval_file {" "sub eval_file {"
"my($package, $filename) = @_;" "my($package, $filename) = @_;"
@ -247,7 +248,8 @@ void Embperl::init_eval_file(void)
" return;" " return;"
"} else {" "} else {"
// we 'my' $filename,$mtime,$package,$sub to prevent them from changing our state up here. // 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'; \");" " eval(\"package $package; my(\\$filename,\\$mtime,\\$package,\\$sub); \\$isloaded = 1; require './$filename'; \");"
// " print $@ if $@;"
/* "local *FH;open FH, $filename or die \"open '$filename' $!\";" /* "local *FH;open FH, $filename or die \"open '$filename' $!\";"
"local($/) = undef;my $sub = <FH>;close FH;" "local($/) = undef;my $sub = <FH>;close FH;"
"my $eval = qq{package $package; sub handler { $sub; }};" "my $eval = qq{package $package; sub handler { $sub; }};"

View File

@ -99,6 +99,10 @@ XS(XS_EQEmuIO_PRINT)
/* Strip newlines from log message 'str' */ /* Strip newlines from log message 'str' */
*std::remove(str, str + strlen(str), '\n') = '\0'; *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 i;
int pos = 0; int pos = 0;
int len = 0; int len = 0;

View File

@ -648,7 +648,7 @@ void EntityList::AddNPC(NPC *npc, bool SendSpawnPacket, bool dontqueue)
parse->EventNPC(EVENT_SPAWN, npc, nullptr, "", 0); parse->EventNPC(EVENT_SPAWN, npc, nullptr, "", 0);
npc->FixZ(); npc->FixZ(1);
uint16 emoteid = npc->GetEmoteID(); uint16 emoteid = npc->GetEmoteID();
if (emoteid != 0) if (emoteid != 0)
@ -1419,10 +1419,10 @@ void EntityList::RemoveFromTargets(Mob *mob, bool RemoveFromXTargets)
continue; continue;
if (RemoveFromXTargets) { if (RemoveFromXTargets) {
if (m->IsClient() && mob->CheckAggro(m)) if (m->IsClient() && (mob->CheckAggro(m) || mob->IsOnFeignMemory(m->CastToClient())))
m->CastToClient()->RemoveXTarget(mob, false); m->CastToClient()->RemoveXTarget(mob, false);
// FadingMemories calls this function passing the client. // 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); mob->CastToClient()->RemoveXTarget(m, false);
} }
@ -1461,7 +1461,7 @@ void EntityList::RefreshAutoXTargets(Client *c)
if (!m || m->GetHP() <= 0) if (!m || m->GetHP() <= 0)
continue; 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 c->AddAutoXTarget(m, false); // we only call this before a bulk, so lets not send right away
break; break;
} }
@ -2617,12 +2617,13 @@ void EntityList::RemoveFromHateLists(Mob *mob, bool settoone)
auto it = npc_list.begin(); auto it = npc_list.begin();
while (it != npc_list.end()) { while (it != npc_list.end()) {
if (it->second->CheckAggro(mob)) { if (it->second->CheckAggro(mob)) {
if (!settoone) if (!settoone) {
it->second->RemoveFromHateList(mob); it->second->RemoveFromHateList(mob);
else
it->second->SetHateAmountOnEnt(mob, 1);
if (mob->IsClient()) if (mob->IsClient())
mob->CastToClient()->RemoveXTarget(it->second, false); // gotta do book keeping mob->CastToClient()->RemoveXTarget(it->second, false); // gotta do book keeping
} else {
it->second->SetHateAmountOnEnt(mob, 1);
}
} }
++it; ++it;
} }
@ -3079,7 +3080,10 @@ void EntityList::ClearAggro(Mob* targ)
c->RemoveXTarget(it->second, false); c->RemoveXTarget(it->second, false);
it->second->RemoveFromHateList(targ); 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; ++it;
} }
} }
@ -3088,7 +3092,8 @@ void EntityList::ClearFeignAggro(Mob *targ)
{ {
auto it = npc_list.begin(); auto it = npc_list.begin();
while (it != npc_list.end()) { 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)) { if (it->second->GetSpecialAbility(IMMUNE_FEIGN_DEATH)) {
++it; ++it;
continue; continue;
@ -3252,7 +3257,7 @@ void EntityList::AddHealAggro(Mob *target, Mob *caster, uint16 hate)
for (auto &e : npc_list) { for (auto &e : npc_list) {
auto &npc = e.second; auto &npc = e.second;
if (!npc->CheckAggro(target) || npc->IsFeared()) if (!npc->CheckAggro(target) || npc->IsFeared() || npc->IsPet())
continue; continue;
if (zone->random.Roll(50)) // witness check -- place holder if (zone->random.Roll(50)) // witness check -- place holder

View File

@ -365,7 +365,7 @@ public:
//trap stuff //trap stuff
Mob* GetTrapTrigger(Trap* trap); Mob* GetTrapTrigger(Trap* trap);
void SendAlarm(Trap* trap, Mob* currenttarget, uint8 kos); void SendAlarm(Trap* trap, Mob* currenttarget, uint8 kos);
Trap* FindNearbyTrap(Mob* searcher, float max_dist); Trap* FindNearbyTrap(Mob* searcher, float max_dist, float &curdist, bool detected = false);
void AddHealAggro(Mob* target, Mob* caster, uint16 hate); void AddHealAggro(Mob* target, Mob* caster, uint16 hate);
Mob* FindDefenseNPC(uint32 npcid); Mob* FindDefenseNPC(uint32 npcid);
@ -473,6 +473,10 @@ public:
void RefreshClientXTargets(Client *c); void RefreshClientXTargets(Client *c);
void SendAlternateAdvancementStats(); void SendAlternateAdvancementStats();
void GetTrapInfo(Client* client);
bool IsTrapGroupSpawned(uint32 trap_id, uint8 group);
void UpdateAllTraps(bool respawn, bool repopnow = false);
void ClearTrapPointers();
protected: protected:
friend class Zone; friend class Zone;
void Depop(bool StartSpawnTimer = false); void Depop(bool StartSpawnTimer = false);

View File

@ -327,8 +327,7 @@ void Client::AddEXP(uint32 in_add_exp, uint8 conlevel, bool resexp) {
aatotalmod *= zone->newzone_data.zone_exp_multiplier; aatotalmod *= zone->newzone_data.zone_exp_multiplier;
} }
// Shouldn't race not affect AA XP?
if(RuleB(Character,UseRaceClassExpBonuses)) if(RuleB(Character,UseRaceClassExpBonuses))
{ {
if(GetBaseRace() == HALFLING){ if(GetBaseRace() == HALFLING){
@ -340,6 +339,12 @@ void Client::AddEXP(uint32 in_add_exp, uint8 conlevel, bool resexp) {
} }
} }
// why wasn't this here? Where should it be?
if(zone->IsHotzone())
{
aatotalmod += RuleR(Zone, HotZoneBonus);
}
if(RuleB(Zone, LevelBasedEXPMods)){ if(RuleB(Zone, LevelBasedEXPMods)){
if(zone->level_exp_mod[GetLevel()].ExpMod){ if(zone->level_exp_mod[GetLevel()].ExpMod){
add_exp *= zone->level_exp_mod[GetLevel()].ExpMod; add_exp *= zone->level_exp_mod[GetLevel()].ExpMod;

View File

@ -163,7 +163,7 @@ void Mob::CalculateNewFearpoint()
ranx = GetX()+zone->random.Int(0, ran-1)-zone->random.Int(0, ran-1); 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); rany = GetY()+zone->random.Int(0, ran-1)-zone->random.Int(0, ran-1);
ranz = FindGroundZ(ranx,rany); ranz = FindGroundZ(ranx,rany);
if (ranz == -999999) if (ranz == BEST_Z_INVALID)
continue; continue;
float fdist = ranz - GetZ(); float fdist = ranz - GetZ();
if (fdist >= -12 && fdist <= 12 && CheckCoordLosNoZLeaps(GetX(),GetY(),GetZ(),ranx,rany,ranz)) if (fdist >= -12 && fdist <= 12 && CheckCoordLosNoZLeaps(GetX(),GetY(),GetZ(),ranx,rany,ranz))

View File

@ -2464,3 +2464,33 @@ bool Group::HasRole(Mob *m, uint8 Role)
return false; 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);
}
}
}
}
}

View File

@ -140,6 +140,7 @@ public:
inline int GetLeadershipAA(int AAID) { return LeaderAbilities.ranks[AAID]; } inline int GetLeadershipAA(int AAID) { return LeaderAbilities.ranks[AAID]; }
void ClearAllNPCMarks(); void ClearAllNPCMarks();
void QueueHPPacketsForNPCHealthAA(Mob* sender, const EQApplicationPacket* app); 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); void ChangeLeader(Mob* newleader);
const char *GetClientNameByIndex(uint8 index); const char *GetClientNameByIndex(uint8 index);
void UpdateXTargetMarkedNPC(uint32 Number, Mob *m); void UpdateXTargetMarkedNPC(uint32 Number, Mob *m);

View File

@ -151,6 +151,7 @@ void Client::SummonHorse(uint16 spell_id) {
uint16 tmpID = horse->GetID(); uint16 tmpID = horse->GetID();
SetHorseId(tmpID); SetHorseId(tmpID);
BuffFadeBySitModifier();
} }

View File

@ -336,6 +336,7 @@ void NPC::AddLootDrop(const EQEmu::ItemData *item2, ItemList* itemlist, int16 ch
eslot = EQEmu::textures::weaponPrimary; eslot = EQEmu::textures::weaponPrimary;
if (item2->Damage > 0) { if (item2->Damage > 0) {
SendAddPlayerState(PlayerState::PrimaryWeaponEquipped); SendAddPlayerState(PlayerState::PrimaryWeaponEquipped);
if (!RuleB(Combat, ClassicNPCBackstab))
SetFacestab(true); SetFacestab(true);
} }
if (item2->IsType2HWeapon()) if (item2->IsType2HWeapon())

View File

@ -1440,6 +1440,54 @@ void Lua_Client::FilteredMessage(Mob *sender, uint32 type, int filter, const cha
self->FilteredMessage(sender, type, (eqFilterType)filter, message); 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() { luabind::scope lua_register_client() {
return luabind::class_<Lua_Client, Lua_Mob>("Client") return luabind::class_<Lua_Client, Lua_Mob>("Client")
.def(luabind::constructor<>()) .def(luabind::constructor<>())
@ -1712,7 +1760,15 @@ luabind::scope lua_register_client() {
.def("IsDead", &Lua_Client::IsDead) .def("IsDead", &Lua_Client::IsDead)
.def("CalcCurrentWeight", &Lua_Client::CalcCurrentWeight) .def("CalcCurrentWeight", &Lua_Client::CalcCurrentWeight)
.def("CalcATK", &Lua_Client::CalcATK) .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() { luabind::scope lua_register_inventory_where() {

View File

@ -301,6 +301,14 @@ public:
int CalcCurrentWeight(); int CalcCurrentWeight();
int CalcATK(); int CalcATK();
void FilteredMessage(Mob *sender, uint32 type, int filter, const char* message); 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 #endif

View File

@ -1686,6 +1686,11 @@ void Lua_Mob::DoKnockback(Lua_Mob caster, uint32 pushback, uint32 pushup) {
self->DoKnockback(caster, pushback, 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) { void Lua_Mob::RemoveNimbusEffect(int effect_id) {
Lua_Safe_Call_Void(); Lua_Safe_Call_Void();
self->RemoveNimbusEffect(effect_id); 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("SetSlotTint", (void(Lua_Mob::*)(int,int,int,int))&Lua_Mob::SetSlotTint)
.def("WearChange", (void(Lua_Mob::*)(int,int,uint32))&Lua_Mob::WearChange) .def("WearChange", (void(Lua_Mob::*)(int,int,uint32))&Lua_Mob::WearChange)
.def("DoKnockback", (void(Lua_Mob::*)(Lua_Mob,uint32,uint32))&Lua_Mob::DoKnockback) .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("RemoveNimbusEffect", (void(Lua_Mob::*)(int))&Lua_Mob::RemoveNimbusEffect)
.def("IsFeared", (bool(Lua_Mob::*)(void))&Lua_Mob::IsFeared) .def("IsFeared", (bool(Lua_Mob::*)(void))&Lua_Mob::IsFeared)
.def("IsBlind", (bool(Lua_Mob::*)(void))&Lua_Mob::IsBlind) .def("IsBlind", (bool(Lua_Mob::*)(void))&Lua_Mob::IsBlind)

View File

@ -323,6 +323,7 @@ public:
void SetSlotTint(int material_slot, int red_tint, int green_tint, int blue_tint); void SetSlotTint(int material_slot, int red_tint, int green_tint, int blue_tint);
void WearChange(int material_slot, int texture, uint32 color); void WearChange(int material_slot, int texture, uint32 color);
void DoKnockback(Lua_Mob caster, uint32 pushback, uint32 pushup); void DoKnockback(Lua_Mob caster, uint32 pushback, uint32 pushup);
void AddNimbusEffect(int effect_id);
void RemoveNimbusEffect(int effect_id); void RemoveNimbusEffect(int effect_id);
bool IsRunning(); bool IsRunning();
void SetRunning(bool running); void SetRunning(bool running);

View File

@ -1154,7 +1154,7 @@ void Merc::CalcRestState() {
// The bot must have been out of combat for RuleI(Character, RestRegenTimeToActivate) seconds, // 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. // must be sitting down, and must not have any detrimental spells affecting them.
// //
if(!RuleI(Character, RestRegenPercent)) if(!RuleB(Character, RestRegenEnabled))
return; return;
RestRegenHP = RestRegenMana = RestRegenEndurance = 0; 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 = 6 * (GetMaxEndurance() / RuleI(Character, RestRegenEnd));
RestRegenEndurance = (GetMaxEndurance() * RuleI(Character, RestRegenPercent) / 100);
} }
bool Merc::HasSkill(EQEmu::skills::SkillType skill_id) const { bool Merc::HasSkill(EQEmu::skills::SkillType skill_id) const {

View File

@ -75,7 +75,6 @@ Mob::Mob(const char* in_name,
uint32 in_drakkin_tattoo, uint32 in_drakkin_tattoo,
uint32 in_drakkin_details, uint32 in_drakkin_details,
EQEmu::TintProfile in_armor_tint, EQEmu::TintProfile in_armor_tint,
uint8 in_aa_title, uint8 in_aa_title,
uint8 in_see_invis, // see through invis/ivu uint8 in_see_invis, // see through invis/ivu
uint8 in_see_invis_undead, uint8 in_see_invis_undead,
@ -98,7 +97,7 @@ Mob::Mob(const char* in_name,
tic_timer(6000), tic_timer(6000),
mana_timer(2000), mana_timer(2000),
spellend_timer(0), spellend_timer(0),
rewind_timer(30000), //Timer used for determining amount of time between actual player position updates for /rewind. rewind_timer(30000),
bindwound_timer(10000), bindwound_timer(10000),
stunned_timer(0), stunned_timer(0),
spun_timer(0), spun_timer(0),
@ -123,9 +122,8 @@ Mob::Mob(const char* in_name,
tar_vector = 0; tar_vector = 0;
currently_fleeing = false; currently_fleeing = false;
last_z = 0;
last_major_update_position = m_Position; last_major_update_position = m_Position;
is_distance_roamer = false;
AI_Init(); AI_Init();
SetMoving(false); SetMoving(false);
@ -172,7 +170,8 @@ Mob::Mob(const char* in_name,
fearspeed = 0.625f; fearspeed = 0.625f;
base_fearspeed = 25; base_fearspeed = 25;
// npcs // npcs
} else { }
else {
base_walkspeed = base_runspeed * 100 / 265; base_walkspeed = base_runspeed * 100 / 265;
walkspeed = ((float)base_walkspeed) * 0.025f; walkspeed = ((float)base_walkspeed) * 0.025f;
base_fearspeed = base_runspeed * 100 / 127; base_fearspeed = base_runspeed * 100 / 127;
@ -1383,7 +1382,7 @@ void Mob::SendHPUpdate(bool skip_self /*= false*/, bool force_update_all /*= fal
if(IsClient()){ if(IsClient()){
Raid *raid = entity_list.GetRaidByClient(CastToClient()); Raid *raid = entity_list.GetRaidByClient(CastToClient());
if (raid) if (raid)
raid->SendHPPacketsFrom(this); raid->SendHPManaEndPacketsFrom(this);
} }
/* Pet - Update master - group and raid if exists */ /* Pet - Update master - group and raid if exists */
@ -1396,7 +1395,7 @@ void Mob::SendHPUpdate(bool skip_self /*= false*/, bool force_update_all /*= fal
Raid *raid = entity_list.GetRaidByClient(GetOwner()->CastToClient()); Raid *raid = entity_list.GetRaidByClient(GetOwner()->CastToClient());
if(raid) if(raid)
raid->SendHPPacketsFrom(this); raid->SendHPManaEndPacketsFrom(this);
} }
/* Send to pet */ /* Send to pet */
@ -1447,6 +1446,7 @@ void Mob::SendPosition() {
if (DistanceSquared(last_major_update_position, m_Position) >= (100 * 100)) { if (DistanceSquared(last_major_update_position, m_Position) >= (100 * 100)) {
entity_list.QueueClients(this, app, true, true); entity_list.QueueClients(this, app, true, true);
last_major_update_position = m_Position; last_major_update_position = m_Position;
is_distance_roamer = true;
} }
else { else {
entity_list.QueueCloseClients(this, app, true, RuleI(Range, MobPositionUpdates), nullptr, false); entity_list.QueueCloseClients(this, app, true, RuleI(Range, MobPositionUpdates), nullptr, false);
@ -1455,6 +1455,20 @@ void Mob::SendPosition() {
safe_delete(app); 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 */ /* Position updates for mobs on the move */
void Mob::SendPositionUpdate(uint8 iSendToSelf) { void Mob::SendPositionUpdate(uint8 iSendToSelf) {
auto app = new EQApplicationPacket(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct)); auto app = new EQApplicationPacket(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct));
@ -1466,6 +1480,11 @@ void Mob::SendPositionUpdate(uint8 iSendToSelf) {
CastToClient()->FastQueuePacket(&app, false); CastToClient()->FastQueuePacket(&app, false);
} }
} }
else if (DistanceSquared(last_major_update_position, m_Position) >= (100 * 100)) {
entity_list.QueueClients(this, app, true, true);
last_major_update_position = m_Position;
is_distance_roamer = true;
}
else { else {
entity_list.QueueCloseClients(this, app, (iSendToSelf == 0), RuleI(Range, MobPositionUpdates), nullptr, false); entity_list.QueueCloseClients(this, app, (iSendToSelf == 0), RuleI(Range, MobPositionUpdates), nullptr, false);
} }
@ -3383,13 +3402,18 @@ int Mob::GetHaste()
else // 1-25 else // 1-25
h += itembonuses.haste > 10 ? 10 : itembonuses.haste; h += itembonuses.haste > 10 ? 10 : itembonuses.haste;
// 60+ 100, 51-59 85, 1-50 level+25 // mobs are different!
if (level > 59) // 60+ Mob *owner = nullptr;
cap = RuleI(Character, HasteCap); if (IsPet())
else if (level > 50) // 51-59 owner = GetOwner();
cap = 85; else if (IsNPC() && CastToNPC()->GetSwarmOwner())
else // 1-50 owner = entity_list.GetMobID(CastToNPC()->GetSwarmOwner());
cap = level + 25; if (owner) {
cap = 10 + level;
cap += std::max(0, owner->GetLevel() - 39) + std::max(0, owner->GetLevel() - 60);
} else {
cap = 150;
}
if(h > cap) if(h > cap)
h = cap; h = cap;
@ -3425,9 +3449,22 @@ void Mob::SetTarget(Mob* mob) {
this->GetTarget()->SendHPUpdate(false, true); this->GetTarget()->SendHPUpdate(false, true);
} }
// For when we want a Ground Z at a location we are not at yet
// Like MoveTo.
float Mob::FindDestGroundZ(glm::vec3 dest, float z_offset)
{
float best_z = BEST_Z_INVALID;
if (zone->zonemap != nullptr)
{
dest.z += z_offset;
best_z = zone->zonemap->FindBestZ(dest, nullptr);
}
return best_z;
}
float Mob::FindGroundZ(float new_x, float new_y, float z_offset) float Mob::FindGroundZ(float new_x, float new_y, float z_offset)
{ {
float ret = -999999; float ret = BEST_Z_INVALID;
if (zone->zonemap != nullptr) if (zone->zonemap != nullptr)
{ {
glm::vec3 me; glm::vec3 me;
@ -3436,7 +3473,7 @@ float Mob::FindGroundZ(float new_x, float new_y, float z_offset)
me.z = m_Position.z + z_offset; me.z = m_Position.z + z_offset;
glm::vec3 hit; glm::vec3 hit;
float best_z = zone->zonemap->FindBestZ(me, &hit); float best_z = zone->zonemap->FindBestZ(me, &hit);
if (best_z != -999999) if (best_z != BEST_Z_INVALID)
{ {
ret = best_z; ret = best_z;
} }
@ -3447,7 +3484,7 @@ float Mob::FindGroundZ(float new_x, float new_y, float z_offset)
// Copy of above function that isn't protected to be exported to Perl::Mob // 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 Mob::GetGroundZ(float new_x, float new_y, float z_offset)
{ {
float ret = -999999; float ret = BEST_Z_INVALID;
if (zone->zonemap != 0) if (zone->zonemap != 0)
{ {
glm::vec3 me; glm::vec3 me;
@ -3456,7 +3493,7 @@ float Mob::GetGroundZ(float new_x, float new_y, float z_offset)
me.z = m_Position.z+z_offset; me.z = m_Position.z+z_offset;
glm::vec3 hit; glm::vec3 hit;
float best_z = zone->zonemap->FindBestZ(me, &hit); float best_z = zone->zonemap->FindBestZ(me, &hit);
if (best_z != -999999) if (best_z != BEST_Z_INVALID)
{ {
ret = best_z; ret = best_z;
} }
@ -3761,7 +3798,7 @@ void Mob::TryTriggerOnValueAmount(bool IsHP, bool IsMana, bool IsEndur, bool IsP
if ((base2 >= 500 && base2 <= 520) && GetHPRatio() < (base2 - 500)*5) if ((base2 >= 500 && base2 <= 520) && GetHPRatio() < (base2 - 500)*5)
use_spell = true; use_spell = true;
else if (base2 = 1004 && GetHPRatio() < 80) else if (base2 == 1004 && GetHPRatio() < 80)
use_spell = true; use_spell = true;
} }
@ -3769,12 +3806,12 @@ void Mob::TryTriggerOnValueAmount(bool IsHP, bool IsMana, bool IsEndur, bool IsP
if ( (base2 = 521 && GetManaRatio() < 20) || (base2 = 523 && GetManaRatio() < 40)) if ( (base2 = 521 && GetManaRatio() < 20) || (base2 = 523 && GetManaRatio() < 40))
use_spell = true; use_spell = true;
else if (base2 = 38311 && GetManaRatio() < 10) else if (base2 == 38311 && GetManaRatio() < 10)
use_spell = true; use_spell = true;
} }
else if (IsEndur){ else if (IsEndur){
if (base2 = 522 && GetEndurancePercent() < 40){ if (base2 == 522 && GetEndurancePercent() < 40){
use_spell = true; use_spell = true;
} }
} }
@ -3935,10 +3972,17 @@ int16 Mob::GetHealRate(uint16 spell_id, Mob* caster) {
bool Mob::TryFadeEffect(int slot) bool Mob::TryFadeEffect(int slot)
{ {
if (!buffs[slot].spellid)
return false;
if(IsValidSpell(buffs[slot].spellid)) if(IsValidSpell(buffs[slot].spellid))
{ {
for(int i = 0; i < EFFECT_COUNT; i++) 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 || if (spells[buffs[slot].spellid].effectid[i] == SE_CastOnFadeEffectAlways ||
spells[buffs[slot].spellid].effectid[i] == SE_CastOnRuneFadeEffect) spells[buffs[slot].spellid].effectid[i] == SE_CastOnRuneFadeEffect)
{ {
@ -4980,6 +5024,18 @@ void Mob::SpreadVirus(uint16 spell_id, uint16 casterID)
} }
} }
void Mob::AddNimbusEffect(int effectid)
{
SetNimbusEffect(effectid);
auto outapp = new EQApplicationPacket(OP_AddNimbusEffect, sizeof(RemoveNimbusEffect_Struct));
auto ane = (RemoveNimbusEffect_Struct *)outapp->pBuffer;
ane->spawnid = GetID();
ane->nimbus_effect = effectid;
entity_list.QueueClients(this, outapp);
safe_delete(outapp);
}
void Mob::RemoveNimbusEffect(int effectid) void Mob::RemoveNimbusEffect(int effectid)
{ {
if (effectid == nimbus_effect1) if (effectid == nimbus_effect1)

View File

@ -162,6 +162,8 @@ public:
inline virtual bool IsMob() const { return true; } inline virtual bool IsMob() const { return true; }
inline virtual bool InZone() const { return true; } inline virtual bool InZone() const { return true; }
bool is_distance_roamer;
//Somewhat sorted: needs documenting! //Somewhat sorted: needs documenting!
//Attack //Attack
@ -199,6 +201,7 @@ public:
void ApplyMeleeDamageMods(uint16 skill, int &damage, Mob * defender = nullptr, ExtraAttackOptions *opts = nullptr); void ApplyMeleeDamageMods(uint16 skill, int &damage, Mob * defender = nullptr, ExtraAttackOptions *opts = nullptr);
int ACSum(); int ACSum();
int offense(EQEmu::skills::SkillType skill); int offense(EQEmu::skills::SkillType skill);
int GetBestMeleeSkill();
void CalcAC() { mitigation_ac = ACSum(); } void CalcAC() { mitigation_ac = ACSum(); }
int GetACSoftcap(); int GetACSoftcap();
double GetSoftcapReturns(); double GetSoftcapReturns();
@ -278,6 +281,7 @@ public:
float ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use_resist_override = false, float ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use_resist_override = false,
int resist_override = 0, bool CharismaCheck = false, bool CharmTick = false, bool IsRoot = false, int resist_override = 0, bool CharismaCheck = false, bool CharmTick = false, bool IsRoot = false,
int level_override = -1); int level_override = -1);
int GetResist(uint8 resist_type);
int ResistPhysical(int level_diff, uint8 caster_level); int ResistPhysical(int level_diff, uint8 caster_level);
int ResistElementalWeaponDmg(const EQEmu::ItemInstance *item); int ResistElementalWeaponDmg(const EQEmu::ItemInstance *item);
int CheckBaneDamage(const EQEmu::ItemInstance *item); int CheckBaneDamage(const EQEmu::ItemInstance *item);
@ -336,6 +340,7 @@ public:
void BuffFadeDetrimentalByCaster(Mob *caster); void BuffFadeDetrimentalByCaster(Mob *caster);
void BuffFadeBySitModifier(); void BuffFadeBySitModifier();
bool IsAffectedByBuff(uint16 spell_id); bool IsAffectedByBuff(uint16 spell_id);
bool IsAffectedByBuffByGlobalGroup(GlobalGroup group);
void BuffModifyDurationBySpellID(uint16 spell_id, int32 newDuration); void BuffModifyDurationBySpellID(uint16 spell_id, int32 newDuration);
int AddBuff(Mob *caster, const uint16 spell_id, int duration = 0, int32 level_override = -1); 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); int CanBuffStack(uint16 spellid, uint8 caster_level, bool iFailIfOverwrite = false);
@ -348,6 +353,7 @@ public:
virtual int GetMaxSongSlots() const { return 0; } virtual int GetMaxSongSlots() const { return 0; }
virtual int GetMaxDiscSlots() const { return 0; } virtual int GetMaxDiscSlots() const { return 0; }
virtual int GetMaxTotalSlots() const { return 0; } virtual int GetMaxTotalSlots() const { return 0; }
bool HasDiscBuff();
virtual uint32 GetFirstBuffSlot(bool disc, bool song); virtual uint32 GetFirstBuffSlot(bool disc, bool song);
virtual uint32 GetLastBuffSlot(bool disc, bool song); virtual uint32 GetLastBuffSlot(bool disc, bool song);
virtual void InitializeBuffSlots() { buffs = nullptr; current_buff_count = 0; } virtual void InitializeBuffSlots() { buffs = nullptr; current_buff_count = 0; }
@ -376,6 +382,7 @@ public:
inline virtual uint32 GetNimbusEffect1() const { return nimbus_effect1; } inline virtual uint32 GetNimbusEffect1() const { return nimbus_effect1; }
inline virtual uint32 GetNimbusEffect2() const { return nimbus_effect2; } inline virtual uint32 GetNimbusEffect2() const { return nimbus_effect2; }
inline virtual uint32 GetNimbusEffect3() const { return nimbus_effect3; } inline virtual uint32 GetNimbusEffect3() const { return nimbus_effect3; }
void AddNimbusEffect(int effectid);
void RemoveNimbusEffect(int effectid); void RemoveNimbusEffect(int effectid);
inline const glm::vec3& GetTargetRingLocation() const { return m_TargetRing; } inline const glm::vec3& GetTargetRingLocation() const { return m_TargetRing; }
inline float GetTargetRingX() const { return m_TargetRing.x; } inline float GetTargetRingX() const { return m_TargetRing.x; }
@ -543,6 +550,7 @@ public:
virtual void GMMove(float x, float y, float z, float heading = 0.01, bool SendUpdate = true); virtual void GMMove(float x, float y, float z, float heading = 0.01, bool SendUpdate = true);
void SetDelta(const glm::vec4& delta); void SetDelta(const glm::vec4& delta);
void SetTargetDestSteps(uint8 target_steps) { tar_ndx = target_steps; } void SetTargetDestSteps(uint8 target_steps) { tar_ndx = target_steps; }
void SendPositionUpdateToClient(Client *client);
void SendPositionUpdate(uint8 iSendToSelf = 0); void SendPositionUpdate(uint8 iSendToSelf = 0);
void MakeSpawnUpdateNoDelta(PlayerPositionUpdateServer_Struct* spu); void MakeSpawnUpdateNoDelta(PlayerPositionUpdateServer_Struct* spu);
void MakeSpawnUpdate(PlayerPositionUpdateServer_Struct* spu); void MakeSpawnUpdate(PlayerPositionUpdateServer_Struct* spu);
@ -585,6 +593,7 @@ public:
void AddFeignMemory(Client* attacker); void AddFeignMemory(Client* attacker);
void RemoveFromFeignMemory(Client* attacker); void RemoveFromFeignMemory(Client* attacker);
void ClearFeignMemory(); void ClearFeignMemory();
bool IsOnFeignMemory(Client *attacker) const;
void PrintHateListToClient(Client *who) { hate_list.PrintHateListToClient(who); } void PrintHateListToClient(Client *who) { hate_list.PrintHateListToClient(who); }
std::list<struct_HateList*>& GetHateList() { return hate_list.GetHateList(); } std::list<struct_HateList*>& GetHateList() { return hate_list.GetHateList(); }
bool CheckLosFN(Mob* other); bool CheckLosFN(Mob* other);
@ -949,7 +958,9 @@ public:
float GetGroundZ(float new_x, float new_y, float z_offset=0.0); 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 SendTo(float new_x, float new_y, float new_z);
void SendToFixZ(float new_x, float new_y, float new_z); void SendToFixZ(float new_x, float new_y, float new_z);
void FixZ(); float GetZOffset() const;
void FixZ(int32 z_find_offset = 5);
float GetFixedZ(glm::vec3 position, int32 z_find_offset = 5);
void NPCSpecialAttacks(const char* parse, int permtag, bool reset = true, bool remove = false); void NPCSpecialAttacks(const char* parse, int permtag, bool reset = true, bool remove = false);
inline uint32 DontHealMeBefore() const { return pDontHealMeBefore; } inline uint32 DontHealMeBefore() const { return pDontHealMeBefore; }
inline uint32 DontBuffMeBefore() const { return pDontBuffMeBefore; } inline uint32 DontBuffMeBefore() const { return pDontBuffMeBefore; }
@ -1103,8 +1114,6 @@ public:
int GetWeaponDamage(Mob *against, const EQEmu::ItemData *weapon_item); int GetWeaponDamage(Mob *against, const EQEmu::ItemData *weapon_item);
int GetWeaponDamage(Mob *against, const EQEmu::ItemInstance *weapon_item, uint32 *hate = nullptr); int GetWeaponDamage(Mob *against, const EQEmu::ItemInstance *weapon_item, uint32 *hate = nullptr);
float last_z;
// Bots HealRotation methods // Bots HealRotation methods
#ifdef BOTS #ifdef BOTS
bool IsHealRotationTarget() { return (m_target_of_heal_rotation.use_count() && m_target_of_heal_rotation.get()); } bool IsHealRotationTarget() { return (m_target_of_heal_rotation.use_count() && m_target_of_heal_rotation.get()); }
@ -1221,7 +1230,8 @@ protected:
glm::vec4 m_Position; glm::vec4 m_Position;
/* Used to determine when an NPC has traversed so many units - to send a zone wide pos update */ /* Used to determine when an NPC has traversed so many units - to send a zone wide pos update */
glm::vec4 last_major_update_position; 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 base_size;
float size; float size;
float runspeed; float runspeed;
@ -1262,6 +1272,7 @@ protected:
virtual int16 GetFocusEffect(focusType type, uint16 spell_id) { return 0; } virtual int16 GetFocusEffect(focusType type, uint16 spell_id) { return 0; }
void CalculateNewFearpoint(); void CalculateNewFearpoint();
float FindGroundZ(float new_x, float new_y, float z_offset=0.0); float FindGroundZ(float new_x, float new_y, float z_offset=0.0);
float FindDestGroundZ(glm::vec3 dest, float z_offset=0.0);
glm::vec3 UpdatePath(float ToX, float ToY, float ToZ, float Speed, bool &WaypointChange, bool &NodeReached); glm::vec3 UpdatePath(float ToX, float ToY, float ToZ, float Speed, bool &WaypointChange, bool &NodeReached);
void PrintRoute(); void PrintRoute();

View File

@ -57,7 +57,11 @@ bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
if(AI_HasSpells() == false) if(AI_HasSpells() == false)
return 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) if (zone->random.Int(0, 100) >= iChance)
return false; return false;
} }
@ -123,7 +127,7 @@ bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
} }
case SpellType_Root: { case SpellType_Root: {
Mob *rootee = GetHateRandom(); Mob *rootee = GetHateRandom();
if (rootee && !rootee->IsRooted() && zone->random.Roll(50) if (rootee && !rootee->IsRooted() && !rootee->IsFeared() && zone->random.Roll(50)
&& rootee->DontRootMeBefore() < Timer::GetCurrentTime() && rootee->DontRootMeBefore() < Timer::GetCurrentTime()
&& rootee->CanBuffStack(AIspells[i].spellid, GetLevel(), true) >= 0 && rootee->CanBuffStack(AIspells[i].spellid, GetLevel(), true) >= 0
) { ) {
@ -499,7 +503,7 @@ void NPC::AI_Start(uint32 iMoveDelay) {
AIautocastspell_timer->Disable(); AIautocastspell_timer->Disable();
} else { } else {
AIautocastspell_timer = std::unique_ptr<Timer>(new Timer(750)); AIautocastspell_timer = std::unique_ptr<Timer>(new Timer(750));
AIautocastspell_timer->Start(RandomTimer(0, 15000), false); AIautocastspell_timer->Start(RandomTimer(0, 300), false);
} }
if (NPCTypedata) { if (NPCTypedata) {
@ -1001,10 +1005,9 @@ void Mob::AI_Process() {
if (this->GetTarget()) { if (this->GetTarget()) {
/* If we are engaged, moving and following client, let's look for best Z more often */ /* 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()); float target_distance = DistanceNoZ(this->GetPosition(), this->GetTarget()->GetPosition());
if (target_distance >= 25) {
this->FixZ(); this->FixZ();
}
else if (!this->CheckLosFN(this->GetTarget())) { if (target_distance <= 15 && !this->CheckLosFN(this->GetTarget())) {
Mob* target = this->GetTarget(); Mob* target = this->GetTarget();
m_Position.x = target->GetX(); m_Position.x = target->GetX();
@ -1295,7 +1298,7 @@ void Mob::AI_Process() {
if (AI_PursueCastCheck()) { if (AI_PursueCastCheck()) {
//we did something, so do not process movement. //we did something, so do not process movement.
} }
else if (AI_movement_timer->Check()) else if (AI_movement_timer->Check() && target)
{ {
if (!IsRooted()) { if (!IsRooted()) {
Log(Logs::Detail, Logs::AI, "Pursuing %s while engaged.", target->GetName()); Log(Logs::Detail, Logs::AI, "Pursuing %s while engaged.", target->GetName());
@ -1388,19 +1391,33 @@ void Mob::AI_Process() {
//if(owner->IsClient()) //if(owner->IsClient())
// printf("Pet start pos: (%f, %f, %f)\n", GetX(), GetY(), GetZ()); // printf("Pet start pos: (%f, %f, %f)\n", GetX(), GetY(), GetZ());
float dist = DistanceSquared(m_Position, owner->GetPosition()); glm::vec4 ownerPos = owner->GetPosition();
if (dist >= 400) float dist = DistanceSquared(m_Position, ownerPos);
float distz = ownerPos.z - m_Position.z;
if (dist >= 400 || distz > 100)
{ {
int speed = GetWalkspeed(); int speed = GetWalkspeed();
if (dist >= 5625) if (dist >= 5625)
speed = GetRunspeed(); speed = GetRunspeed();
CalculateNewPosition2(owner->GetX(), owner->GetY(), owner->GetZ(), speed); if (distz > 100)
{
m_Position = ownerPos;
SendPositionUpdate();
moved = true;
}
else
{
CalculateNewPosition2(owner->GetX(),
owner->GetY(), owner->GetZ(), speed);
}
} }
else else
{ {
if(moved) if(moved)
{ {
this->FixZ();
SetCurrentSpeed(0); SetCurrentSpeed(0);
moved = false; moved = false;
} }
@ -1532,16 +1549,17 @@ void NPC::AI_DoMovement() {
roambox_movingto_x = zone->random.Real(roambox_min_x+1,roambox_max_x-1); 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) 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); 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)", // Keep calling with updates, using wherever we are in Z.
roambox_distance, roambox_min_x, roambox_max_x, roambox_min_y, roambox_max_y, roambox_movingto_x, roambox_movingto_y); if (!MakeNewPositionAndSendUpdate(roambox_movingto_x,
roambox_movingto_y, m_Position.z, walksp))
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))
{ {
this->FixZ(); // FixZ on final arrival point.
roambox_movingto_x = roambox_max_x + 1; // force update roambox_movingto_x = roambox_max_x + 1; // force update
pLastFightingDelayMoving = Timer::GetCurrentTime() + RandomTimer(roambox_min_delay, roambox_delay); pLastFightingDelayMoving = Timer::GetCurrentTime() + RandomTimer(roambox_min_delay, roambox_delay);
SetMoving(false); SetMoving(false);
@ -1578,18 +1596,22 @@ void NPC::AI_DoMovement() {
} }
this->FixZ(); this->FixZ();
SendPosition(); SendPosition();
//kick off event_waypoint arrive //kick off event_waypoint arrive
char temp[16]; char temp[16];
sprintf(temp, "%d", cur_wp); sprintf(temp, "%d", cur_wp);
parse->EventNPC(EVENT_WAYPOINT_ARRIVE, CastToNPC(), nullptr, temp, 0); parse->EventNPC(EVENT_WAYPOINT_ARRIVE, CastToNPC(), nullptr, temp, 0);
// start moving directly to next waypoint if we're at a 0 pause waypoint and we didn't get quest halted. // No need to move as we are there. Next loop will
if (!AI_walking_timer->Enabled()) // take care of normal grids, even at pause 0.
AI_SetupNextWaypoint(); // We do need to call and setup a wp if we're cur_wp=-2
else // as that is where roamer is unset and we don't want
// the next trip through to move again based on grid stuff.
doMove = false; doMove = false;
if (cur_wp == -2) {
AI_SetupNextWaypoint();
}
// wipe feign memory since we reached our first waypoint // wipe feign memory since we reached our first waypoint
if(cur_wp == 1) if(cur_wp == 1)
ClearFeignMemory(); ClearFeignMemory();
@ -2589,7 +2611,7 @@ void NPC::AddSpellToNPCList(int16 iPriority, int16 iSpellID, uint32 iType,
// If we're going from an empty list, we need to start the timer // If we're going from an empty list, we need to start the timer
if (AIspells.size() == 1) if (AIspells.size() == 1)
AIautocastspell_timer->Start(RandomTimer(0, 15000), false); AIautocastspell_timer->Start(RandomTimer(0, 300), false);
} }
void NPC::RemoveSpellFromNPCList(int16 spell_id) void NPC::RemoveSpellFromNPCList(int16 spell_id)

View File

@ -542,9 +542,11 @@ int main(int argc, char** argv) {
process_timer.Stop(); process_timer.Stop();
process_timer.Start(1000, true); process_timer.Start(1000, true);
if (zone && zone->GetZoneID() && zone->GetInstanceVersion()) {
uint32 shutdown_timer = database.getZoneShutDownDelay(zone->GetZoneID(), zone->GetInstanceVersion()); uint32 shutdown_timer = database.getZoneShutDownDelay(zone->GetZoneID(), zone->GetInstanceVersion());
zone->StartShutdownTimer(shutdown_timer); zone->StartShutdownTimer(shutdown_timer);
} }
}
else if (!previous_loaded && current_loaded) { else if (!previous_loaded && current_loaded) {
process_timer.Stop(); process_timer.Stop();
process_timer.Start(32, true); process_timer.Start(32, true);

View File

@ -436,6 +436,26 @@ void NPC::SetTarget(Mob* mob) {
//attack_timer.Disable(); //attack_timer.Disable();
attack_dw_timer.Disable(); attack_dw_timer.Disable();
} }
// either normal pet and owner is client or charmed pet and owner is client
Mob *owner = nullptr;
if (IsPet() && IsPetOwnerClient()) {
owner = GetOwner();
} else if (IsCharmed()) {
owner = GetOwner();
if (owner && !owner->IsClient())
owner = nullptr;
}
if (owner) {
auto client = owner->CastToClient();
if (client->ClientVersionBit() & EQEmu::versions::bit_UFAndLater) {
auto app = new EQApplicationPacket(OP_PetHoTT, sizeof(ClientTarget_Struct));
auto ct = (ClientTarget_Struct *)app->pBuffer;
ct->new_target = mob ? mob->GetID() : 0;
client->FastQueuePacket(&app);
}
}
Mob::SetTarget(mob); Mob::SetTarget(mob);
} }

View File

@ -70,7 +70,7 @@ Object::Object(uint32 id, uint32 type, uint32 icon, const Object_Struct& object,
//creating a re-ocurring ground spawn. //creating a re-ocurring ground spawn.
Object::Object(const EQEmu::ItemInstance* inst, char* name,float max_x,float min_x,float max_y,float min_y,float z,float heading,uint32 respawntimer) Object::Object(const EQEmu::ItemInstance* inst, char* name,float max_x,float min_x,float max_y,float min_y,float z,float heading,uint32 respawntimer)
: respawn_timer(respawntimer), decay_timer(300000) : respawn_timer(respawntimer * 1000), decay_timer(300000)
{ {
user = nullptr; user = nullptr;

View File

@ -475,22 +475,6 @@ Pet::Pet(NPCType *type_data, Mob *owner, PetType type, uint16 spell_id, int16 po
// Class should use npc constructor to set light properties // Class should use npc constructor to set light properties
} }
void Pet::SetTarget(Mob *mob)
{
if (mob == GetTarget())
return;
auto owner = GetOwner();
if (owner && owner->IsClient() && owner->CastToClient()->ClientVersionBit() & EQEmu::versions::bit_UFAndLater) {
auto app = new EQApplicationPacket(OP_PetHoTT, sizeof(ClientTarget_Struct));
auto ct = (ClientTarget_Struct *)app->pBuffer;
ct->new_target = mob ? mob->GetID() : 0;
owner->CastToClient()->QueuePacket(app);
safe_delete(app);
}
NPC::SetTarget(mob);
}
bool ZoneDatabase::GetPetEntry(const char *pet_type, PetRecord *into) { bool ZoneDatabase::GetPetEntry(const char *pet_type, PetRecord *into) {
return GetPoweredPetEntry(pet_type, 0, into); return GetPoweredPetEntry(pet_type, 0, into);
} }
@ -671,13 +655,18 @@ void NPC::SetPetState(SpellBuff_Struct *pet_buffs, uint32 *items) {
continue; continue;
const EQEmu::ItemData* item2 = database.GetItem(items[i]); const EQEmu::ItemData* item2 = database.GetItem(items[i]);
if (item2 && item2->NoDrop != 0) {
//dont bother saving item charges for now, NPCs never use them if (item2) {
//and nobody should be able to get them off the corpse..? bool noDrop=(item2->NoDrop == 0); // Field is reverse logic
bool petCanHaveNoDrop = (RuleB(Pets, CanTakeNoDrop) &&
_CLIENTPET(this) && GetPetType() <= petOther);
if (!noDrop || petCanHaveNoDrop) {
AddLootDrop(item2, &itemlist, 0, 1, 255, true, true); AddLootDrop(item2, &itemlist, 0, 1, 255, true, true);
} }
} }
} }
}
// Load the equipmentset from the DB. Might be worthwhile to load these into // Load the equipmentset from the DB. Might be worthwhile to load these into
// shared memory at some point due to the number of queries needed to load a // shared memory at some point due to the number of queries needed to load a

View File

@ -7,7 +7,6 @@ struct NPCType;
class Pet : public NPC { class Pet : public NPC {
public: public:
Pet(NPCType *type_data, Mob *owner, PetType type, uint16 spell_id, int16 power); Pet(NPCType *type_data, Mob *owner, PetType type, uint16 spell_id, int16 power);
virtual void SetTarget(Mob *mob);
virtual bool CheckSpellLevelRestriction(uint16 spell_id); virtual bool CheckSpellLevelRestriction(uint16 spell_id);
}; };

View File

@ -149,7 +149,7 @@ void Raid::AddMember(Client *c, uint32 group, bool rleader, bool groupleader, bo
raid_update = c->GetRaid(); raid_update = c->GetRaid();
if (raid_update) { if (raid_update) {
raid_update->SendHPManaEndPacketsTo(c); raid_update->SendHPManaEndPacketsTo(c);
raid_update->SendHPPacketsFrom(c); raid_update->SendHPManaEndPacketsFrom(c);
} }
auto pack = new ServerPacket(ServerOP_RaidAdd, sizeof(ServerRaidGeneralAction_Struct)); auto pack = new ServerPacket(ServerOP_RaidAdd, sizeof(ServerRaidGeneralAction_Struct));
@ -323,6 +323,10 @@ void Raid::SaveRaidLeaderAA()
void Raid::UpdateGroupAAs(uint32 gid) void Raid::UpdateGroupAAs(uint32 gid)
{ {
if (gid < 0 || gid > MAX_RAID_GROUPS)
return;
Client *gl = GetGroupLeader(gid); Client *gl = GetGroupLeader(gid);
if (gl) if (gl)
@ -1591,7 +1595,7 @@ void Raid::SendHPManaEndPacketsTo(Client *client)
} }
} }
void Raid::SendHPPacketsFrom(Mob *mob) void Raid::SendHPManaEndPacketsFrom(Mob *mob)
{ {
if(!mob) if(!mob)
return; 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);
}
}
}
}
}

View File

@ -174,7 +174,7 @@ public:
void VerifyRaid(); void VerifyRaid();
void MemberZoned(Client *c); void MemberZoned(Client *c);
void SendHPManaEndPacketsTo(Client *c); void SendHPManaEndPacketsTo(Client *c);
void SendHPPacketsFrom(Mob *mob); void SendHPManaEndPacketsFrom(Mob *mob);
void SendManaPacketFrom(Mob *mob); void SendManaPacketFrom(Mob *mob);
void SendEndurancePacketFrom(Mob *mob); void SendEndurancePacketFrom(Mob *mob);
void RaidSay(const char *msg, Client *c); void RaidSay(const char *msg, Client *c);
@ -237,6 +237,8 @@ public:
void SetDirtyAutoHaters(); void SetDirtyAutoHaters();
inline XTargetAutoHaters *GetXTargetAutoMgr() { return &m_autohatermgr; } 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]; RaidMember members[MAX_RAID_MEMBERS];
char leadername[64]; char leadername[64];
protected: protected:

View File

@ -37,6 +37,7 @@ int Mob::GetBaseSkillDamage(EQEmu::skills::SkillType skill, Mob *target)
case EQEmu::skills::SkillDragonPunch: case EQEmu::skills::SkillDragonPunch:
case EQEmu::skills::SkillEagleStrike: case EQEmu::skills::SkillEagleStrike:
case EQEmu::skills::SkillTigerClaw: case EQEmu::skills::SkillTigerClaw:
case EQEmu::skills::SkillRoundKick:
if (skill_level >= 25) if (skill_level >= 25)
base++; base++;
if (skill_level >= 75) if (skill_level >= 75)

View File

@ -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]; 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; bool c_override = false;
if (caster && caster->IsClient() && GetCastedSpellInvSlot() > 0) { if (caster && caster->IsClient() && GetCastedSpellInvSlot() > 0) {
const EQEmu::ItemInstance *inst = caster->CastToClient()->GetInv().GetItem(GetCastedSpellInvSlot()); 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].magic_rune = 0;
buffs[buffslot].numhits = 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) if (spells[spell_id].EndurUpkeep > 0)
SetEndurUpkeep(true); 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)) if (!IsPowerDistModSpell(spell_id))
SetSpellPowerDistanceMod(0); SetSpellPowerDistanceMod(0);
@ -277,8 +281,9 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove
caster->SetMana(0); caster->SetMana(0);
} else if (spell_id == 2755 && caster) //Lifeburn } 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? 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? //do any AAs apply to these spells?
@ -1791,8 +1796,12 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove
Message_StringID(4, CORPSE_CANT_SENSE); Message_StringID(4, CORPSE_CANT_SENSE);
} }
} }
else if (caster) else if (caster) {
caster->Message_StringID(MT_SpellFailure, SPELL_LEVEL_REQ); char level[4];
ConvertArray(effect_value, level);
caster->Message_StringID(MT_SpellFailure,
SPELL_LEVEL_REQ, level);
}
} }
else { else {
Message_StringID(4, TARGET_NOT_FOUND); Message_StringID(4, TARGET_NOT_FOUND);
@ -3655,16 +3664,6 @@ void Mob::DoBuffTic(const Buffs_Struct &buff, int slot, Mob *caster)
break; 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_Invisibility:
case SE_InvisVsAnimals: case SE_InvisVsAnimals:
case SE_InvisVsUndead: { case SE_InvisVsUndead: {

View File

@ -1427,8 +1427,10 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, CastingSlot slo
ZeroCastingVars(); ZeroCastingVars();
// set the rapid recast timer for next time around // 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 <users's ping to not cause issues
delaytimer = true; delaytimer = true;
spellend_timer.Start(400,true); spellend_timer.Start(10, true);
Log(Logs::Detail, Logs::Spells, "Spell casting of %d is finished.", spell_id); Log(Logs::Detail, Logs::Spells, "Spell casting of %d is finished.", spell_id);
@ -3188,6 +3190,12 @@ uint32 Client::GetLastBuffSlot(bool disc, bool song)
return GetCurrentBuffSlots(); return GetCurrentBuffSlots();
} }
bool Mob::HasDiscBuff()
{
int slot = GetFirstBuffSlot(true, false);
return buffs[slot].spellid != SPELL_UNKNOWN;
}
// returns the slot the buff was added to, -1 if it wasn't added due to // returns the slot the buff was added to, -1 if it wasn't added due to
// stacking problems, and -2 if this is not a buff // stacking problems, and -2 if this is not a buff
// if caster is null, the buff will be added with the caster level being // if caster is null, the buff will be added with the caster level being
@ -3307,8 +3315,8 @@ int Mob::AddBuff(Mob *caster, uint16 spell_id, int duration, int32 level_overrid
buffs[emptyslot].spellid = spell_id; buffs[emptyslot].spellid = spell_id;
buffs[emptyslot].casterlevel = caster_level; buffs[emptyslot].casterlevel = caster_level;
if (caster && caster->IsClient()) if (caster && !caster->IsAura()) // maybe some other things we don't want to ...
strcpy(buffs[emptyslot].caster_name, caster->GetName()); strcpy(buffs[emptyslot].caster_name, caster->GetCleanName());
else else
memset(buffs[emptyslot].caster_name, 0, 64); memset(buffs[emptyslot].caster_name, 0, 64);
buffs[emptyslot].casterid = caster ? caster->GetID() : 0; buffs[emptyslot].casterid = caster ? caster->GetID() : 0;
@ -4232,6 +4240,19 @@ bool Mob::IsAffectedByBuff(uint16 spell_id)
return false; 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<int>(group))
return true;
}
return false;
}
// checks if 'this' can be affected by spell_id from caster // checks if 'this' can be affected by spell_id from caster
// returns true if the spell should fail, false otherwise // returns true if the spell should fail, false otherwise
bool Mob::IsImmuneToSpell(uint16 spell_id, Mob *caster) bool Mob::IsImmuneToSpell(uint16 spell_id, Mob *caster)
@ -4416,6 +4437,36 @@ bool Mob::IsImmuneToSpell(uint16 spell_id, Mob *caster)
return false; return false;
} }
int Mob::GetResist(uint8 resist_type)
{
switch(resist_type)
{
case RESIST_FIRE:
return GetFR();
case RESIST_COLD:
return GetCR();
case RESIST_MAGIC:
return GetMR();
case RESIST_DISEASE:
return GetDR();
case RESIST_POISON:
return GetPR();
case RESIST_CORRUPTION:
return GetCorrup();
case RESIST_PRISMATIC:
return (GetFR() + GetCR() + GetMR() + GetDR() + GetPR()) / 5;
case RESIST_CHROMATIC:
return std::min({GetFR(), GetCR(), GetMR(), GetDR(), GetPR()});
case RESIST_PHYSICAL:
if (IsNPC())
return GetPhR();
else
return 0;
default:
return 0;
}
}
// //
// Spell resists: // Spell resists:
// returns an effectiveness index from 0 to 100. for most spells, 100 means // returns an effectiveness index from 0 to 100. for most spells, 100 means
@ -4499,68 +4550,16 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use
return 100; return 100;
} }
int target_resist; int target_resist = GetResist(resist_type);
switch(resist_type)
{
case RESIST_FIRE:
target_resist = GetFR();
break;
case RESIST_COLD:
target_resist = GetCR();
break;
case RESIST_MAGIC:
target_resist = GetMR();
break;
case RESIST_DISEASE:
target_resist = GetDR();
break;
case RESIST_POISON:
target_resist = GetPR();
break;
case RESIST_CORRUPTION:
target_resist = GetCorrup();
break;
case RESIST_PRISMATIC:
target_resist = (GetFR() + GetCR() + GetMR() + GetDR() + GetPR()) / 5;
break;
case RESIST_CHROMATIC:
{
target_resist = GetFR();
int temp = GetCR();
if(temp < target_resist)
{
target_resist = temp;
}
temp = GetMR(); // JULY 24, 2002 changes
if(temp < target_resist) int level = GetLevel();
{ if (IsPetOwnerClient() && caster->IsNPC() && !caster->IsPetOwnerClient()) {
target_resist = temp; auto owner = GetOwner();
if (owner != nullptr) {
target_resist = std::max(target_resist, owner->GetResist(resist_type));
level = owner->GetLevel();
} }
temp = GetDR();
if(temp < target_resist)
{
target_resist = temp;
}
temp = GetPR();
if(temp < target_resist)
{
target_resist = temp;
}
}
break;
case RESIST_PHYSICAL:
{
if (IsNPC())
target_resist = GetPhR();
else
target_resist = 0;
}
default:
target_resist = 0;
} }
//Setup our base resist chance. //Setup our base resist chance.
@ -4569,7 +4568,7 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use
//Adjust our resist chance based on level modifiers //Adjust our resist chance based on level modifiers
uint8 caster_level = level_override > 0 ? level_override : caster->GetLevel(); uint8 caster_level = level_override > 0 ? level_override : caster->GetLevel();
int temp_level_diff = GetLevel() - caster_level; int temp_level_diff = level - caster_level;
//Physical Resists are calclated using their own formula derived from extensive parsing. //Physical Resists are calclated using their own formula derived from extensive parsing.
if (resist_type == RESIST_PHYSICAL) { if (resist_type == RESIST_PHYSICAL) {
@ -4578,7 +4577,7 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use
else { else {
if(IsNPC() && GetLevel() >= RuleI(Casting,ResistFalloff)) if(IsNPC() && level >= RuleI(Casting,ResistFalloff))
{ {
int a = (RuleI(Casting,ResistFalloff)-1) - caster_level; int a = (RuleI(Casting,ResistFalloff)-1) - caster_level;
if(a > 0) if(a > 0)
@ -4591,7 +4590,7 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use
} }
} }
if(IsClient() && GetLevel() >= 21 && temp_level_diff > 15) if(IsClient() && level >= 21 && temp_level_diff > 15)
{ {
temp_level_diff = 15; temp_level_diff = 15;
} }
@ -4607,16 +4606,16 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use
level_mod = -level_mod; level_mod = -level_mod;
} }
if(IsNPC() && (caster_level - GetLevel()) < -20) if(IsNPC() && (caster_level - level) < -20)
{ {
level_mod = 1000; level_mod = 1000;
} }
//Even more level stuff this time dealing with damage spells //Even more level stuff this time dealing with damage spells
if(IsNPC() && IsDamageSpell(spell_id) && GetLevel() >= 17) if(IsNPC() && IsDamageSpell(spell_id) && level >= 17)
{ {
int level_diff; int level_diff;
if(GetLevel() >= RuleI(Casting,ResistFalloff)) if(level >= RuleI(Casting,ResistFalloff))
{ {
level_diff = (RuleI(Casting,ResistFalloff)-1) - caster_level; level_diff = (RuleI(Casting,ResistFalloff)-1) - caster_level;
if(level_diff < 0) if(level_diff < 0)
@ -4626,7 +4625,7 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use
} }
else else
{ {
level_diff = GetLevel() - caster_level; level_diff = level - caster_level;
} }
level_mod += (2 * level_diff); level_mod += (2 * level_diff);
} }
@ -4737,17 +4736,17 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use
if(IsNPC()) if(IsNPC())
{ {
if(GetLevel() > caster_level && GetLevel() >= 17 && caster_level <= 50) if(level > caster_level && level >= 17 && caster_level <= 50)
{ {
partial_modifier += 5; partial_modifier += 5;
} }
if(GetLevel() >= 30 && caster_level < 50) if(level >= 30 && caster_level < 50)
{ {
partial_modifier += (caster_level - 25); partial_modifier += (caster_level - 25);
} }
if(GetLevel() < 15) if(level < 15)
{ {
partial_modifier -= 5; partial_modifier -= 5;
} }
@ -4755,9 +4754,9 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use
if(caster->IsNPC()) if(caster->IsNPC())
{ {
if((GetLevel() - caster_level) >= 20) if((level - caster_level) >= 20)
{ {
partial_modifier += (GetLevel() - caster_level) * 1.5; partial_modifier += (level - caster_level) * 1.5;
} }
} }
@ -5533,6 +5532,8 @@ void Client::SendBuffNumHitPacket(Buffs_Struct &buff, int slot)
bi->entries[0].spell_id = buff.spellid; bi->entries[0].spell_id = buff.spellid;
bi->entries[0].tics_remaining = buff.ticsremaining; bi->entries[0].tics_remaining = buff.ticsremaining;
bi->entries[0].num_hits = buff.numhits; 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); FastQueuePacket(&outapp);
} }
@ -5618,6 +5619,7 @@ EQApplicationPacket *Mob::MakeBuffsPacket(bool for_target)
else else
buff->type = 0; buff->type = 0;
buff->name_lengths = 0; // hacky shit
uint32 index = 0; uint32 index = 0;
for(int i = 0; i < buff_count; ++i) for(int i = 0; i < buff_count; ++i)
{ {
@ -5627,6 +5629,8 @@ EQApplicationPacket *Mob::MakeBuffsPacket(bool for_target)
buff->entries[index].spell_id = buffs[i].spellid; buff->entries[index].spell_id = buffs[i].spellid;
buff->entries[index].tics_remaining = buffs[i].ticsremaining; buff->entries[index].tics_remaining = buffs[i].ticsremaining;
buff->entries[index].num_hits = buffs[i].numhits; 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; ++index;
} }
} }

View File

@ -19,6 +19,7 @@
#define PROC_TOOLOW 126 //Your will is not sufficient to command this weapon. #define PROC_TOOLOW 126 //Your will is not sufficient to command this weapon.
#define PROC_PETTOOLOW 127 //Your pet's will is not sufficient to command its weapon. #define PROC_PETTOOLOW 127 //Your pet's will is not sufficient to command its weapon.
#define YOU_FLURRY 128 //You unleash a flurry of attacks. #define YOU_FLURRY 128 //You unleash a flurry of attacks.
#define FAILED_DISARM_TRAP 129 //You failed to disarm the trap.
#define DOORS_LOCKED 130 //It's locked and you're not holding the key. #define DOORS_LOCKED 130 //It's locked and you're not holding the key.
#define DOORS_CANT_PICK 131 //This lock cannot be picked. #define DOORS_CANT_PICK 131 //This lock cannot be picked.
#define DOORS_INSUFFICIENT_SKILL 132 //You are not sufficiently skilled to pick this lock. #define DOORS_INSUFFICIENT_SKILL 132 //You are not sufficiently skilled to pick this lock.
@ -98,6 +99,7 @@
#define DUP_LORE 290 //Duplicate lore items are not allowed. #define DUP_LORE 290 //Duplicate lore items are not allowed.
#define TGB_ON 293 //Target other group buff is *ON*. #define TGB_ON 293 //Target other group buff is *ON*.
#define TGB_OFF 294 //Target other group buff is *OFF*. #define TGB_OFF 294 //Target other group buff is *OFF*.
#define DISARMED_TRAP 305 //You have disarmed the trap.
#define LDON_SENSE_TRAP1 306 //You do not Sense any traps. #define LDON_SENSE_TRAP1 306 //You do not Sense any traps.
#define TRADESKILL_NOCOMBINE 334 //You cannot combine these items in this container type! #define TRADESKILL_NOCOMBINE 334 //You cannot combine these items in this container type!
#define TRADESKILL_FAILED 336 //You lacked the skills to fashion the items together. #define TRADESKILL_FAILED 336 //You lacked the skills to fashion the items together.
@ -114,6 +116,8 @@
#define MEND_WORSEN 351 //You have worsened your wounds! #define MEND_WORSEN 351 //You have worsened your wounds!
#define MEND_FAIL 352 //You have failed to mend your wounds. #define MEND_FAIL 352 //You have failed to mend your wounds.
#define LDON_SENSE_TRAP2 367 //You have not detected any traps. #define LDON_SENSE_TRAP2 367 //You have not detected any traps.
#define TRAP_TOO_FAR 368 //You are too far away from that trap to affect it.
#define FAIL_DISARM_DETECTED_TRAP 370 //You fail to disarm the detected trap.
#define LOOT_LORE_ERROR 371 //You cannot loot this Lore Item. You already have one. #define LOOT_LORE_ERROR 371 //You cannot loot this Lore Item. You already have one.
#define PICK_LORE 379 //You cannot pick up a lore item you already possess. #define PICK_LORE 379 //You cannot pick up a lore item you already possess.
#define CONSENT_DENIED 390 //You do not have consent to summon that corpse. #define CONSENT_DENIED 390 //You do not have consent to summon that corpse.
@ -421,6 +425,7 @@
#define SENSE_ANIMAL 12472 //You sense an animal in this direction. #define SENSE_ANIMAL 12472 //You sense an animal in this direction.
#define SENSE_SUMMONED 12473 //You sense a summoned being in this direction. #define SENSE_SUMMONED 12473 //You sense a summoned being in this direction.
#define SENSE_NOTHING 12474 //You don't sense anything. #define SENSE_NOTHING 12474 //You don't sense anything.
#define SENSE_TRAP 12475 //You sense a trap in this direction.
#define LDON_SENSE_TRAP3 12476 //You don't sense any traps. #define LDON_SENSE_TRAP3 12476 //You don't sense any traps.
#define INTERRUPT_SPELL_OTHER 12478 //%1's casting is interrupted! #define INTERRUPT_SPELL_OTHER 12478 //%1's casting is interrupted!
#define YOU_HIT_NONMELEE 12481 //You were hit by non-melee for %1 damage. #define YOU_HIT_NONMELEE 12481 //You were hit by non-melee for %1 damage.

View File

@ -523,7 +523,6 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st
else else
qs_audit->char1_count += detail->charges; qs_audit->char1_count += detail->charges;
//for (uint8 sub_slot = SUB_BEGIN; ((sub_slot < inst->GetItem()->BagSlots) && (sub_slot < EmuConstants::ITEM_CONTAINER_SIZE)); ++sub_slot) {
for (uint8 sub_slot = EQEmu::inventory::containerBegin; (sub_slot < EQEmu::inventory::ContainerCount); ++sub_slot) { // this is to catch ALL items for (uint8 sub_slot = EQEmu::inventory::containerBegin; (sub_slot < EQEmu::inventory::ContainerCount); ++sub_slot) { // this is to catch ALL items
const EQEmu::ItemInstance* bag_inst = inst->GetItem(sub_slot); const EQEmu::ItemInstance* bag_inst = inst->GetItem(sub_slot);
@ -743,7 +742,6 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st
qs_audit->char1_count += detail->charges; qs_audit->char1_count += detail->charges;
// 'step 3' should never really see containers..but, just in case... // 'step 3' should never really see containers..but, just in case...
//for (uint8 sub_slot = SUB_BEGIN; ((sub_slot < inst->GetItem()->BagSlots) && (sub_slot < EmuConstants::ITEM_CONTAINER_SIZE)); ++sub_slot) {
for (uint8 sub_slot = EQEmu::inventory::containerBegin; (sub_slot < EQEmu::inventory::ContainerCount); ++sub_slot) { // this is to catch ALL items for (uint8 sub_slot = EQEmu::inventory::containerBegin; (sub_slot < EQEmu::inventory::ContainerCount); ++sub_slot) { // this is to catch ALL items
const EQEmu::ItemInstance* bag_inst = inst->GetItem(sub_slot); const EQEmu::ItemInstance* bag_inst = inst->GetItem(sub_slot);
@ -888,8 +886,12 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st
const EQEmu::ItemData* item = inst->GetItem(); const EQEmu::ItemData* item = inst->GetItem();
if(item && quest_npc == false) { if(item && quest_npc == false) {
bool isPetAndCanHaveNoDrop = (RuleB(Pets, CanTakeNoDrop) &&
_CLIENTPET(tradingWith) &&
tradingWith->GetPetType()<=petOther);
// if it was not a NO DROP or Attuned item (or if a GM is trading), let the NPC have it // if it was not a NO DROP or Attuned item (or if a GM is trading), let the NPC have it
if(GetGM() || (item->NoDrop != 0 && inst->IsAttuned() == false)) { if(GetGM() || (inst->IsAttuned() == false &&
(item->NoDrop != 0 || isPetAndCanHaveNoDrop))) {
// pets need to look inside bags and try to equip items found there // pets need to look inside bags and try to equip items found there
if (item->IsClassBag() && item->BagSlots > 0) { if (item->IsClassBag() && item->BagSlots > 0) {
for (int16 bslot = EQEmu::inventory::containerBegin; bslot < item->BagSlots; bslot++) { for (int16 bslot = EQEmu::inventory::containerBegin; bslot < item->BagSlots; bslot++) {

View File

@ -52,10 +52,12 @@ CREATE TABLE traps (
Trap::Trap() : Trap::Trap() :
Entity(), Entity(),
respawn_timer(600000), respawn_timer(600000),
chkarea_timer(500), chkarea_timer(1000),
reset_timer(5000),
m_Position(glm::vec3()) m_Position(glm::vec3())
{ {
trap_id = 0; trap_id = 0;
db_id = 0;
maxzdiff = 0; maxzdiff = 0;
radius = 0; radius = 0;
effect = 0; effect = 0;
@ -64,12 +66,20 @@ Trap::Trap() :
skill = 0; skill = 0;
level = 0; level = 0;
respawn_timer.Disable(); respawn_timer.Disable();
reset_timer.Disable();
detected = false; detected = false;
disarmed = false; disarmed = false;
respawn_time = 0; respawn_time = 0;
respawn_var = 0; respawn_var = 0;
hiddenTrigger = nullptr; hiddenTrigger = nullptr;
ownHiddenTrigger = false; ownHiddenTrigger = false;
chance = 0;
triggered_number = 0;
times_triggered = 0;
group = 0;
despawn_when_triggered = false;
charid = 0;
undetectable = false;
} }
Trap::~Trap() Trap::~Trap()
@ -80,8 +90,7 @@ Trap::~Trap()
bool Trap::Process() bool Trap::Process()
{ {
if (chkarea_timer.Enabled() && chkarea_timer.Check() if (chkarea_timer.Enabled() && chkarea_timer.Check() && !reset_timer.Enabled())
/*&& zone->GetClientCount() > 0*/ )
{ {
Mob* trigger = entity_list.GetTrapTrigger(this); Mob* trigger = entity_list.GetTrapTrigger(this);
if (trigger && !(trigger->IsClient() && trigger->CastToClient()->GetGM())) if (trigger && !(trigger->IsClient() && trigger->CastToClient()->GetGM()))
@ -89,6 +98,13 @@ bool Trap::Process()
Trigger(trigger); Trigger(trigger);
} }
} }
else if (reset_timer.Enabled() && reset_timer.Check())
{
Log(Logs::General, Logs::Traps, "Reset timer disabled in Reset Check Process for trap %d.", trap_id);
reset_timer.Disable();
charid = 0;
}
if (respawn_timer.Enabled() && respawn_timer.Check()) if (respawn_timer.Enabled() && respawn_timer.Check())
{ {
detected = false; detected = false;
@ -96,11 +112,15 @@ bool Trap::Process()
chkarea_timer.Enable(); chkarea_timer.Enable();
respawn_timer.Disable(); respawn_timer.Disable();
} }
return true; return true;
} }
void Trap::Trigger(Mob* trigger) void Trap::Trigger(Mob* trigger)
{ {
Log(Logs::General, Logs::Traps, "Trap %d triggered by %s for the %d time!", trap_id, trigger->GetName(), times_triggered + 1);
int i = 0; int i = 0;
const NPCType* tmp = 0; const NPCType* tmp = 0;
switch (effect) switch (effect)
@ -128,7 +148,7 @@ void Trap::Trigger(Mob* trigger)
entity_list.MessageClose(trigger,false,effectvalue,13,"%s",message.c_str()); entity_list.MessageClose(trigger,false,effectvalue,13,"%s",message.c_str());
} }
entity_list.SendAlarm(this,trigger,effectvalue); entity_list.SendAlarm(this,trigger, effectvalue2);
break; break;
case trapTypeMysticSpawn: case trapTypeMysticSpawn:
if (message.empty()) if (message.empty())
@ -201,12 +221,41 @@ void Trap::Trigger(Mob* trigger)
safe_delete(outapp); safe_delete(outapp);
} }
} }
respawn_timer.Start((respawn_time + zone->random.Int(0, respawn_var)) * 1000);
chkarea_timer.Disable(); if (trigger && trigger->IsClient())
disarmed = true; {
trigger->CastToClient()->trapid = trap_id;
charid = trigger->CastToClient()->CharacterID();
} }
Trap* EntityList::FindNearbyTrap(Mob* searcher, float max_dist) { bool update = false;
if (despawn_when_triggered)
{
Log(Logs::General, Logs::Traps, "Trap %d is despawning after being triggered.", trap_id);
update = true;
}
else
{
reset_timer.Start(5000);
}
if (triggered_number > 0)
++times_triggered;
if (triggered_number > 0 && triggered_number <= times_triggered)
{
Log(Logs::General, Logs::Traps, "Triggered number for trap %d reached. %d/%d", trap_id, times_triggered, triggered_number);
update = true;
}
if (update)
{
UpdateTrap();
}
}
Trap* EntityList::FindNearbyTrap(Mob* searcher, float max_dist, float &trap_curdist, bool detected)
{
float dist = 999999; float dist = 999999;
Trap* current_trap = nullptr; Trap* current_trap = nullptr;
@ -215,61 +264,161 @@ Trap* EntityList::FindNearbyTrap(Mob* searcher, float max_dist) {
for (auto it = trap_list.begin(); it != trap_list.end(); ++it) { for (auto it = trap_list.begin(); it != trap_list.end(); ++it) {
cur = it->second; cur = it->second;
if(cur->disarmed) if(cur->disarmed || (detected && !cur->detected) || cur->undetectable)
continue; continue;
auto diff = glm::vec3(searcher->GetPosition()) - cur->m_Position; auto diff = glm::vec3(searcher->GetPosition()) - cur->m_Position;
float curdist = diff.x * diff.x + diff.y * diff.y + diff.z * diff.z; float curdist = diff.x*diff.x + diff.y*diff.y;
diff.z = std::abs(diff.z);
if (curdist < max_dist2 && curdist < dist) if (curdist < max_dist2 && curdist < dist && diff.z <= cur->maxzdiff)
{ {
Log(Logs::General, Logs::Traps, "Trap %d is curdist %0.1f", cur->db_id, curdist);
dist = curdist; dist = curdist;
current_trap = cur; current_trap = cur;
} }
} }
if (current_trap != nullptr)
{
Log(Logs::General, Logs::Traps, "Trap %d is the closest trap.", current_trap->db_id);
trap_curdist = dist;
}
else
trap_curdist = INVALID_INDEX;
return current_trap; return current_trap;
} }
Mob* EntityList::GetTrapTrigger(Trap* trap) { Mob* EntityList::GetTrapTrigger(Trap* trap)
Mob* savemob = 0; {
float maxdist = trap->radius * trap->radius; float maxdist = trap->radius * trap->radius;
for (auto it = client_list.begin(); it != client_list.end(); ++it)
for (auto it = client_list.begin(); it != client_list.end(); ++it) { {
Client* cur = it->second; Client* cur = it->second;
auto diff = glm::vec3(cur->GetPosition()) - trap->m_Position; auto diff = glm::vec3(cur->GetPosition()) - trap->m_Position;
diff.z = std::abs(diff.z); diff.z = std::abs(diff.z);
if ((diff.x*diff.x + diff.y*diff.y) <= maxdist if ((diff.x*diff.x + diff.y*diff.y) <= maxdist
&& diff.z < trap->maxzdiff) && diff.z <= trap->maxzdiff)
{ {
if (zone->random.Roll(trap->chance)) //This prevents the trap from triggering on players while zoning.
return(cur); if (strcmp(cur->GetName(), "No name") == 0)
continue;
if (cur->trapid == 0 && !cur->GetGM() && (trap->chance == 0 || zone->random.Roll(trap->chance)))
{
Log(Logs::General, Logs::Traps, "%s is about to trigger trap %d of chance %d. diff: %0.2f maxdist: %0.2f zdiff: %0.2f maxzdiff: %0.2f", cur->GetName(), trap->trap_id, trap->chance, (diff.x*diff.x + diff.y*diff.y), maxdist, diff.z, trap->maxzdiff);
return cur;
}
}
else else
savemob = cur; {
if (cur->trapid == trap->trap_id)
{
Log(Logs::General, Logs::Traps, "%s is clearing trapid for trap %d", cur->GetName(), trap->trap_id);
cur->trapid = 0;
}
}
} }
return nullptr;
} }
return savemob; bool EntityList::IsTrapGroupSpawned(uint32 trap_id, uint8 group)
{
auto it = trap_list.begin();
while (it != trap_list.end())
{
Trap* cur = it->second;
if (cur->IsTrap() && cur->group == group && cur->trap_id != trap_id)
{
return true;
}
++it;
} }
//todo: rewrite this to not need direct access to trap members. return false;
}
void EntityList::UpdateAllTraps(bool respawn, bool repopnow)
{
auto it = trap_list.begin();
while (it != trap_list.end())
{
Trap* cur = it->second;
if (cur->IsTrap())
{
cur->UpdateTrap(respawn, repopnow);
}
++it;
}
Log(Logs::General, Logs::Traps, "All traps updated.");
}
void EntityList::GetTrapInfo(Client* client)
{
uint8 count = 0;
auto it = trap_list.begin();
while (it != trap_list.end())
{
Trap* cur = it->second;
if (cur->IsTrap())
{
bool isset = (cur->chkarea_timer.Enabled() && !cur->reset_timer.Enabled());
client->Message(CC_Default, " Trap: (%d) found at %0.2f,%0.2f,%0.2f. Times Triggered: %d Is Active: %d Group: %d Message: %s", cur->trap_id, cur->m_Position.x, cur->m_Position.y, cur->m_Position.z, cur->times_triggered, isset, cur->group, cur->message.c_str());
++count;
}
++it;
}
client->Message(CC_Default, "%d traps found.", count);
}
void EntityList::ClearTrapPointers()
{
auto it = trap_list.begin();
while (it != trap_list.end())
{
Trap* cur = it->second;
if (cur->IsTrap())
{
cur->DestroyHiddenTrigger();
}
++it;
}
}
bool ZoneDatabase::LoadTraps(const char* zonename, int16 version) { bool ZoneDatabase::LoadTraps(const char* zonename, int16 version) {
std::string query = StringFormat("SELECT id, x, y, z, effect, effectvalue, effectvalue2, skill, " std::string query = StringFormat("SELECT id, x, y, z, effect, effectvalue, effectvalue2, skill, "
"maxzdiff, radius, chance, message, respawn_time, respawn_var, level " "maxzdiff, radius, chance, message, respawn_time, respawn_var, level, "
"FROM traps WHERE zone='%s' AND version=%u", zonename, version); "`group`, triggered_number, despawn_when_triggered, undetectable FROM traps WHERE zone='%s' AND version=%u", zonename, version);
auto results = QueryDatabase(query); auto results = QueryDatabase(query);
if (!results.Success()) { if (!results.Success()) {
return false; return false;
} }
for (auto row = results.begin(); row != results.end(); ++row) { for (auto row = results.begin(); row != results.end(); ++row) {
uint32 tid = atoi(row[0]);
uint8 grp = atoi(row[15]);
if (grp > 0)
{
// If a member of our group is already spawned skip loading this trap.
if (entity_list.IsTrapGroupSpawned(tid, grp))
{
continue;
}
}
auto trap = new Trap(); auto trap = new Trap();
trap->trap_id = atoi(row[0]); trap->trap_id = tid;
trap->db_id = tid;
trap->m_Position = glm::vec3(atof(row[1]), atof(row[2]), atof(row[3])); trap->m_Position = glm::vec3(atof(row[1]), atof(row[2]), atof(row[3]));
trap->effect = atoi(row[4]); trap->effect = atoi(row[4]);
trap->effectvalue = atoi(row[5]); trap->effectvalue = atoi(row[5]);
@ -282,8 +431,13 @@ bool ZoneDatabase::LoadTraps(const char* zonename, int16 version) {
trap->respawn_time = atoi(row[12]); trap->respawn_time = atoi(row[12]);
trap->respawn_var = atoi(row[13]); trap->respawn_var = atoi(row[13]);
trap->level = atoi(row[14]); trap->level = atoi(row[14]);
trap->group = grp;
trap->triggered_number = atoi(row[16]);
trap->despawn_when_triggered = atobool(row[17]);
trap->undetectable = atobool(row[18]);
entity_list.AddTrap(trap); entity_list.AddTrap(trap);
trap->CreateHiddenTrigger(); trap->CreateHiddenTrigger();
Log(Logs::General, Logs::Traps, "Trap %d successfully loaded.", trap->trap_id);
} }
return true; return true;
@ -318,3 +472,87 @@ void Trap::CreateHiddenTrigger()
hiddenTrigger = npca; hiddenTrigger = npca;
ownHiddenTrigger = true; ownHiddenTrigger = true;
} }
bool ZoneDatabase::SetTrapData(Trap* trap, bool repopnow) {
uint32 dbid = trap->db_id;
std::string query;
if (trap->group > 0)
{
query = StringFormat("SELECT id, x, y, z, effect, effectvalue, effectvalue2, skill, "
"maxzdiff, radius, chance, message, respawn_time, respawn_var, level, "
"triggered_number, despawn_when_triggered, undetectable FROM traps WHERE zone='%s' AND `group`=%d AND id != %d ORDER BY RAND() LIMIT 1", zone->GetShortName(), trap->group, dbid);
}
else
{
// We could just use the existing data here, but querying the DB is not expensive, and allows content developers to change traps without rebooting.
query = StringFormat("SELECT id, x, y, z, effect, effectvalue, effectvalue2, skill, "
"maxzdiff, radius, chance, message, respawn_time, respawn_var, level, "
"triggered_number, despawn_when_triggered, undetectable FROM traps WHERE zone='%s' AND id = %d", zone->GetShortName(), dbid);
}
auto results = QueryDatabase(query);
if (!results.Success()) {
return false;
}
for (auto row = results.begin(); row != results.end(); ++row) {
trap->db_id = atoi(row[0]);
trap->m_Position = glm::vec3(atof(row[1]), atof(row[2]), atof(row[3]));
trap->effect = atoi(row[4]);
trap->effectvalue = atoi(row[5]);
trap->effectvalue2 = atoi(row[6]);
trap->skill = atoi(row[7]);
trap->maxzdiff = atof(row[8]);
trap->radius = atof(row[9]);
trap->chance = atoi(row[10]);
trap->message = row[11];
trap->respawn_time = atoi(row[12]);
trap->respawn_var = atoi(row[13]);
trap->level = atoi(row[14]);
trap->triggered_number = atoi(row[15]);
trap->despawn_when_triggered = atobool(row[16]);
trap->undetectable = atobool(row[17]);
trap->CreateHiddenTrigger();
if (repopnow)
{
trap->chkarea_timer.Enable();
}
else
{
trap->respawn_timer.Start((trap->respawn_time + zone->random.Int(0, trap->respawn_var)) * 1000);
}
if (trap->trap_id != trap->db_id)
Log(Logs::General, Logs::Traps, "Trap (%d) DBID has changed from %d to %d", trap->trap_id, dbid, trap->db_id);
return true;
}
return false;
}
void Trap::UpdateTrap(bool respawn, bool repopnow)
{
respawn_timer.Disable();
chkarea_timer.Disable();
reset_timer.Disable();
if (hiddenTrigger)
{
hiddenTrigger->Depop();
hiddenTrigger = nullptr;
}
times_triggered = 0;
Client* trigger = entity_list.GetClientByCharID(charid);
if (trigger)
{
trigger->trapid = 0;
}
charid = 0;
if (respawn)
{
database.SetTrapData(this, repopnow);
}
}

View File

@ -49,11 +49,14 @@ public:
NPC * GetHiddenTrigger() { return hiddenTrigger; } NPC * GetHiddenTrigger() { return hiddenTrigger; }
void SetHiddenTrigger(NPC* n) { hiddenTrigger = n; } void SetHiddenTrigger(NPC* n) { hiddenTrigger = n; }
void CreateHiddenTrigger(); void CreateHiddenTrigger();
void DestroyHiddenTrigger() { hiddenTrigger = nullptr; }
void UpdateTrap(bool respawn = true, bool repopnow = false);
//Trap data, leave this unprotected //Trap data, leave this unprotected
Timer respawn_timer; //Respawn Time when Trap's been disarmed Timer respawn_timer; //Respawn Time when Trap's been disarmed
Timer chkarea_timer; Timer chkarea_timer;
uint32 trap_id; //Database ID of trap Timer reset_timer; //How long a trap takes to reset before triggering again.
uint32 trap_id; //Original ID of the trap from DB. This value never changes.
uint32 db_id; //The DB ID of the trap that currently is spawned.
glm::vec3 m_Position; glm::vec3 m_Position;
float maxzdiff; //maximum z diff to be triggerable float maxzdiff; //maximum z diff to be triggerable
float radius; //radius around trap to be triggerable float radius; //radius around trap to be triggerable
@ -67,6 +70,12 @@ public:
bool disarmed; bool disarmed;
uint32 respawn_time; uint32 respawn_time;
uint32 respawn_var; uint32 respawn_var;
uint8 triggered_number;
uint8 times_triggered;
uint8 group;
bool despawn_when_triggered;
uint32 charid; //ID of character that triggered trap. This is cleared when the trap despawns are resets.
bool undetectable;
std::string message; std::string message;
protected: protected:

View File

@ -176,9 +176,15 @@ void NPC::MoveTo(const glm::vec4& position, bool saveguardspot)
cur_wp = -2; // flag as quest controlled w/no grid cur_wp = -2; // flag as quest controlled w/no grid
Log(Logs::Detail, Logs::AI, "MoveTo %s without a grid.", to_string(static_cast<glm::vec3>(position)).c_str()); Log(Logs::Detail, Logs::AI, "MoveTo %s without a grid.", to_string(static_cast<glm::vec3>(position)).c_str());
} }
glm::vec3 dest(position);
m_CurrentWayPoint = position;
m_CurrentWayPoint.z = GetFixedZ(dest);
if (saveguardspot) if (saveguardspot)
{ {
m_GuardPoint = position; m_GuardPoint = m_CurrentWayPoint;
if (m_GuardPoint.w == 0) if (m_GuardPoint.w == 0)
m_GuardPoint.w = 0.0001; //hack to make IsGuarding simpler m_GuardPoint.w = 0.0001; //hack to make IsGuarding simpler
@ -189,7 +195,6 @@ void NPC::MoveTo(const glm::vec4& position, bool saveguardspot)
Log(Logs::Detail, Logs::AI, "Setting guard position to %s", to_string(static_cast<glm::vec3>(m_GuardPoint)).c_str()); Log(Logs::Detail, Logs::AI, "Setting guard position to %s", to_string(static_cast<glm::vec3>(m_GuardPoint)).c_str());
} }
m_CurrentWayPoint = position;
cur_wp_pause = 0; cur_wp_pause = 0;
pLastFightingDelayMoving = 0; pLastFightingDelayMoving = 0;
if (AI_walking_timer->Enabled()) if (AI_walking_timer->Enabled())
@ -838,35 +843,50 @@ void Mob::SendToFixZ(float new_x, float new_y, float new_z) {
} }
} }
void Mob::FixZ() { float Mob::GetFixedZ(glm::vec3 dest, int32 z_find_offset)
{
BenchTimer timer; BenchTimer timer;
timer.reset(); timer.reset();
float new_z = dest.z;
if (zone->HasMap() && RuleB(Map, FixZWhenMoving) && (flymode != 1 && flymode != 2)) if (zone->HasMap() && RuleB(Map, FixZWhenMoving) &&
(flymode != 1 && flymode != 2))
{ {
if (!RuleB(Watermap, CheckForWaterWhenMoving) || !zone->HasWaterMap() || if (!RuleB(Watermap, CheckForWaterWhenMoving) || !zone->HasWaterMap()
(zone->HasWaterMap() && !zone->watermap->InWater(glm::vec3(m_Position)))) || (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 */ /* 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->FindDestGroundZ(dest, z_find_offset);
new_z += (this->GetSize() / 1.55); if (new_z != BEST_Z_INVALID)
{
new_z += this->GetZOffset();
// If bad new Z restore old one
if (new_z < -2000) {
new_z = m_Position.z;
}
}
}
auto duration = timer.elapsed(); auto duration = timer.elapsed();
Log( Log(Logs::Moderate, Logs::FixZ,
Logs::Moderate, "Mob::GetFixedZ() (%s) returned %4.3f at %4.3f, %4.3f, %4.3f - Took %lf",
Logs::FixZ, this->GetCleanName(), new_z, dest.x, dest.y, dest.z, duration);
"Mob::FixZ() (%s) returned %4.3f at %4.3f, %4.3f, %4.3f - Took %lf", }
this->GetCleanName(),
new_z,
m_Position.x,
m_Position.y,
m_Position.z,
duration
);
if ((new_z > -2000) && new_z != -999999) { return new_z;
}
void Mob::FixZ(int32 z_find_offset /*= 5*/)
{
glm::vec3 current_loc(m_Position);
float new_z = GetFixedZ(current_loc, z_find_offset);
if (!IsClient() && new_z != m_Position.z)
{
if ((new_z > -2000) && new_z != BEST_Z_INVALID) {
if (RuleB(Map, MobZVisualDebug)) if (RuleB(Map, MobZVisualDebug))
this->SendAppearanceEffect(78, 0, 0, 0, 0); this->SendAppearanceEffect(78, 0, 0, 0, 0);
@ -876,12 +896,117 @@ void Mob::FixZ() {
if (RuleB(Map, MobZVisualDebug)) if (RuleB(Map, MobZVisualDebug))
this->SendAppearanceEffect(103, 0, 0, 0, 0); this->SendAppearanceEffect(103, 0, 0, 0, 0);
Log(Logs::General, Logs::FixZ, "%s is failing to find Z %f", this->GetCleanName(), std::abs(m_Position.z - new_z)); Log(Logs::General, Logs::FixZ, "%s is failing to find Z %f",
this->GetCleanName(), std::abs(m_Position.z - new_z));
}
}
} }
last_z = m_Position.z; float Mob::GetZOffset() const {
} float offset = 3.125f;
switch (race) {
case 436:
offset = 0.577f;
break;
case 430:
offset = 0.5f;
break;
case 432:
offset = 1.9f;
break;
case 435:
offset = 0.93f;
break;
case 450:
offset = 0.938f;
break;
case 479:
offset = 0.8f;
break;
case 451:
offset = 0.816f;
break;
case 437:
offset = 0.527f;
break;
case 439:
offset = 1.536f;
break;
case 415:
offset = 1.0f;
break;
case 438:
offset = 0.776f;
break;
case 452:
offset = 0.776f;
break;
case 441:
offset = 0.816f;
break;
case 440:
offset = 0.938f;
break;
case 468:
offset = 1.0f;
break;
case 459:
offset = 1.0f;
break;
case 462:
offset = 1.5f;
break;
case 530:
offset = 1.2f;
break;
case 549:
offset = 0.5f;
break;
case 548:
offset = 0.5f;
break;
case 547:
offset = 0.5f;
break;
case 604:
offset = 1.2f;
break;
case 653:
offset = 5.9f;
break;
case 658:
offset = 4.0f;
break;
case 323:
offset = 5.0f;
break;
case 663:
offset = 5.0f;
break;
case 664:
offset = 4.0f;
break;
case 703:
offset = 9.0f;
break;
case 688:
offset = 5.0f;
break;
case 669:
offset = 7.0f;
break;
case 687:
offset = 2.0f;
break;
case 686:
offset = 2.0f;
break;
default:
offset = 3.125f;
} }
return 0.2 * GetSize() * offset;
} }
int ZoneDatabase::GetHighestGrid(uint32 zoneid) { int ZoneDatabase::GetHighestGrid(uint32 zoneid) {

View File

@ -1428,7 +1428,8 @@ void Zone::StartShutdownTimer(uint32 set_time) {
bool Zone::Depop(bool StartSpawnTimer) { bool Zone::Depop(bool StartSpawnTimer) {
std::map<uint32,NPCType *>::iterator itr; std::map<uint32,NPCType *>::iterator itr;
entity_list.Depop(StartSpawnTimer); entity_list.Depop(StartSpawnTimer);
entity_list.ClearTrapPointers();
entity_list.UpdateAllTraps(false);
/* Refresh npctable (cache), getting current info from database. */ /* Refresh npctable (cache), getting current info from database. */
while(!npctable.empty()) { while(!npctable.empty()) {
itr = npctable.begin(); itr = npctable.begin();
@ -1496,6 +1497,8 @@ void Zone::Repop(uint32 delay) {
iterator.RemoveCurrent(); iterator.RemoveCurrent();
} }
entity_list.ClearTrapPointers();
quest_manager.ClearAllTimers(); quest_manager.ClearAllTimers();
if (!database.PopulateZoneSpawnList(zoneid, spawn2_list, GetInstanceVersion(), delay)) if (!database.PopulateZoneSpawnList(zoneid, spawn2_list, GetInstanceVersion(), delay))
@ -1503,6 +1506,8 @@ void Zone::Repop(uint32 delay) {
initgrids_timer.Start(); initgrids_timer.Start();
entity_list.UpdateAllTraps(true, true);
//MODDING HOOK FOR REPOP //MODDING HOOK FOR REPOP
mod_repop(); mod_repop();
} }
@ -1510,7 +1515,7 @@ void Zone::Repop(uint32 delay) {
void Zone::GetTimeSync() void Zone::GetTimeSync()
{ {
if (worldserver.Connected() && !zone_has_current_time) { if (worldserver.Connected() && !zone_has_current_time) {
auto pack = new ServerPacket(ServerOP_GetWorldTime, 0); auto pack = new ServerPacket(ServerOP_GetWorldTime, 1);
worldserver.SendPacket(pack); worldserver.SendPacket(pack);
safe_delete(pack); safe_delete(pack);
} }

View File

@ -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){ 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 */ clock_t t = std::clock(); /* Function timer start */
std::string query = StringFormat( std::string query = StringFormat(
"REPLACE INTO `character_data` (" "REPLACE INTO `character_data` ("
@ -2953,11 +2958,13 @@ uint32 ZoneDatabase::GetKarma(uint32 acct_id)
if (!results.Success()) if (!results.Success())
return 0; 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) void ZoneDatabase::UpdateKarma(uint32 acct_id, uint32 amount)
{ {
std::string query = StringFormat("UPDATE account SET karma = %i WHERE id = %i", amount, acct_id); std::string query = StringFormat("UPDATE account SET karma = %i WHERE id = %i", amount, acct_id);

View File

@ -16,6 +16,7 @@ class NPC;
class Petition; class Petition;
class Spawn2; class Spawn2;
class SpawnGroupList; class SpawnGroupList;
class Trap;
struct CharacterEventLog_Struct; struct CharacterEventLog_Struct;
struct Door; struct Door;
struct ExtendedProfile_Struct; struct ExtendedProfile_Struct;
@ -478,7 +479,7 @@ public:
/* Traps */ /* Traps */
bool LoadTraps(const char* zonename, int16 version); bool LoadTraps(const char* zonename, int16 version);
char* GetTrapMessage(uint32 trap_id); bool SetTrapData(Trap* trap, bool repopnow = false);
/* Time */ /* Time */
uint32 GetZoneTZ(uint32 zoneid, uint32 version); uint32 GetZoneTZ(uint32 zoneid, uint32 version);