mirror of
https://github.com/EQEmu/Server.git
synced 2026-05-22 16:28:28 +00:00
Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a2b2a6a5cf | |||
| 43a5bff84a | |||
| e983d07228 | |||
| 90db12483a | |||
| ff71cfbd5b | |||
| 08bb9de437 | |||
| 16e341906d | |||
| 216b3a039f | |||
| b813cf71bb | |||
| 48ecd1222f | |||
| e758b407e9 | |||
| 758dd1875e | |||
| 8e2961dda5 | |||
| 5522eda6e4 | |||
| ac1469bac2 | |||
| c2989e019a | |||
| e16b481ba2 | |||
| 4fc0ffd173 | |||
| b883888a19 | |||
| 50ad97aa0b | |||
| dca892e258 | |||
| f9fe4ea2ec | |||
| cc30c72538 | |||
| d1fd40cd85 | |||
| f3af458cb3 | |||
| a093d04594 | |||
| 115df81400 |
@@ -1,3 +1,68 @@
|
||||
## [23.5.0] 4/10/2025
|
||||
|
||||
### API
|
||||
|
||||
* World API Optimizations ([#4850](https://github.com/EQEmu/Server/pull/4850)) @Akkadius 2025-04-10
|
||||
|
||||
### Bots
|
||||
|
||||
* Add valid state checks to ^clickitem ([#4830](https://github.com/EQEmu/Server/pull/4830)) @nytmyr 2025-04-10
|
||||
* Flag all buffs with SE_DamageShield as Damage Shield ([#4833](https://github.com/EQEmu/Server/pull/4833)) @nytmyr 2025-04-10
|
||||
* Positioning rewrite ([#4856](https://github.com/EQEmu/Server/pull/4856)) @nytmyr 2025-04-10
|
||||
* Restore old buff overwrite blocking ([#4832](https://github.com/EQEmu/Server/pull/4832)) @nytmyr 2025-04-10
|
||||
|
||||
### Bugfix
|
||||
|
||||
* Load zone variables before encounter_load. ([#4846](https://github.com/EQEmu/Server/pull/4846)) @zimp-wow 2025-04-10
|
||||
* Prevent depops from blocking new spawns. ([#4841](https://github.com/EQEmu/Server/pull/4841)) @zimp-wow 2025-04-10
|
||||
* Prevent final shutdown from persisting incomplete state. ([#4849](https://github.com/EQEmu/Server/pull/4849)) @zimp-wow 2025-04-10
|
||||
|
||||
### Code
|
||||
|
||||
* Remove queryserv dump flag ([#4842](https://github.com/EQEmu/Server/pull/4842)) @joligario 2025-04-10
|
||||
* Update link for legacy EQEmu loginserver account setup ([#4826](https://github.com/EQEmu/Server/pull/4826)) @joligario 2025-04-10
|
||||
|
||||
### Crash
|
||||
|
||||
* Fix rarer exception crash issue in PlayerEventLogs::ProcessBatchQueue ([#4835](https://github.com/EQEmu/Server/pull/4835)) @Akkadius 2025-04-03
|
||||
|
||||
### Database
|
||||
|
||||
* Fix manifest for `helmtexture` in `horses` table ([#4852](https://github.com/EQEmu/Server/pull/4852)) @joligario 2025-04-10
|
||||
|
||||
### Feature
|
||||
|
||||
* Add rule to consume command text from any channel ([#4839](https://github.com/EQEmu/Server/pull/4839)) @catapultam-habeo 2025-04-10
|
||||
|
||||
### Fixes
|
||||
|
||||
* Add the bazaar search limit to query ([#4829](https://github.com/EQEmu/Server/pull/4829)) @neckkola 2025-04-10
|
||||
* Backfill expire_at (not sure why this didn't make it in there to begin with) @Akkadius 2025-03-31
|
||||
* Bazaar Search window not working in a DZ ([#4828](https://github.com/EQEmu/Server/pull/4828)) @neckkola 2025-04-10
|
||||
* Databuckets Account Cache Loading ([#4855](https://github.com/EQEmu/Server/pull/4855)) @Akkadius 2025-04-10
|
||||
* Fix missing timer_name check on Mob::StopTimer ([#4840](https://github.com/EQEmu/Server/pull/4840)) @zimp-wow 2025-04-04
|
||||
* FixHeading Infinite Loop Fix ([#4854](https://github.com/EQEmu/Server/pull/4854)) @KimLS 2025-04-10
|
||||
* Make sure we don't expire default value instances @Akkadius 2025-03-31
|
||||
* Regression in World SendEmoteMessageRaw ([#4837](https://github.com/EQEmu/Server/pull/4837)) @Akkadius 2025-04-03
|
||||
* Remove QS Tables From Export @Akkadius 2025-04-10
|
||||
* Zone State Spawn2 Location Restore ([#4844](https://github.com/EQEmu/Server/pull/4844)) @Akkadius 2025-04-10
|
||||
|
||||
### Netcode
|
||||
|
||||
* Fix Stale Client Edge Case ([#4853](https://github.com/EQEmu/Server/pull/4853)) @Akkadius 2025-04-10
|
||||
|
||||
### Performance
|
||||
|
||||
* Character Save Optimizations ([#4851](https://github.com/EQEmu/Server/pull/4851)) @Akkadius 2025-04-10
|
||||
* Network Ring Buffers ([#4857](https://github.com/EQEmu/Server/pull/4857)) @Akkadius 2025-04-10
|
||||
* Pre-Compute CLE Server Lists ([#4838](https://github.com/EQEmu/Server/pull/4838)) @Akkadius 2025-04-10
|
||||
|
||||
### Spells
|
||||
|
||||
* Fear resistance effects edge case fixes and support for SPA 102 as an AA ([#4848](https://github.com/EQEmu/Server/pull/4848)) @KayenEQ 2025-04-10
|
||||
* Update to SPA 180 SE_ResistSpellChance to not block unresistable spells. ([#4847](https://github.com/EQEmu/Server/pull/4847)) @KayenEQ 2025-04-10
|
||||
* Update to SPA 378 SE_SpellEffectResistChance ([#4845](https://github.com/EQEmu/Server/pull/4845)) @KayenEQ 2025-04-10
|
||||
|
||||
## [23.4.0] 3/30/2025
|
||||
|
||||
### API
|
||||
|
||||
@@ -671,6 +671,7 @@ SET(common_headers
|
||||
net/console_server_connection.h
|
||||
net/crc32.h
|
||||
net/daybreak_connection.h
|
||||
net/daybreak_pooling.h
|
||||
net/daybreak_structs.h
|
||||
net/dns.h
|
||||
net/endian.h
|
||||
@@ -682,6 +683,7 @@ SET(common_headers
|
||||
net/servertalk_server.h
|
||||
net/servertalk_server_connection.h
|
||||
net/tcp_connection.h
|
||||
net/tcp_connection_pooling.h
|
||||
net/tcp_server.h
|
||||
net/websocket_server.h
|
||||
net/websocket_server_connection.h
|
||||
@@ -742,6 +744,7 @@ SOURCE_GROUP(Net FILES
|
||||
net/crc32.h
|
||||
net/daybreak_connection.cpp
|
||||
net/daybreak_connection.h
|
||||
net/daybreak_pooling.h
|
||||
net/daybreak_structs.h
|
||||
net/dns.h
|
||||
net/endian.h
|
||||
@@ -762,6 +765,7 @@ SOURCE_GROUP(Net FILES
|
||||
net/servertalk_server_connection.h
|
||||
net/tcp_connection.cpp
|
||||
net/tcp_connection.h
|
||||
net/tcp_connection_pooling.h
|
||||
net/tcp_server.cpp
|
||||
net/tcp_server.h
|
||||
net/websocket_server.cpp
|
||||
|
||||
+2
-1
@@ -279,7 +279,8 @@ Bazaar::GetSearchResults(
|
||||
trader_items_ids,
|
||||
std::string(search.item_name),
|
||||
field_criteria_items,
|
||||
where_criteria_items
|
||||
where_criteria_items,
|
||||
search.max_results
|
||||
);
|
||||
|
||||
if (item_results.empty()) {
|
||||
|
||||
@@ -65,7 +65,6 @@ private:
|
||||
bool dump_system_tables = false;
|
||||
bool dump_content_tables = false;
|
||||
bool dump_player_tables = false;
|
||||
bool dump_query_server_tables = false;
|
||||
bool dump_login_server_tables = false;
|
||||
bool dump_with_no_data = false;
|
||||
bool dump_table_lock = false;
|
||||
|
||||
@@ -6942,8 +6942,8 @@ CREATE TABLE `character_pet_name` (
|
||||
.version = 9310,
|
||||
.description = "2025_03_7_expand_horse_def.sql",
|
||||
.check = "SHOW COLUMNS FROM `horses` LIKE 'helmtexture'",
|
||||
.condition = "missing",
|
||||
.match = "TINYINT(2)",
|
||||
.condition = "empty",
|
||||
.match = "",
|
||||
.sql = R"(
|
||||
ALTER TABLE `horses`
|
||||
ADD COLUMN `helmtexture` TINYINT(2) NOT NULL DEFAULT -1 AFTER `texture`;
|
||||
|
||||
@@ -74,7 +74,7 @@ namespace Logs {
|
||||
Spawns,
|
||||
Spells,
|
||||
Status, // deprecated
|
||||
TCPConnection,
|
||||
TCPConnection, // deprecated
|
||||
Tasks,
|
||||
Tradeskills,
|
||||
Trading,
|
||||
@@ -150,6 +150,8 @@ namespace Logs {
|
||||
BotSpellTypeChecks,
|
||||
NpcHandin,
|
||||
ZoneState,
|
||||
NetClient,
|
||||
NetTCP,
|
||||
MaxCategoryID /* Don't Remove this */
|
||||
};
|
||||
|
||||
@@ -183,7 +185,7 @@ namespace Logs {
|
||||
"Spawns",
|
||||
"Spells",
|
||||
"Status (Deprecated)",
|
||||
"TCP Connection",
|
||||
"TCP Connection (Deprecated)",
|
||||
"Tasks",
|
||||
"Tradeskills",
|
||||
"Trading",
|
||||
@@ -258,7 +260,9 @@ namespace Logs {
|
||||
"Bot Spell Checks",
|
||||
"Bot Spell Type Checks",
|
||||
"NpcHandin",
|
||||
"ZoneState"
|
||||
"ZoneState",
|
||||
"Net Server <-> Client",
|
||||
"Net TCP"
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -261,26 +261,6 @@
|
||||
OutF(LogSys, Logs::Detail, Logs::Spells, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
|
||||
} while (0)
|
||||
|
||||
#define LogStatus(message, ...) do {\
|
||||
if (LogSys.IsLogEnabled(Logs::General, Logs::Status))\
|
||||
OutF(LogSys, Logs::General, Logs::Status, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
|
||||
} while (0)
|
||||
|
||||
#define LogStatusDetail(message, ...) do {\
|
||||
if (LogSys.IsLogEnabled(Logs::Detail, Logs::Status))\
|
||||
OutF(LogSys, Logs::Detail, Logs::Status, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
|
||||
} while (0)
|
||||
|
||||
#define LogTCPConnection(message, ...) do {\
|
||||
if (LogSys.IsLogEnabled(Logs::General, Logs::TCPConnection))\
|
||||
OutF(LogSys, Logs::General, Logs::TCPConnection, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
|
||||
} while (0)
|
||||
|
||||
#define LogTCPConnectionDetail(message, ...) do {\
|
||||
if (LogSys.IsLogEnabled(Logs::Detail, Logs::TCPConnection))\
|
||||
OutF(LogSys, Logs::Detail, Logs::TCPConnection, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
|
||||
} while (0)
|
||||
|
||||
#define LogTasks(message, ...) do {\
|
||||
if (LogSys.IsLogEnabled(Logs::General, Logs::Tasks))\
|
||||
OutF(LogSys, Logs::General, Logs::Tasks, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
|
||||
@@ -924,6 +904,26 @@
|
||||
OutF(LogSys, Logs::Detail, Logs::ZoneState, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
|
||||
} while (0)
|
||||
|
||||
#define LogNetClient(message, ...) do {\
|
||||
if (LogSys.IsLogEnabled(Logs::General, Logs::NetClient))\
|
||||
OutF(LogSys, Logs::General, Logs::NetClient, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
|
||||
} while (0)
|
||||
|
||||
#define LogNetClientDetail(message, ...) do {\
|
||||
if (LogSys.IsLogEnabled(Logs::Detail, Logs::NetClient))\
|
||||
OutF(LogSys, Logs::Detail, Logs::NetClient, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
|
||||
} while (0)
|
||||
|
||||
#define LogNetTCP(message, ...) do {\
|
||||
if (LogSys.IsLogEnabled(Logs::General, Logs::NetTCP))\
|
||||
OutF(LogSys, Logs::General, Logs::NetTCP, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
|
||||
} while (0)
|
||||
|
||||
#define LogNetTCPDetail(message, ...) do {\
|
||||
if (LogSys.IsLogEnabled(Logs::Detail, Logs::NetTCP))\
|
||||
OutF(LogSys, Logs::Detail, Logs::NetTCP, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
|
||||
} while (0)
|
||||
|
||||
#define Log(debug_level, log_category, message, ...) do {\
|
||||
if (LogSys.IsLogEnabled(debug_level, log_category))\
|
||||
LogSys.Out(debug_level, log_category, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
#include "daybreak_connection.h"
|
||||
#include "../event/event_loop.h"
|
||||
#include "../event/task.h"
|
||||
#include "../data_verification.h"
|
||||
#include "crc32.h"
|
||||
#include "../eqemu_logsys.h"
|
||||
#include <zlib.h>
|
||||
#include <fmt/format.h>
|
||||
#include <sstream>
|
||||
|
||||
// observed client receive window is 300 packets, 140KB
|
||||
constexpr size_t MAX_CLIENT_RECV_PACKETS_PER_WINDOW = 300;
|
||||
constexpr size_t MAX_CLIENT_RECV_BYTES_PER_WINDOW = 140 * 1024;
|
||||
|
||||
// buffer pools
|
||||
SendBufferPool send_buffer_pool;
|
||||
|
||||
EQ::Net::DaybreakConnectionManager::DaybreakConnectionManager()
|
||||
{
|
||||
@@ -53,16 +57,22 @@ void EQ::Net::DaybreakConnectionManager::Attach(uv_loop_t *loop)
|
||||
uv_ip4_addr("0.0.0.0", m_options.port, &recv_addr);
|
||||
int rc = uv_udp_bind(&m_socket, (const struct sockaddr *)&recv_addr, UV_UDP_REUSEADDR);
|
||||
|
||||
rc = uv_udp_recv_start(&m_socket,
|
||||
[](uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) {
|
||||
buf->base = new char[suggested_size];
|
||||
memset(buf->base, 0, suggested_size);
|
||||
buf->len = suggested_size;
|
||||
},
|
||||
rc = uv_udp_recv_start(
|
||||
&m_socket,
|
||||
[](uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) {
|
||||
if (suggested_size > 65536) {
|
||||
buf->base = new char[suggested_size];
|
||||
buf->len = suggested_size;
|
||||
return;
|
||||
}
|
||||
|
||||
static thread_local char temp_buf[65536];
|
||||
buf->base = temp_buf;
|
||||
buf->len = 65536;
|
||||
},
|
||||
[](uv_udp_t* handle, ssize_t nread, const uv_buf_t* buf, const struct sockaddr* addr, unsigned flags) {
|
||||
DaybreakConnectionManager *c = (DaybreakConnectionManager*)handle->data;
|
||||
if (nread < 0 || addr == nullptr) {
|
||||
delete[] buf->base;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -70,7 +80,10 @@ void EQ::Net::DaybreakConnectionManager::Attach(uv_loop_t *loop)
|
||||
uv_ip4_name((const sockaddr_in*)addr, endpoint, 16);
|
||||
auto port = ntohs(((const sockaddr_in*)addr)->sin_port);
|
||||
c->ProcessPacket(endpoint, port, buf->base, nread);
|
||||
delete[] buf->base;
|
||||
|
||||
if (buf->len > 65536) {
|
||||
delete[] buf->base;
|
||||
}
|
||||
});
|
||||
|
||||
m_attached = loop;
|
||||
@@ -310,7 +323,7 @@ EQ::Net::DaybreakConnection::DaybreakConnection(DaybreakConnectionManager *owner
|
||||
m_last_session_stats = Clock::now();
|
||||
m_outgoing_budget = owner->m_options.outgoing_data_rate;
|
||||
|
||||
LogNetcode("New session [{}] with encode key [{}]", m_connect_code, HostToNetwork(m_encode_key));
|
||||
LogNetClient("New session [{}] with encode key [{}]", m_connect_code, HostToNetwork(m_encode_key));
|
||||
}
|
||||
|
||||
//new connection made as client
|
||||
@@ -342,16 +355,16 @@ EQ::Net::DaybreakConnection::~DaybreakConnection()
|
||||
|
||||
void EQ::Net::DaybreakConnection::Close()
|
||||
{
|
||||
if (m_status == StatusConnected) {
|
||||
if (m_status != StatusDisconnected && m_status != StatusDisconnecting) {
|
||||
FlushBuffer();
|
||||
SendDisconnect();
|
||||
}
|
||||
|
||||
if (m_status != StatusDisconnecting) {
|
||||
m_close_time = Clock::now();
|
||||
ChangeStatus(StatusDisconnecting);
|
||||
}
|
||||
else {
|
||||
ChangeStatus(StatusDisconnecting);
|
||||
}
|
||||
|
||||
ChangeStatus(StatusDisconnecting);
|
||||
}
|
||||
|
||||
void EQ::Net::DaybreakConnection::QueuePacket(Packet &p)
|
||||
@@ -634,7 +647,7 @@ void EQ::Net::DaybreakConnection::ProcessDecodedPacket(const Packet &p)
|
||||
p.PutSerialize(0, reply);
|
||||
InternalSend(p);
|
||||
|
||||
LogNetcode("[OP_SessionRequest] Session [{}] started with encode key [{}]", m_connect_code, HostToNetwork(m_encode_key));
|
||||
LogNetClient("[OP_SessionRequest] Session [{}] started with encode key [{}]", m_connect_code, HostToNetwork(m_encode_key));
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -653,7 +666,7 @@ void EQ::Net::DaybreakConnection::ProcessDecodedPacket(const Packet &p)
|
||||
m_max_packet_size = reply.max_packet_size;
|
||||
ChangeStatus(StatusConnected);
|
||||
|
||||
LogNetcode(
|
||||
LogNetClient(
|
||||
"[OP_SessionResponse] Session [{}] refresh with encode key [{}]",
|
||||
m_connect_code,
|
||||
HostToNetwork(m_encode_key)
|
||||
@@ -782,7 +795,7 @@ void EQ::Net::DaybreakConnection::ProcessDecodedPacket(const Packet &p)
|
||||
SendDisconnect();
|
||||
}
|
||||
|
||||
LogNetcode(
|
||||
LogNetClient(
|
||||
"[OP_SessionDisconnect] Session [{}] disconnect with encode key [{}]",
|
||||
m_connect_code,
|
||||
HostToNetwork(m_encode_key)
|
||||
@@ -852,7 +865,7 @@ bool EQ::Net::DaybreakConnection::ValidateCRC(Packet &p)
|
||||
}
|
||||
|
||||
if (p.Length() < (size_t)m_crc_bytes) {
|
||||
LogNetcode("Session [{}] ignored packet (crc bytes invalid on session)", m_connect_code);
|
||||
LogNetClient("Session [{}] ignored packet (crc bytes invalid on session)", m_connect_code);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1043,7 +1056,7 @@ void EQ::Net::DaybreakConnection::Decompress(Packet &p, size_t offset, size_t le
|
||||
return;
|
||||
}
|
||||
|
||||
static uint8_t new_buffer[4096];
|
||||
static thread_local uint8_t new_buffer[4096];
|
||||
uint8_t *buffer = (uint8_t*)p.Data() + offset;
|
||||
uint32_t new_length = 0;
|
||||
|
||||
@@ -1064,7 +1077,7 @@ void EQ::Net::DaybreakConnection::Decompress(Packet &p, size_t offset, size_t le
|
||||
|
||||
void EQ::Net::DaybreakConnection::Compress(Packet &p, size_t offset, size_t length)
|
||||
{
|
||||
uint8_t new_buffer[2048] = { 0 };
|
||||
static thread_local uint8_t new_buffer[2048] = { 0 };
|
||||
uint8_t *buffer = (uint8_t*)p.Data() + offset;
|
||||
uint32_t new_length = 0;
|
||||
bool send_uncompressed = true;
|
||||
@@ -1091,10 +1104,6 @@ void EQ::Net::DaybreakConnection::ProcessResend()
|
||||
}
|
||||
}
|
||||
|
||||
// observed client receive window is 300 packets, 140KB
|
||||
constexpr size_t MAX_CLIENT_RECV_PACKETS_PER_WINDOW = 300;
|
||||
constexpr size_t MAX_CLIENT_RECV_BYTES_PER_WINDOW = 140 * 1024;
|
||||
|
||||
void EQ::Net::DaybreakConnection::ProcessResend(int stream)
|
||||
{
|
||||
if (m_status == DbProtocolStatus::StatusDisconnected) {
|
||||
@@ -1117,23 +1126,24 @@ void EQ::Net::DaybreakConnection::ProcessResend(int stream)
|
||||
auto &first_packet = s->sent_packets.begin()->second;
|
||||
auto time_since_first_sent = std::chrono::duration_cast<std::chrono::milliseconds>(now - first_packet.first_sent).count();
|
||||
|
||||
if (time_since_first_sent >= m_owner->m_options.resend_timeout) {
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
|
||||
// make sure that the first_packet in the list first_sent time is within the resend_delay and now
|
||||
// if it is not, then we need to resend all packets in the list
|
||||
if (time_since_first_sent <= first_packet.resend_delay && !m_acked_since_last_resend) {
|
||||
LogNetcodeDetail(
|
||||
"Not resending packets for stream [{}] time since first sent [{}] resend delay [{}] m_acked_since_last_resend [{}]",
|
||||
LogNetClient(
|
||||
"Not resending packets for stream [{}] packets [{}] time_first_sent [{}] resend_delay [{}] m_acked_since_last_resend [{}]",
|
||||
stream,
|
||||
s->sent_packets.size(),
|
||||
time_since_first_sent,
|
||||
first_packet.resend_delay,
|
||||
m_acked_since_last_resend
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (time_since_first_sent >= m_owner->m_options.resend_timeout) {
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (LogSys.IsLogEnabled(Logs::Detail, Logs::Netcode)) {
|
||||
@@ -1142,7 +1152,7 @@ void EQ::Net::DaybreakConnection::ProcessResend(int stream)
|
||||
total_size += e.second.packet.Length();
|
||||
}
|
||||
|
||||
LogNetcodeDetail(
|
||||
LogNetClient(
|
||||
"Resending packets for stream [{}] packet count [{}] total packet size [{}] m_acked_since_last_resend [{}]",
|
||||
stream,
|
||||
s->sent_packets.size(),
|
||||
@@ -1154,7 +1164,7 @@ void EQ::Net::DaybreakConnection::ProcessResend(int stream)
|
||||
for (auto &e: s->sent_packets) {
|
||||
if (m_resend_packets_sent >= MAX_CLIENT_RECV_PACKETS_PER_WINDOW ||
|
||||
m_resend_bytes_sent >= MAX_CLIENT_RECV_BYTES_PER_WINDOW) {
|
||||
LogNetcodeDetail(
|
||||
LogNetClient(
|
||||
"Stopping resend because we hit thresholds m_resend_packets_sent [{}] max [{}] m_resend_bytes_sent [{}] max [{}]",
|
||||
m_resend_packets_sent,
|
||||
MAX_CLIENT_RECV_PACKETS_PER_WINDOW,
|
||||
@@ -1333,99 +1343,97 @@ void EQ::Net::DaybreakConnection::SendKeepAlive()
|
||||
InternalSend(p);
|
||||
}
|
||||
|
||||
void EQ::Net::DaybreakConnection::InternalSend(Packet &p)
|
||||
{
|
||||
void EQ::Net::DaybreakConnection::InternalSend(Packet &p) {
|
||||
if (m_owner->m_options.outgoing_data_rate > 0.0) {
|
||||
auto new_budget = m_outgoing_budget - (p.Length() / 1024.0);
|
||||
if (new_budget <= 0.0) {
|
||||
m_stats.dropped_datarate_packets++;
|
||||
return;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
m_outgoing_budget = new_budget;
|
||||
}
|
||||
}
|
||||
|
||||
m_last_send = Clock::now();
|
||||
|
||||
auto send_func = [](uv_udp_send_t* req, int status) {
|
||||
delete[](char*)req->data;
|
||||
delete req;
|
||||
};
|
||||
auto pooled_opt = send_buffer_pool.acquire();
|
||||
if (!pooled_opt) {
|
||||
m_stats.dropped_datarate_packets++;
|
||||
return;
|
||||
}
|
||||
|
||||
auto [send_req, data, ctx] = *pooled_opt;
|
||||
ctx->pool = &send_buffer_pool; // set pool pointer
|
||||
|
||||
sockaddr_in send_addr{};
|
||||
uv_ip4_addr(m_endpoint.c_str(), m_port, &send_addr);
|
||||
uv_buf_t send_buffers[1];
|
||||
|
||||
if (PacketCanBeEncoded(p)) {
|
||||
|
||||
m_stats.bytes_before_encode += p.Length();
|
||||
|
||||
DynamicPacket out;
|
||||
out.PutPacket(0, p);
|
||||
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
switch (m_encode_passes[i]) {
|
||||
case EncodeCompression:
|
||||
if (out.GetInt8(0) == 0)
|
||||
Compress(out, DaybreakHeader::size(), out.Length() - DaybreakHeader::size());
|
||||
else
|
||||
Compress(out, 1, out.Length() - 1);
|
||||
break;
|
||||
case EncodeXOR:
|
||||
if (out.GetInt8(0) == 0)
|
||||
Encode(out, DaybreakHeader::size(), out.Length() - DaybreakHeader::size());
|
||||
else
|
||||
Encode(out, 1, out.Length() - 1);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
for (auto &m_encode_passe: m_encode_passes) {
|
||||
switch (m_encode_passe) {
|
||||
case EncodeCompression:
|
||||
if (out.GetInt8(0) == 0) {
|
||||
Compress(out, DaybreakHeader::size(), out.Length() - DaybreakHeader::size());
|
||||
} else {
|
||||
Compress(out, 1, out.Length() - 1);
|
||||
}
|
||||
break;
|
||||
case EncodeXOR:
|
||||
if (out.GetInt8(0) == 0) {
|
||||
Encode(out, DaybreakHeader::size(), out.Length() - DaybreakHeader::size());
|
||||
} else {
|
||||
Encode(out, 1, out.Length() - 1);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
AppendCRC(out);
|
||||
|
||||
uv_udp_send_t *send_req = new uv_udp_send_t;
|
||||
memset(send_req, 0, sizeof(*send_req));
|
||||
sockaddr_in send_addr;
|
||||
uv_ip4_addr(m_endpoint.c_str(), m_port, &send_addr);
|
||||
uv_buf_t send_buffers[1];
|
||||
|
||||
char *data = new char[out.Length()];
|
||||
memcpy(data, out.Data(), out.Length());
|
||||
send_buffers[0] = uv_buf_init(data, out.Length());
|
||||
send_req->data = send_buffers[0].base;
|
||||
|
||||
m_stats.sent_bytes += out.Length();
|
||||
m_stats.sent_packets++;
|
||||
if (m_owner->m_options.simulated_out_packet_loss && m_owner->m_options.simulated_out_packet_loss >= m_owner->m_rand.Int(0, 100)) {
|
||||
delete[](char*)send_req->data;
|
||||
delete send_req;
|
||||
return;
|
||||
}
|
||||
|
||||
uv_udp_send(send_req, &m_owner->m_socket, send_buffers, 1, (sockaddr*)&send_addr, send_func);
|
||||
return;
|
||||
} else {
|
||||
memcpy(data, p.Data(), p.Length());
|
||||
send_buffers[0] = uv_buf_init(data, p.Length());
|
||||
}
|
||||
|
||||
m_stats.bytes_before_encode += p.Length();
|
||||
|
||||
uv_udp_send_t *send_req = new uv_udp_send_t;
|
||||
sockaddr_in send_addr;
|
||||
uv_ip4_addr(m_endpoint.c_str(), m_port, &send_addr);
|
||||
uv_buf_t send_buffers[1];
|
||||
|
||||
char *data = new char[p.Length()];
|
||||
memcpy(data, p.Data(), p.Length());
|
||||
send_buffers[0] = uv_buf_init(data, p.Length());
|
||||
send_req->data = send_buffers[0].base;
|
||||
|
||||
m_stats.sent_bytes += p.Length();
|
||||
m_stats.sent_packets++;
|
||||
|
||||
if (m_owner->m_options.simulated_out_packet_loss && m_owner->m_options.simulated_out_packet_loss >= m_owner->m_rand.Int(0, 100)) {
|
||||
delete[](char*)send_req->data;
|
||||
delete send_req;
|
||||
if (m_owner->m_options.simulated_out_packet_loss &&
|
||||
m_owner->m_options.simulated_out_packet_loss >= m_owner->m_rand.Int(0, 100)) {
|
||||
send_buffer_pool.release(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
uv_udp_send(send_req, &m_owner->m_socket, send_buffers, 1, (sockaddr*)&send_addr, send_func);
|
||||
int send_result = uv_udp_send(
|
||||
send_req, &m_owner->m_socket, send_buffers, 1, (sockaddr *)&send_addr,
|
||||
[](uv_udp_send_t *req, int status) {
|
||||
auto *ctx = reinterpret_cast<EmbeddedContext *>(req->data);
|
||||
if (!ctx) {
|
||||
std::cerr << "Error: send_req->data is null in callback!" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
if (status < 0) {
|
||||
std::cerr << "uv_udp_send failed: " << uv_strerror(status) << std::endl;
|
||||
}
|
||||
|
||||
ctx->pool->release(ctx);
|
||||
}
|
||||
);
|
||||
|
||||
if (send_result < 0) {
|
||||
std::cerr << "uv_udp_send() failed: " << uv_strerror(send_result) << std::endl;
|
||||
send_buffer_pool.release(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
void EQ::Net::DaybreakConnection::InternalQueuePacket(Packet &p, int stream_id, bool reliable)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "../random.h"
|
||||
#include "packet.h"
|
||||
#include "daybreak_structs.h"
|
||||
#include "daybreak_pooling.h"
|
||||
#include <uv.h>
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include <iostream>
|
||||
#include "../eqemu_logsys.h"
|
||||
#include <uv.h>
|
||||
|
||||
constexpr size_t UDP_BUFFER_SIZE = 512;
|
||||
|
||||
struct EmbeddedContext {
|
||||
size_t pool_index;
|
||||
class SendBufferPool* pool;
|
||||
};
|
||||
|
||||
class SendBufferPool {
|
||||
public:
|
||||
explicit SendBufferPool(size_t initial_capacity = 64)
|
||||
: m_capacity(initial_capacity), m_head(0)
|
||||
{
|
||||
LogNetClient("[SendBufferPool] Initializing with capacity [{}]", (int)m_capacity);
|
||||
|
||||
m_pool.reserve(m_capacity);
|
||||
m_locks = std::make_unique<std::atomic_bool[]>(m_capacity);
|
||||
|
||||
for (size_t i = 0; i < m_capacity; ++i) {
|
||||
auto* req = new PooledUdpSend();
|
||||
req->context.pool_index = i;
|
||||
req->context.pool = this;
|
||||
req->uv_req.data = &req->context;
|
||||
|
||||
m_pool.emplace_back(std::unique_ptr<PooledUdpSend>(req));
|
||||
m_locks[i].store(false, std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::tuple<uv_udp_send_t*, char*, EmbeddedContext*>> acquire() {
|
||||
size_t cap = m_capacity.load(std::memory_order_acquire);
|
||||
for (size_t i = 0; i < cap; ++i) {
|
||||
size_t index = m_head.fetch_add(1, std::memory_order_relaxed) % cap;
|
||||
bool expected = false;
|
||||
if (m_locks[index].compare_exchange_strong(expected, true)) {
|
||||
auto* req = m_pool[index].get();
|
||||
LogNetClientDetail("[SendBufferPool] Acquired [{}]", index);
|
||||
return std::make_tuple(&req->uv_req, req->buffer.data(), &req->context);
|
||||
}
|
||||
}
|
||||
|
||||
LogNetClient("[SendBufferPool] Growing from [{}] to [{}]", cap, cap * 2);
|
||||
grow();
|
||||
return acquireAfterGrowth();
|
||||
}
|
||||
|
||||
void release(EmbeddedContext* ctx) {
|
||||
if (!ctx || ctx->pool != this || ctx->pool_index >= m_capacity.load(std::memory_order_acquire)) {
|
||||
LogNetClient("[SendBufferPool] Invalid context release [{}]", ctx ? ctx->pool_index : -1);
|
||||
return;
|
||||
}
|
||||
m_locks[ctx->pool_index].store(false, std::memory_order_release);
|
||||
LogNetClientDetail("[SendBufferPool] Released [{}]", ctx->pool_index);
|
||||
}
|
||||
|
||||
private:
|
||||
struct PooledUdpSend {
|
||||
uv_udp_send_t uv_req;
|
||||
std::array<char, UDP_BUFFER_SIZE> buffer;
|
||||
EmbeddedContext context;
|
||||
};
|
||||
|
||||
std::vector<std::unique_ptr<PooledUdpSend>> m_pool;
|
||||
std::unique_ptr<std::atomic_bool[]> m_locks;
|
||||
std::atomic<size_t> m_capacity;
|
||||
std::atomic<size_t> m_head;
|
||||
std::mutex m_grow_mutex;
|
||||
|
||||
void grow() {
|
||||
std::lock_guard<std::mutex> lock(m_grow_mutex);
|
||||
|
||||
size_t old_cap = m_capacity.load(std::memory_order_acquire);
|
||||
size_t new_cap = old_cap * 2;
|
||||
|
||||
m_pool.reserve(new_cap);
|
||||
for (size_t i = old_cap; i < new_cap; ++i) {
|
||||
auto* req = new PooledUdpSend();
|
||||
req->context.pool_index = i;
|
||||
req->context.pool = this;
|
||||
req->uv_req.data = &req->context;
|
||||
|
||||
m_pool.emplace_back(std::unique_ptr<PooledUdpSend>(req));
|
||||
}
|
||||
|
||||
auto new_locks = std::make_unique<std::atomic_bool[]>(new_cap);
|
||||
for (size_t i = 0; i < old_cap; ++i) {
|
||||
new_locks[i].store(m_locks[i].load(std::memory_order_acquire));
|
||||
}
|
||||
for (size_t i = old_cap; i < new_cap; ++i) {
|
||||
new_locks[i].store(false, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
m_locks = std::move(new_locks);
|
||||
m_capacity.store(new_cap, std::memory_order_release);
|
||||
|
||||
LogNetClient("[SendBufferPool] Grew to [{}] from [{}]", new_cap, old_cap);
|
||||
}
|
||||
|
||||
std::optional<std::tuple<uv_udp_send_t*, char*, EmbeddedContext*>> acquireAfterGrowth() {
|
||||
size_t cap = m_capacity.load(std::memory_order_acquire);
|
||||
for (size_t i = 0; i < cap; ++i) {
|
||||
size_t index = m_head.fetch_add(1, std::memory_order_relaxed) % cap;
|
||||
bool expected = false;
|
||||
if (m_locks[index].compare_exchange_strong(expected, true)) {
|
||||
auto* req = m_pool[index].get();
|
||||
LogNetClient("[SendBufferPool] Acquired after grow [{}]", index);
|
||||
return std::make_tuple(&req->uv_req, req->buffer.data(), &req->context);
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
@@ -171,3 +171,4 @@ namespace EQ
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -62,15 +62,15 @@ void EQ::Net::ServertalkClient::Connect()
|
||||
m_connecting = true;
|
||||
EQ::Net::TCPConnection::Connect(m_addr, m_port, false, [this](std::shared_ptr<EQ::Net::TCPConnection> connection) {
|
||||
if (connection == nullptr) {
|
||||
LogF(Logs::General, Logs::TCPConnection, "Error connecting to {0}:{1}, attempting to reconnect...", m_addr, m_port);
|
||||
LogNetTCP("Error connecting to {0}:{1}, attempting to reconnect...", m_addr, m_port);
|
||||
m_connecting = false;
|
||||
return;
|
||||
}
|
||||
|
||||
LogF(Logs::General, Logs::TCPConnection, "Connected to {0}:{1}", m_addr, m_port);
|
||||
LogNetTCP("Connected to {0}:{1}", m_addr, m_port);
|
||||
m_connection = connection;
|
||||
m_connection->OnDisconnect([this](EQ::Net::TCPConnection *c) {
|
||||
LogF(Logs::General, Logs::TCPConnection, "Connection lost to {0}:{1}, attempting to reconnect...", m_addr, m_port);
|
||||
LogNetTCP("Connection lost to {0}:{1}, attempting to reconnect...", m_addr, m_port);
|
||||
m_connection.reset();
|
||||
});
|
||||
|
||||
|
||||
@@ -58,15 +58,15 @@ void EQ::Net::ServertalkLegacyClient::Connect()
|
||||
m_connecting = true;
|
||||
EQ::Net::TCPConnection::Connect(m_addr, m_port, false, [this](std::shared_ptr<EQ::Net::TCPConnection> connection) {
|
||||
if (connection == nullptr) {
|
||||
LogF(Logs::General, Logs::TCPConnection, "Error connecting to {0}:{1}, attempting to reconnect...", m_addr, m_port);
|
||||
LogNetTCP("Error connecting to {0}:{1}, attempting to reconnect...", m_addr, m_port);
|
||||
m_connecting = false;
|
||||
return;
|
||||
}
|
||||
|
||||
LogF(Logs::General, Logs::TCPConnection, "Connected to {0}:{1}", m_addr, m_port);
|
||||
LogNetTCP("Connected to {0}:{1}", m_addr, m_port);
|
||||
m_connection = connection;
|
||||
m_connection->OnDisconnect([this](EQ::Net::TCPConnection *c) {
|
||||
LogF(Logs::General, Logs::TCPConnection, "Connection lost to {0}:{1}, attempting to reconnect...", m_addr, m_port);
|
||||
LogNetTCP("Connection lost to {0}:{1}, attempting to reconnect...", m_addr, m_port);
|
||||
m_connection.reset();
|
||||
});
|
||||
|
||||
@@ -131,7 +131,7 @@ void EQ::Net::ServertalkLegacyClient::ProcessReadBuffer()
|
||||
}
|
||||
else {
|
||||
EQ::Net::StaticPacket p(&m_buffer[current + 4], length);
|
||||
|
||||
|
||||
auto cb = m_message_callbacks.find(opcode);
|
||||
if (cb != m_message_callbacks.end()) {
|
||||
cb->second(opcode, p);
|
||||
|
||||
+108
-55
@@ -1,5 +1,8 @@
|
||||
#include "tcp_connection.h"
|
||||
#include "../event/event_loop.h"
|
||||
#include <iostream>
|
||||
|
||||
WriteReqPool tcp_write_pool;
|
||||
|
||||
void on_close_handle(uv_handle_t* handle) {
|
||||
delete (uv_tcp_t *)handle;
|
||||
@@ -64,36 +67,37 @@ void EQ::Net::TCPConnection::Connect(const std::string &addr, int port, bool ipv
|
||||
});
|
||||
}
|
||||
|
||||
void EQ::Net::TCPConnection::Start() {
|
||||
uv_read_start((uv_stream_t*)m_socket, [](uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) {
|
||||
buf->base = new char[suggested_size];
|
||||
buf->len = suggested_size;
|
||||
}, [](uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) {
|
||||
void EQ::Net::TCPConnection::Start()
|
||||
{
|
||||
uv_read_start(
|
||||
(uv_stream_t *) m_socket, [](uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) {
|
||||
if (suggested_size > 65536) {
|
||||
buf->base = new char[suggested_size];
|
||||
buf->len = suggested_size;
|
||||
return;
|
||||
}
|
||||
|
||||
TCPConnection *connection = (TCPConnection*)stream->data;
|
||||
static thread_local char temp_buf[65536];
|
||||
buf->base = temp_buf;
|
||||
buf->len = 65536;
|
||||
}, [](uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) {
|
||||
auto *connection = (TCPConnection *) stream->data;
|
||||
|
||||
if (nread > 0) {
|
||||
connection->Read(buf->base, nread);
|
||||
if (nread > 0) {
|
||||
connection->Read(buf->base, nread);
|
||||
}
|
||||
else if (nread == UV_EOF) {
|
||||
connection->Disconnect();
|
||||
}
|
||||
else if (nread < 0) {
|
||||
connection->Disconnect();
|
||||
}
|
||||
|
||||
if (buf->base) {
|
||||
delete[] buf->base;
|
||||
if (buf->len > 65536) {
|
||||
delete [] buf->base;
|
||||
}
|
||||
}
|
||||
else if (nread == UV_EOF) {
|
||||
connection->Disconnect();
|
||||
|
||||
if (buf->base) {
|
||||
delete[] buf->base;
|
||||
}
|
||||
}
|
||||
else if (nread < 0) {
|
||||
connection->Disconnect();
|
||||
|
||||
if (buf->base) {
|
||||
delete[] buf->base;
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
}
|
||||
|
||||
void EQ::Net::TCPConnection::OnRead(std::function<void(TCPConnection*, const unsigned char*, size_t)> cb)
|
||||
@@ -130,43 +134,92 @@ void EQ::Net::TCPConnection::Read(const char *data, size_t count)
|
||||
}
|
||||
}
|
||||
|
||||
void EQ::Net::TCPConnection::Write(const char *data, size_t count)
|
||||
{
|
||||
if (!m_socket) {
|
||||
void EQ::Net::TCPConnection::Write(const char* data, size_t count) {
|
||||
if (!m_socket || !data || count == 0) {
|
||||
std::cerr << "TCPConnection::Write - Invalid socket or data\n";
|
||||
return;
|
||||
}
|
||||
|
||||
struct WriteBaton
|
||||
{
|
||||
TCPConnection *connection;
|
||||
char *buffer;
|
||||
};
|
||||
|
||||
WriteBaton *baton = new WriteBaton;
|
||||
baton->connection = this;
|
||||
baton->buffer = new char[count];
|
||||
|
||||
uv_write_t *write_req = new uv_write_t;
|
||||
memset(write_req, 0, sizeof(uv_write_t));
|
||||
write_req->data = baton;
|
||||
uv_buf_t send_buffers[1];
|
||||
|
||||
memcpy(baton->buffer, data, count);
|
||||
send_buffers[0] = uv_buf_init(baton->buffer, count);
|
||||
|
||||
uv_write(write_req, (uv_stream_t*)m_socket, send_buffers, 1, [](uv_write_t* req, int status) {
|
||||
WriteBaton *baton = (WriteBaton*)req->data;
|
||||
delete[] baton->buffer;
|
||||
delete req;
|
||||
|
||||
if (status < 0) {
|
||||
baton->connection->Disconnect();
|
||||
if (count <= TCP_BUFFER_SIZE) {
|
||||
// Fast path: use pooled request with embedded buffer
|
||||
auto req_opt = tcp_write_pool.acquire();
|
||||
if (!req_opt) {
|
||||
std::cerr << "TCPConnection::Write - Out of write requests\n";
|
||||
return;
|
||||
}
|
||||
|
||||
delete baton;
|
||||
});
|
||||
TCPWriteReq* write_req = *req_opt;
|
||||
|
||||
// Fill buffer and set context
|
||||
memcpy(write_req->buffer.data(), data, count);
|
||||
write_req->connection = this;
|
||||
write_req->magic = 0xC0FFEE;
|
||||
|
||||
uv_buf_t buf = uv_buf_init(write_req->buffer.data(), static_cast<unsigned int>(count));
|
||||
|
||||
int result = uv_write(
|
||||
&write_req->req,
|
||||
reinterpret_cast<uv_stream_t*>(m_socket),
|
||||
&buf,
|
||||
1,
|
||||
[](uv_write_t* req, int status) {
|
||||
auto* full_req = reinterpret_cast<TCPWriteReq*>(req);
|
||||
if (full_req->magic != 0xC0FFEE) {
|
||||
std::cerr << "uv_write callback - invalid magic, skipping release\n";
|
||||
return;
|
||||
}
|
||||
|
||||
tcp_write_pool.release(full_req);
|
||||
|
||||
if (status < 0 && full_req->connection) {
|
||||
std::cerr << "uv_write failed: " << uv_strerror(status) << std::endl;
|
||||
full_req->connection->Disconnect();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (result < 0) {
|
||||
std::cerr << "uv_write() failed immediately: " << uv_strerror(result) << std::endl;
|
||||
tcp_write_pool.release(write_req);
|
||||
}
|
||||
|
||||
} else {
|
||||
// Slow path: allocate heap buffer for large write
|
||||
LogNetTCP("[TCPConnection] Large write of [{}] bytes, using heap buffer", count);
|
||||
|
||||
char* heap_buffer = new char[count];
|
||||
memcpy(heap_buffer, data, count);
|
||||
|
||||
uv_write_t* write_req = new uv_write_t;
|
||||
write_req->data = heap_buffer;
|
||||
|
||||
uv_buf_t buf = uv_buf_init(heap_buffer, static_cast<unsigned int>(count));
|
||||
|
||||
int result = uv_write(
|
||||
write_req,
|
||||
reinterpret_cast<uv_stream_t*>(m_socket),
|
||||
&buf,
|
||||
1,
|
||||
[](uv_write_t* req, int status) {
|
||||
char* data = static_cast<char*>(req->data);
|
||||
delete[] data;
|
||||
delete req;
|
||||
|
||||
if (status < 0) {
|
||||
std::cerr << "uv_write (large) failed: " << uv_strerror(status) << std::endl;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (result < 0) {
|
||||
std::cerr << "uv_write() (large) failed immediately: " << uv_strerror(result) << std::endl;
|
||||
delete[] heap_buffer;
|
||||
delete write_req;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::string EQ::Net::TCPConnection::LocalIP() const
|
||||
{
|
||||
sockaddr_storage addr;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "tcp_connection_pooling.h"
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
@@ -16,7 +17,7 @@ namespace EQ
|
||||
~TCPConnection();
|
||||
|
||||
static void Connect(const std::string &addr, int port, bool ipv6, std::function<void(std::shared_ptr<TCPConnection>)> cb);
|
||||
|
||||
|
||||
void Start();
|
||||
void OnRead(std::function<void(TCPConnection*, const unsigned char *, size_t)> cb);
|
||||
void OnDisconnect(std::function<void(TCPConnection*)> cb);
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
#pragma once
|
||||
|
||||
#include "../eqemu_logsys.h"
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <mutex>
|
||||
#include <uv.h>
|
||||
#include <iostream>
|
||||
|
||||
namespace EQ { namespace Net { class TCPConnection; } }
|
||||
|
||||
constexpr size_t TCP_BUFFER_SIZE = 8192;
|
||||
|
||||
struct TCPWriteReq {
|
||||
uv_write_t req{};
|
||||
std::array<char, TCP_BUFFER_SIZE> buffer{};
|
||||
size_t buffer_index{};
|
||||
EQ::Net::TCPConnection* connection{};
|
||||
uint32_t magic = 0xC0FFEE;
|
||||
};
|
||||
|
||||
class WriteReqPool {
|
||||
public:
|
||||
explicit WriteReqPool(size_t initial_capacity = 512)
|
||||
: m_capacity(initial_capacity), m_head(0) {
|
||||
initialize_pool(m_capacity);
|
||||
}
|
||||
|
||||
std::optional<TCPWriteReq*> acquire() {
|
||||
size_t cap = m_capacity.load(std::memory_order_acquire);
|
||||
|
||||
for (size_t i = 0; i < cap; ++i) {
|
||||
size_t index = m_head.fetch_add(1, std::memory_order_relaxed) % cap;
|
||||
|
||||
bool expected = false;
|
||||
if (m_locks[index].compare_exchange_strong(expected, true, std::memory_order_acquire)) {
|
||||
LogNetTCPDetail("[WriteReqPool] Acquired buffer index [{}]", index);
|
||||
return m_reqs[index].get();
|
||||
}
|
||||
}
|
||||
|
||||
LogNetTCP("[WriteReqPool] Growing from [{}] to [{}]", cap, cap * 2);
|
||||
grow();
|
||||
return acquireAfterGrow();
|
||||
}
|
||||
|
||||
void release(TCPWriteReq* req) {
|
||||
if (!req) return;
|
||||
|
||||
const size_t index = req->buffer_index;
|
||||
const size_t cap = m_capacity.load(std::memory_order_acquire);
|
||||
|
||||
if (index >= cap || m_reqs[index].get() != req) {
|
||||
std::cerr << "WriteReqPool::release - Invalid or stale pointer (index=" << index << ")\n";
|
||||
return;
|
||||
}
|
||||
|
||||
m_locks[index].store(false, std::memory_order_release);
|
||||
LogNetTCPDetail("[WriteReqPool] Released buffer index [{}]", index);
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<TCPWriteReq>> m_reqs;
|
||||
std::unique_ptr<std::atomic_bool[]> m_locks;
|
||||
std::atomic<size_t> m_capacity;
|
||||
std::atomic<size_t> m_head;
|
||||
std::mutex m_grow_mutex;
|
||||
|
||||
void initialize_pool(size_t count) {
|
||||
m_reqs.reserve(count);
|
||||
m_locks = std::make_unique<std::atomic_bool[]>(count);
|
||||
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
auto req = std::make_unique<TCPWriteReq>();
|
||||
req->buffer_index = i;
|
||||
req->req.data = req.get(); // optional: for use in libuv callbacks
|
||||
m_locks[i].store(false, std::memory_order_relaxed);
|
||||
m_reqs.emplace_back(std::move(req));
|
||||
}
|
||||
|
||||
m_capacity.store(count, std::memory_order_release);
|
||||
}
|
||||
|
||||
void grow() {
|
||||
std::lock_guard<std::mutex> lock(m_grow_mutex);
|
||||
|
||||
const size_t old_cap = m_capacity.load(std::memory_order_acquire);
|
||||
const size_t new_cap = old_cap * 2;
|
||||
|
||||
m_reqs.reserve(new_cap);
|
||||
for (size_t i = old_cap; i < new_cap; ++i) {
|
||||
auto req = std::make_unique<TCPWriteReq>();
|
||||
req->buffer_index = i;
|
||||
req->req.data = req.get(); // optional
|
||||
m_reqs.emplace_back(std::move(req));
|
||||
}
|
||||
|
||||
auto new_locks = std::make_unique<std::atomic_bool[]>(new_cap);
|
||||
for (size_t i = 0; i < old_cap; ++i) {
|
||||
new_locks[i].store(m_locks[i].load(std::memory_order_acquire));
|
||||
}
|
||||
for (size_t i = old_cap; i < new_cap; ++i) {
|
||||
new_locks[i].store(false, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
m_locks = std::move(new_locks);
|
||||
m_capacity.store(new_cap, std::memory_order_release);
|
||||
}
|
||||
|
||||
std::optional<TCPWriteReq*> acquireAfterGrow() {
|
||||
const size_t cap = m_capacity.load(std::memory_order_acquire);
|
||||
|
||||
for (size_t i = 0; i < cap; ++i) {
|
||||
bool expected = false;
|
||||
if (m_locks[i].compare_exchange_strong(expected, true, std::memory_order_acquire)) {
|
||||
LogNetTCP("[WriteReqPool] Acquired buffer index [{}] after grow", i);
|
||||
return m_reqs[i].get();
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
@@ -54,17 +54,31 @@ public:
|
||||
{
|
||||
BulkTraders_Struct all_entries{};
|
||||
std::vector<DistinctTraders_Struct> distinct_traders;
|
||||
MySQLRequestResult results;
|
||||
|
||||
auto results = db.QueryDatabase(fmt::format(
|
||||
"SELECT DISTINCT(t.char_id), t.char_zone_id, t.char_zone_instance_id, t.char_entity_id, c.name "
|
||||
"FROM trader AS t "
|
||||
"JOIN character_data AS c ON t.char_id = c.id "
|
||||
"WHERE t.char_zone_instance_id = {} "
|
||||
"ORDER BY t.char_zone_instance_id ASC "
|
||||
"LIMIT {}",
|
||||
char_zone_instance_id,
|
||||
max_results)
|
||||
);
|
||||
if (RuleB(Bazaar, UseAlternateBazaarSearch)) {
|
||||
results = db.QueryDatabase(fmt::format(
|
||||
"SELECT DISTINCT(t.char_id), t.char_zone_id, t.char_zone_instance_id, t.char_entity_id, c.name "
|
||||
"FROM trader AS t "
|
||||
"JOIN character_data AS c ON t.char_id = c.id "
|
||||
"WHERE t.char_zone_instance_id = {} "
|
||||
"ORDER BY t.char_zone_instance_id ASC "
|
||||
"LIMIT {}",
|
||||
char_zone_instance_id,
|
||||
max_results)
|
||||
);
|
||||
}
|
||||
else {
|
||||
results = db.QueryDatabase(fmt::format(
|
||||
"SELECT DISTINCT(t.char_id), t.char_zone_id, t.char_zone_instance_id, t.char_entity_id, c.name "
|
||||
"FROM trader AS t "
|
||||
"JOIN character_data AS c ON t.char_id = c.id "
|
||||
"ORDER BY t.char_zone_instance_id ASC "
|
||||
"LIMIT {}",
|
||||
char_zone_instance_id,
|
||||
max_results)
|
||||
);
|
||||
}
|
||||
|
||||
distinct_traders.reserve(results.RowCount());
|
||||
|
||||
|
||||
+2
-6
@@ -347,6 +347,7 @@ RULE_STRING(World, SupportedClients, "RoF2", "Comma-delimited list of clients to
|
||||
RULE_STRING(World, CustomFilesKey, "", "Enable if the server requires custom files and sends a key to validate. Empty string to disable. Example: eqcustom_v1")
|
||||
RULE_STRING(World, CustomFilesUrl, "github.com/knervous/eqnexus/releases", "URL to display at character select if client is missing custom files")
|
||||
RULE_INT(World, CustomFilesAdminLevel, 20, "Admin level at which custom file key is not required when CustomFilesKey is specified")
|
||||
RULE_BOOL(World, RealTimeCalculateGuilds, false, "(Temp feature flag) If true, guilds will be calculated in real time instead of at zone boot. This is a performance hit but allows for more dynamic guilds.")
|
||||
RULE_CATEGORY_END()
|
||||
|
||||
RULE_CATEGORY(Zone)
|
||||
@@ -857,12 +858,6 @@ RULE_BOOL(Bots, BotArcheryConsumesAmmo, true, "Set to false to disable Archery A
|
||||
RULE_BOOL(Bots, BotThrowingConsumesAmmo, true, "Set to false to disable Throwing Ammo Consumption")
|
||||
RULE_INT(Bots, StackSizeMin, 20, "20 Default. -1 to disable and use default max stack size. Minimum stack size to give a bot (Arrows/Throwing).")
|
||||
RULE_INT(Bots, HasOrMayGetAggroThreshold, 90, "90 Default. Percent threshold of total hate where bots will stop casting spells that generate hate if they are set to try to not pull aggro via spells.")
|
||||
RULE_REAL(Bots, LowerMeleeDistanceMultiplier, 0.35, "Closest % of the hit box a melee bot will get to the target. Default 0.35")
|
||||
RULE_REAL(Bots, LowerTauntingMeleeDistanceMultiplier, 0.25, "Closest % of the hit box a taunting melee bot will get to the target. Default 0.25")
|
||||
RULE_REAL(Bots, LowerMaxMeleeRangeDistanceMultiplier, 0.80, "Closest % of the hit box a max melee range melee bot will get to the target. Default 0.80")
|
||||
RULE_REAL(Bots, UpperMeleeDistanceMultiplier, 0.55, "Furthest % of the hit box a melee bot will get from the target. Default 0.55")
|
||||
RULE_REAL(Bots, UpperTauntingMeleeDistanceMultiplier, 0.45, "Furthest % of the hit box a taunting melee bot will get from the target. Default 0.45")
|
||||
RULE_REAL(Bots, UpperMaxMeleeRangeDistanceMultiplier, 0.95, "Furthest % of the hit box a max melee range melee bot will get from the target. Default 0.95")
|
||||
RULE_BOOL(Bots, DisableSpecialAbilitiesAtMaxMelee, true, "If true, when bots are at max melee distance, special abilities including taunt will be disabled. Default True.")
|
||||
RULE_INT(Bots, MinJitterTimer, 500, "Minimum ms between bot movement jitter checks.")
|
||||
RULE_INT(Bots, MaxJitterTimer, 2500, "Maximum ms between bot movement jitter checks. Set to 0 to disable timer checks.")
|
||||
@@ -928,6 +923,7 @@ RULE_BOOL(Chat, AutoInjectSaylinksToSay, true, "Automatically injects saylinks i
|
||||
RULE_BOOL(Chat, AutoInjectSaylinksToClientMessage, true, "Automatically injects saylinks into dialogue that has [brackets in them]")
|
||||
RULE_BOOL(Chat, QuestDialogueUsesDialogueWindow, false, "Pipes all quest dialogue to dialogue window")
|
||||
RULE_BOOL(Chat, DialogueWindowAnimatesNPCsIfNoneSet, true, "If there is no animation specified in the dialogue window markdown then it will choose a random greet animation such as wave or salute")
|
||||
RULE_BOOL(Chat, AlwaysCaptureCommandText, false, "Consume command text (# and ^ by default), regardless of which channel it is sent to")
|
||||
RULE_CATEGORY_END()
|
||||
|
||||
RULE_CATEGORY(Merchant)
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@
|
||||
|
||||
// Build variables
|
||||
// these get injected during the build pipeline
|
||||
#define CURRENT_VERSION "23.4.0-dev" // always append -dev to the current version for custom-builds
|
||||
#define CURRENT_VERSION "23.5.0-dev" // always append -dev to the current version for custom-builds
|
||||
#define LOGIN_VERSION "0.8.0"
|
||||
#define COMPILE_DATE __DATE__
|
||||
#define COMPILE_TIME __TIME__
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "eqemu-server",
|
||||
"version": "23.4.0",
|
||||
"version": "23.5.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/EQEmu/Server.git"
|
||||
|
||||
@@ -33,7 +33,6 @@ bash -c "${world_bin} database:dump --login-tables --drop-table-syntax-only --du
|
||||
bash -c "${world_bin} database:dump --player-tables --drop-table-syntax-only --dump-output-to-console > ${dump_path}drop_tables_player.sql"
|
||||
bash -c "${world_bin} database:dump --system-tables --drop-table-syntax-only --dump-output-to-console > ${dump_path}drop_tables_system.sql"
|
||||
bash -c "${world_bin} database:dump --state-tables --drop-table-syntax-only --dump-output-to-console > ${dump_path}drop_tables_state.sql"
|
||||
bash -c "${world_bin} database:dump --query-serv-tables --drop-table-syntax-only --dump-output-to-console > ${dump_path}drop_tables_queryserv.sql"
|
||||
|
||||
#############################################
|
||||
# generate "create_" table files
|
||||
@@ -45,7 +44,6 @@ bash -c "${world_bin} database:dump --login-tables --table-structure-only --dump
|
||||
bash -c "${world_bin} database:dump --player-tables --table-structure-only --dump-output-to-console | sed 's/ AUTO_INCREMENT=[0-9]*\b//g' > ${dump_path}create_tables_player.sql"
|
||||
bash -c "${world_bin} database:dump --state-tables --table-structure-only --dump-output-to-console | sed 's/ AUTO_INCREMENT=[0-9]*\b//g' > ${dump_path}create_tables_state.sql"
|
||||
bash -c "${world_bin} database:dump --static-instance-data --dump-output-to-console >> ${dump_path}create_tables_state.sql"
|
||||
bash -c "${world_bin} database:dump --query-serv-tables --table-structure-only --dump-output-to-console | sed 's/ AUTO_INCREMENT=[0-9]*\b//g' > ${dump_path}create_tables_queryserv.sql"
|
||||
|
||||
# with content
|
||||
bash -c "${world_bin} database:dump --content-tables --dump-output-to-console > ${dump_path}create_tables_content.sql"
|
||||
|
||||
+145
-102
@@ -42,11 +42,16 @@ extern ZSList zoneserver_list;
|
||||
uint32 numplayers = 0; //this really wants to be a member variable of ClientList...
|
||||
|
||||
ClientList::ClientList()
|
||||
: CLStale_timer(10000)
|
||||
: CLStale_timer(10000),
|
||||
m_poll_cache_timer(6000)
|
||||
{
|
||||
NextCLEID = 1;
|
||||
|
||||
m_tick = std::make_unique<EQ::Timer>(5000, true, std::bind(&ClientList::OnTick, this, std::placeholders::_1));
|
||||
|
||||
// pre-allocate / pin memory for the zone server caches
|
||||
m_gm_zone_server_ids.reserve(512);
|
||||
m_guild_zone_server_ids.reserve(1024);
|
||||
}
|
||||
|
||||
ClientList::~ClientList() {
|
||||
@@ -57,6 +62,10 @@ void ClientList::Process() {
|
||||
if (CLStale_timer.Check())
|
||||
CLCheckStale();
|
||||
|
||||
if (m_poll_cache_timer.Check()) {
|
||||
RebuildZoneServerCaches();
|
||||
}
|
||||
|
||||
LinkedListIterator<Client*> iterator(list);
|
||||
|
||||
iterator.Reset();
|
||||
@@ -384,6 +393,7 @@ void ClientList::ClientUpdate(ZoneServer *zoneserver, ServerClientList_Struct *s
|
||||
}
|
||||
else {
|
||||
cle->Update(zoneserver, scl);
|
||||
AddToZoneServerCaches(cle);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -458,6 +468,7 @@ void ClientList::ClientUpdate(ZoneServer *zoneserver, ServerClientList_Struct *s
|
||||
);
|
||||
|
||||
clientlist.Insert(cle);
|
||||
AddToZoneServerCaches(cle);
|
||||
zoneserver->ChangeWID(scl->charid, cle->GetID());
|
||||
}
|
||||
|
||||
@@ -1608,7 +1619,7 @@ void ClientList::OnTick(EQ::Timer *t)
|
||||
/**
|
||||
* @param response
|
||||
*/
|
||||
void ClientList::GetClientList(Json::Value &response)
|
||||
void ClientList::GetClientList(Json::Value &response, bool full_list)
|
||||
{
|
||||
LinkedListIterator<ClientListEntry *> Iterator(clientlist);
|
||||
|
||||
@@ -1619,62 +1630,68 @@ void ClientList::GetClientList(Json::Value &response)
|
||||
|
||||
Json::Value row;
|
||||
|
||||
row["account_id"] = cle->AccountID();
|
||||
row["account_name"] = cle->AccountName();
|
||||
row["admin"] = cle->Admin();
|
||||
row["id"] = cle->GetID();
|
||||
row["ip"] = cle->GetIP();
|
||||
row["loginserver_account_id"] = cle->LSAccountID();
|
||||
row["loginserver_id"] = cle->LSID();
|
||||
row["loginserver_name"] = cle->LSName();
|
||||
row["online"] = cle->Online();
|
||||
row["world_admin"] = cle->WorldAdmin();
|
||||
row["id"] = cle->GetID();
|
||||
row["name"] = cle->name();
|
||||
row["level"] = cle->level();
|
||||
row["ip"] = cle->GetIP();
|
||||
row["gm"] = cle->GetGM();
|
||||
row["race"] = cle->race();
|
||||
row["class"] = cle->class_();
|
||||
row["client_version"] = cle->GetClientVersion();
|
||||
row["admin"] = cle->Admin();
|
||||
row["account_id"] = cle->AccountID();
|
||||
row["account_name"] = cle->AccountName();
|
||||
row["character_id"] = cle->CharID();
|
||||
row["anon"] = cle->Anon();
|
||||
row["guild_id"] = cle->GuildID();
|
||||
|
||||
if (full_list) {
|
||||
row["loginserver_account_id"] = cle->LSAccountID();
|
||||
row["loginserver_id"] = cle->LSID();
|
||||
row["loginserver_name"] = cle->LSName();
|
||||
row["online"] = cle->Online();
|
||||
row["world_admin"] = cle->WorldAdmin();
|
||||
row["guild_rank"] = cle->GuildRank();
|
||||
row["guild_tribute_opt_in"] = cle->GuildTributeOptIn();
|
||||
row["instance"] = cle->instance();
|
||||
row["is_local_client"] = cle->IsLocalClient();
|
||||
row["lfg"] = cle->LFG();
|
||||
row["lfg_comments"] = cle->GetLFGComments();
|
||||
row["lfg_from_level"] = cle->GetLFGFromLevel();
|
||||
row["lfg_match_filter"] = cle->GetLFGMatchFilter();
|
||||
row["lfg_to_level"] = cle->GetLFGToLevel();
|
||||
row["tells_off"] = cle->TellsOff();
|
||||
row["zone"] = cle->zone();
|
||||
}
|
||||
|
||||
auto server = cle->Server();
|
||||
if (server) {
|
||||
row["server"]["client_address"] = server->GetCAddress();
|
||||
row["server"]["client_local_address"] = server->GetCLocalAddress();
|
||||
row["server"]["client_port"] = server->GetCPort();
|
||||
row["server"]["compile_time"] = server->GetCompileTime();
|
||||
row["server"]["id"] = server->GetID();
|
||||
row["server"]["instance_id"] = server->GetInstanceID();
|
||||
row["server"]["ip"] = server->GetIP();
|
||||
row["server"]["is_booting"] = server->IsBootingUp();
|
||||
row["server"]["launch_name"] = server->GetLaunchName();
|
||||
row["server"]["launched_name"] = server->GetLaunchedName();
|
||||
row["server"]["number_players"] = server->NumPlayers();
|
||||
row["server"]["port"] = server->GetPort();
|
||||
row["server"]["previous_zone_id"] = server->GetPrevZoneID();
|
||||
row["server"]["static_zone"] = server->IsStaticZone();
|
||||
row["server"]["uui"] = server->GetUUID();
|
||||
row["server"]["zone_id"] = server->GetZoneID();
|
||||
row["server"]["zone_long_name"] = server->GetZoneLongName();
|
||||
row["server"]["zone_name"] = server->GetZoneName();
|
||||
row["server"]["zone_os_pid"] = server->GetZoneOSProcessID();
|
||||
row["server"]["zone_id"] = server->GetZoneID();
|
||||
row["server"]["zone_long_name"] = server->GetZoneLongName();
|
||||
row["server"]["zone_name"] = server->GetZoneName();
|
||||
row["server"]["zone_os_pid"] = server->GetZoneOSProcessID();
|
||||
row["server"]["id"] = server->GetID();
|
||||
|
||||
if (full_list) {
|
||||
row["server"]["client_address"] = server->GetCAddress();
|
||||
row["server"]["client_local_address"] = server->GetCLocalAddress();
|
||||
row["server"]["client_port"] = server->GetCPort();
|
||||
row["server"]["compile_time"] = server->GetCompileTime();
|
||||
row["server"]["instance_id"] = server->GetInstanceID();
|
||||
row["server"]["ip"] = server->GetIP();
|
||||
row["server"]["is_booting"] = server->IsBootingUp();
|
||||
row["server"]["launch_name"] = server->GetLaunchName();
|
||||
row["server"]["launched_name"] = server->GetLaunchedName();
|
||||
row["server"]["number_players"] = server->NumPlayers();
|
||||
row["server"]["port"] = server->GetPort();
|
||||
row["server"]["previous_zone_id"] = server->GetPrevZoneID();
|
||||
row["server"]["static_zone"] = server->IsStaticZone();
|
||||
row["server"]["uui"] = server->GetUUID();
|
||||
}
|
||||
}
|
||||
else {
|
||||
row["server"] = Json::Value();
|
||||
}
|
||||
row["anon"] = cle->Anon();
|
||||
row["character_id"] = cle->CharID();
|
||||
row["class"] = cle->class_();
|
||||
row["client_version"] = cle->GetClientVersion();
|
||||
row["gm"] = cle->GetGM();
|
||||
row["guild_id"] = cle->GuildID();
|
||||
row["guild_rank"] = cle->GuildRank();
|
||||
row["guild_tribute_opt_in"] = cle->GuildTributeOptIn();
|
||||
row["instance"] = cle->instance();
|
||||
row["is_local_client"] = cle->IsLocalClient();
|
||||
row["level"] = cle->level();
|
||||
row["lfg"] = cle->LFG();
|
||||
row["lfg_comments"] = cle->GetLFGComments();
|
||||
row["lfg_from_level"] = cle->GetLFGFromLevel();
|
||||
row["lfg_match_filter"] = cle->GetLFGMatchFilter();
|
||||
row["lfg_to_level"] = cle->GetLFGToLevel();
|
||||
row["name"] = cle->name();
|
||||
row["race"] = cle->race();
|
||||
row["tells_off"] = cle->TellsOff();
|
||||
row["zone"] = cle->zone();
|
||||
|
||||
response.append(row);
|
||||
|
||||
@@ -1851,71 +1868,97 @@ std::map<uint32, ClientListEntry *> ClientList::GetGuildClientsWithTributeOptIn(
|
||||
return guild_members;
|
||||
}
|
||||
|
||||
#include <unordered_set>
|
||||
void ClientList::RebuildZoneServerCaches()
|
||||
{
|
||||
// Clear without freeing memory (buckets stay allocated)
|
||||
m_gm_zone_server_ids.clear();
|
||||
m_guild_zone_server_ids.clear();
|
||||
|
||||
LinkedListIterator<ClientListEntry*> iterator(clientlist);
|
||||
iterator.Reset();
|
||||
|
||||
while (iterator.MoreElements()) {
|
||||
ClientListEntry* cle = iterator.GetData();
|
||||
|
||||
if (cle->Online() != CLE_Status::InZone || !cle->Server()) {
|
||||
iterator.Advance();
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32_t server_id = cle->Server()->GetID();
|
||||
|
||||
// Track GM zone server
|
||||
if (cle->GetGM()) {
|
||||
m_gm_zone_server_ids.insert(server_id);
|
||||
}
|
||||
|
||||
// Track guild zone servers
|
||||
if (cle->GuildID() > 0) {
|
||||
auto& guild_set = m_guild_zone_server_ids[cle->GuildID()];
|
||||
guild_set.insert(server_id);
|
||||
}
|
||||
|
||||
iterator.Advance();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint32_t> ClientList::GetGuildZoneServers(uint32 guild_id)
|
||||
{
|
||||
std::vector<uint32_t> zone_server_ids;
|
||||
std::unordered_set<uint32_t> seen_ids;
|
||||
if (RuleB(World, RealTimeCalculateGuilds)) {
|
||||
std::vector<uint32_t> zone_server_ids;
|
||||
std::unordered_set<uint32_t> seen_ids;
|
||||
|
||||
LinkedListIterator<ClientListEntry *> iterator(clientlist);
|
||||
LinkedListIterator<ClientListEntry *> iterator(clientlist);
|
||||
|
||||
iterator.Reset();
|
||||
while (iterator.MoreElements()) {
|
||||
ClientListEntry *cle = iterator.GetData();
|
||||
iterator.Reset();
|
||||
while (iterator.MoreElements()) {
|
||||
ClientListEntry *cle = iterator.GetData();
|
||||
|
||||
if (cle->Online() != CLE_Status::InZone) {
|
||||
iterator.Advance();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!cle->Server()) {
|
||||
iterator.Advance();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cle->GuildID() == guild_id) {
|
||||
uint32_t id = cle->Server()->GetID();
|
||||
if (seen_ids.insert(id).second) {
|
||||
zone_server_ids.emplace_back(id);
|
||||
if (cle->Online() != CLE_Status::InZone) {
|
||||
iterator.Advance();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!cle->Server()) {
|
||||
iterator.Advance();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cle->GuildID() == guild_id) {
|
||||
uint32_t id = cle->Server()->GetID();
|
||||
if (seen_ids.insert(id).second) {
|
||||
zone_server_ids.emplace_back(id);
|
||||
}
|
||||
}
|
||||
|
||||
iterator.Advance();
|
||||
}
|
||||
|
||||
iterator.Advance();
|
||||
return zone_server_ids;
|
||||
}
|
||||
|
||||
return zone_server_ids;
|
||||
auto it = m_guild_zone_server_ids.find(guild_id);
|
||||
if (it == m_guild_zone_server_ids.end()) {
|
||||
return {};
|
||||
}
|
||||
return {it->second.begin(), it->second.end()};
|
||||
}
|
||||
|
||||
std::vector<uint32_t> ClientList::GetZoneServersWithGMs()
|
||||
void ClientList::AddToZoneServerCaches(ClientListEntry* cle)
|
||||
{
|
||||
std::vector<uint32_t> zone_server_ids;
|
||||
std::unordered_set<uint32_t> seen_ids;
|
||||
LinkedListIterator<ClientListEntry *> iterator(clientlist);
|
||||
|
||||
iterator.Reset();
|
||||
while (iterator.MoreElements()) {
|
||||
ClientListEntry *cle = iterator.GetData();
|
||||
|
||||
if (cle->Online() != CLE_Status::InZone) {
|
||||
iterator.Advance();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!cle->Server()) {
|
||||
iterator.Advance();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cle->Admin() > 0) {
|
||||
uint32_t id = cle->Server()->GetID();
|
||||
if (seen_ids.insert(id).second) {
|
||||
zone_server_ids.emplace_back(id);
|
||||
}
|
||||
}
|
||||
|
||||
iterator.Advance();
|
||||
if (!cle || cle->Online() != CLE_Status::InZone || !cle->Server()) {
|
||||
return;
|
||||
}
|
||||
|
||||
return zone_server_ids;
|
||||
uint32_t server_id = cle->Server()->GetID();
|
||||
|
||||
// Add GM zone server if applicable
|
||||
if (cle->GetGM()) {
|
||||
m_gm_zone_server_ids.insert(server_id);
|
||||
}
|
||||
|
||||
// Add guild zone server if applicable
|
||||
if (cle->GuildID() > 0) {
|
||||
m_guild_zone_server_ids[cle->GuildID()].insert(server_id);
|
||||
}
|
||||
}
|
||||
|
||||
+15
-3
@@ -60,15 +60,13 @@ public:
|
||||
void CLCheckStale();
|
||||
void CLEKeepAlive(uint32 numupdates, uint32* wid);
|
||||
void CLEAdd(uint32 login_server_id, const char* login_server_name, const char* login_name, const char* login_key, int16 world_admin = AccountStatus::Player, uint32 ip_address = 0, uint8 is_local=0);
|
||||
std::vector<uint32_t> GetGuildZoneServers(uint32 guild_id);
|
||||
std::vector<uint32_t> GetZoneServersWithGMs();
|
||||
void UpdateClientGuild(uint32 char_id, uint32 guild_id);
|
||||
bool IsAccountInGame(uint32 iLSID);
|
||||
|
||||
int GetClientCount();
|
||||
void GetClients(const char *zone_name, std::vector<ClientListEntry *> &into);
|
||||
|
||||
void GetClientList(Json::Value &response);
|
||||
void GetClientList(Json::Value &response, bool full_list = false);
|
||||
void GetGuildClientList(Json::Value& response, uint32 guild_id);
|
||||
|
||||
void SendCharacterMessage(uint32_t character_id, int chat_type, const std::string& message);
|
||||
@@ -78,6 +76,15 @@ public:
|
||||
void SendCharacterMessageID(const std::string& character_name, int chat_type, int eqstr_id, std::initializer_list<std::string> args = {});
|
||||
void SendCharacterMessageID(ClientListEntry* character, int chat_type, int eqstr_id, std::initializer_list<std::string> args = {});
|
||||
|
||||
void AddToZoneServerCaches(ClientListEntry* cle);
|
||||
void RebuildZoneServerCaches();
|
||||
|
||||
std::vector<uint32_t> GetGuildZoneServers(uint32 guild_id);
|
||||
inline std::vector<uint32_t> GetZoneServersWithGMs()
|
||||
{
|
||||
return {m_gm_zone_server_ids.begin(), m_gm_zone_server_ids.end()};
|
||||
}
|
||||
|
||||
private:
|
||||
void OnTick(EQ::Timer *t);
|
||||
inline uint32 GetNextCLEID() { return NextCLEID++; }
|
||||
@@ -92,6 +99,11 @@ private:
|
||||
|
||||
|
||||
std::unique_ptr<EQ::Timer> m_tick;
|
||||
|
||||
// Zone server routing caches
|
||||
Timer m_poll_cache_timer;
|
||||
std::unordered_set<uint32_t> m_gm_zone_server_ids;
|
||||
std::unordered_map<uint32_t, std::unordered_set<uint32_t>> m_guild_zone_server_ids;
|
||||
};
|
||||
|
||||
#endif /*CLIENTLIST_H_*/
|
||||
|
||||
@@ -111,9 +111,17 @@ void callGetDatabaseSchema(Json::Value &response)
|
||||
response.append(schema);
|
||||
}
|
||||
|
||||
void callGetClientList(Json::Value &response)
|
||||
void callGetClientList(Json::Value &response, const std::vector<std::string> &args)
|
||||
{
|
||||
client_list.GetClientList(response);
|
||||
// if args has "full"
|
||||
bool full_list = false;
|
||||
if (args.size() > 1) {
|
||||
if (args[1] == "full") {
|
||||
full_list = true;
|
||||
}
|
||||
}
|
||||
|
||||
client_list.GetClientList(response, full_list);
|
||||
}
|
||||
|
||||
void getReloadTypes(Json::Value &response)
|
||||
@@ -127,6 +135,12 @@ void getReloadTypes(Json::Value &response)
|
||||
}
|
||||
}
|
||||
|
||||
void getServerCounts(Json::Value &response, const std::vector<std::string> &args)
|
||||
{
|
||||
response["zone_count"] = zoneserver_list.GetServerListCount();
|
||||
response["client_count"] = client_list.GetClientCount();
|
||||
}
|
||||
|
||||
void EQEmuApiWorldDataService::reload(Json::Value &r, const std::vector<std::string> &args)
|
||||
{
|
||||
std::vector<std::string> commands{};
|
||||
@@ -174,7 +188,7 @@ void EQEmuApiWorldDataService::get(Json::Value &r, const std::vector<std::string
|
||||
callGetDatabaseSchema(r);
|
||||
}
|
||||
if (m == "get_client_list") {
|
||||
callGetClientList(r);
|
||||
callGetClientList(r, args);
|
||||
}
|
||||
if (m == "get_reload_types") {
|
||||
getReloadTypes(r);
|
||||
@@ -185,6 +199,9 @@ void EQEmuApiWorldDataService::get(Json::Value &r, const std::vector<std::string
|
||||
if (m == "get_guild_details") {
|
||||
callGetGuildDetails(r, args);
|
||||
}
|
||||
if (m == "get_server_counts") {
|
||||
getServerCounts(r, args);
|
||||
}
|
||||
if (m == "lock_status") {
|
||||
r["locked"] = WorldConfig::get()->Locked;
|
||||
}
|
||||
@@ -192,7 +209,6 @@ void EQEmuApiWorldDataService::get(Json::Value &r, const std::vector<std::string
|
||||
|
||||
void EQEmuApiWorldDataService::callGetGuildDetails(Json::Value &response, const std::vector<std::string> &args)
|
||||
{
|
||||
|
||||
std::string command = !args[1].empty() ? args[1] : "";
|
||||
if (command.empty()) {
|
||||
return;
|
||||
|
||||
@@ -286,7 +286,7 @@ void LoginServer::ProcessLSFatalError(uint16_t opcode, EQ::Net::Packet &p)
|
||||
if (error.find("Worldserver Account / Password INVALID") != std::string::npos) {
|
||||
reason = "Usually this indicates you do not have a valid [account] and [password] (worldserver) account associated with your loginserver configuration. ";
|
||||
if (fmt::format("{}", m_loginserver_address).find("login.eqemulator.net") != std::string::npos) {
|
||||
reason += "For Legacy EQEmulator connections, you need to register your server @ http://www.eqemulator.org/account/?LS";
|
||||
reason += "For Legacy EQEmulator connections, you need to register your server @ https://www.eqemulator.org/index.php?pageid=ws_mgmt";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -72,6 +72,7 @@ public:
|
||||
ZoneServer* FindByZoneID(uint32 ZoneID);
|
||||
|
||||
const std::list<std::unique_ptr<ZoneServer>> &getZoneServerList() const;
|
||||
inline uint32_t GetServerListCount() { return zone_server_list.size(); }
|
||||
void SendServerReload(ServerReload::Type type, uchar *packet = nullptr);
|
||||
|
||||
private:
|
||||
|
||||
+9
-12
@@ -1165,10 +1165,6 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon)
|
||||
}
|
||||
|
||||
case SE_ResistFearChance: {
|
||||
if (base_value == 100) // If we reach 100% in a single spell/item then we should be immune to
|
||||
// negative fear resist effects until our immunity is over
|
||||
newbon->Fearless = true;
|
||||
|
||||
newbon->ResistFearChance += base_value; // these should stack
|
||||
break;
|
||||
}
|
||||
@@ -2474,9 +2470,6 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne
|
||||
|
||||
case SE_ResistFearChance:
|
||||
{
|
||||
if(effect_value == 100) // If we reach 100% in a single spell/item then we should be immune to negative fear resist effects until our immunity is over
|
||||
new_bonus->Fearless = true;
|
||||
|
||||
new_bonus->ResistFearChance += effect_value; // these should stack
|
||||
break;
|
||||
}
|
||||
@@ -4689,11 +4682,7 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id)
|
||||
break;
|
||||
|
||||
case SE_ResistFearChance:
|
||||
if (negate_spellbonus) {
|
||||
spellbonuses.Fearless = false;
|
||||
spellbonuses.ResistFearChance = effect_value;
|
||||
}
|
||||
|
||||
if (negate_spellbonus) {spellbonuses.ResistFearChance = effect_value; }
|
||||
if (negate_aabonus) { aabonuses.ResistFearChance = effect_value; }
|
||||
if (negate_itembonus) { itembonuses.ResistFearChance = effect_value; }
|
||||
break;
|
||||
@@ -5331,6 +5320,14 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id)
|
||||
spellbonuses.SEResist[e] = effect_value;
|
||||
spellbonuses.SEResist[e + 1] = effect_value;
|
||||
}
|
||||
if (negate_itembonus) {
|
||||
itembonuses.SEResist[e] = effect_value;
|
||||
itembonuses.SEResist[e + 1] = effect_value;
|
||||
}
|
||||
if (negate_aabonus) {
|
||||
aabonuses.SEResist[e] = effect_value;
|
||||
aabonuses.SEResist[e + 1] = effect_value;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
+154
-200
@@ -245,6 +245,8 @@ Bot::Bot(
|
||||
|
||||
EquipBot();
|
||||
|
||||
m_combat_jitter_timer.Start();
|
||||
|
||||
if (GetClass() == Class::Rogue) {
|
||||
m_rogue_evade_timer.Start();
|
||||
}
|
||||
@@ -2186,8 +2188,7 @@ void Bot::AI_Process()
|
||||
}
|
||||
|
||||
if (HOLDING || (raid && r_group == RAID_GROUPLESS)) {
|
||||
glm::vec3 Goal(0, 0, 0);
|
||||
TryNonCombatMovementChecks(bot_owner, follow_mob, Goal);
|
||||
TryNonCombatMovementChecks(bot_owner, follow_mob);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -2217,8 +2218,6 @@ void Bot::AI_Process()
|
||||
}
|
||||
|
||||
//ALT COMBAT (ACQUIRE HATE)
|
||||
glm::vec3 Goal(0, 0, 0);
|
||||
|
||||
// We have aggro to choose from
|
||||
if (IsEngaged()) {
|
||||
if (rest_timer.Enabled()) {
|
||||
@@ -2293,15 +2292,15 @@ void Bot::AI_Process()
|
||||
const EQ::ItemInstance* p_item = GetBotItem(EQ::invslot::slotPrimary);
|
||||
const EQ::ItemInstance* s_item = GetBotItem(EQ::invslot::slotSecondary);
|
||||
|
||||
CombatRangeInput input = {
|
||||
.target = tar,
|
||||
.target_distance = tar_distance,
|
||||
.stop_melee_level = stop_melee_level,
|
||||
.p_item = p_item,
|
||||
.s_item = s_item
|
||||
CombatRangeInput i = {
|
||||
.target = tar,
|
||||
.target_distance = tar_distance,
|
||||
.stop_melee_level = stop_melee_level,
|
||||
.p_item = p_item,
|
||||
.s_item = s_item
|
||||
};
|
||||
|
||||
CombatRangeOutput o = EvaluateCombatRange(input);
|
||||
CombatRangeOutput o = EvaluateCombatRange(i);
|
||||
|
||||
// Combat range variables
|
||||
bool at_combat_range = o.at_combat_range;
|
||||
@@ -2362,7 +2361,7 @@ void Bot::AI_Process()
|
||||
}
|
||||
}
|
||||
|
||||
TryPursueTarget(leash_distance, Goal);
|
||||
TryPursueTarget(leash_distance);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -2385,52 +2384,30 @@ void Bot::AI_Process()
|
||||
(bot_owner->GetBotPulling() && NOT_RETURNING_BOT);
|
||||
|
||||
if (!other_bot_pulling && at_combat_range) {
|
||||
bool jitter_cooldown = false;
|
||||
|
||||
if (m_combat_jitter_timer.GetRemainingTime() > 1 && m_combat_jitter_timer.Enabled()) {
|
||||
jitter_cooldown = true;
|
||||
}
|
||||
|
||||
if (
|
||||
IsMoving() ||
|
||||
GetCombatJitterFlag() ||
|
||||
GetCombatOutOfRangeJitterFlag()
|
||||
) {
|
||||
if (
|
||||
!GetCombatJitterFlag() ||
|
||||
!IsMoving() ||
|
||||
GetCombatOutOfRangeJitterFlag()
|
||||
) {
|
||||
StopMoving(CalculateHeadingToTarget(tar->GetX(), tar->GetY()));
|
||||
}
|
||||
CombatPositioningInput cpi {
|
||||
.tar = tar,
|
||||
.stop_melee_level = stop_melee_level,
|
||||
.tar_distance = tar_distance,
|
||||
.melee_distance_min = melee_distance_min,
|
||||
.melee_distance = melee_distance,
|
||||
.melee_distance_max = melee_distance_max,
|
||||
.behind_mob = behind_mob,
|
||||
.front_mob = front_mob
|
||||
};
|
||||
|
||||
if (DoCombatPositioning(cpi) && IsMoving()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!jitter_cooldown &&
|
||||
AI_movement_timer->Check() &&
|
||||
(!spellend_timer.Enabled() || GetClass() == Class::Bard)
|
||||
) {
|
||||
DoCombatPositioning(tar, Goal, stop_melee_level, tar_distance, melee_distance_min, melee_distance, melee_distance_max, behind_mob, front_mob);
|
||||
if (!IsSitting() && !IsFacingMob(tar)) {
|
||||
FaceTarget(tar);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
if (!IsSitting() && !IsFacingMob(tar)) {
|
||||
FaceTarget(tar);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!IsBotNonSpellFighter() && AI_HasSpells() && AI_EngagedCastCheck()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsMoving()) {
|
||||
StopMoving(CalculateHeadingToTarget(tar->GetX(), tar->GetY()));
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!tar->GetSpecialAbility(SpecialAbility::RangedAttackImmunity) &&
|
||||
IsBotRanged() &&
|
||||
@@ -2479,7 +2456,7 @@ void Bot::AI_Process()
|
||||
|
||||
// ENGAGED NOT AT COMBAT RANGE
|
||||
|
||||
else if (!other_bot_pulling && !TryPursueTarget(leash_distance, Goal)) {
|
||||
else if (!other_bot_pulling && !TryPursueTarget(leash_distance)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2492,7 +2469,7 @@ void Bot::AI_Process()
|
||||
TryMeditate();
|
||||
}
|
||||
else { // Out-of-combat behavior
|
||||
DoOutOfCombatChecks(bot_owner, follow_mob, Goal, leash_distance, fm_distance);
|
||||
DoOutOfCombatChecks(bot_owner, follow_mob, leash_distance, fm_distance);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2509,8 +2486,10 @@ bool Bot::TryBardMovementCasts() {// Basically, bard bots get a chance to cast i
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Bot::TryNonCombatMovementChecks(Client* bot_owner, const Mob* follow_mob, glm::vec3& Goal) {// Non-engaged movement checks
|
||||
bool Bot::TryNonCombatMovementChecks(Client* bot_owner, const Mob* follow_mob) {// Non-engaged movement checks
|
||||
if (AI_movement_timer->Check() && (!IsCasting() || GetClass() == Class::Bard)) {
|
||||
glm::vec3 Goal(0, 0, 0);
|
||||
|
||||
if (GUARDING) {
|
||||
Goal = GetGuardPoint();
|
||||
}
|
||||
@@ -2564,7 +2543,7 @@ bool Bot::TryIdleChecks(float fm_distance) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void Bot::DoOutOfCombatChecks(Client* bot_owner, Mob* follow_mob, glm::vec3& Goal, float leash_distance, float fm_distance) {
|
||||
void Bot::DoOutOfCombatChecks(Client* bot_owner, Mob* follow_mob, float leash_distance, float fm_distance) {
|
||||
SetAttackFlag(false);
|
||||
SetCombatRoundForAlerts(false);
|
||||
SetAttackingFlag(false);
|
||||
@@ -2597,7 +2576,7 @@ void Bot::DoOutOfCombatChecks(Client* bot_owner, Mob* follow_mob, glm::vec3& Goa
|
||||
}
|
||||
|
||||
// Ok to idle
|
||||
if (TryNonCombatMovementChecks(bot_owner, follow_mob, Goal)) {
|
||||
if (TryNonCombatMovementChecks(bot_owner, follow_mob)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2808,15 +2787,14 @@ bool Bot::TryMeditate() {
|
||||
}
|
||||
|
||||
// This code actually gets processed when we are too far away from target and have not engaged yet
|
||||
bool Bot::TryPursueTarget(float leash_distance, glm::vec3& Goal) {
|
||||
bool Bot::TryPursueTarget(float leash_distance) {
|
||||
if (AI_movement_timer->Check() && (!spellend_timer.Enabled() || GetClass() == Class::Bard)) {
|
||||
if (GetTarget() && !IsRooted()) {
|
||||
LogAIDetail("Pursuing [{}] while engaged", GetTarget()->GetCleanName());
|
||||
Goal = GetTarget()->GetPosition();
|
||||
glm::vec3 Goal = GetTarget()->GetPosition();
|
||||
|
||||
if (DistanceSquared(m_Position, Goal) <= leash_distance) {
|
||||
RunTo(Goal.x, Goal.y, Goal.z);
|
||||
SetCombatOutOfRangeJitter();
|
||||
} else {
|
||||
WipeHateList();
|
||||
SetTarget(nullptr);
|
||||
@@ -3130,8 +3108,8 @@ CombatRangeOutput Bot::EvaluateCombatRange(const CombatRangeInput& input) {
|
||||
bool is_backstab_weapon = input.p_item && input.p_item->GetItemBackstabDamage();
|
||||
|
||||
if (IsTaunting()) { // Taunting bots
|
||||
o.melee_distance_min = o.melee_distance_max * RuleR(Bots, LowerTauntingMeleeDistanceMultiplier);
|
||||
o.melee_distance = o.melee_distance_max * RuleR(Bots, UpperTauntingMeleeDistanceMultiplier);
|
||||
o.melee_distance_min = o.melee_distance_max * 0.25f;
|
||||
o.melee_distance = o.melee_distance_max * 0.45f;
|
||||
}
|
||||
else if (IsBotRanged()) { // Archers/Throwers
|
||||
float min_distance = RuleI(Combat, MinRangedAttackDist);
|
||||
@@ -3139,22 +3117,22 @@ CombatRangeOutput Bot::EvaluateCombatRange(const CombatRangeInput& input) {
|
||||
float desired_range = GetBotDistanceRanged();
|
||||
|
||||
max_distance = (max_distance == 0 ? desired_range : max_distance); // stay ranged even if items/ammo aren't correct
|
||||
o.melee_distance_min = std::max(min_distance, (desired_range / 2));
|
||||
o.melee_distance_min = std::max(min_distance, (desired_range * 0.75f));
|
||||
o.melee_distance = std::min(max_distance, desired_range);
|
||||
}
|
||||
else if (input.stop_melee_level) { // Casters
|
||||
float desired_range = GetBotDistanceRanged();
|
||||
|
||||
o.melee_distance_min = std::max(o.melee_distance_max, (desired_range / 2));
|
||||
o.melee_distance_min = std::max(o.melee_distance_max, (desired_range * 0.75f));
|
||||
o.melee_distance = std::max((o.melee_distance_max * 1.25f), desired_range);
|
||||
}
|
||||
else if (GetMaxMeleeRange()) { // Melee bots set to max melee range
|
||||
o.melee_distance_min = o.melee_distance_max * RuleR(Bots, LowerMaxMeleeRangeDistanceMultiplier);
|
||||
o.melee_distance = o.melee_distance_max * RuleR(Bots, UpperMaxMeleeRangeDistanceMultiplier);
|
||||
o.melee_distance_min = o.melee_distance_max * 0.80f;
|
||||
o.melee_distance = o.melee_distance_max * 0.95f;
|
||||
}
|
||||
else { // Regular melee
|
||||
o.melee_distance_min = o.melee_distance_max * RuleR(Bots, LowerMeleeDistanceMultiplier);
|
||||
o.melee_distance = o.melee_distance_max * RuleR(Bots, UpperMeleeDistanceMultiplier);
|
||||
o.melee_distance_min = o.melee_distance_max * 0.30f;
|
||||
o.melee_distance = o.melee_distance_max * 0.65f;
|
||||
}
|
||||
|
||||
o.at_combat_range = (input.target_distance <= o.melee_distance);
|
||||
@@ -9738,7 +9716,7 @@ bool Bot::CastChecks(uint16 spell_id, Mob* tar, uint16 spell_type, bool precheck
|
||||
)
|
||||
)
|
||||
&&
|
||||
tar->CanBuffStack(spell_id, GetLevel(), false) < 0
|
||||
tar->CanBuffStack(spell_id, GetLevel(), true) < 0
|
||||
) {
|
||||
LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} on {} due to !CanBuffStack.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName());
|
||||
return false;
|
||||
@@ -11842,7 +11820,7 @@ bool Bot::IsValidSpellTypeBySpellID(uint16 spell_type, uint16 spell_id) {
|
||||
return false;
|
||||
case BotSpellTypes::ResistBuffs:
|
||||
case BotSpellTypes::PetResistBuffs:
|
||||
if (IsResistanceBuffSpell(spell_id)) {
|
||||
if (IsResistanceBuffSpell(spell_id) && !IsEffectInSpell(spell_id, SE_DamageShield)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -11966,193 +11944,171 @@ void Bot::SetCastedSpellType(uint16 spell_type) {
|
||||
_castedSpellType = spell_type;
|
||||
}
|
||||
|
||||
void Bot::DoFaceCheckWithJitter(Mob* tar) {
|
||||
if (!tar) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsMoving()) {
|
||||
return;
|
||||
}
|
||||
|
||||
SetCombatJitter();
|
||||
if (!IsFacingMob(tar)) {
|
||||
FaceTarget(tar);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
void Bot::DoFaceCheckNoJitter(Mob* tar) {
|
||||
if (!tar) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsMoving()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsFacingMob(tar)) {
|
||||
FaceTarget(tar);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
void Bot::RunToGoalWithJitter(glm::vec3 Goal) {
|
||||
RunTo(Goal.x, Goal.y, Goal.z);
|
||||
SetCombatJitter();
|
||||
}
|
||||
|
||||
void Bot::SetCombatOutOfRangeJitter() {
|
||||
SetCombatOutOfRangeJitterFlag();
|
||||
|
||||
if (RuleI(Bots, MaxJitterTimer) > 0) {
|
||||
m_combat_jitter_timer.Start(zone->random.Int(RuleI(Bots, MinJitterTimer), RuleI(Bots, MaxJitterTimer)), true);
|
||||
}
|
||||
}
|
||||
|
||||
void Bot::SetCombatJitter() {
|
||||
SetCombatJitterFlag();
|
||||
|
||||
if (RuleI(Bots, MaxJitterTimer) > 0) {
|
||||
m_combat_jitter_timer.Start(zone->random.Int(RuleI(Bots, MinJitterTimer), RuleI(Bots, MaxJitterTimer)), true);
|
||||
}
|
||||
}
|
||||
|
||||
void Bot::DoCombatPositioning(
|
||||
Mob* tar,
|
||||
glm::vec3 Goal,
|
||||
bool stop_melee_level,
|
||||
float tar_distance,
|
||||
float melee_distance_min,
|
||||
float melee_distance,
|
||||
float melee_distance_max,
|
||||
bool behind_mob,
|
||||
bool front_mob
|
||||
) {
|
||||
if (!tar->IsFeared()) {
|
||||
bool is_too_close = tar_distance < melee_distance_min;
|
||||
bool los_adjust = !HasRequiredLoSForPositioning(tar);
|
||||
bool Bot::DoCombatPositioning(const CombatPositioningInput& input)
|
||||
{
|
||||
bool adjustment_needed = false;
|
||||
bool is_too_close = input.tar_distance < input.melee_distance_min;
|
||||
bool los_adjust = !HasRequiredLoSForPositioning(input.tar);
|
||||
bool behind_mob_set = !input.stop_melee_level &&
|
||||
!IsBotRanged() &&
|
||||
GetBehindMob(); // Don't want casters or ranged to find positions behind the target.
|
||||
bool adjustment_allowed = !IsMoving() &&
|
||||
m_combat_jitter_timer.Check() &&
|
||||
(!spellend_timer.Enabled() || GetClass() == Class::Bard);
|
||||
|
||||
if (tar->IsRooted() && !IsTaunting()) { // Move non-taunting melee out of range
|
||||
bool rooted_adjust = tar_distance <= melee_distance_max && HasTargetReflection();
|
||||
|
||||
if (rooted_adjust) {
|
||||
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, (melee_distance_max + 1), (melee_distance_max * 1.25f), GetBehindMob(), !GetBehindMob())) {
|
||||
RunToGoalWithJitter(Goal);
|
||||
return;
|
||||
}
|
||||
if (!IsMoving() && !IsSitting() && !IsFacingMob(input.tar)) {
|
||||
FaceTarget(input.tar);
|
||||
}
|
||||
|
||||
FindPositionInput find_position_input = {
|
||||
.tar = input.tar,
|
||||
.distance_min = input.melee_distance_min,
|
||||
.distance_max = input.melee_distance_max,
|
||||
.behind_only = behind_mob_set,
|
||||
.front_only = IsTaunting(),
|
||||
.bypass_los = false,
|
||||
};
|
||||
|
||||
bool is_melee = (!input.stop_melee_level && !IsBotRanged());
|
||||
|
||||
if (input.tar->IsRooted() && !IsTaunting()) { // Move non-taunting melee out of range
|
||||
adjustment_needed =
|
||||
(input.tar_distance <= input.melee_distance_max) &&
|
||||
HasTargetReflection();
|
||||
|
||||
if (adjustment_needed && adjustment_allowed) {
|
||||
find_position_input.distance_min = input.melee_distance_max + 1;
|
||||
find_position_input.distance_max = input.melee_distance_max * 1.25f;
|
||||
|
||||
PlotBotPositionAroundTarget(find_position_input);
|
||||
}
|
||||
} else {
|
||||
if (input.tar->IsFeared()) {
|
||||
adjustment_needed = los_adjust;
|
||||
|
||||
if (adjustment_needed && adjustment_allowed) {
|
||||
find_position_input.distance_min = input.melee_distance_min;
|
||||
find_position_input.distance_max = input.melee_distance;
|
||||
find_position_input.behind_only = false;
|
||||
find_position_input.front_only = false;
|
||||
|
||||
PlotBotPositionAroundTarget(find_position_input);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (IsTaunting()) { // Taunting adjustments
|
||||
bool taunting_adjust = (!front_mob || is_too_close || los_adjust);
|
||||
else if (IsTaunting() || HasTargetReflection()) { // Taunting/Aggro adjustments
|
||||
adjustment_needed =
|
||||
is_too_close ||
|
||||
los_adjust ||
|
||||
(is_melee && !input.front_mob);
|
||||
|
||||
if (taunting_adjust) {
|
||||
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, false, true)) {
|
||||
RunToGoalWithJitter(Goal);
|
||||
if (adjustment_needed && adjustment_allowed) {
|
||||
find_position_input.distance_min = input.melee_distance_min;
|
||||
find_position_input.distance_max = input.melee_distance;
|
||||
find_position_input.behind_only = false;
|
||||
find_position_input.front_only = true;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
PlotBotPositionAroundTarget(find_position_input);
|
||||
}
|
||||
else {
|
||||
if (tar->IsEnraged() && !stop_melee_level && !IsBotRanged()) { // Move non-taunting melee bots behind target during enrage
|
||||
bool enraged_adjust = !behind_mob || is_too_close || los_adjust;
|
||||
} else {
|
||||
if (input.tar->IsEnraged() && is_melee) { // Move non-taunting melee bots behind target during enrage
|
||||
adjustment_needed =
|
||||
!behind_mob_set ||
|
||||
is_too_close ||
|
||||
los_adjust;
|
||||
|
||||
if (enraged_adjust) {
|
||||
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, true)) {
|
||||
RunToGoalWithJitter(Goal);
|
||||
if (adjustment_needed && adjustment_allowed) {
|
||||
find_position_input.distance_min = input.melee_distance_min;
|
||||
find_position_input.distance_max = input.melee_distance;
|
||||
find_position_input.behind_only = true;
|
||||
find_position_input.front_only = false;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
PlotBotPositionAroundTarget(find_position_input);
|
||||
}
|
||||
else { // Regular adjustments
|
||||
bool regular_adjust =
|
||||
is_too_close ||
|
||||
los_adjust ||
|
||||
(!GetBehindMob() && !front_mob) ||
|
||||
(GetBehindMob() && !behind_mob);
|
||||
} else { // Regular adjustments
|
||||
adjustment_needed =
|
||||
is_too_close ||
|
||||
los_adjust ||
|
||||
(behind_mob_set && !input.behind_mob);
|
||||
|
||||
if (regular_adjust) {
|
||||
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, GetBehindMob(), !GetBehindMob())) {
|
||||
RunToGoalWithJitter(Goal);
|
||||
if (adjustment_needed && adjustment_allowed) {
|
||||
find_position_input.distance_min = input.melee_distance_min;
|
||||
find_position_input.distance_max = input.melee_distance;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
PlotBotPositionAroundTarget(find_position_input);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DoFaceCheckNoJitter(tar);
|
||||
if (!adjustment_needed && IsMoving()) {
|
||||
StopMoving();
|
||||
}
|
||||
|
||||
return adjustment_needed;
|
||||
}
|
||||
|
||||
bool Bot::PlotBotPositionAroundTarget(Mob* target, float& x_dest, float& y_dest, float& z_dest, float min_distance, float max_distance, bool behind_only, bool front_only, bool bypass_los) {
|
||||
bool Bot::PlotBotPositionAroundTarget(const FindPositionInput& input) {
|
||||
bool Result = false;
|
||||
|
||||
if (target) {
|
||||
float look_heading = 0;
|
||||
|
||||
min_distance = min_distance;
|
||||
max_distance = max_distance;
|
||||
float temp_x = 0;
|
||||
float temp_y = 0;
|
||||
float temp_z = target->GetZ();
|
||||
float best_z = 0;
|
||||
auto offset = GetZOffset();
|
||||
const float tar_x = target->GetX();
|
||||
const float tar_y = target->GetY();
|
||||
float tar_distance = 0;
|
||||
|
||||
glm::vec3 temp_z_Position;
|
||||
glm::vec4 temp_m_Position;
|
||||
|
||||
const uint16 max_iterations_allowed = 50;
|
||||
uint16 counter = 0;
|
||||
if (input.tar) {
|
||||
glm::vec3 temp_goal(0, 0, input.tar->GetZ());
|
||||
glm::vec3 tar_position(input.tar->GetX(), input.tar->GetY(), input.tar->GetZ());
|
||||
float look_heading = 0;
|
||||
float best_z = 0;
|
||||
float tar_distance = 0;
|
||||
float desired_angle = 0;
|
||||
const float offset = GetZOffset();
|
||||
const uint16 max_iterations_allowed = 50;
|
||||
uint16 counter = 0;
|
||||
|
||||
while (counter < max_iterations_allowed) {
|
||||
temp_x = tar_x + zone->random.Real(-max_distance, max_distance);
|
||||
temp_y = tar_y + zone->random.Real(-max_distance, max_distance);
|
||||
|
||||
temp_z_Position.x = temp_x;
|
||||
temp_z_Position.y = temp_y;
|
||||
temp_z_Position.z = temp_z;
|
||||
best_z = GetFixedZ(temp_z_Position);
|
||||
temp_goal.x = tar_position.x + zone->random.Real(-input.distance_max, input.distance_max);
|
||||
temp_goal.y = tar_position.y + zone->random.Real(-input.distance_max, input.distance_max);
|
||||
best_z = GetFixedZ(temp_goal);
|
||||
|
||||
if (best_z != BEST_Z_INVALID) {
|
||||
temp_z = best_z;
|
||||
temp_goal.z = best_z;
|
||||
}
|
||||
else {
|
||||
counter++;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
temp_m_Position.x = temp_x;
|
||||
temp_m_Position.y = temp_y;
|
||||
temp_m_Position.z = temp_z;
|
||||
tar_distance = Distance(input.tar->GetPosition(), temp_goal);
|
||||
|
||||
tar_distance = Distance(target->GetPosition(), temp_m_Position);
|
||||
|
||||
if (tar_distance > max_distance || tar_distance < min_distance) {
|
||||
if (tar_distance > input.distance_max || tar_distance < std::max(input.distance_min, (input.distance_max * 0.75f))) {
|
||||
counter++;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (front_only && !InFrontMob(target, temp_x, temp_y)) {
|
||||
if (input.front_only && !InFrontMob(input.tar, temp_goal.x, temp_goal.y)) {
|
||||
counter++;
|
||||
|
||||
continue;
|
||||
}
|
||||
else if (behind_only && !BehindMob(target, temp_x, temp_y)) {
|
||||
else if (input.behind_only && !BehindMob(input.tar, temp_goal.x, temp_goal.y)) {
|
||||
counter++;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!bypass_los && CastToBot()->RequiresLoSForPositioning() && !CheckPositioningLosFN(target, temp_x, temp_y, temp_z)) {
|
||||
if (!input.bypass_los && CastToBot()->RequiresLoSForPositioning() && !CheckPositioningLosFN(input.tar, temp_goal.x, temp_goal.y, temp_goal.z)) {
|
||||
counter++;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -12161,9 +12117,7 @@ bool Bot::PlotBotPositionAroundTarget(Mob* target, float& x_dest, float& y_dest,
|
||||
}
|
||||
|
||||
if (Result) {
|
||||
x_dest = temp_x;
|
||||
y_dest = temp_y;
|
||||
z_dest = temp_z;
|
||||
RunToGoalWithJitter(temp_goal);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+5
-32
@@ -39,9 +39,6 @@
|
||||
|
||||
constexpr uint32 BOT_KEEP_ALIVE_INTERVAL = 5000; // 5 seconds
|
||||
|
||||
constexpr uint32 BOT_COMBAT_JITTER_INTERVAL_MIN = 1500; // 1.5 seconds
|
||||
constexpr uint32 BOT_COMBAT_JITTER_INTERVAL_MAX = 3000; // 3 seconds
|
||||
|
||||
constexpr uint32 MAG_EPIC_1_0 = 28034;
|
||||
|
||||
extern WorldServer worldserver;
|
||||
@@ -232,21 +229,6 @@ static std::map<uint16, std::string> botSubType_names = {
|
||||
{ CommandedSubTypes::Selo, "Selo" }
|
||||
};
|
||||
|
||||
struct CombatRangeInput {
|
||||
Mob* target;
|
||||
float target_distance;
|
||||
bool stop_melee_level;
|
||||
const EQ::ItemInstance* p_item;
|
||||
const EQ::ItemInstance* s_item;
|
||||
};
|
||||
|
||||
struct CombatRangeOutput {
|
||||
bool at_combat_range = false;
|
||||
float melee_distance_min = 0.0f;
|
||||
float melee_distance = 0.0f;
|
||||
float melee_distance_max = 0.0f;
|
||||
};
|
||||
|
||||
class Bot : public NPC {
|
||||
friend class Mob;
|
||||
public:
|
||||
@@ -577,7 +559,7 @@ public:
|
||||
uint16 GetPetBotSpellType(uint16 spell_type);
|
||||
|
||||
// Movement checks
|
||||
bool PlotBotPositionAroundTarget(Mob* target, float& x_dest, float& y_dest, float& z_dest, float min_distance, float max_distance, bool behind_only = false, bool front_only = false, bool bypass_los = false);
|
||||
bool PlotBotPositionAroundTarget(const FindPositionInput& input);
|
||||
std::vector<Mob*> GetSpellTargetList(bool entire_raid = false);
|
||||
void SetSpellTargetList(std::vector<Mob*> spell_target_list) { _spell_target_list = spell_target_list; }
|
||||
std::vector<Mob*> GetGroupSpellTargetList() { return _group_spell_target_list; }
|
||||
@@ -1102,15 +1084,8 @@ public:
|
||||
bool CheckIfCasting(float fm_distance);
|
||||
void HealRotationChecks();
|
||||
|
||||
bool GetCombatJitterFlag() { return m_combat_jitter_flag; }
|
||||
void SetCombatJitterFlag(bool flag = true) { m_combat_jitter_flag = flag; }
|
||||
bool GetCombatOutOfRangeJitterFlag() { return m_combat_out_of_range_jitter_flag; }
|
||||
void SetCombatOutOfRangeJitterFlag(bool flag = true) { m_combat_out_of_range_jitter_flag = flag; }
|
||||
void SetCombatJitter();
|
||||
void SetCombatOutOfRangeJitter();
|
||||
void DoCombatPositioning(Mob* tar, glm::vec3 Goal, bool stop_melee_level, float tar_distance, float melee_distance_min, float melee_distance, float melee_distance_max, bool behind_mob, bool front_mob);
|
||||
void DoFaceCheckWithJitter(Mob* tar);
|
||||
void DoFaceCheckNoJitter(Mob* tar);
|
||||
bool DoCombatPositioning(const CombatPositioningInput& input);
|
||||
void RunToGoalWithJitter(glm::vec3 Goal);
|
||||
bool RequiresLoSForPositioning();
|
||||
bool HasRequiredLoSForPositioning(Mob* tar);
|
||||
@@ -1118,12 +1093,12 @@ public:
|
||||
// Try Combat Methods
|
||||
bool TryEvade(Mob* tar);
|
||||
bool TryFacingTarget(Mob* tar);
|
||||
bool TryPursueTarget(float leash_distance, glm::vec3& Goal);
|
||||
bool TryPursueTarget(float leash_distance);
|
||||
bool TryMeditate();
|
||||
bool TryAutoDefend(Client* bot_owner, float leash_distance);
|
||||
bool TryIdleChecks(float fm_distance);
|
||||
bool TryNonCombatMovementChecks(Client* bot_owner, const Mob* follow_mob, glm::vec3& Goal);
|
||||
void DoOutOfCombatChecks(Client* bot_owner, Mob* follow_mob, glm::vec3& Goal, float leash_distance, float fm_distance);
|
||||
bool TryNonCombatMovementChecks(Client* bot_owner, const Mob* follow_mob);
|
||||
void DoOutOfCombatChecks(Client* bot_owner, Mob* follow_mob, float leash_distance, float fm_distance);
|
||||
bool TryBardMovementCasts();
|
||||
bool BotRangedAttack(Mob* other, bool can_double_attack = false);
|
||||
bool CheckDoubleRangedAttack();
|
||||
@@ -1189,8 +1164,6 @@ private:
|
||||
Timer m_auto_save_timer;
|
||||
|
||||
Timer m_combat_jitter_timer;
|
||||
bool m_combat_jitter_flag;
|
||||
bool m_combat_out_of_range_jitter_flag;
|
||||
|
||||
bool m_dirtyautohaters;
|
||||
bool m_guard_flag;
|
||||
|
||||
@@ -48,9 +48,10 @@ void bot_command_click_item(Client* c, const Seperator* sep)
|
||||
sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end());
|
||||
|
||||
Mob* tar = c->GetTarget();
|
||||
bool is_success = false;
|
||||
|
||||
for (auto my_bot : sbl) {
|
||||
if (my_bot->BotPassiveCheck()) {
|
||||
if (!my_bot->ValidStateCheck(c)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -68,6 +69,11 @@ void bot_command_click_item(Client* c, const Seperator* sep)
|
||||
continue;
|
||||
}
|
||||
|
||||
is_success = true;
|
||||
my_bot->TryItemClick(slot_id);
|
||||
}
|
||||
|
||||
if (!is_success) {
|
||||
c->Message(Chat::Yellow, "None of your bots are capable of doing that currently.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
#include "../common/types.h"
|
||||
#include "../common/timer.h"
|
||||
#include "mob.h"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
@@ -151,4 +152,39 @@ struct BotSpellTypesByClass {
|
||||
std::string description;
|
||||
};
|
||||
|
||||
struct CombatRangeInput {
|
||||
Mob* target;
|
||||
float target_distance;
|
||||
bool stop_melee_level;
|
||||
const EQ::ItemInstance* p_item;
|
||||
const EQ::ItemInstance* s_item;
|
||||
};
|
||||
|
||||
struct CombatRangeOutput {
|
||||
bool at_combat_range = false;
|
||||
float melee_distance_min = 0.0f;
|
||||
float melee_distance = 0.0f;
|
||||
float melee_distance_max = 0.0f;
|
||||
};
|
||||
|
||||
struct CombatPositioningInput {
|
||||
Mob* tar;
|
||||
bool stop_melee_level;
|
||||
float tar_distance;
|
||||
float melee_distance_min;
|
||||
float melee_distance;
|
||||
float melee_distance_max;
|
||||
bool behind_mob;
|
||||
bool front_mob;
|
||||
};
|
||||
|
||||
struct FindPositionInput {
|
||||
Mob* tar;
|
||||
float distance_min;
|
||||
float distance_max;
|
||||
bool behind_only;
|
||||
bool front_only;
|
||||
bool bypass_los;
|
||||
};
|
||||
|
||||
#endif // BOT_STRUCTS
|
||||
|
||||
@@ -38,7 +38,11 @@ inline void SetupStateZone()
|
||||
SetupZone("soldungb");
|
||||
zone->Process();
|
||||
// depop the zone controller
|
||||
entity_list.GetNPCByNPCTypeID(ZONE_CONTROLLER_NPC_ID)->Depop();
|
||||
auto controller = entity_list.GetNPCByNPCTypeID(ZONE_CONTROLLER_NPC_ID);
|
||||
if (controller != nullptr) {
|
||||
controller->Depop();
|
||||
}
|
||||
|
||||
entity_list.MobProcess(); // process the depop
|
||||
}
|
||||
|
||||
@@ -352,8 +356,12 @@ inline void TestSpawns()
|
||||
npc->Death(npc, npc->GetHP() + 1, SPELL_UNKNOWN, EQ::skills::SkillHandtoHand);
|
||||
}
|
||||
|
||||
bool condition = (int) entity_list.GetNPCList().size() == 0 && (int) entity_list.GetCorpseList().size() == 115;
|
||||
RunTest("Spawns > All NPC's killed (0 NPCs) (115 Corpses)", true, condition);
|
||||
bool condition = (int) entity_list.GetNPCList().size() == 0 && (int) entity_list.GetCorpseList().size() == entries;
|
||||
RunTest(
|
||||
fmt::format("Spawns > All NPC's killed (0 NPCs) ([{}] Corpses)", entries),
|
||||
true,
|
||||
condition
|
||||
);
|
||||
|
||||
std::vector<uint32_t> spawn2_ids = {};
|
||||
|
||||
@@ -377,11 +385,15 @@ inline void TestSpawns()
|
||||
zone->Shutdown();
|
||||
SetupStateZone();
|
||||
|
||||
condition = (int) entity_list.GetNPCList().size() == 0 && (int) entity_list.GetCorpseList().size() == 115;
|
||||
condition = (int) entity_list.GetNPCList().size() == 0 && (int) entity_list.GetCorpseList().size() == entries;
|
||||
if (!condition) {
|
||||
PrintEntityCounts();
|
||||
}
|
||||
RunTest("Spawns > After restore (0 NPCs) (115 Corpses)", true, condition);
|
||||
RunTest(
|
||||
fmt::format("Spawns > After restore (0 NPCs) ([{}] Corpses)", entries),
|
||||
true,
|
||||
condition
|
||||
);
|
||||
|
||||
for (auto &e: entity_list.GetCorpseList()) {
|
||||
auto c = e.second;
|
||||
@@ -405,11 +417,15 @@ inline void TestSpawns()
|
||||
|
||||
zone->Process();
|
||||
|
||||
condition = (int) entity_list.GetNPCList().size() == 115 && (int) entity_list.GetCorpseList().size() == 115;
|
||||
condition = (int) entity_list.GetNPCList().size() == entries && (int) entity_list.GetCorpseList().size() == entries;
|
||||
if (!condition) {
|
||||
PrintEntityCounts();
|
||||
}
|
||||
RunTest("Spawns > After respawn (115 NPCs) (115 Corpses)", true, condition);
|
||||
RunTest(
|
||||
fmt::format("Spawns > After respawn ([{}] NPCs) ([{}] Corpses)", entries, entries),
|
||||
true,
|
||||
condition
|
||||
);
|
||||
|
||||
for (auto &c: entity_list.GetCorpseList()) {
|
||||
c.second->DepopNPCCorpse();
|
||||
@@ -417,11 +433,15 @@ inline void TestSpawns()
|
||||
|
||||
entity_list.CorpseProcess();
|
||||
|
||||
condition = (int) entity_list.GetNPCList().size() == 115 && (int) entity_list.GetCorpseList().size() == 0;
|
||||
condition = (int) entity_list.GetNPCList().size() == entries && (int) entity_list.GetCorpseList().size() == 0;
|
||||
if (!condition) {
|
||||
PrintEntityCounts();
|
||||
}
|
||||
RunTest("Spawns > After respawn (115 NPCs) (0 Corpses)", true, condition);
|
||||
RunTest(
|
||||
fmt::format("Spawns > After respawn ([{}] NPCs) (0 Corpses)", entries),
|
||||
true,
|
||||
condition
|
||||
);
|
||||
|
||||
// lets set NPC's up with a predictable loottable for testing
|
||||
uint32_t loottable_id = SeedLootTable();
|
||||
@@ -462,20 +482,28 @@ inline void TestSpawns()
|
||||
npc->Death(npc, npc->GetHP() + 1, SPELL_UNKNOWN, EQ::skills::SkillHandtoHand);
|
||||
}
|
||||
|
||||
condition = (int) entity_list.GetNPCList().size() == 105 && (int) entity_list.GetCorpseList().size() == 10;
|
||||
condition = (int) entity_list.GetNPCList().size() == (entries - 10) && (int) entity_list.GetCorpseList().size() == 10;
|
||||
if (!condition) {
|
||||
PrintEntityCounts();
|
||||
}
|
||||
RunTest("Spawns > Kill 10 NPC's before save/restore (105 NPCs) (10 Corpses)", true, condition);
|
||||
RunTest(
|
||||
fmt::format("Spawns > Kill 10 NPC's before save/restore ([{}] NPCs) (10 Corpses)", (entries - 10)),
|
||||
true,
|
||||
condition
|
||||
);
|
||||
|
||||
zone->Shutdown();
|
||||
SetupStateZone();
|
||||
|
||||
condition = (int) entity_list.GetNPCList().size() == 105 && (int) entity_list.GetCorpseList().size() == 10;
|
||||
condition = (int) entity_list.GetNPCList().size() == (entries - 10) && (int) entity_list.GetCorpseList().size() == 10;
|
||||
if (!condition) {
|
||||
PrintEntityCounts();
|
||||
}
|
||||
RunTest("Spawns > After restore (105 NPCs) (10 Corpses)", true, condition);
|
||||
RunTest(
|
||||
fmt::format("Spawns > After restore ([{}] NPCs) (10 Corpses)", (entries - 10)),
|
||||
true,
|
||||
condition
|
||||
);
|
||||
|
||||
// validate that all corpses and npc's have cloak of flames
|
||||
bool test_failed = false;
|
||||
@@ -572,6 +600,14 @@ inline void TestSpawns()
|
||||
false
|
||||
);
|
||||
|
||||
int max_respawn = 0;
|
||||
const auto& l = RespawnTimesRepository::All(database);
|
||||
for (const auto& e : l) {
|
||||
if (e.duration > max_respawn) {
|
||||
max_respawn = e.duration;
|
||||
}
|
||||
}
|
||||
|
||||
entity_list.MobProcess();
|
||||
|
||||
zone->Process();
|
||||
@@ -587,16 +623,20 @@ inline void TestSpawns()
|
||||
npc->SetEntityVariable("previously_spawned", "true");
|
||||
}
|
||||
|
||||
Timer::RollForward(302401); // longest respawn time in zone
|
||||
Timer::RollForward(max_respawn); // longest respawn time in zone
|
||||
zone->Process();
|
||||
entity_list.MobProcess(); // processing depops
|
||||
|
||||
condition = (int) entity_list.GetNPCList().size() == 115 && (int) entity_list.GetCorpseList().size() == 10;
|
||||
condition = (int) entity_list.GetNPCList().size() == entries && (int) entity_list.GetCorpseList().size() == 10;
|
||||
if (!condition) {
|
||||
PrintEntityCounts();
|
||||
PrintZoneNpcs();
|
||||
}
|
||||
RunTest("Spawns > After respawn, ensure we have expected entity counts (115 NPCs) (10 Corpses)", true, condition);
|
||||
RunTest(
|
||||
fmt::format("Spawns > After respawn, ensure we have expected entity counts ([{}] NPCs) (10 Corpses)", entries),
|
||||
true,
|
||||
condition
|
||||
);
|
||||
|
||||
entity_list.MobProcess(); // processing depops
|
||||
|
||||
|
||||
@@ -995,6 +995,8 @@ bool Client::Save(uint8 iCommitNow) {
|
||||
if(!ClientDataLoaded())
|
||||
return false;
|
||||
|
||||
BenchTimer timer;
|
||||
|
||||
/* Wrote current basics to PP for saves */
|
||||
if (!m_lock_save_position) {
|
||||
m_pp.x = m_Position.x;
|
||||
@@ -1022,6 +1024,8 @@ bool Client::Save(uint8 iCommitNow) {
|
||||
m_pp.endurance = current_endurance;
|
||||
}
|
||||
|
||||
database.TransactionBegin();
|
||||
|
||||
/* Save Character Currency */
|
||||
database.SaveCharacterCurrency(CharacterID(), &m_pp);
|
||||
|
||||
@@ -1105,6 +1109,10 @@ bool Client::Save(uint8 iCommitNow) {
|
||||
database.botdb.SaveBotSettings(this);
|
||||
}
|
||||
|
||||
database.TransactionCommit();
|
||||
|
||||
LogInfo("Save for [{}] took [{}]", GetCleanName(), timer.elapsed());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1209,6 +1217,58 @@ void Client::ChannelMessageReceived(uint8 chan_num, uint8 language, uint8 lang_s
|
||||
|
||||
LogDebug("Client::ChannelMessageReceived() Channel:[{}] message:[{}]", chan_num, message);
|
||||
|
||||
if (RuleB(Chat, AlwaysCaptureCommandText)) {
|
||||
if (message[0] == COMMAND_CHAR) {
|
||||
if (command_dispatch(this, message, false) == -2) {
|
||||
if (parse->PlayerHasQuestSub(EVENT_COMMAND)) {
|
||||
int i = parse->EventPlayer(EVENT_COMMAND, this, message, 0);
|
||||
if (i == 0 && !RuleB(Chat, SuppressCommandErrors)) {
|
||||
Message(Chat::Red, "Command '%s' not recognized.", message);
|
||||
}
|
||||
}
|
||||
else if (parse->PlayerHasQuestSub(EVENT_SAY)) {
|
||||
int i = parse->EventPlayer(EVENT_SAY, this, message, 0);
|
||||
if (i == 0 && !RuleB(Chat, SuppressCommandErrors)) {
|
||||
Message(Chat::Red, "Command '%s' not recognized.", message);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!RuleB(Chat, SuppressCommandErrors)) {
|
||||
Message(Chat::Red, "Command '%s' not recognized.", message);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (message[0] == BOT_COMMAND_CHAR) {
|
||||
if (RuleB(Bots, Enabled)) {
|
||||
if (bot_command_dispatch(this, message) == -2) {
|
||||
if (parse->PlayerHasQuestSub(EVENT_BOT_COMMAND)) {
|
||||
int i = parse->EventPlayer(EVENT_BOT_COMMAND, this, message, 0);
|
||||
if (i == 0 && !RuleB(Chat, SuppressCommandErrors)) {
|
||||
Message(Chat::Red, "Bot command '%s' not recognized.", message);
|
||||
}
|
||||
}
|
||||
else if (parse->PlayerHasQuestSub(EVENT_SAY)) {
|
||||
int i = parse->EventPlayer(EVENT_SAY, this, message, 0);
|
||||
if (i == 0 && !RuleB(Chat, SuppressCommandErrors)) {
|
||||
Message(Chat::Red, "Bot command '%s' not recognized.", message);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!RuleB(Chat, SuppressCommandErrors)) {
|
||||
Message(Chat::Red, "Bot command '%s' not recognized.", message);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Message(Chat::Red, "Bots are disabled on this server.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (targetname == nullptr) {
|
||||
targetname = (!GetTarget()) ? "" : GetTarget()->GetName();
|
||||
}
|
||||
|
||||
@@ -483,6 +483,9 @@ public:
|
||||
|
||||
virtual bool Save() { return Save(0); }
|
||||
bool Save(uint8 iCommitNow); // 0 = delayed, 1=async now, 2=sync now
|
||||
inline void SaveCharacterData() {
|
||||
database.SaveCharacterData(this, &m_pp, &m_epp);
|
||||
};
|
||||
|
||||
/* New PP Save Functions */
|
||||
bool SaveCurrency(){ return database.SaveCharacterCurrency(this->CharacterID(), &m_pp); }
|
||||
|
||||
@@ -217,6 +217,8 @@ bool Client::Process() {
|
||||
GetMerc()->Depop();
|
||||
}
|
||||
instalog = true;
|
||||
|
||||
camp_timer.Disable();
|
||||
}
|
||||
|
||||
if (IsStunned() && stunned_timer.Check())
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "data_bucket.h"
|
||||
#include "zonedb.h"
|
||||
#include "mob.h"
|
||||
#include "client.h"
|
||||
#include "worldserver.h"
|
||||
#include <ctime>
|
||||
#include <cctype>
|
||||
@@ -359,7 +360,8 @@ bool DataBucket::GetDataBuckets(Mob *mob)
|
||||
BulkLoadEntitiesToCache(DataBucketLoadType::Bot, {id});
|
||||
}
|
||||
else if (mob->IsClient()) {
|
||||
BulkLoadEntitiesToCache(DataBucketLoadType::Account, {id});
|
||||
uint32 account_id = mob->CastToClient()->AccountID();
|
||||
BulkLoadEntitiesToCache(DataBucketLoadType::Account, {account_id});
|
||||
BulkLoadEntitiesToCache(DataBucketLoadType::Client, {id});
|
||||
}
|
||||
|
||||
|
||||
@@ -3159,6 +3159,13 @@ void EntityList::Depop(bool StartSpawnTimer)
|
||||
UpdateFindableNPCState(pnpc, true);
|
||||
}
|
||||
|
||||
// Depop below will eventually remove this npc from the entity list
|
||||
// but that can happen AFTER we've already tried to spawn its replacement.
|
||||
// So go ahead and remove it from the limits so it doesn't count.
|
||||
if (npc_limit_list.count(pnpc->GetID())) {
|
||||
npc_limit_list.erase(pnpc->GetID());
|
||||
}
|
||||
|
||||
pnpc->WipeHateList();
|
||||
pnpc->Depop(StartSpawnTimer);
|
||||
}
|
||||
|
||||
@@ -678,6 +678,7 @@ int main(int argc, char **argv)
|
||||
safe_delete(Config);
|
||||
|
||||
if (zone != 0) {
|
||||
zone->SetSaveZoneState(false);
|
||||
zone->Shutdown(true);
|
||||
}
|
||||
//Fix for Linux world server problem.
|
||||
|
||||
+5
-8
@@ -1698,13 +1698,6 @@ void Mob::StopMoving()
|
||||
|
||||
void Mob::StopMoving(float new_heading)
|
||||
{
|
||||
if (IsBot()) {
|
||||
auto bot = CastToBot();
|
||||
|
||||
bot->SetCombatJitterFlag(false);
|
||||
bot->SetCombatOutOfRangeJitterFlag(false);
|
||||
}
|
||||
|
||||
StopNavigation();
|
||||
RotateTo(new_heading);
|
||||
|
||||
@@ -4621,8 +4614,12 @@ void Mob::SetZone(uint32 zone_id, uint32 instance_id)
|
||||
{
|
||||
CastToClient()->GetPP().zone_id = zone_id;
|
||||
CastToClient()->GetPP().zoneInstance = instance_id;
|
||||
CastToClient()->SaveCharacterData();
|
||||
}
|
||||
|
||||
if (!IsClient()) {
|
||||
Save(); // bots or other things might be covered here for some reason
|
||||
}
|
||||
Save();
|
||||
}
|
||||
|
||||
void Mob::Kill() {
|
||||
|
||||
+1
-1
@@ -962,7 +962,7 @@ public:
|
||||
uint16 GetSympatheticFocusEffect(focusType type, uint16 spell_id);
|
||||
bool TryFadeEffect(int slot);
|
||||
void DispelMagic(Mob* casterm, uint16 spell_id, int effect_value);
|
||||
uint16 GetSpellEffectResistChance(uint16 spell_id);
|
||||
bool TrySpellEffectResist(uint16 spell_id);
|
||||
int32 GetVulnerability(Mob *caster, uint32 spell_id, uint32 ticsremaining, bool from_buff_tic = false);
|
||||
int64 GetFcDamageAmtIncoming(Mob *caster, int32 spell_id, bool from_buff_tic = false);
|
||||
int64 GetFocusIncoming(focusType type, int effect, Mob *caster, uint32 spell_id); //**** This can be removed when bot healing focus code is updated ****
|
||||
|
||||
@@ -933,16 +933,11 @@ void MobMovementManager::SendCommandToClients(
|
||||
|
||||
float MobMovementManager::FixHeading(float in)
|
||||
{
|
||||
auto h = in;
|
||||
while (h > 512.0) {
|
||||
h -= 512.0;
|
||||
int h = static_cast<int>(in) % 512;
|
||||
if (h < 0) {
|
||||
h += 512;
|
||||
}
|
||||
|
||||
while (h < 0.0) {
|
||||
h += 512.0;
|
||||
}
|
||||
|
||||
return h;
|
||||
return static_cast<float>(h);
|
||||
}
|
||||
|
||||
void MobMovementManager::DumpStats(Client *client)
|
||||
|
||||
+1
-1
@@ -699,7 +699,7 @@ void QuestManager::stoptimer(const std::string& timer_name, Mob* m)
|
||||
}
|
||||
|
||||
for (auto e = QTimerList.begin(); e != QTimerList.end(); ++e) {
|
||||
if (e->mob && e->mob == m) {
|
||||
if (e->mob && e->mob == m && e->name == timer_name) {
|
||||
parse->EventMob(EVENT_TIMER_STOP, m, nullptr, [&]() { return timer_name; });
|
||||
|
||||
QTimerList.erase(e);
|
||||
|
||||
+7
-1
@@ -277,7 +277,13 @@ bool Spawn2::Process() {
|
||||
}
|
||||
}
|
||||
|
||||
NPC *npc = new NPC(tmp, this, glm::vec4(x, y, z, heading), GravityBehavior::Water);
|
||||
// zone state restore
|
||||
if (m_stored_location != glm::vec4(0, 0, -1000, 0)) {
|
||||
loc = m_stored_location;
|
||||
m_stored_location = glm::vec4(0, 0, -1000, 0);
|
||||
}
|
||||
|
||||
NPC *npc = new NPC(tmp, this, loc, GravityBehavior::Water);
|
||||
|
||||
npcthis = npc;
|
||||
|
||||
|
||||
@@ -79,6 +79,7 @@ public:
|
||||
inline void SetResumedFromZoneSuspend(bool resumed) { m_resumed_from_zone_suspend = resumed; }
|
||||
inline void SetEntityVariables(std::map<std::string, std::string> vars) { m_entity_variables = vars; }
|
||||
inline void SetResumedNPCID(uint32 npc_id) { m_resumed_npc_id = npc_id; }
|
||||
inline void SetStoredLocation(const glm::vec4& loc) { m_stored_location = loc; }
|
||||
|
||||
protected:
|
||||
friend class Zone;
|
||||
@@ -108,6 +109,7 @@ private:
|
||||
bool m_resumed_from_zone_suspend = false;
|
||||
uint32 m_resumed_npc_id = 0;
|
||||
std::map<std::string, std::string> m_entity_variables = {};
|
||||
glm::vec4 m_stored_location = {0, 0, -1000, 0}; // use -1000 to indicate unset/zero-state
|
||||
};
|
||||
|
||||
class SpawnCondition {
|
||||
|
||||
+27
-14
@@ -7465,44 +7465,57 @@ void Mob::DispelMagic(Mob* caster, uint16 spell_id, int effect_value)
|
||||
}
|
||||
}
|
||||
|
||||
uint16 Mob::GetSpellEffectResistChance(uint16 spell_id)
|
||||
bool Mob::TrySpellEffectResist(uint16 spell_id)
|
||||
{
|
||||
/*
|
||||
SEResist variable
|
||||
0 = spell effect id to be check if can resist
|
||||
1 = percent chance of resistance
|
||||
*/
|
||||
|
||||
if(!IsValidSpell(spell_id))
|
||||
return 0;
|
||||
|
||||
if (!aabonuses.SEResist[1] && !spellbonuses.SEResist[1] && !itembonuses.SEResist[1])
|
||||
return 0;
|
||||
if (!IsValidSpell(spell_id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16 resist_chance = 0;
|
||||
if (!aabonuses.SEResist[1] && !spellbonuses.SEResist[1] && !itembonuses.SEResist[1]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int resist_chance = 0;
|
||||
|
||||
for(int i = 0; i < EFFECT_COUNT; ++i)
|
||||
{
|
||||
bool found = false;
|
||||
if (spells[spell_id].effect_id[i] == SE_Blank) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for(int d = 0; d < MAX_RESISTABLE_EFFECTS*2; d+=2)
|
||||
{
|
||||
resist_chance = 0;
|
||||
if (spells[spell_id].effect_id[i] == aabonuses.SEResist[d]){
|
||||
resist_chance += aabonuses.SEResist[d+1];
|
||||
found = true;
|
||||
}
|
||||
|
||||
if (spells[spell_id].effect_id[i] == itembonuses.SEResist[d]){
|
||||
resist_chance += itembonuses.SEResist[d+1];
|
||||
found = true;
|
||||
}
|
||||
|
||||
|
||||
if (spells[spell_id].effect_id[i] == spellbonuses.SEResist[d]){
|
||||
resist_chance += spellbonuses.SEResist[d+1];
|
||||
found = true;
|
||||
}
|
||||
|
||||
if (found)
|
||||
continue;
|
||||
if (resist_chance) {
|
||||
if (zone->random.Roll(resist_chance)) {
|
||||
LogSpells("Resisted spell from Spell Effect Resistance, had [{}] chance to resist spell effect id [{}]", resist_chance, spells[spell_id].effect_id[i]);
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return resist_chance;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Mob::TryDispel(uint8 caster_level, uint8 buff_level, int level_modifier){
|
||||
|
||||
+11
-16
@@ -5359,16 +5359,17 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use
|
||||
|
||||
if (!CharmTick) {
|
||||
//Check for Spell Effect specific resistance chances (ie AA Mental Fortitude)
|
||||
int se_resist_bonuses = GetSpellEffectResistChance(spell_id);
|
||||
if (se_resist_bonuses && zone->random.Roll(se_resist_bonuses)) {
|
||||
if (resist_type != RESIST_NONE && TrySpellEffectResist(spell_id)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Check for Chance to Resist Spell bonuses (ie Sanctification Discipline)
|
||||
int resist_bonuses = CalcResistChanceBonus();
|
||||
if (resist_bonuses && zone->random.Roll(resist_bonuses) && !IsResurrectionSicknessSpell(spell_id)) {
|
||||
LogSpells("Resisted spell in sanctification, had [{}] chance to resist", resist_bonuses);
|
||||
return 0;
|
||||
if (!spells[spell_id].no_resist && resist_type != RESIST_NONE) {
|
||||
int resist_bonuses = CalcResistChanceBonus();
|
||||
if (resist_bonuses && zone->random.Roll(resist_bonuses) && !IsResurrectionSicknessSpell(spell_id)) {
|
||||
LogSpells("Resisted spell in sanctification, had [{}] chance to resist", resist_bonuses);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5667,18 +5668,12 @@ int16 Mob::CalcResistChanceBonus()
|
||||
|
||||
int16 Mob::CalcFearResistChance()
|
||||
{
|
||||
int resistchance = spellbonuses.ResistFearChance + itembonuses.ResistFearChance;
|
||||
if (IsOfClientBot()) {
|
||||
resistchance += aabonuses.ResistFearChance;
|
||||
if (aabonuses.Fearless == true) {
|
||||
resistchance = 100;
|
||||
}
|
||||
}
|
||||
if (spellbonuses.Fearless == true || itembonuses.Fearless == true) {
|
||||
resistchance = 100;
|
||||
int resist_chance = spellbonuses.ResistFearChance + itembonuses.ResistFearChance + aabonuses.ResistFearChance;
|
||||
if (spellbonuses.Fearless || itembonuses.Fearless || aabonuses.Fearless) {
|
||||
resist_chance = 100;
|
||||
}
|
||||
|
||||
return resistchance;
|
||||
return resist_chance;
|
||||
}
|
||||
|
||||
float Mob::GetAOERange(uint16 spell_id)
|
||||
|
||||
@@ -2739,8 +2739,6 @@ void Client::SendBulkBazaarTraders()
|
||||
|
||||
SetTraderCount(results.count);
|
||||
|
||||
SetTraderCount(results.count);
|
||||
|
||||
auto p_size = 4 + 12 * results.count + results.name_length;
|
||||
auto buffer = std::make_unique<char[]>(p_size);
|
||||
memset(buffer.get(), 0, p_size);
|
||||
|
||||
+9
-1
@@ -887,7 +887,7 @@ void Zone::Shutdown(bool quiet)
|
||||
c.second->WorldKick();
|
||||
}
|
||||
|
||||
if (RuleB(Zone, StateSavingOnShutdown) && zone && zone->IsLoaded()) {
|
||||
if (m_save_zone_state && RuleB(Zone, StateSavingOnShutdown) && zone && zone->IsLoaded()) {
|
||||
SaveZoneState();
|
||||
}
|
||||
|
||||
@@ -1010,6 +1010,7 @@ Zone::Zone(uint32 in_zoneid, uint32 in_instanceid, const char* in_short_name)
|
||||
|
||||
SetIdleWhenEmpty(true);
|
||||
SetSecondsBeforeIdle(60);
|
||||
SetSaveZoneState(false);
|
||||
|
||||
if (database.GetServerType() == 1) {
|
||||
pvpzone = true;
|
||||
@@ -1190,6 +1191,11 @@ bool Zone::Init(bool is_static) {
|
||||
|
||||
RespawnTimesRepository::ClearExpiredRespawnTimers(database);
|
||||
|
||||
// Loading zone variables so they're available for things like encounter_load
|
||||
if (RuleB(Zone, StateSavingOnShutdown)) {
|
||||
zone->LoadZoneVariablesState();
|
||||
}
|
||||
|
||||
// make sure that anything that needs to be loaded prior to scripts is loaded before here
|
||||
// this is to ensure that the scripts have access to the data they need
|
||||
parse->ReloadQuests(true);
|
||||
@@ -1204,6 +1210,8 @@ bool Zone::Init(bool is_static) {
|
||||
}
|
||||
|
||||
content_db.PopulateZoneSpawnList(zoneid, spawn2_list, GetInstanceVersion());
|
||||
SetSaveZoneState(true);
|
||||
|
||||
database.LoadCharacterCorpses(zoneid, instanceid);
|
||||
|
||||
content_db.LoadTraps(short_name, GetInstanceVersion());
|
||||
|
||||
@@ -353,6 +353,9 @@ public:
|
||||
uint32 GetInstanceTimeRemaining() const;
|
||||
void SetInstanceTimeRemaining(uint32 instance_time_remaining);
|
||||
|
||||
inline bool GetSaveZoneState() const { return m_save_zone_state; }
|
||||
inline void SetSaveZoneState(bool save_state) { m_save_zone_state = save_state; }
|
||||
|
||||
/**
|
||||
* GMSay Callback for LogSys
|
||||
*
|
||||
@@ -473,6 +476,7 @@ public:
|
||||
inline uint32 GetZoneServerId() const { return m_zone_server_id; }
|
||||
|
||||
// zone state
|
||||
bool LoadZoneVariablesState();
|
||||
bool LoadZoneState(
|
||||
std::unordered_map<uint32, uint32> spawn_times,
|
||||
std::vector<Spawn2DisabledRepository::Spawn2Disabled> disabled_spawns
|
||||
@@ -516,6 +520,7 @@ private:
|
||||
uint32 m_last_ucss_update;
|
||||
bool m_idle_when_empty;
|
||||
uint32 m_seconds_before_idle;
|
||||
bool m_save_zone_state;
|
||||
|
||||
GlobalLootManager m_global_loot;
|
||||
LinkedList<ZoneClientAuth_Struct *> client_auth_list;
|
||||
|
||||
+66
-14
@@ -5,6 +5,7 @@
|
||||
#include "corpse.h"
|
||||
#include "zone.h"
|
||||
#include "zone_save_state.h"
|
||||
#include "../common/repositories/spawn2_repository.h"
|
||||
|
||||
// IsZoneStateValid checks if the zone state is valid
|
||||
// if these fields are all empty or zero value for an entire zone state, it's considered invalid
|
||||
@@ -381,6 +382,31 @@ inline void LoadZoneVariables(Zone *z, const std::string &variables)
|
||||
}
|
||||
}
|
||||
|
||||
bool Zone::LoadZoneVariablesState()
|
||||
{
|
||||
auto spawn_states = ZoneStateSpawnsRepository::GetWhere(
|
||||
database,
|
||||
fmt::format(
|
||||
"zone_id = {} AND instance_id = {} AND is_zone = 1 ORDER BY spawn2_id",
|
||||
zoneid,
|
||||
zone->GetInstanceID()
|
||||
)
|
||||
);
|
||||
|
||||
if (spawn_states.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto &s: spawn_states) {
|
||||
if (s.is_zone) {
|
||||
LoadZoneVariables(zone, s.entity_variables);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Zone::LoadZoneState(
|
||||
std::unordered_map<uint32, uint32> spawn_times,
|
||||
std::vector<Spawn2DisabledRepository::Spawn2Disabled> disabled_spawns
|
||||
@@ -389,18 +415,19 @@ bool Zone::LoadZoneState(
|
||||
auto spawn_states = ZoneStateSpawnsRepository::GetWhere(
|
||||
database,
|
||||
fmt::format(
|
||||
"zone_id = {} AND instance_id = {} ORDER BY spawn2_id",
|
||||
"zone_id = {} AND instance_id = {} AND is_zone = 0 ORDER BY spawn2_id",
|
||||
zoneid,
|
||||
zone->GetInstanceID()
|
||||
)
|
||||
);
|
||||
|
||||
LogInfo("Loading zone state spawns for zone [{}] spawns [{}]", GetShortName(), spawn_states.size());
|
||||
|
||||
if (spawn_states.empty()) {
|
||||
LogInfo("No zone state spawns found for zone [{}] instance [{}]", GetShortName(), zone->GetInstanceID());
|
||||
return false;
|
||||
}
|
||||
|
||||
LogInfo("Loading zone state spawns for zone [{}] instance [{}] spawns [{}]", GetShortName(), zone->GetInstanceID(), spawn_states.size());
|
||||
|
||||
if (!IsZoneStateValid(spawn_states)) {
|
||||
LogZoneState("Invalid zone state data for zone [{}]", GetShortName());
|
||||
ClearZoneState(zoneid, zone->GetInstanceID());
|
||||
@@ -414,15 +441,27 @@ bool Zone::LoadZoneState(
|
||||
zone->initgrids_timer.Trigger();
|
||||
zone->Process();
|
||||
|
||||
// load zone variables first
|
||||
int count = 0;
|
||||
// load base spawn2 data for spawn locations
|
||||
std::vector<std::string> spawn2_ids;
|
||||
for (auto &s: spawn_states) {
|
||||
if (s.is_zone) {
|
||||
LoadZoneVariables(zone, s.entity_variables);
|
||||
break;
|
||||
if (s.spawn2_id > 0) {
|
||||
spawn2_ids.push_back(std::to_string(s.spawn2_id));
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Spawn2Repository::Spawn2> spawn2s;
|
||||
if (!spawn2_ids.empty()) {
|
||||
spawn2s = Spawn2Repository::GetWhere(
|
||||
content_db,
|
||||
fmt::format(
|
||||
"id IN ({})",
|
||||
Strings::Join(spawn2_ids, ",")
|
||||
)
|
||||
);
|
||||
|
||||
LogZoneState("Loaded [{}] spawn2s", spawn2s.size());
|
||||
}
|
||||
|
||||
// spawn2
|
||||
for (auto &s: spawn_states) {
|
||||
if (s.spawngroup_id == 0 || s.is_corpse || s.is_zone) {
|
||||
@@ -445,13 +484,26 @@ bool Zone::LoadZoneState(
|
||||
}
|
||||
}
|
||||
|
||||
// find spawn 2 by id
|
||||
Spawn2Repository::Spawn2 spawn2;
|
||||
for (auto &sp: spawn2s) {
|
||||
if (sp.id == s.spawn2_id) {
|
||||
spawn2 = sp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!spawn2.id) {
|
||||
LogZoneState("Failed to load spawn2 data for spawn2_id [{}]", s.spawn2_id);
|
||||
}
|
||||
|
||||
auto new_spawn = new Spawn2(
|
||||
s.spawn2_id,
|
||||
s.spawngroup_id,
|
||||
s.x,
|
||||
s.y,
|
||||
s.z,
|
||||
s.heading,
|
||||
spawn2.id > 0 ? spawn2.x : s.x,
|
||||
spawn2.id > 0 ? spawn2.y : s.y,
|
||||
spawn2.id > 0 ? spawn2.z : s.z,
|
||||
spawn2.id > 0 ? spawn2.heading : s.heading,
|
||||
s.respawn_time,
|
||||
s.variance,
|
||||
spawn_time_left,
|
||||
@@ -463,14 +515,14 @@ bool Zone::LoadZoneState(
|
||||
(EmuAppearance) s.anim
|
||||
);
|
||||
|
||||
new_spawn->SetStoredLocation(glm::vec4(s.x, s.y, s.z, s.heading));
|
||||
|
||||
if (spawn_time_left == 0) {
|
||||
new_spawn->SetResumedNPCID(s.npc_id);
|
||||
new_spawn->SetResumedFromZoneSuspend(true);
|
||||
new_spawn->SetEntityVariables(GetVariablesDeserialized(s.entity_variables));
|
||||
}
|
||||
|
||||
count++;
|
||||
|
||||
spawn2_list.Insert(new_spawn);
|
||||
new_spawn->Process();
|
||||
auto n = new_spawn->GetNPC();
|
||||
|
||||
+6
-2
@@ -528,8 +528,12 @@ void Client::DoZoneSuccess(ZoneChange_Struct *zc, uint16 zone_id, uint32 instanc
|
||||
m_pp.zone_id = zone_id;
|
||||
m_pp.zoneInstance = instance_id;
|
||||
|
||||
//Force a save so its waiting for them when they zone
|
||||
Save(2);
|
||||
// save character position
|
||||
m_pp.x = m_Position.x;
|
||||
m_pp.y = m_Position.y;
|
||||
m_pp.z = m_Position.z;
|
||||
m_pp.heading = m_Position.w;
|
||||
SaveCharacterData();
|
||||
|
||||
m_lock_save_position = true;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user