Compare commits

..

56 Commits

Author SHA1 Message Date
KimLS 7300776a85 Fix for bug where packets were not combining when sent inside cwnd, altered some settings 2017-07-06 18:03:19 -07:00
KimLS 3878bd0c76 Move to an older implementation 2017-07-06 17:25:00 -07:00
KimLS a7c0e82c9e Tweaks and bug fixes 2017-07-05 20:24:39 -07:00
KimLS 49505a7a45 Bug fix plus tweak to reset cwnd when no longer needed 2017-07-04 21:03:49 -07:00
KimLS 10b01e62df More tweaks 2017-07-04 12:45:23 -07:00
KimLS c6bb4a6470 Some tweaks 2017-07-03 21:31:25 -07:00
KimLS 15606a99fc Merge branch 'eqstream' of github.com:EQEmu/Server into eqstream 2017-07-01 13:47:04 -07:00
KimLS eddc9c9baf Only increase cwnd when full, fiddled with formulas 2017-07-01 13:46:39 -07:00
Akkadius 7bbc4a6a44 Merge branch 'master' into eqstream 2017-07-01 03:15:18 -05:00
Akkadius 1f39a0cb3e Fix some NPC and Corpse falling through the ground issues 2017-07-01 03:07:45 -05:00
KimLS 8d680b2222 Some changes 2017-06-30 22:40:05 -07:00
Michael Cook (mackal) 21ef83bcbe Fix /invite xtarget raid issue 2017-06-30 13:50:43 -04:00
KimLS 6253162166 Change settings 2017-06-29 14:15:11 -07:00
KimLS 1110b284d8 Merge branch 'master' of github.com:EQEmu/Server 2017-06-29 14:14:24 -07:00
KimLS 5c6f684808 Some work on congestion avoidance now uses a sliding congestion window instead of a static one 2017-06-29 14:13:55 -07:00
Akkadius 9b1a449fba Fix Z when following during pull, not when engaged and stationary or moving slightly 2017-06-28 21:44:31 -05:00
Akkadius e4f337edb6 Merge branch 'master' of https://github.com/EQEmu/Server 2017-06-28 21:23:18 -05:00
Akkadius 5a9744b429 Add a fail-safe for dynamic zone shutdown timers 2017-06-28 21:23:02 -05:00
Michael Cook (mackal) e0237ce526 Quick fix for AE LOS issue 2017-06-28 19:58:28 -04:00
Akkadius 4d2825d817 Make sure we also fix Z when mobs flee up/down stairs 2017-06-28 16:43:38 -05:00
Michael Cook (mackal) 09ccd23d0b Fix syntax issue 2017-06-28 13:30:20 -04:00
Michael Cook (mackal) cbbd01b391 Quick fix for NPC attack skill issue 2017-06-28 13:28:08 -04:00
Akkadius 539fa8b262 Fixed issues with Z correctness when NPCs are pathing on normal grids
Fixed issues with Z correctness when NPCs are engaged with players following
NPC corpses should fall into the ground far less
2017-06-28 02:38:20 -05:00
Michael Cook (mackal) 592bbd3180 Target AEs don't always hit target 2017-06-27 20:38:46 -04:00
Michael Cook (mackal) b09792812a Final AESpell optimization pass 2017-06-26 15:01:06 -04:00
Akkadius 9154938827 Merge branch 'master' of https://github.com/EQEmu/Server 2017-06-26 00:13:23 -05:00
Akkadius 4f7b8e0934 Add Critical Hit range (default 80) to legacy_combat.lua and fix a variable in Pet Criticals 2017-06-26 00:12:56 -05:00
Akkadius c0f53647b8 Revert 5fac13075b until we don't creep client resend up to 4 seconds so quickly, this causes issues in higher traffic amounts and takes longer for clients to recover than needed 2017-06-25 20:37:37 -05:00
Michael Cook (mackal) 3e1b75b814 Fix Rain target limit (massive nerf)
Added rule Spells:OldRainTargets, set to true if you don't want the nerf
2017-06-25 18:18:27 -04:00
Michael Cook (mackal) 497170c453 Optimize Entity::AESpell
Probably could use more work, but quick testing had this method taking
the least amount of time in various situations
2017-06-25 16:30:37 -04:00
Akkadius 6773412e40 Merge branch 'master' of https://github.com/EQEmu/Server 2017-06-24 21:13:31 -05:00
Akkadius 1c8dea909e New rules made by developers are now automatically created when world boots up, this keeps from having to issue schema SQL updates every time rules are added.
- Whenever a rule isn't present in the database, it will be automatically created
- utils/sql/git/required/2017_06_24_rule_values_expand.sql required for avoid floating point value inserts
2017-06-24 21:11:46 -05:00
KimLS d6ac686a54 Merge branch 'master' of github.com:EQEmu/Server 2017-06-24 18:29:52 -07:00
KimLS 5fac13075b Up resend delay max a few seconds 2017-06-24 18:29:18 -07:00
Akkadius 6cc774faf4 Adjust netcode variables proven to be stable for players 2017-06-24 20:10:12 -05:00
Akkadius 8f4ec1b960 Merge branch 'master' of https://github.com/EQEmu/Server 2017-06-24 20:07:31 -05:00
Akkadius 357be65a69 Speed up saylink retrieval x 1,000 - helpful for dialogues, plugins with many saylinks 2017-06-24 20:07:01 -05:00
Michael Cook (mackal) f164833b00 Adventure Merchants don't change heading either 2017-06-24 15:07:58 -04:00
Michael Cook (mackal) 627859ba73 Merchants don't change heading 2017-06-24 14:58:40 -04:00
KimLS a7c239b801 Some changes to the outbound queue and default settings 2017-06-23 21:52:28 -07:00
KimLS 1cabb091e7 Added packet buffering for dbg connections to avoid connections sending a ton of packets at once 2017-06-22 22:26:12 -07:00
Akkadius d0e612b5ff Fix some lua calls/variables in legacy_combat.lua 2017-06-20 02:31:29 -05:00
KimLS 0a8b21d4ab Merge branch 'luamod' 2017-06-19 14:45:08 -07:00
Akkadius 27fd6316f1 Merge pull request #624 from noudess/master
Fixed a typo in #object command query and fixed a fishing bug.
2017-06-15 17:15:34 -05:00
Michael Cook (mackal) 4e15364d42 Fix auto completing to wrong thing ... 2017-06-13 21:48:02 -04:00
Michael Cook (mackal) 35c194e2eb Fix SE_FcSpellVulnerability 2017-06-13 21:33:42 -04:00
Michael Cook (mackal) 0c5c6587e5 Fix resist focus issue 2017-06-13 21:30:34 -04:00
Michael Cook (mackal) b5a81fbd07 Fix life burn
Need to investigate more, but reuse
2017-06-13 14:00:09 -04:00
Michael Cook (mackal) 0a0d4fbb70 SE_SpellResistReduction should be RNG
I think before there was an implied 1 to base now it's base to base2
2017-06-12 16:57:35 -04:00
Michael Cook (mackal) c1669299aa Merge pull request #626 from Natedog2012/master
Add CrossZoneSetEntityVariableByClientName
2017-06-12 13:47:23 -04:00
Natedog2012 d62219d0ad Add CrossZoneSetEntityVariableByClientName to lua as well 2017-06-12 10:22:14 -07:00
Natedog2012 59ddf507e6 Add CrossZoneSetEntityVariableByClientName 2017-06-12 08:42:14 -07:00
Michael Cook (mackal) 4d94d5fe17 Merge pull request #625 from KinglyKrab/master
Implement EVENT_USE_SKILL in Perl/Lua.
2017-06-10 23:01:09 -04:00
Kinglykrab d64f2e40c5 Implement EVENT_USE_SKILL in Perl/Lua.
- Exports skill_id and skill_level in Perl/Lua whenever a skill is used (bash, kick, taunt, etc.)
2017-06-10 22:20:45 -04:00
Paul Coene 75d7c40098 Merge remote-tracking branch 'upstream/master' 2017-06-10 11:28:12 -04:00
Paul Coene 7c377e8904 Fix typo on database call in #object.
Fix issue with fishing.. Search needs to search from my z for full line length
not for only the diff between my feet and the possible water location.
2017-06-10 11:25:09 -04:00
41 changed files with 414 additions and 244 deletions
+28 -17
View File
@@ -1,25 +1,36 @@
EQEMu Changelog (Started on Sept 24, 2003 15:50)
-------------------------------------------------------
== 6/28/2017 ==
Akkadius: Fixed issues with Z correctness when NPCs are pathing on normal grids
Akkadius: Fixed issues with Z correctness when NPCs are engaged with players following
Akkadius: NPC corpses should fall into the ground far less
== 6/25/2017 ==
Akkadius: New rules made by developers are now automatically created when world boots up, this keeps
from having to issue schema SQL updates every time rules are added.
- Whenever a rule isn't present in the database, it will be automatically created
Akkadius: Sped up saylink retrieval x1000 helpful for dialogues, plugins with many saylinks
== 4/16/2017 ==
KLS: Merge eqstream branch
-UDP client stack completely rewritten should both have better throughput and recover better (peq has had far fewer reports of desyncs).
-TCP Server to Server connection stack completely rewritten.
-Server connections reconnect much more reliably and quickly now.
-Now supports optional packet encryption via libsodium (https://download.libsodium.org/doc/).
-Protocol behind the tcp connections has changed (see breaking changes section).
-API significantly changed and should be easier to write new servers or handlers for.
-Telnet console connection has been separated out from the current port (see breaking changes section).
-Because of changes to the TCP stack, lsreconnect and echo have been disabled.
-The server tic rate has been changed to be approx 30 fps from 500+ fps.
-Changed how missiles and movement were calculated slightly to account for this (Missiles in particular are not perfect but close enough).
- UDP client stack completely rewritten should both have better throughput and recover better (peq has had far fewer reports of desyncs).
- TCP Server to Server connection stack completely rewritten.
- Server connections reconnect much more reliably and quickly now.
- Now supports optional packet encryption via libsodium (https://download.libsodium.org/doc/).
- Protocol behind the tcp connections has changed (see breaking changes section).
- API significantly changed and should be easier to write new servers or handlers for.
- Telnet console connection has been separated out from the current port (see breaking changes section).
- Because of changes to the TCP stack, lsreconnect and echo have been disabled.
- The server tic rate has been changed to be approx 30 fps from 500+ fps.
- Changed how missiles and movement were calculated slightly to account for this (Missiles in particular are not perfect but close enough).
-Breaking changes:
-Users who use the cmake install feature should be aware that the install directory is now %cmake_install_dir%/bin instead of just %cmake_install_dir%/
-To support new features such as encryption the underlying protocol had to change... however some servers such as the public login server will be slow to change so we've included a compatibility layer for legacy login connections:
-You should add <legacy>1</legacy> to the login section of your configuration file when connecting to a server that is using the old protocol.
-The central eqemu login server uses the old protocol and probably will for the forseeable future so if your server is connecting to it be sure to add that tag to your configuration file in that section.
-Telnet no longer uses the same port as the Server to Server connection and because of this the tcp tag no longer has any effect on telnet connections.
-To enable telnet you need to add a telnet tag in the world section of configuration such as:
- Breaking changes:
- Users who use the cmake install feature should be aware that the install directory is now %cmake_install_dir%/bin instead of just %cmake_install_dir%/
- To support new features such as encryption the underlying protocol had to change... however some servers such as the public login server will be slow to change so we've included a compatibility layer for legacy login connections:
- You should add <legacy>1</legacy> to the login section of your configuration file when connecting to a server that is using the old protocol.
- The central eqemu login server uses the old protocol and probably will for the forseeable future so if your server is connecting to it be sure to add that tag to your configuration file in that section.
- Telnet no longer uses the same port as the Server to Server connection and because of this the tcp tag no longer has any effect on telnet connections.
- To enable telnet you need to add a telnet tag in the world section of configuration such as:
<telnet ip="0.0.0.0" port="9001" enabled="true"/>
== 4/1/2017 ==
+70 -24
View File
@@ -141,7 +141,7 @@ void EQ::Net::DaybreakConnectionManager::Process()
connection->SendConnect();
}
}
break;
break;
case StatusConnected: {
if (m_options.keepalive_delay_ms != 0) {
auto time_since_last_send = std::chrono::duration_cast<std::chrono::milliseconds>(now - connection->m_last_send);
@@ -277,7 +277,7 @@ EQ::Net::DaybreakConnection::DaybreakConnection(DaybreakConnectionManager *owner
m_encode_passes[1] = owner->m_options.encode_passes[1];
m_hold_time = Clock::now();
m_buffered_packets_length = 0;
m_rolling_ping = 900;
m_rolling_ping = 500;
m_resend_delay = (m_rolling_ping * m_owner->m_options.resend_delay_factor) + m_owner->m_options.resend_delay_ms;
m_combined.reset(new char[512]);
m_combined[0] = 0;
@@ -300,7 +300,7 @@ EQ::Net::DaybreakConnection::DaybreakConnection(DaybreakConnectionManager *owner
m_crc_bytes = 0;
m_hold_time = Clock::now();
m_buffered_packets_length = 0;
m_rolling_ping = 900;
m_rolling_ping = 500;
m_resend_delay = (m_rolling_ping * m_owner->m_options.resend_delay_factor) + m_owner->m_options.resend_delay_ms;
m_combined.reset(new char[512]);
m_combined[0] = 0;
@@ -365,7 +365,7 @@ void EQ::Net::DaybreakConnection::Process()
FlushBuffer();
}
ProcessQueue();
ProcessInboundQueue();
}
catch (std::exception ex) {
LogF(Logs::Detail, Logs::Netcode, "Error processing connection: {0}", ex.what());
@@ -440,7 +440,7 @@ void EQ::Net::DaybreakConnection::ProcessPacket(Packet &p)
}
}
void EQ::Net::DaybreakConnection::ProcessQueue()
void EQ::Net::DaybreakConnection::ProcessInboundQueue()
{
for (int i = 0; i < 4; ++i) {
auto stream = &m_streams[i];
@@ -459,6 +459,31 @@ void EQ::Net::DaybreakConnection::ProcessQueue()
}
}
void EQ::Net::DaybreakConnection::ProcessOutboundQueue()
{
for (int i = 0; i < 4; ++i) {
auto stream = &m_streams[i];
if (stream->outstanding_bytes == 0) {
continue;
}
while (!stream->buffered_packets.empty()) {
auto &buff = stream->buffered_packets.front();
if (stream->outstanding_bytes + buff.sent.packet.Length() >= m_owner->m_options.max_outstanding_bytes ||
stream->outstanding_packets.size() + 1 >= m_owner->m_options.max_outstanding_packets) {
break;
}
stream->outstanding_bytes += buff.sent.packet.Length();
stream->outstanding_packets.insert(std::make_pair(buff.seq, buff.sent));
InternalBufferedSend(buff.sent.packet);
stream->buffered_packets.pop_front();
}
}
}
void EQ::Net::DaybreakConnection::RemoveFromQueue(int stream, uint16_t seq)
{
auto s = &m_streams[stream];
@@ -1019,14 +1044,14 @@ void EQ::Net::DaybreakConnection::ProcessResend(int stream)
auto now = Clock::now();
auto s = &m_streams[stream];
for (auto &entry : s->sent_packets) {
for (auto &entry : s->outstanding_packets) {
auto time_since_last_send = std::chrono::duration_cast<std::chrono::milliseconds>(now - entry.second.last_sent);
if (entry.second.times_resent == 0) {
if ((size_t)time_since_last_send.count() > m_resend_delay) {
InternalBufferedSend(entry.second.packet);
entry.second.last_sent = now;
entry.second.times_resent++;
m_rolling_ping += 300;
m_rolling_ping += 100;
}
}
else {
@@ -1040,7 +1065,7 @@ void EQ::Net::DaybreakConnection::ProcessResend(int stream)
InternalBufferedSend(entry.second.packet);
entry.second.last_sent = now;
entry.second.times_resent++;
m_rolling_ping += 300;
m_rolling_ping += 100;
}
}
}
@@ -1051,11 +1076,11 @@ void EQ::Net::DaybreakConnection::Ack(int stream, uint16_t seq)
auto now = Clock::now();
auto s = &m_streams[stream];
auto iter = s->sent_packets.begin();
while (iter != s->sent_packets.end()) {
auto iter = s->outstanding_packets.begin();
while (iter != s->outstanding_packets.end()) {
auto order = CompareSequence(seq, iter->first);
if (order != SequenceFuture) {
if (order != SequenceFuture) {
uint64_t round_time = (uint64_t)std::chrono::duration_cast<std::chrono::milliseconds>(now - iter->second.last_sent).count();
m_stats.max_ping = std::max(m_stats.max_ping, round_time);
@@ -1063,7 +1088,9 @@ void EQ::Net::DaybreakConnection::Ack(int stream, uint16_t seq)
m_stats.last_ping = round_time;
m_rolling_ping = (m_rolling_ping * 2 + round_time) / 3;
iter = s->sent_packets.erase(iter);
s->outstanding_bytes -= iter->second.packet.Length();
iter = s->outstanding_packets.erase(iter);
ProcessOutboundQueue();
}
else {
++iter;
@@ -1075,8 +1102,8 @@ void EQ::Net::DaybreakConnection::OutOfOrderAck(int stream, uint16_t seq)
{
auto now = Clock::now();
auto s = &m_streams[stream];
auto iter = s->sent_packets.find(seq);
if (iter != s->sent_packets.end()) {
auto iter = s->outstanding_packets.find(seq);
if (iter != s->outstanding_packets.end()) {
uint64_t round_time = (uint64_t)std::chrono::duration_cast<std::chrono::milliseconds>(now - iter->second.last_sent).count();
m_stats.max_ping = std::max(m_stats.max_ping, round_time);
@@ -1084,10 +1111,31 @@ void EQ::Net::DaybreakConnection::OutOfOrderAck(int stream, uint16_t seq)
m_stats.last_ping = round_time;
m_rolling_ping = (m_rolling_ping * 2 + round_time) / 3;
s->sent_packets.erase(iter);
s->outstanding_bytes -= iter->second.packet.Length();
s->outstanding_packets.erase(iter);
ProcessOutboundQueue();
}
}
void EQ::Net::DaybreakConnection::BufferPacket(int stream, uint16_t seq, DaybreakSentPacket &sent)
{
auto s = &m_streams[stream];
//If we can send the packet then send it
//else buffer it to be sent when we can send it
if (s->outstanding_bytes + sent.packet.Length() >= m_owner->m_options.max_outstanding_bytes || s->outstanding_packets.size() + 1 >= m_owner->m_options.max_outstanding_packets) {
//Would go over one of the limits, buffer this packet.
DaybreakBufferedPacket bp;
bp.sent = std::move(sent);
bp.seq = seq;
s->buffered_packets.push_back(bp);
return;
}
s->outstanding_bytes += sent.packet.Length();
s->outstanding_packets.insert(std::make_pair(seq, sent));
InternalBufferedSend(sent.packet);
}
void EQ::Net::DaybreakConnection::SendAck(int stream_id, uint16_t seq)
{
DaybreakReliableHeader ack;
@@ -1139,6 +1187,10 @@ void EQ::Net::DaybreakConnection::InternalBufferedSend(Packet &p)
FlushBuffer();
}
if (m_buffered_packets.size() == 0) {
m_hold_time = Clock::now();
}
DynamicPacket copy;
copy.PutPacket(0, p);
m_buffered_packets.push_back(copy);
@@ -1293,11 +1345,9 @@ void EQ::Net::DaybreakConnection::InternalQueuePacket(Packet &p, int stream_id,
sent.last_sent = Clock::now();
sent.first_sent = Clock::now();
sent.times_resent = 0;
stream->sent_packets.insert(std::make_pair(stream->sequence_out, sent));
BufferPacket(stream_id, stream->sequence_out, sent);
stream->sequence_out++;
InternalBufferedSend(first_packet);
while (used < length) {
auto left = length - used;
DynamicPacket packet;
@@ -1321,10 +1371,8 @@ void EQ::Net::DaybreakConnection::InternalQueuePacket(Packet &p, int stream_id,
sent.last_sent = Clock::now();
sent.first_sent = Clock::now();
sent.times_resent = 0;
stream->sent_packets.insert(std::make_pair(stream->sequence_out, sent));
BufferPacket(stream_id, stream->sequence_out, sent);
stream->sequence_out++;
InternalBufferedSend(packet);
}
}
else {
@@ -1341,10 +1389,8 @@ void EQ::Net::DaybreakConnection::InternalQueuePacket(Packet &p, int stream_id,
sent.last_sent = Clock::now();
sent.first_sent = Clock::now();
sent.times_resent = 0;
stream->sent_packets.insert(std::make_pair(stream->sequence_out, sent));
BufferPacket(stream_id, stream->sequence_out, sent);
stream->sequence_out++;
InternalBufferedSend(packet);
}
}
+23 -8
View File
@@ -9,7 +9,7 @@
#include <memory>
#include <map>
#include <unordered_map>
#include <queue>
#include <deque>
#include <list>
namespace EQ
@@ -145,6 +145,12 @@ namespace EQ
size_t times_resent;
};
struct DaybreakBufferedPacket
{
uint16_t seq;
DaybreakSentPacket sent;
};
struct DaybreakStream
{
DaybreakStream() {
@@ -152,17 +158,20 @@ namespace EQ
sequence_out = 0;
fragment_current_bytes = 0;
fragment_total_bytes = 0;
outstanding_bytes = 0;
}
uint16_t sequence_in;
uint16_t sequence_out;
std::unordered_map<uint16_t, Packet*> packet_queue;
std::deque<DaybreakBufferedPacket> buffered_packets;
DynamicPacket fragment_packet;
uint32_t fragment_current_bytes;
uint32_t fragment_total_bytes;
std::unordered_map<uint16_t, DaybreakSentPacket> sent_packets;
std::unordered_map<uint16_t, DaybreakSentPacket> outstanding_packets;
size_t outstanding_bytes;
};
DaybreakStream m_streams[4];
@@ -170,7 +179,8 @@ namespace EQ
void Process();
void ProcessPacket(Packet &p);
void ProcessQueue();
void ProcessInboundQueue();
void ProcessOutboundQueue();
void RemoveFromQueue(int stream, uint16_t seq);
void AddToQueue(int stream, uint16_t seq, const Packet &p);
void ProcessDecodedPacket(const Packet &p);
@@ -186,6 +196,7 @@ namespace EQ
void ProcessResend(int stream);
void Ack(int stream, uint16_t seq);
void OutOfOrderAck(int stream, uint16_t seq);
void BufferPacket(int stream, uint16_t seq, DaybreakSentPacket &sent);
void SendConnect();
void SendKeepAlive();
@@ -206,10 +217,10 @@ namespace EQ
DaybreakConnectionManagerOptions() {
max_connection_count = 0;
keepalive_delay_ms = 9000;
resend_delay_ms = 300;
resend_delay_ms = 150;
resend_delay_factor = 1.5;
resend_delay_min = 350;
resend_delay_max = 8000;
resend_delay_min = 300;
resend_delay_max = 3000;
connect_delay_ms = 500;
stale_connection_ms = 90000;
connect_stale_ms = 5000;
@@ -219,12 +230,14 @@ namespace EQ
encode_passes[1] = DaybreakEncodeType::EncodeNone;
port = 0;
hold_size = 448;
hold_length_ms = 10;
hold_length_ms = 50;
simulated_in_packet_loss = 0;
simulated_out_packet_loss = 0;
tic_rate_hertz = 60.0;
resend_timeout = 90000;
connection_close_time = 2000;
max_outstanding_packets = 400;
max_outstanding_bytes = 400 * 512;
}
size_t max_packet_size;
@@ -247,6 +260,8 @@ namespace EQ
size_t connection_close_time;
DaybreakEncodeType encode_passes[2];
int port;
size_t max_outstanding_packets;
size_t max_outstanding_bytes;
};
class DaybreakConnectionManager
@@ -283,4 +298,4 @@ namespace EQ
friend class DaybreakConnection;
};
}
}
}
+3
View File
@@ -274,6 +274,8 @@ RULE_BOOL(Map, FixPathingZWhenLoading, true) //increases zone boot times a bit
RULE_BOOL(Map, FixPathingZAtWaypoints, false) //alternative to `WhenLoading`, accomplishes the same thing but does it at each waypoint instead of once at boot time.
RULE_BOOL(Map, FixPathingZWhenMoving, false) //very CPU intensive, but helps hopping with widely spaced waypoints.
RULE_BOOL(Map, FixPathingZOnSendTo, false) //try to repair Z coords in the SendTo routine as well.
RULE_BOOL(Map, FixZWhenMoving, true) // Automatically fix NPC Z coordinates when moving/pathing/engaged (Far less CPU intensive than its predecessor)
RULE_BOOL(Map, MobZVisualDebug, false) // Displays spell effects determining whether or not NPC is hitting Best Z calcs (blue for hit, red for miss)
RULE_REAL(Map, FixPathingZMaxDeltaMoving, 20) //at runtime while pathing: max change in Z to allow the BestZ code to apply.
RULE_REAL(Map, FixPathingZMaxDeltaWaypoint, 20) //at runtime at each waypoint: max change in Z to allow the BestZ code to apply.
RULE_REAL(Map, FixPathingZMaxDeltaSendTo, 20) //at runtime in SendTo: max change in Z to allow the BestZ code to apply.
@@ -397,6 +399,7 @@ RULE_BOOL(Spells, FlatItemExtraSpellAmt, false) // allow SpellDmg stat to affect
RULE_BOOL(Spells, IgnoreSpellDmgLvlRestriction, false) // ignore the 5 level spread on applying SpellDmg
RULE_BOOL(Spells, AllowItemTGB, false) // TGB doesn't work with items on live, custom servers want it though
RULE_BOOL(Spells, NPCInnateProcOverride, true) // NPC innate procs override the target type to single target.
RULE_BOOL(Spells, OldRainTargets, false) // use old incorrectly implemented max targets for rains
RULE_CATEGORY_END()
RULE_CATEGORY(Combat)
+7
View File
@@ -189,6 +189,7 @@
#define ServerOP_ReloadWorld 0x4009
#define ServerOP_ReloadLogs 0x4010
#define ServerOP_ReloadPerlExportSettings 0x4011
#define ServerOP_CZSetEntityVariableByClientName 0x4012
/* Query Server OP Codes */
#define ServerOP_QSPlayerLogTrades 0x5010
#define ServerOP_QSPlayerLogHandins 0x5011
@@ -1263,6 +1264,12 @@ struct CZSetEntVarByNPCTypeID_Struct {
char m_var[256];
};
struct CZSetEntVarByClientName_Struct {
char CharName[64];
char id[256];
char m_var[256];
};
struct ReloadWorld_Struct{
uint32 Option;
};
+1 -1
View File
@@ -30,7 +30,7 @@
Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt
*/
#define CURRENT_BINARY_DATABASE_VERSION 9110
#define CURRENT_BINARY_DATABASE_VERSION 9112
#ifdef BOTS
#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9017
#else
+12 -10
View File
@@ -42,6 +42,8 @@ WeaponSkillFalloff = RuleR.Get(Rule.WeaponSkillFalloff);
ArcheryHitPenalty = RuleR.Get(Rule.ArcheryHitPenalty);
UseOldDamageIntervalRules = RuleB.Get(Rule.UseOldDamageIntervalRules);
CriticalMessageRange = RuleI.Get(Rule.CriticalDamage);
function MeleeMitigation(e)
e.IgnoreDefault = true;
@@ -237,9 +239,9 @@ function TryCriticalHit(e)
e.hit.damage_done = (e.hit.damage_done * SlayDmgBonus * 2.25) / 100;
if (self:GetGender() == 1) then
entity_list:FilteredMessageClose(self, false, 200, MT.CritMelee, Filter.MeleeCrits, string.format('%s\'s holy blade cleanses her target! (%d)', self:GetCleanName(), e.hit.damage_done));
entity_list:FilteredMessageClose(self, false, CriticalMessageRange, MT.CritMelee, Filter.MeleeCrits, string.format('%s\'s holy blade cleanses her target! (%d)', self:GetCleanName(), e.hit.damage_done));
else
entity_list:FilteredMessageClose(self, false, 200, MT.CritMelee, Filter.MeleeCrits, string.format('%s\'s holy blade cleanses his target! (%d)', self:GetCleanName(), e.hit.damage_done));
entity_list:FilteredMessageClose(self, false, CriticalMessageRange, MT.CritMelee, Filter.MeleeCrits, string.format('%s\'s holy blade cleanses his target! (%d)', self:GetCleanName(), e.hit.damage_done));
end
return e;
@@ -325,15 +327,15 @@ function TryCriticalHit(e)
end
if (crip_success) then
entity_list:FilteredMessageClose(self, false, 200, MT.CritMelee, Filter.MeleeCrits, string.format('%s lands a Crippling Blow! (%d)', self:GetCleanName(), e.hit.damage_done));
entity_list:FilteredMessageClose(self, false, CriticalMessageRange, MT.CritMelee, Filter.MeleeCrits, string.format('%s lands a Crippling Blow! (%d)', self:GetCleanName(), e.hit.damage_done));
if (defender:GetLevel() <= 55 and not defender:GetSpecialAbility(SpecialAbility.unstunable)) then
defender:Emote("staggers.");
defender:Stun(0);
end
elseif (deadlySuccess) then
entity_list:FilteredMessageClose(self, false, 200, MT.CritMelee, Filter.MeleeCrits, string.format('%s scores a Deadly Strike! (%d)', self:GetCleanName(), e.hit.damage_done));
entity_list:FilteredMessageClose(self, false, CriticalMessageRange, MT.CritMelee, Filter.MeleeCrits, string.format('%s scores a Deadly Strike! (%d)', self:GetCleanName(), e.hit.damage_done));
else
entity_list:FilteredMessageClose(self, false, 200, MT.CritMelee, Filter.MeleeCrits, string.format('%s scores a critical hit! (%d)', self:GetCleanName(), e.hit.damage_done));
entity_list:FilteredMessageClose(self, false, CriticalMessageRange, MT.CritMelee, Filter.MeleeCrits, string.format('%s scores a critical hit! (%d)', self:GetCleanName(), e.hit.damage_done));
end
end
end
@@ -376,11 +378,11 @@ function TryPetCriticalHit(self, defender, hit)
if(Random.RollReal(critChance)) then
local entity_list = eq.get_entity_list();
critMod = critMod + GetCritDmgMob(self, hit.skill) * 2;
critMod = critMod + GetCritDmgMod(self, hit.skill) * 2;
hit.damage_done = (hit.damage_done * critMod) / 100;
entity_list:FilteredMessageClose(this, false, 200,
entity_list:FilteredMessageClose(this, false, CriticalMessageRange,
MT.CritMelee, Filter.MeleeCrits, string.format('%s scores a critical hit! (%d)',
self:GetCleanName(), hit.damage_done));
self:GetCleanName(), e.hit.damage_done));
end
end
@@ -719,7 +721,7 @@ function GetDamageTable(attacker, skill)
if attacker:GetClass() == 7 then
local monkDamageTableBonus = 20;
return (dmg_table[GetLevel() - 50] * (100 + monkDamageTableBonus) / 100);
return (dmg_table[attacker:GetLevel() - 50] * (100 + monkDamageTableBonus) / 100);
else
return dmg_table[attacker:GetLevel() - 50];
end
@@ -749,4 +751,4 @@ function ApplyMeleeDamageBonus(e)
e.hit.damage_done = e.hit.damage_done + (e.hit.damage_done * dmgbonusmod / 100);
return e;
end
end
+2
View File
@@ -364,6 +364,8 @@
9108|2017_04_07_ignore_despawn.sql|SHOW COLUMNS FROM `npc_types` LIKE 'ignore_despawn'|empty|
9109|2017_04_08_doors_disable_timer.sql|SHOW COLUMNS FROM `doors` LIKE 'disable_timer'|empty|
9110|2017_04_10_graveyard.sql|show index from graveyard WHERE key_name = 'zone_id_nonunique'|empty|
9111|2017_06_24_saylink_index.sql|SHOW INDEX FROM `saylink` WHERE `key_name` = 'phrase_index'|empty|
9112|2017_06_24_rule_values_expand.sql|SHOW COLUMNS FROM rule_values WHERE Field = 'rule_value' and Type = 'varchar(30)'|empty|
# Upgrade conditions:
# This won't be needed after this system is implemented, but it is used database that are not
@@ -0,0 +1,2 @@
ALTER TABLE `rule_values`
MODIFY COLUMN `rule_value` varchar(30) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL DEFAULT '' AFTER `rule_name`;
@@ -0,0 +1,2 @@
ALTER TABLE `saylink`
ADD INDEX `phrase_index` (`phrase`) USING BTREE ;
+2
View File
@@ -332,6 +332,8 @@ int main(int argc, char** argv) {
database.ClearMerchantTemp();
}
RuleManager::Instance()->SaveRules(&database);
Log(Logs::General, Logs::World_Server, "Loading EQ time of day..");
TimeOfDay_Struct eqTime;
time_t realtime;
+1
View File
@@ -1285,6 +1285,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
case ServerOP_CZSignalNPC:
case ServerOP_CZSetEntityVariableByNPCTypeID:
case ServerOP_CZSignalClient:
case ServerOP_CZSetEntityVariableByClientName:
case ServerOP_WWMarquee:
case ServerOP_DepopAllPlayersCorpses:
case ServerOP_DepopPlayerCorpse:
+15 -3
View File
@@ -53,9 +53,8 @@ extern WorldServer worldserver;
extern EntityList entity_list;
extern Zone* zone;
EQEmu::skills::SkillType Mob::AttackAnimation(int Hand, const EQEmu::ItemInstance* weapon)
EQEmu::skills::SkillType Mob::AttackAnimation(int Hand, const EQEmu::ItemInstance* weapon, EQEmu::skills::SkillType skillinuse)
{
EQEmu::skills::SkillType skillinuse = EQEmu::skills::Skill1HBlunt;
// Determine animation
int type = 0;
if (weapon && weapon->IsClassCommon()) {
@@ -1924,7 +1923,7 @@ bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool
//do attack animation regardless of whether or not we can hit below
int16 charges = 0;
EQEmu::ItemInstance weapon_inst(weapon, charges);
my_hit.skill = AttackAnimation(Hand, &weapon_inst);
my_hit.skill = AttackAnimation(Hand, &weapon_inst, my_hit.skill);
//basically "if not immune" then do the attack
if (weapon_damage > 0) {
@@ -2388,6 +2387,19 @@ bool NPC::Death(Mob* killer_mob, int32 damage, uint16 spell, EQEmu::skills::Skil
entity_list.UnMarkNPC(GetID());
entity_list.RemoveNPC(GetID());
/* Fix Z on Corpse Creation */
glm::vec3 dest(m_Position.x, m_Position.y, m_Position.z);
float new_z = zone->zonemap->FindBestZ(dest, nullptr);
corpse->SetFlyMode(1);
float size = GetSize();
if (size > 10)
size = 10;
new_z += size / 2;
corpse->GMMove(m_Position.x, m_Position.y, new_z, m_Position.w);
this->SetID(0);
if (killer != 0 && emoteid != 0)
+5 -2
View File
@@ -68,6 +68,7 @@ Beacon::Beacon(Mob *at_mob, int lifetime)
resist_adjust = 0;
spell_iterations = 0;
caster_id = 0;
max_targets = 4; // default
if(lifetime)
remove_timer.Start();
@@ -93,10 +94,10 @@ bool Beacon::Process()
)
{
Mob *caster = entity_list.GetMob(caster_id);
if(caster && spell_iterations--)
if(caster && spell_iterations-- && max_targets)
{
bool affect_caster = (!caster->IsNPC() && !caster->IsAIControlled()); //NPC AE spells do not affect the NPC caster
entity_list.AESpell(caster, this, spell_id, affect_caster, resist_adjust);
entity_list.AESpell(caster, this, spell_id, affect_caster, resist_adjust, &max_targets);
}
else
{
@@ -126,6 +127,8 @@ void Beacon::AELocationSpell(Mob *caster, uint16 cast_spell_id, int16 resist_adj
this->resist_adjust = resist_adjust;
spell_iterations = spells[spell_id].AEDuration / 2500;
spell_iterations = spell_iterations < 1 ? 1 : spell_iterations; // at least 1
if (spells[spell_id].aemaxtargets)
max_targets = spells[spell_id].aemaxtargets;
spell_timer.Start(2500);
spell_timer.Trigger();
}
+1
View File
@@ -56,6 +56,7 @@ protected:
int16 resist_adjust;
int spell_iterations;
Timer spell_timer;
int max_targets;
uint16 caster_id;
private:
+8
View File
@@ -48,6 +48,14 @@ void Mob::CalcBonuses()
SetAttackTimer();
CalcAC();
/* Fast walking NPC's are prone to disappear into walls/hills
We set this here because NPC's can cast spells to change walkspeed/runspeed
*/
float get_walk_speed = static_cast<float>(0.025f * this->GetWalkspeed());
if (get_walk_speed >= 0.9 && this->fix_z_timer.GetDuration() != 100) {
this->fix_z_timer.SetTimer(100);
}
rooted = FindType(SE_Root);
}
+4 -2
View File
@@ -2368,7 +2368,9 @@ bool Client::CheckIncreaseSkill(EQEmu::skills::SkillType skillid, Mob *against_w
return false;
int skillval = GetRawSkill(skillid);
int maxskill = GetMaxSkillAfterSpecializationRules(skillid, MaxSkill(skillid));
char buffer[24] = { 0 };
snprintf(buffer, 23, "%d %d", skillid, skillval);
parse->EventPlayer(EVENT_USE_SKILL, this, buffer, 0);
if(against_who)
{
if(against_who->GetSpecialAbility(IMMUNE_AGGRO) || against_who->IsClient() ||
@@ -4240,7 +4242,7 @@ bool Client::GroupFollow(Client* inviter) {
RemoveAutoXTargets();
}
SetXTargetAutoMgr(GetXTargetAutoMgr());
SetXTargetAutoMgr(raid->GetXTargetAutoMgr());
if (!GetXTargetAutoMgr()->empty())
SetDirtyAutoHaters();
-1
View File
@@ -2075,7 +2075,6 @@ void Client::Handle_OP_AdventureMerchantRequest(const EQApplicationPacket *app)
return;
merchantid = tmp->CastToNPC()->MerchantType;
tmp->CastToNPC()->FaceTarget(this->CastToMob());
const EQEmu::ItemData *item = nullptr;
std::list<MerchantList> merlist = zone->merchanttable[merchantid];
-2
View File
@@ -985,8 +985,6 @@ void Client::BulkSendMerchantInventory(int merchant_id, int npcid) {
Message_StringID(10, GENERIC_STRINGID_SAY, merch->GetCleanName(), handy_id, this->GetName(), handyitem->Name);
else
Message_StringID(10, GENERIC_STRINGID_SAY, merch->GetCleanName(), handy_id, this->GetName());
merch->CastToNPC()->FaceTarget(this->CastToMob());
}
// safe_delete_array(cpi);
+3 -3
View File
@@ -8734,9 +8734,9 @@ void command_object(Client *c, const Seperator *sep)
// Verify no other objects already in this spot (accidental double-click of Hotkey?)
query = StringFormat(
"SELECT COUNT(*) FROM object WHERE zoneid = %u "
"AND version=%u AND (posx BETWEEN %.1f AND %.1f) "
"AND (posy BETWEEN %.1f AND %.1f) "
"AND (posz BETWEEN %.1f AND %.1f)",
"AND version=%u AND (xpos BETWEEN %.1f AND %.1f) "
"AND (ypos BETWEEN %.1f AND %.1f) "
"AND (zpos BETWEEN %.1f AND %.1f)",
zone->GetZoneID(), zone->GetInstanceVersion(), od.x - 0.2f,
od.x + 0.2f, // Yes, we're actually using a bounding box instead of a radius.
od.y - 0.2f, od.y + 0.2f, // Much less processing power used this way.
+29 -24
View File
@@ -694,7 +694,7 @@ void EntityList::AETaunt(Client* taunter, float range, int32 bonus_hate)
// causes caster to hit every mob within dist range of center with
// spell_id.
// NPC spells will only affect other NPCs with compatible faction
void EntityList::AESpell(Mob *caster, Mob *center, uint16 spell_id, bool affect_caster, int16 resist_adjust)
void EntityList::AESpell(Mob *caster, Mob *center, uint16 spell_id, bool affect_caster, int16 resist_adjust, int *max_targets)
{
Mob *curmob = nullptr;
@@ -703,12 +703,25 @@ void EntityList::AESpell(Mob *caster, Mob *center, uint16 spell_id, bool affect_
float min_range2 = spells[spell_id].min_range * spells[spell_id].min_range;
float dist_targ = 0;
const auto &position = spells[spell_id].targettype == ST_Ring ? caster->GetTargetRingLocation() : static_cast<glm::vec3>(center->GetPosition());
glm::vec2 min = { position.x - dist, position.y - dist };
glm::vec2 max = { position.x + dist, position.y + dist };
bool bad = IsDetrimentalSpell(spell_id);
bool isnpc = caster->IsNPC();
int MAX_TARGETS_ALLOWED = 4;
if (spells[spell_id].aemaxtargets)
MAX_TARGETS_ALLOWED = spells[spell_id].aemaxtargets;
if (RuleB(Spells, OldRainTargets))
max_targets = nullptr; // ignore it!
// if we have a passed in value, use it, otherwise default to data
// detrimental Target AEs have a default value of 4 for PCs and unlimited for NPCs
int max_targets_allowed = 0; // unlimited
if (max_targets) // rains pass this in since they need to preserve the count through waves
max_targets_allowed = *max_targets;
else if (spells[spell_id].aemaxtargets)
max_targets_allowed = spells[spell_id].aemaxtargets;
else if (IsTargetableAESpell(spell_id) && bad && !isnpc)
max_targets_allowed = 4;
int iCounter = 0;
@@ -732,13 +745,10 @@ void EntityList::AESpell(Mob *caster, Mob *center, uint16 spell_id, bool affect_
continue;
if (spells[spell_id].pcnpc_only_flag == 2 && (curmob->IsClient() || curmob->IsMerc()))
continue;
if (!IsWithinAxisAlignedBox(static_cast<glm::vec2>(curmob->GetPosition()), min, max))
continue;
if (spells[spell_id].targettype == ST_Ring) {
dist_targ = DistanceSquared(static_cast<glm::vec3>(curmob->GetPosition()), caster->GetTargetRingLocation());
}
else if (center) {
dist_targ = DistanceSquared(curmob->GetPosition(), center->GetPosition());
}
dist_targ = DistanceSquared(curmob->GetPosition(), position);
if (dist_targ > dist2) //make sure they are in range
continue;
@@ -761,7 +771,7 @@ void EntityList::AESpell(Mob *caster, Mob *center, uint16 spell_id, bool affect_
if (bad) {
if (!caster->IsAttackAllowed(curmob, true))
continue;
if (center && !spells[spell_id].npc_no_los && !center->CheckLosFN(curmob))
if (center && !spells[spell_id].npc_no_los && !center->CheckLosFN(curmob))
continue;
if (!center && !spells[spell_id].npc_no_los && !caster->CheckLosFN(caster->GetTargetRingX(), caster->GetTargetRingY(), caster->GetTargetRingZ(), curmob->GetSize()))
continue;
@@ -776,22 +786,17 @@ void EntityList::AESpell(Mob *caster, Mob *center, uint16 spell_id, bool affect_
}
curmob->CalcSpellPowerDistanceMod(spell_id, dist_targ);
caster->SpellOnTarget(spell_id, curmob, false, true, resist_adjust);
//if we get here... cast the spell.
if (IsTargetableAESpell(spell_id) && bad) {
if (iCounter < MAX_TARGETS_ALLOWED) {
caster->SpellOnTarget(spell_id, curmob, false, true, resist_adjust);
}
} else {
if (spells[spell_id].aemaxtargets && iCounter < spells[spell_id].aemaxtargets)
caster->SpellOnTarget(spell_id, curmob, false, true, resist_adjust);
if (!spells[spell_id].aemaxtargets)
caster->SpellOnTarget(spell_id, curmob, false, true, resist_adjust);
}
if (!isnpc || spells[spell_id].aemaxtargets) //npcs are not target limited (unless casting a spell with a target limit)...
if (max_targets_allowed) { // if we have a limit, increment count
iCounter++;
if (iCounter >= max_targets_allowed) // we done
break;
}
}
if (max_targets && max_targets_allowed)
*max_targets = *max_targets - iCounter;
}
void EntityList::MassGroupBuff(Mob *caster, Mob *center, uint16 spell_id, bool affect_caster)
+7
View File
@@ -117,6 +117,7 @@ const char *QuestEventSubroutines[_LargestEventID] = {
"EVENT_TICK",
"EVENT_SPAWN_ZONE",
"EVENT_DEATH_ZONE",
"EVENT_USE_SKILL",
};
PerlembParser::PerlembParser() : perl(nullptr) {
@@ -1441,6 +1442,12 @@ void PerlembParser::ExportEventVariables(std::string &package_name, QuestEventID
ExportVar(package_name.c_str(), "killed_npc_id", sep.arg[4]);
break;
}
case EVENT_USE_SKILL:{
Seperator sep(data);
ExportVar(package_name.c_str(), "skill_id", sep.arg[0]);
ExportVar(package_name.c_str(), "skill_level", sep.arg[1]);
break;
}
default: {
break;
+19
View File
@@ -3599,6 +3599,24 @@ XS(XS__crosszonesetentityvariablebynpctypeid)
XSRETURN_EMPTY;
}
XS(XS__crosszonesetentityvariablebyclientname);
XS(XS__crosszonesetentityvariablebyclientname)
{
dXSARGS;
if (items != 3)
Perl_croak(aTHX_ "Usage: crosszonesetentityvariablebyclientname(clientname, id, m_var)");
if (items == 3) {
const char *clientname = (const char *)SvPV_nolen(ST(0));
const char *id = (const char *)SvPV_nolen(ST(1));
const char *m_var = (const char *)SvPV_nolen(ST(2));
quest_manager.CrossZoneSetEntityVariableByClientName(clientname, id, m_var);
}
XSRETURN_EMPTY;
}
XS(XS__crosszonesignalnpcbynpctypeid);
XS(XS__crosszonesignalnpcbynpctypeid)
{
@@ -3766,6 +3784,7 @@ EXTERN_C XS(boot_quest)
newXS(strcpy(buf, "createguild"), XS__createguild, file);
newXS(strcpy(buf, "crosszonemessageplayerbyname"), XS__crosszonemessageplayerbyname, file);
newXS(strcpy(buf, "crosszonesetentityvariablebynpctypeid"), XS__crosszonesetentityvariablebynpctypeid, file);
newXS(strcpy(buf, "crosszonesetentityvariablebyclientname"), XS__crosszonesetentityvariablebyclientname, file);
newXS(strcpy(buf, "crosszonesignalclientbycharid"), XS__crosszonesignalclientbycharid, file);
newXS(strcpy(buf, "crosszonesignalclientbyname"), XS__crosszonesignalclientbyname, file);
newXS(strcpy(buf, "crosszonesignalnpcbynpctypeid"), XS__crosszonesignalnpcbynpctypeid, file);
+1 -1
View File
@@ -357,7 +357,7 @@ public:
void AEAttack(Mob *attacker, float dist, int Hand = EQEmu::inventory::slotPrimary, int count = 0, bool IsFromSpell = false);
void AETaunt(Client *caster, float range=0, int32 bonus_hate=0);
void AESpell(Mob *caster, Mob *center, uint16 spell_id, bool affect_caster = true, int16 resist_adjust = 0);
void AESpell(Mob *caster, Mob *center, uint16 spell_id, bool affect_caster = true, int16 resist_adjust = 0, int *max_targets = nullptr);
void MassGroupBuff(Mob *caster, Mob *center, uint16 spell_id, bool affect_caster = true);
void AEBardPulse(Mob *caster, Mob *center, uint16 spell_id, bool affect_caster = true);
+1
View File
@@ -85,6 +85,7 @@ typedef enum {
EVENT_TICK,
EVENT_SPAWN_ZONE,
EVENT_DEATH_ZONE,
EVENT_USE_SKILL,
_LargestEventID
} QuestEventID;
+1 -1
View File
@@ -196,7 +196,7 @@ bool Client::CanFish() {
float step_size = RuleR(Watermap, FishingLineStepSize);
for(float i = 0.0f; i < len; i += step_size) {
for(float i = 0.0f; i < LineLength; i += step_size) {
glm::vec3 dest(rodPosition.x, rodPosition.y, m_Position.z - i);
bool in_lava = zone->watermap->InLava(dest);
+7 -1
View File
@@ -900,6 +900,10 @@ void lua_cross_zone_message_player_by_name(uint32 type, const char *player, cons
quest_manager.CrossZoneMessagePlayerByName(type, player, message);
}
void lua_cross_zone_set_entity_variable_by_client_name(const char *player, const char *id, const char *m_var) {
quest_manager.CrossZoneSetEntityVariableByClientName(player, id, m_var);
}
void lua_world_wide_marquee(uint32 type, uint32 priority, uint32 fadein, uint32 fadeout, uint32 duration, const char *message) {
quest_manager.WorldWideMarquee(type, priority, fadein, fadeout, duration, message);
}
@@ -1658,6 +1662,7 @@ luabind::scope lua_register_general() {
luabind::def("cross_zone_signal_client_by_char_id", &lua_cross_zone_signal_client_by_char_id),
luabind::def("cross_zone_signal_client_by_name", &lua_cross_zone_signal_client_by_name),
luabind::def("cross_zone_message_player_by_name", &lua_cross_zone_message_player_by_name),
luabind::def("cross_zone_set_entity_variable_by_client_name", &lua_cross_zone_set_entity_variable_by_client_name),
luabind::def("world_wide_marquee", &lua_world_wide_marquee),
luabind::def("get_qglobals", (luabind::adl::object(*)(lua_State*,Lua_NPC,Lua_Client))&lua_get_qglobals),
luabind::def("get_qglobals", (luabind::adl::object(*)(lua_State*,Lua_Client))&lua_get_qglobals),
@@ -1791,7 +1796,8 @@ luabind::scope lua_register_events() {
luabind::value("unhandled_opcode", static_cast<int>(EVENT_UNHANDLED_OPCODE)),
luabind::value("tick", static_cast<int>(EVENT_TICK)),
luabind::value("spawn_zone", static_cast<int>(EVENT_SPAWN_ZONE)),
luabind::value("death_zone", static_cast<int>(EVENT_DEATH_ZONE))
luabind::value("death_zone", static_cast<int>(EVENT_DEATH_ZONE)),
luabind::value("use_skill", static_cast<int>(EVENT_USE_SKILL))
];
}
+3 -1
View File
@@ -122,7 +122,8 @@ const char *LuaEvents[_LargestEventID] = {
"event_unhandled_opcode",
"event_tick",
"event_spawn_zone",
"event_death_zone"
"event_death_zone",
"event_use_skill"
};
extern Zone *zone;
@@ -204,6 +205,7 @@ LuaParser::LuaParser() {
PlayerArgumentDispatch[EVENT_LEAVE_AREA] = handle_player_area;
PlayerArgumentDispatch[EVENT_RESPAWN] = handle_player_respawn;
PlayerArgumentDispatch[EVENT_UNHANDLED_OPCODE] = handle_player_packet;
PlayerArgumentDispatch[EVENT_USE_SKILL] = handle_player_use_skill;
ItemArgumentDispatch[EVENT_ITEM_CLICK] = handle_item_click;
ItemArgumentDispatch[EVENT_ITEM_CLICK_CAST] = handle_item_click;
+9
View File
@@ -505,6 +505,15 @@ void handle_player_null(QuestInterface *parse, lua_State* L, Client* client, std
std::vector<EQEmu::Any> *extra_pointers) {
}
void handle_player_use_skill(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, std::vector<EQEmu::Any> *extra_pointers) {
Seperator sep(data.c_str());
lua_pushinteger(L, std::stoi(sep.arg[0]));
lua_setfield(L, -2, "skill_id");
lua_pushinteger(L, std::stoi(sep.arg[1]));
lua_setfield(L, -2, "skill_level");
}
//Item
void handle_item_click(QuestInterface *parse, lua_State* L, Client* client, EQEmu::ItemInstance* item, Mob *mob, std::string data, uint32 extra_data,
std::vector<EQEmu::Any> *extra_pointers) {
+2
View File
@@ -95,6 +95,8 @@ void handle_player_packet(QuestInterface *parse, lua_State* L, Client* client, s
std::vector<EQEmu::Any> *extra_pointers);
void handle_player_null(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data,
std::vector<EQEmu::Any> *extra_pointers);
void handle_player_use_skill(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data,
std::vector<EQEmu::Any> *extra_pointers);
//Item
void handle_item_click(QuestInterface *parse, lua_State* L, Client* client, EQEmu::ItemInstance* item, Mob *mob, std::string data, uint32 extra_data,
+8 -2
View File
@@ -112,13 +112,17 @@ Mob::Mob(const char* in_name,
m_Position(position),
tmHidden(-1),
mitigation_ac(0),
m_specialattacks(eSpecialAttacks::None)
m_specialattacks(eSpecialAttacks::None),
fix_z_timer(300),
fix_z_timer_engaged(100)
{
targeted = 0;
tar_ndx=0;
tar_vector=0;
currently_fleeing = false;
last_z = 0;
AI_Init();
SetMoving(false);
moved=false;
@@ -3829,7 +3833,7 @@ int32 Mob::GetVulnerability(Mob* caster, uint32 spell_id, uint32 ticsremaining)
if((IsValidSpell(buffs[i].spellid) && IsEffectInSpell(buffs[i].spellid, SE_FcSpellVulnerability))){
int32 focus = caster->CalcFocusEffect(focusSpellVulnerability, buffs[i].spellid, spell_id);
int32 focus = caster->CalcFocusEffect(focusSpellVulnerability, buffs[i].spellid, spell_id, true);
if (!focus)
continue;
@@ -3847,6 +3851,8 @@ int32 Mob::GetVulnerability(Mob* caster, uint32 spell_id, uint32 ticsremaining)
}
}
tmp_focus = caster->CalcFocusEffect(focusSpellVulnerability, buffs[tmp_buffslot].spellid, spell_id);
if (tmp_focus < -99)
tmp_focus = -99;
+6 -1
View File
@@ -233,7 +233,7 @@ public:
inline bool SeeImprovedHide() const { return see_improved_hide; }
bool IsInvisible(Mob* other = 0) const;
void SetInvisible(uint8 state);
EQEmu::skills::SkillType AttackAnimation(int Hand, const EQEmu::ItemInstance* weapon);
EQEmu::skills::SkillType AttackAnimation(int Hand, const EQEmu::ItemInstance* weapon, EQEmu::skills::SkillType skillinuse = EQEmu::skills::Skill1HBlunt);
//Song
bool UseBardSpellLogic(uint16 spell_id = 0xffff, int slot = -1);
@@ -913,6 +913,7 @@ public:
float GetGroundZ(float new_x, float new_y, float z_offset=0.0);
void SendTo(float new_x, float new_y, float new_z);
void SendToFixZ(float new_x, float new_y, float new_z);
void FixZ();
void NPCSpecialAttacks(const char* parse, int permtag, bool reset = true, bool remove = false);
inline uint32 DontHealMeBefore() const { return pDontHealMeBefore; }
inline uint32 DontBuffMeBefore() const { return pDontBuffMeBefore; }
@@ -1066,6 +1067,8 @@ public:
int GetWeaponDamage(Mob *against, const EQEmu::ItemData *weapon_item);
int GetWeaponDamage(Mob *against, const EQEmu::ItemInstance *weapon_item, uint32 *hate = nullptr);
float last_z;
// Bots HealRotation methods
#ifdef BOTS
bool IsHealRotationTarget() { return (m_target_of_heal_rotation.use_count() && m_target_of_heal_rotation.get()); }
@@ -1373,6 +1376,8 @@ protected:
bool flee_mode;
Timer flee_timer;
Timer fix_z_timer;
Timer fix_z_timer_engaged;
bool pAIControlled;
bool roamer;
+11
View File
@@ -743,6 +743,10 @@ void Client::AI_Process()
if(RuleB(Combat, EnableFearPathing)){
if(currently_fleeing) {
if (fix_z_timer_engaged.Check())
this->FixZ();
if(IsRooted()) {
//make sure everybody knows were not moving, for appearance sake
if(IsMoving())
@@ -782,6 +786,7 @@ void Client::AI_Process()
}
return;
}
}
}
@@ -991,6 +996,12 @@ void Mob::AI_Process() {
if (engaged) {
/* Fix Z when following during pull, not when engaged and stationary */
if (moving && fix_z_timer_engaged.Check())
if(this->GetTarget())
if(DistanceNoZ(this->GetPosition(), this->GetTarget()->GetPosition()) > 50)
this->FixZ();
if (!(m_PlayerState & static_cast<uint32>(PlayerState::Aggressive)))
SendAddPlayerState(PlayerState::Aggressive);
// we are prevented from getting here if we are blind and don't have a target in range
+3
View File
@@ -541,6 +541,9 @@ int main(int argc, char** argv) {
if (previous_loaded && !current_loaded) {
process_timer.Stop();
process_timer.Start(1000, true);
uint32 shutdown_timer = database.getZoneShutDownDelay(zone->GetZoneID(), zone->GetInstanceVersion());
zone->StartShutdownTimer(shutdown_timer);
}
else if (!previous_loaded && current_loaded) {
process_timer.Stop();
+18 -10
View File
@@ -2743,20 +2743,14 @@ const char* QuestManager::saylink(char* Phrase, bool silent, const char* LinkNam
if (results.RowCount() >= 1) {
for (auto row = results.begin();row != results.end(); ++row)
sayid = atoi(row[0]);
} else { // Add a new saylink entry to the database and query it again for the new sayid number
} else {
std::string insert_query = StringFormat("INSERT INTO `saylink` (`phrase`) VALUES ('%s')", escaped_string);
results = database.QueryDatabase(insert_query);
if (!results.Success()) {
Log(Logs::General, Logs::Error, "Error in saylink phrase queries", results.ErrorMessage().c_str());
} else {
results = database.QueryDatabase(query);
if (results.Success()) {
if (results.RowCount() >= 1)
for(auto row = results.begin(); row != results.end(); ++row)
sayid = atoi(row[0]);
} else {
Log(Logs::General, Logs::Error, "Error in saylink phrase queries", results.ErrorMessage().c_str());
}
}
else {
sayid = results.LastInsertedID();
}
}
}
@@ -3004,6 +2998,20 @@ void QuestManager::CrossZoneMessagePlayerByName(uint32 Type, const char *CharNam
safe_delete(pack);
}
void QuestManager::CrossZoneSetEntityVariableByClientName(const char *CharName, const char *id, const char *m_var){
uint32 message_len = strlen(id) + 1;
uint32 message_len2 = strlen(m_var) + 1;
uint32 message_len3 = strlen(CharName) + 1;
auto pack = new ServerPacket(ServerOP_CZSetEntityVariableByClientName,
sizeof(CZSetEntVarByClientName_Struct) + message_len + message_len2 + message_len3);
CZSetEntVarByClientName_Struct* CZ = (CZSetEntVarByClientName_Struct*)pack->pBuffer;
strn0cpy(CZ->CharName, CharName, 64);
strn0cpy(CZ->id, id, 256);
strn0cpy(CZ->m_var, m_var, 256);
worldserver.SendPacket(pack);
safe_delete(pack);
}
void QuestManager::CrossZoneSetEntityVariableByNPCTypeID(uint32 npctype_id, const char *id, const char *m_var){
uint32 message_len = strlen(id) + 1;
uint32 message_len2 = strlen(m_var) + 1;
+1
View File
@@ -255,6 +255,7 @@ public:
void CrossZoneSignalNPCByNPCTypeID(uint32 npctype_id, uint32 data);
void CrossZoneSignalPlayerByName(const char *CharName, uint32 data);
void CrossZoneSetEntityVariableByNPCTypeID(uint32 npctype_id, const char *id, const char *m_var);
void CrossZoneSetEntityVariableByClientName(const char *CharName, const char *id, const char *m_var);
void CrossZoneMessagePlayerByName(uint32 Type, const char *CharName, const char *Message);
void WorldWideMarquee(uint32 Type, uint32 Priority, uint32 FadeIn, uint32 FadeOut, uint32 Duration, const char *Message);
bool EnableRecipe(uint32 recipe_id);
+28 -11
View File
@@ -277,12 +277,8 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove
caster->SetMana(0);
} else if (spell_id == 2755 && caster) //Lifeburn
{
dmg = caster->GetHP()*-15/10;
caster->SetHP(1);
if(caster->IsClient()){
caster->CastToClient()->SetFeigned(true);
caster->SendAppearancePacket(AT_Anim, 115);
}
dmg = -1 * caster->GetHP(); // just your current HP or should it be Max 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?
}
//do any AAs apply to these spells?
@@ -5024,8 +5020,19 @@ int16 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo
break;
case SE_SpellResistReduction:
if (type == focusResistRate && focus_spell.base[i] > value)
value = focus_spell.base[i];
if (type == focusResistRate) {
if (best_focus) {
if (focus_spell.base2[i] != 0) {
value = focus_spell.base2[i];
} else {
value = focus_spell.base[i];
}
} else if (focus_spell.base2[i] == 0 || focus_spell.base[i] == focus_spell.base2[i]) {
value = focus_spell.base[i];
} else {
value = zone->random.Int(focus_spell.base[i], focus_spell.base2[i]);
}
}
break;
case SE_SpellHateMod:
@@ -5071,8 +5078,18 @@ int16 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo
break;
case SE_FcSpellVulnerability:
if (type == focusSpellVulnerability)
value = focus_spell.base[i];
if (type == focusSpellVulnerability) {
if (best_focus) {
if (focus_spell.base2[i] != 0)
value = focus_spell.base2[i];
else
value = focus_spell.base[i];
} else if (focus_spell.base2[i] == 0 || focus_spell.base[i] == focus_spell.base2[i]) {
value = focus_spell.base[i];
} else {
value = zone->random.Int(focus_spell.base[i], focus_spell.base2[i]);
}
}
break;
case SE_FcTwincast:
@@ -5319,7 +5336,7 @@ int16 Client::GetFocusEffect(focusType type, uint16 spell_id)
//Improved Healing, Damage & Mana Reduction are handled differently in that some are random percentages
//In these cases we need to find the most powerful effect, so that each piece of gear wont get its own chance
if(RuleB(Spells, LiveLikeFocusEffects) && (type == focusManaCost || type == focusImprovedHeal || type == focusImprovedDamage || type == focusImprovedDamage2))
if(RuleB(Spells, LiveLikeFocusEffects) && (type == focusManaCost || type == focusImprovedHeal || type == focusImprovedDamage || type == focusImprovedDamage2 || type == focusResistRate))
rand_effectiveness = true;
//Check if item focus effect exists for the client.
+1 -15
View File
@@ -2237,21 +2237,7 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, CastingSlot slot, ui
// special ae duration spell
ae_center->CastToBeacon()->AELocationSpell(this, spell_id, resist_adjust);
} else {
// regular PB AE or targeted AE spell - spell_target is null if PB
if(spell_target) // this must be an AETarget spell
{
bool cast_on_target = true;
if (spells[spell_id].targettype == ST_TargetAENoPlayersPets && spell_target->IsPetOwnerClient())
cast_on_target = false;
if (spells[spell_id].targettype == ST_AreaClientOnly && !spell_target->IsClient())
cast_on_target = false;
if (spells[spell_id].targettype == ST_AreaNPCOnly && !spell_target->IsNPC())
cast_on_target = false;
// affect the target too
if (cast_on_target)
SpellOnTarget(spell_id, spell_target, false, true, resist_adjust);
}
// unsure if we actually need this? Need to find some spell examples
if(ae_center && ae_center == this && IsBeneficialSpell(spell_id))
SpellOnTarget(spell_id, this);
+58 -101
View File
@@ -513,39 +513,8 @@ bool Mob::MakeNewPositionAndSendUpdate(float x, float y, float z, int speed, boo
m_Position.y = new_y;
m_Position.z = new_z;
uint8 NPCFlyMode = 0;
if (IsNPC()) {
if (CastToNPC()->GetFlyMode() == 1 || CastToNPC()->GetFlyMode() == 2)
NPCFlyMode = 1;
}
//fix up pathing Z
if (!NPCFlyMode && checkZ && zone->HasMap() && RuleB(Map, FixPathingZWhenMoving))
{
if (!RuleB(Watermap, CheckForWaterWhenMoving) || !zone->HasWaterMap() ||
(zone->HasWaterMap() && !zone->watermap->InWater(glm::vec3(m_Position))))
{
glm::vec3 dest(m_Position.x, m_Position.y, m_Position.z);
float newz = zone->zonemap->FindBestZ(dest, nullptr) + 2.0f;
if ((newz > -2000) &&
std::abs(newz - dest.z) < RuleR(Map, FixPathingZMaxDeltaMoving)) // Sanity check.
{
if ((std::abs(x - m_Position.x) < 0.5) &&
(std::abs(y - m_Position.y) < 0.5)) {
if (std::abs(z - m_Position.z) <=
RuleR(Map, FixPathingZMaxDeltaMoving))
m_Position.z = z;
else
m_Position.z = newz + 1;
}
else
m_Position.z = newz + 1;
}
}
}
if(fix_z_timer.Check())
this->FixZ();
tar_ndx++;
return true;
@@ -651,37 +620,8 @@ bool Mob::MakeNewPositionAndSendUpdate(float x, float y, float z, int speed, boo
m_Position.w = CalculateHeadingToTarget(x, y);
}
uint8 NPCFlyMode = 0;
if (IsNPC()) {
if (CastToNPC()->GetFlyMode() == 1 || CastToNPC()->GetFlyMode() == 2)
NPCFlyMode = 1;
}
//fix up pathing Z
if (!NPCFlyMode && checkZ && zone->HasMap() && RuleB(Map, FixPathingZWhenMoving)) {
if (!RuleB(Watermap, CheckForWaterWhenMoving) || !zone->HasWaterMap() ||
(zone->HasWaterMap() && !zone->watermap->InWater(glm::vec3(m_Position))))
{
glm::vec3 dest(m_Position.x, m_Position.y, m_Position.z);
float newz = zone->zonemap->FindBestZ(dest, nullptr);
if ((newz > -2000) &&
std::abs(newz - dest.z) < RuleR(Map, FixPathingZMaxDeltaMoving)) // Sanity check.
{
if (std::abs(x - m_Position.x) < 0.5 && std::abs(y - m_Position.y) < 0.5) {
if (std::abs(z - m_Position.z) <= RuleR(Map, FixPathingZMaxDeltaMoving))
m_Position.z = z;
else
m_Position.z = newz + 1;
}
else
m_Position.z = newz + 1;
}
}
}
if (fix_z_timer.Check())
this->FixZ();
SetMoving(true);
moved = true;
@@ -769,39 +709,8 @@ bool Mob::CalculateNewPosition(float x, float y, float z, int speed, bool checkZ
Log(Logs::Detail, Logs::AI, "Next position (%.3f, %.3f, %.3f)", m_Position.x, m_Position.y, m_Position.z);
}
uint8 NPCFlyMode = 0;
if (IsNPC()) {
if (CastToNPC()->GetFlyMode() == 1 || CastToNPC()->GetFlyMode() == 2)
NPCFlyMode = 1;
}
//fix up pathing Z
if (!NPCFlyMode && checkZ && zone->HasMap() && RuleB(Map, FixPathingZWhenMoving))
{
if (!RuleB(Watermap, CheckForWaterWhenMoving) || !zone->HasWaterMap() ||
(zone->HasWaterMap() && !zone->watermap->InWater(glm::vec3(m_Position))))
{
glm::vec3 dest(m_Position.x, m_Position.y, m_Position.z);
float newz = zone->zonemap->FindBestZ(dest, nullptr) + 2.0f;
Log(Logs::Detail, Logs::AI, "BestZ returned %4.3f at %4.3f, %4.3f, %4.3f", newz, m_Position.x, m_Position.y, m_Position.z);
if ((newz > -2000) &&
std::abs(newz - dest.z) < RuleR(Map, FixPathingZMaxDeltaMoving)) // Sanity check.
{
if (std::abs(x - m_Position.x) < 0.5 && std::abs(y - m_Position.y) < 0.5) {
if (std::abs(z - m_Position.z) <= RuleR(Map, FixPathingZMaxDeltaMoving))
m_Position.z = z;
else
m_Position.z = newz + 1;
}
else
m_Position.z = newz + 1;
}
}
}
if (fix_z_timer.Check())
this->FixZ();
//OP_MobUpdate
if ((old_test_vector != test_vector) || tar_ndx>20) { //send update
@@ -943,9 +852,6 @@ void Mob::SendToFixZ(float new_x, float new_y, float new_z) {
m_Position.y = new_y;
m_Position.z = new_z + 0.1;
//fix up pathing Z, this shouldent be needed IF our waypoints
//are corrected instead
if (zone->HasMap() && RuleB(Map, FixPathingZOnSendTo))
{
if (!RuleB(Watermap, CheckForWaterOnSendTo) || !zone->HasWaterMap() ||
@@ -955,7 +861,7 @@ void Mob::SendToFixZ(float new_x, float new_y, float new_z) {
float newz = zone->zonemap->FindBestZ(dest, nullptr);
Log(Logs::Detail, Logs::AI, "BestZ returned %4.3f at %4.3f, %4.3f, %4.3f", newz, m_Position.x, m_Position.y, m_Position.z);
Log(Logs::Moderate, Logs::Pathing, "BestZ returned %4.3f at %4.3f, %4.3f, %4.3f", newz, m_Position.x, m_Position.y, m_Position.z);
if ((newz > -2000) && std::abs(newz - dest.z) < RuleR(Map, FixPathingZMaxDeltaSendTo)) // Sanity check.
m_Position.z = newz + 1;
@@ -963,6 +869,57 @@ void Mob::SendToFixZ(float new_x, float new_y, float new_z) {
}
}
void Mob::FixZ() {
BenchTimer timer;
timer.reset();
if (zone->HasMap() && RuleB(Map, FixZWhenMoving) && (flymode != 1 && flymode != 2))
{
if (!RuleB(Watermap, CheckForWaterWhenMoving) || !zone->HasWaterMap() ||
(zone->HasWaterMap() && !zone->watermap->InWater(glm::vec3(m_Position))))
{
float new_z = this->FindGroundZ(m_Position.x, m_Position.y, 10);
auto duration = timer.elapsed();
Log(
Logs::Moderate,
Logs::Pathing,
"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
);
float size = GetSize();
if (size > 10)
size = 10;
new_z += size / 2;
if ((new_z > -2000) && std::abs(m_Position.z - new_z) < 35) {
if (RuleB(Map, MobZVisualDebug))
this->SendAppearanceEffect(78, 0, 0, 0, 0);
m_Position.z = new_z;
}
else {
if (RuleB(Map, MobZVisualDebug))
this->SendAppearanceEffect(103, 0, 0, 0, 0);
Log(Logs::General, Logs::Debug, "%s is failing to find Z %f", this->GetCleanName(), std::abs(m_Position.z - new_z));
}
last_z = m_Position.z;
}
}
}
int ZoneDatabase::GetHighestGrid(uint32 zoneid) {
std::string query = StringFormat("SELECT COALESCE(MAX(id), 0) FROM grid WHERE zoneid = %i", zoneid);
+9
View File
@@ -1857,6 +1857,15 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p)
}
break;
}
case ServerOP_CZSetEntityVariableByClientName:
{
CZSetEntVarByClientName_Struct* CZCS = (CZSetEntVarByClientName_Struct*)pack->pBuffer;
Client* client = entity_list.GetClientByName(CZCS->CharName);
if (client != 0) {
client->SetEntityVariable(CZCS->id, CZCS->m_var);
}
break;
}
case ServerOP_WWMarquee:
{
WWMarquee_Struct* WWMS = (WWMarquee_Struct*)pack->pBuffer;
+3 -3
View File
@@ -1414,11 +1414,11 @@ bool Zone::HasWeather()
void Zone::StartShutdownTimer(uint32 set_time) {
if (set_time > autoshutdown_timer.GetRemainingTime()) {
if (set_time == (RuleI(Zone, AutoShutdownDelay)))
{
if (set_time == (RuleI(Zone, AutoShutdownDelay))) {
set_time = database.getZoneShutDownDelay(GetZoneID(), GetInstanceVersion());
}
autoshutdown_timer.Start(set_time, false);
autoshutdown_timer.SetTimer(set_time);
Log(Logs::General, Logs::Zone_Server, "Zone::StartShutdownTimer set to %u", set_time);
}
}