Compare commits

..

2 Commits

Author SHA1 Message Date
KayenEQ d18a30b047 root no break variable update
root no break variable update
2025-04-06 14:34:52 -04:00
KayenEQ 359f90a987 Root break update
Roots will not break from damage spells if spell skill is set to 200. Live mechanic.
2025-04-03 18:42:53 -04:00
53 changed files with 660 additions and 1277 deletions
-65
View File
@@ -1,68 +1,3 @@
## [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
-4
View File
@@ -671,7 +671,6 @@ 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
@@ -683,7 +682,6 @@ 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
@@ -744,7 +742,6 @@ 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
@@ -765,7 +762,6 @@ 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
+1 -2
View File
@@ -279,8 +279,7 @@ Bazaar::GetSearchResults(
trader_items_ids,
std::string(search.item_name),
field_criteria_items,
where_criteria_items,
search.max_results
where_criteria_items
);
if (item_results.empty()) {
+1
View File
@@ -65,6 +65,7 @@ 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;
+2 -2
View File
@@ -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 = "empty",
.match = "",
.condition = "missing",
.match = "TINYINT(2)",
.sql = R"(
ALTER TABLE `horses`
ADD COLUMN `helmtexture` TINYINT(2) NOT NULL DEFAULT -1 AFTER `texture`;
+3 -7
View File
@@ -74,7 +74,7 @@ namespace Logs {
Spawns,
Spells,
Status, // deprecated
TCPConnection, // deprecated
TCPConnection,
Tasks,
Tradeskills,
Trading,
@@ -150,8 +150,6 @@ namespace Logs {
BotSpellTypeChecks,
NpcHandin,
ZoneState,
NetClient,
NetTCP,
MaxCategoryID /* Don't Remove this */
};
@@ -185,7 +183,7 @@ namespace Logs {
"Spawns",
"Spells",
"Status (Deprecated)",
"TCP Connection (Deprecated)",
"TCP Connection",
"Tasks",
"Tradeskills",
"Trading",
@@ -260,9 +258,7 @@ namespace Logs {
"Bot Spell Checks",
"Bot Spell Type Checks",
"NpcHandin",
"ZoneState",
"Net Server <-> Client",
"Net TCP"
"ZoneState"
};
}
+20 -20
View File
@@ -261,6 +261,26 @@
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__);\
@@ -904,26 +924,6 @@
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__);\
+97 -105
View File
@@ -1,16 +1,12 @@
#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>
// 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;
#include <sstream>
EQ::Net::DaybreakConnectionManager::DaybreakConnectionManager()
{
@@ -57,22 +53,16 @@ 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) {
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;
},
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;
},
[](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;
}
@@ -80,10 +70,7 @@ 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);
if (buf->len > 65536) {
delete[] buf->base;
}
delete[] buf->base;
});
m_attached = loop;
@@ -323,7 +310,7 @@ EQ::Net::DaybreakConnection::DaybreakConnection(DaybreakConnectionManager *owner
m_last_session_stats = Clock::now();
m_outgoing_budget = owner->m_options.outgoing_data_rate;
LogNetClient("New session [{}] with encode key [{}]", m_connect_code, HostToNetwork(m_encode_key));
LogNetcode("New session [{}] with encode key [{}]", m_connect_code, HostToNetwork(m_encode_key));
}
//new connection made as client
@@ -355,16 +342,16 @@ EQ::Net::DaybreakConnection::~DaybreakConnection()
void EQ::Net::DaybreakConnection::Close()
{
if (m_status != StatusDisconnected && m_status != StatusDisconnecting) {
if (m_status == StatusConnected) {
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)
@@ -647,7 +634,7 @@ void EQ::Net::DaybreakConnection::ProcessDecodedPacket(const Packet &p)
p.PutSerialize(0, reply);
InternalSend(p);
LogNetClient("[OP_SessionRequest] Session [{}] started with encode key [{}]", m_connect_code, HostToNetwork(m_encode_key));
LogNetcode("[OP_SessionRequest] Session [{}] started with encode key [{}]", m_connect_code, HostToNetwork(m_encode_key));
}
break;
@@ -666,7 +653,7 @@ void EQ::Net::DaybreakConnection::ProcessDecodedPacket(const Packet &p)
m_max_packet_size = reply.max_packet_size;
ChangeStatus(StatusConnected);
LogNetClient(
LogNetcode(
"[OP_SessionResponse] Session [{}] refresh with encode key [{}]",
m_connect_code,
HostToNetwork(m_encode_key)
@@ -795,7 +782,7 @@ void EQ::Net::DaybreakConnection::ProcessDecodedPacket(const Packet &p)
SendDisconnect();
}
LogNetClient(
LogNetcode(
"[OP_SessionDisconnect] Session [{}] disconnect with encode key [{}]",
m_connect_code,
HostToNetwork(m_encode_key)
@@ -865,7 +852,7 @@ bool EQ::Net::DaybreakConnection::ValidateCRC(Packet &p)
}
if (p.Length() < (size_t)m_crc_bytes) {
LogNetClient("Session [{}] ignored packet (crc bytes invalid on session)", m_connect_code);
LogNetcode("Session [{}] ignored packet (crc bytes invalid on session)", m_connect_code);
return false;
}
@@ -1056,7 +1043,7 @@ void EQ::Net::DaybreakConnection::Decompress(Packet &p, size_t offset, size_t le
return;
}
static thread_local uint8_t new_buffer[4096];
static uint8_t new_buffer[4096];
uint8_t *buffer = (uint8_t*)p.Data() + offset;
uint32_t new_length = 0;
@@ -1077,7 +1064,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)
{
static thread_local uint8_t new_buffer[2048] = { 0 };
uint8_t new_buffer[2048] = { 0 };
uint8_t *buffer = (uint8_t*)p.Data() + offset;
uint32_t new_length = 0;
bool send_uncompressed = true;
@@ -1104,6 +1091,10 @@ 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) {
@@ -1126,24 +1117,23 @@ 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) {
LogNetClient(
"Not resending packets for stream [{}] packets [{}] time_first_sent [{}] resend_delay [{}] m_acked_since_last_resend [{}]",
LogNetcodeDetail(
"Not resending packets for stream [{}] time since 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)) {
@@ -1152,7 +1142,7 @@ void EQ::Net::DaybreakConnection::ProcessResend(int stream)
total_size += e.second.packet.Length();
}
LogNetClient(
LogNetcodeDetail(
"Resending packets for stream [{}] packet count [{}] total packet size [{}] m_acked_since_last_resend [{}]",
stream,
s->sent_packets.size(),
@@ -1164,7 +1154,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) {
LogNetClient(
LogNetcodeDetail(
"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,
@@ -1343,97 +1333,99 @@ 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 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];
auto send_func = [](uv_udp_send_t* req, int status) {
delete[](char*)req->data;
delete req;
};
if (PacketCanBeEncoded(p)) {
m_stats.bytes_before_encode += p.Length();
DynamicPacket out;
out.PutPacket(0, p);
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;
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;
}
}
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());
} else {
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 += 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;
}
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)) {
send_buffer_pool.release(ctx);
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;
}
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);
}
uv_udp_send(send_req, &m_owner->m_socket, send_buffers, 1, (sockaddr*)&send_addr, send_func);
}
void EQ::Net::DaybreakConnection::InternalQueuePacket(Packet &p, int stream_id, bool reliable)
-1
View File
@@ -3,7 +3,6 @@
#include "../random.h"
#include "packet.h"
#include "daybreak_structs.h"
#include "daybreak_pooling.h"
#include <uv.h>
#include <chrono>
#include <functional>
-123
View File
@@ -1,123 +0,0 @@
#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;
}
};
-1
View File
@@ -171,4 +171,3 @@ namespace EQ
};
}
}
+3 -3
View File
@@ -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) {
LogNetTCP("Error connecting to {0}:{1}, attempting to reconnect...", m_addr, m_port);
LogF(Logs::General, Logs::TCPConnection, "Error connecting to {0}:{1}, attempting to reconnect...", m_addr, m_port);
m_connecting = false;
return;
}
LogNetTCP("Connected to {0}:{1}", m_addr, m_port);
LogF(Logs::General, Logs::TCPConnection, "Connected to {0}:{1}", m_addr, m_port);
m_connection = connection;
m_connection->OnDisconnect([this](EQ::Net::TCPConnection *c) {
LogNetTCP("Connection lost to {0}:{1}, attempting to reconnect...", m_addr, m_port);
LogF(Logs::General, Logs::TCPConnection, "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) {
LogNetTCP("Error connecting to {0}:{1}, attempting to reconnect...", m_addr, m_port);
LogF(Logs::General, Logs::TCPConnection, "Error connecting to {0}:{1}, attempting to reconnect...", m_addr, m_port);
m_connecting = false;
return;
}
LogNetTCP("Connected to {0}:{1}", m_addr, m_port);
LogF(Logs::General, Logs::TCPConnection, "Connected to {0}:{1}", m_addr, m_port);
m_connection = connection;
m_connection->OnDisconnect([this](EQ::Net::TCPConnection *c) {
LogNetTCP("Connection lost to {0}:{1}, attempting to reconnect...", m_addr, m_port);
LogF(Logs::General, Logs::TCPConnection, "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);
+55 -108
View File
@@ -1,8 +1,5 @@
#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;
@@ -67,37 +64,36 @@ 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) {
if (suggested_size > 65536) {
buf->base = new char[suggested_size];
buf->len = suggested_size;
return;
}
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) {
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;
TCPConnection *connection = (TCPConnection*)stream->data;
if (nread > 0) {
connection->Read(buf->base, nread);
}
else if (nread == UV_EOF) {
connection->Disconnect();
}
else if (nread < 0) {
connection->Disconnect();
}
if (nread > 0) {
connection->Read(buf->base, nread);
if (buf->len > 65536) {
delete [] buf->base;
if (buf->base) {
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)
@@ -134,92 +130,43 @@ 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 || !data || count == 0) {
std::cerr << "TCPConnection::Write - Invalid socket or data\n";
void EQ::Net::TCPConnection::Write(const char *data, size_t count)
{
if (!m_socket) {
return;
}
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;
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();
}
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;
}
}
delete baton;
});
}
std::string EQ::Net::TCPConnection::LocalIP() const
{
sockaddr_storage addr;
+1 -2
View File
@@ -1,6 +1,5 @@
#pragma once
#include "tcp_connection_pooling.h"
#include <functional>
#include <string>
#include <memory>
@@ -17,7 +16,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);
-125
View File
@@ -1,125 +0,0 @@
#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;
}
};
+10 -24
View File
@@ -54,31 +54,17 @@ public:
{
BulkTraders_Struct all_entries{};
std::vector<DistinctTraders_Struct> distinct_traders;
MySQLRequestResult 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)
);
}
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)
);
distinct_traders.reserve(results.RowCount());
+6 -2
View File
@@ -347,7 +347,6 @@ 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)
@@ -858,6 +857,12 @@ 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.")
@@ -923,7 +928,6 @@ 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)
+2
View File
@@ -246,6 +246,8 @@
#define MAX_INVISIBILTY_LEVEL 254
#define NO_ROOT_BREAK_SKILL_ID 200 //Live mechanic where if skill id in a spell is set to 200 it will prevent that spell from having a chance to break roots.
//instrument item id's used as song components
#define INSTRUMENT_HAND_DRUM 13000
#define INSTRUMENT_WOODEN_FLUTE 13001
+1 -1
View File
@@ -25,7 +25,7 @@
// Build variables
// these get injected during the build pipeline
#define CURRENT_VERSION "23.5.0-dev" // always append -dev to the current version for custom-builds
#define CURRENT_VERSION "23.4.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
View File
@@ -1,6 +1,6 @@
{
"name": "eqemu-server",
"version": "23.5.0",
"version": "23.4.0",
"repository": {
"type": "git",
"url": "https://github.com/EQEmu/Server.git"
+2
View File
@@ -33,6 +33,7 @@ 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
@@ -44,6 +45,7 @@ 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"
+93 -136
View File
@@ -42,16 +42,11 @@ extern ZSList zoneserver_list;
uint32 numplayers = 0; //this really wants to be a member variable of ClientList...
ClientList::ClientList()
: CLStale_timer(10000),
m_poll_cache_timer(6000)
: CLStale_timer(10000)
{
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() {
@@ -62,10 +57,6 @@ void ClientList::Process() {
if (CLStale_timer.Check())
CLCheckStale();
if (m_poll_cache_timer.Check()) {
RebuildZoneServerCaches();
}
LinkedListIterator<Client*> iterator(list);
iterator.Reset();
@@ -393,7 +384,6 @@ void ClientList::ClientUpdate(ZoneServer *zoneserver, ServerClientList_Struct *s
}
else {
cle->Update(zoneserver, scl);
AddToZoneServerCaches(cle);
}
return;
}
@@ -468,7 +458,6 @@ void ClientList::ClientUpdate(ZoneServer *zoneserver, ServerClientList_Struct *s
);
clientlist.Insert(cle);
AddToZoneServerCaches(cle);
zoneserver->ChangeWID(scl->charid, cle->GetID());
}
@@ -1619,7 +1608,7 @@ void ClientList::OnTick(EQ::Timer *t)
/**
* @param response
*/
void ClientList::GetClientList(Json::Value &response, bool full_list)
void ClientList::GetClientList(Json::Value &response)
{
LinkedListIterator<ClientListEntry *> Iterator(clientlist);
@@ -1630,68 +1619,62 @@ void ClientList::GetClientList(Json::Value &response, bool full_list)
Json::Value row;
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();
}
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();
auto server = cle->Server();
if (server) {
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();
}
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();
}
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);
@@ -1868,97 +1851,71 @@ std::map<uint32, ClientListEntry *> ClientList::GetGuildClientsWithTributeOptIn(
return guild_members;
}
void ClientList::RebuildZoneServerCaches()
#include <unordered_set>
std::vector<uint32_t> ClientList::GetGuildZoneServers(uint32 guild_id)
{
// Clear without freeing memory (buckets stay allocated)
m_gm_zone_server_ids.clear();
m_guild_zone_server_ids.clear();
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();
ClientListEntry *cle = iterator.GetData();
if (cle->Online() != CLE_Status::InZone || !cle->Server()) {
if (cle->Online() != CLE_Status::InZone) {
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);
if (!cle->Server()) {
iterator.Advance();
continue;
}
// Track guild zone servers
if (cle->GuildID() > 0) {
auto& guild_set = m_guild_zone_server_ids[cle->GuildID()];
guild_set.insert(server_id);
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();
}
return zone_server_ids;
}
std::vector<uint32_t> ClientList::GetGuildZoneServers(uint32 guild_id)
std::vector<uint32_t> ClientList::GetZoneServersWithGMs()
{
if (RuleB(World, RealTimeCalculateGuilds)) {
std::vector<uint32_t> zone_server_ids;
std::unordered_set<uint32_t> seen_ids;
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();
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.Reset();
while (iterator.MoreElements()) {
ClientListEntry *cle = iterator.GetData();
if (cle->Online() != CLE_Status::InZone) {
iterator.Advance();
continue;
}
return zone_server_ids;
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();
}
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()};
}
void ClientList::AddToZoneServerCaches(ClientListEntry* cle)
{
if (!cle || cle->Online() != CLE_Status::InZone || !cle->Server()) {
return;
}
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);
}
return zone_server_ids;
}
+3 -15
View File
@@ -60,13 +60,15 @@ 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, bool full_list = false);
void GetClientList(Json::Value &response);
void GetGuildClientList(Json::Value& response, uint32 guild_id);
void SendCharacterMessage(uint32_t character_id, int chat_type, const std::string& message);
@@ -76,15 +78,6 @@ 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++; }
@@ -99,11 +92,6 @@ 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_*/
+4 -20
View File
@@ -111,17 +111,9 @@ void callGetDatabaseSchema(Json::Value &response)
response.append(schema);
}
void callGetClientList(Json::Value &response, const std::vector<std::string> &args)
void callGetClientList(Json::Value &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);
client_list.GetClientList(response);
}
void getReloadTypes(Json::Value &response)
@@ -135,12 +127,6 @@ 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{};
@@ -188,7 +174,7 @@ void EQEmuApiWorldDataService::get(Json::Value &r, const std::vector<std::string
callGetDatabaseSchema(r);
}
if (m == "get_client_list") {
callGetClientList(r, args);
callGetClientList(r);
}
if (m == "get_reload_types") {
getReloadTypes(r);
@@ -199,9 +185,6 @@ 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;
}
@@ -209,6 +192,7 @@ 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;
+1 -1
View File
@@ -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 @ https://www.eqemulator.org/index.php?pageid=ws_mgmt";
reason += "For Legacy EQEmulator connections, you need to register your server @ http://www.eqemulator.org/account/?LS";
}
}
-1
View File
@@ -72,7 +72,6 @@ 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:
+3 -1
View File
@@ -4439,8 +4439,9 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons
if (IsValidSpell(spell_id) && !iBuffTic) {
//see if root will break
if (IsRooted() && !FromDamageShield) // neotoyko: only spells cancel root
if (IsRooted() && !FromDamageShield && spells[spell_id].skill != NO_ROOT_BREAK_SKILL_ID) { // neotoyko: only spells cancel root
TryRootFadeByDamage(buffslot, attacker);
}
}
else if (!IsValidSpell(spell_id))
{
@@ -6223,6 +6224,7 @@ bool Mob::TryRootFadeByDamage(int buffslot, Mob* attacker) {
- If multiple roots on target, always and only checks first root slot and if broken only removes that slots root.
- Only roots on determental spells can be broken by damage.
- Root break chance values obtained from live parses.
- If casting skill is set to 200 then spell can not break root
*/
if (!attacker || !spellbonuses.Root[SBIndex::ROOT_EXISTS] || spellbonuses.Root[SBIndex::ROOT_BUFFSLOT] < 0) {
+12 -9
View File
@@ -1165,6 +1165,10 @@ 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;
}
@@ -2470,6 +2474,9 @@ 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;
}
@@ -4682,7 +4689,11 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id)
break;
case SE_ResistFearChance:
if (negate_spellbonus) {spellbonuses.ResistFearChance = effect_value; }
if (negate_spellbonus) {
spellbonuses.Fearless = false;
spellbonuses.ResistFearChance = effect_value;
}
if (negate_aabonus) { aabonuses.ResistFearChance = effect_value; }
if (negate_itembonus) { itembonuses.ResistFearChance = effect_value; }
break;
@@ -5320,14 +5331,6 @@ 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;
}
+216 -170
View File
@@ -245,8 +245,6 @@ Bot::Bot(
EquipBot();
m_combat_jitter_timer.Start();
if (GetClass() == Class::Rogue) {
m_rogue_evade_timer.Start();
}
@@ -2188,7 +2186,8 @@ void Bot::AI_Process()
}
if (HOLDING || (raid && r_group == RAID_GROUPLESS)) {
TryNonCombatMovementChecks(bot_owner, follow_mob);
glm::vec3 Goal(0, 0, 0);
TryNonCombatMovementChecks(bot_owner, follow_mob, Goal);
return;
}
@@ -2218,6 +2217,8 @@ 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()) {
@@ -2292,15 +2293,15 @@ void Bot::AI_Process()
const EQ::ItemInstance* p_item = GetBotItem(EQ::invslot::slotPrimary);
const EQ::ItemInstance* s_item = GetBotItem(EQ::invslot::slotSecondary);
CombatRangeInput i = {
.target = tar,
.target_distance = tar_distance,
.stop_melee_level = stop_melee_level,
.p_item = p_item,
.s_item = s_item
CombatRangeInput input = {
.target = tar,
.target_distance = tar_distance,
.stop_melee_level = stop_melee_level,
.p_item = p_item,
.s_item = s_item
};
CombatRangeOutput o = EvaluateCombatRange(i);
CombatRangeOutput o = EvaluateCombatRange(input);
// Combat range variables
bool at_combat_range = o.at_combat_range;
@@ -2361,7 +2362,7 @@ void Bot::AI_Process()
}
}
TryPursueTarget(leash_distance);
TryPursueTarget(leash_distance, Goal);
return;
}
@@ -2384,30 +2385,52 @@ void Bot::AI_Process()
(bot_owner->GetBotPulling() && NOT_RETURNING_BOT);
if (!other_bot_pulling && at_combat_range) {
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
};
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()));
}
if (DoCombatPositioning(cpi) && IsMoving()) {
return;
}
if (!IsSitting() && !IsFacingMob(tar)) {
FaceTarget(tar);
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);
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() &&
@@ -2456,7 +2479,7 @@ void Bot::AI_Process()
// ENGAGED NOT AT COMBAT RANGE
else if (!other_bot_pulling && !TryPursueTarget(leash_distance)) {
else if (!other_bot_pulling && !TryPursueTarget(leash_distance, Goal)) {
return;
}
@@ -2469,7 +2492,7 @@ void Bot::AI_Process()
TryMeditate();
}
else { // Out-of-combat behavior
DoOutOfCombatChecks(bot_owner, follow_mob, leash_distance, fm_distance);
DoOutOfCombatChecks(bot_owner, follow_mob, Goal, leash_distance, fm_distance);
}
}
@@ -2486,10 +2509,8 @@ bool Bot::TryBardMovementCasts() {// Basically, bard bots get a chance to cast i
return false;
}
bool Bot::TryNonCombatMovementChecks(Client* bot_owner, const Mob* follow_mob) {// Non-engaged movement checks
bool Bot::TryNonCombatMovementChecks(Client* bot_owner, const Mob* follow_mob, glm::vec3& Goal) {// Non-engaged movement checks
if (AI_movement_timer->Check() && (!IsCasting() || GetClass() == Class::Bard)) {
glm::vec3 Goal(0, 0, 0);
if (GUARDING) {
Goal = GetGuardPoint();
}
@@ -2543,7 +2564,7 @@ bool Bot::TryIdleChecks(float fm_distance) {
return false;
}
void Bot::DoOutOfCombatChecks(Client* bot_owner, Mob* follow_mob, float leash_distance, float fm_distance) {
void Bot::DoOutOfCombatChecks(Client* bot_owner, Mob* follow_mob, glm::vec3& Goal, float leash_distance, float fm_distance) {
SetAttackFlag(false);
SetCombatRoundForAlerts(false);
SetAttackingFlag(false);
@@ -2576,7 +2597,7 @@ void Bot::DoOutOfCombatChecks(Client* bot_owner, Mob* follow_mob, float leash_di
}
// Ok to idle
if (TryNonCombatMovementChecks(bot_owner, follow_mob)) {
if (TryNonCombatMovementChecks(bot_owner, follow_mob, Goal)) {
return;
}
@@ -2787,14 +2808,15 @@ 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) {
bool Bot::TryPursueTarget(float leash_distance, glm::vec3& Goal) {
if (AI_movement_timer->Check() && (!spellend_timer.Enabled() || GetClass() == Class::Bard)) {
if (GetTarget() && !IsRooted()) {
LogAIDetail("Pursuing [{}] while engaged", GetTarget()->GetCleanName());
glm::vec3 Goal = GetTarget()->GetPosition();
Goal = GetTarget()->GetPosition();
if (DistanceSquared(m_Position, Goal) <= leash_distance) {
RunTo(Goal.x, Goal.y, Goal.z);
SetCombatOutOfRangeJitter();
} else {
WipeHateList();
SetTarget(nullptr);
@@ -3108,8 +3130,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 * 0.25f;
o.melee_distance = o.melee_distance_max * 0.45f;
o.melee_distance_min = o.melee_distance_max * RuleR(Bots, LowerTauntingMeleeDistanceMultiplier);
o.melee_distance = o.melee_distance_max * RuleR(Bots, UpperTauntingMeleeDistanceMultiplier);
}
else if (IsBotRanged()) { // Archers/Throwers
float min_distance = RuleI(Combat, MinRangedAttackDist);
@@ -3117,22 +3139,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 * 0.75f));
o.melee_distance_min = std::max(min_distance, (desired_range / 2));
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 * 0.75f));
o.melee_distance_min = std::max(o.melee_distance_max, (desired_range / 2));
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 * 0.80f;
o.melee_distance = o.melee_distance_max * 0.95f;
o.melee_distance_min = o.melee_distance_max * RuleR(Bots, LowerMaxMeleeRangeDistanceMultiplier);
o.melee_distance = o.melee_distance_max * RuleR(Bots, UpperMaxMeleeRangeDistanceMultiplier);
}
else { // Regular melee
o.melee_distance_min = o.melee_distance_max * 0.30f;
o.melee_distance = o.melee_distance_max * 0.65f;
o.melee_distance_min = o.melee_distance_max * RuleR(Bots, LowerMeleeDistanceMultiplier);
o.melee_distance = o.melee_distance_max * RuleR(Bots, UpperMeleeDistanceMultiplier);
}
o.at_combat_range = (input.target_distance <= o.melee_distance);
@@ -9716,7 +9738,7 @@ bool Bot::CastChecks(uint16 spell_id, Mob* tar, uint16 spell_type, bool precheck
)
)
&&
tar->CanBuffStack(spell_id, GetLevel(), true) < 0
tar->CanBuffStack(spell_id, GetLevel(), false) < 0
) {
LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} on {} due to !CanBuffStack.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName());
return false;
@@ -11820,7 +11842,7 @@ bool Bot::IsValidSpellTypeBySpellID(uint16 spell_type, uint16 spell_id) {
return false;
case BotSpellTypes::ResistBuffs:
case BotSpellTypes::PetResistBuffs:
if (IsResistanceBuffSpell(spell_id) && !IsEffectInSpell(spell_id, SE_DamageShield)) {
if (IsResistanceBuffSpell(spell_id)) {
return true;
}
@@ -11944,171 +11966,193 @@ 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::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);
}
}
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);
void Bot::SetCombatJitter() {
SetCombatJitterFlag();
if (!IsMoving() && !IsSitting() && !IsFacingMob(input.tar)) {
FaceTarget(input.tar);
if (RuleI(Bots, MaxJitterTimer) > 0) {
m_combat_jitter_timer.Start(zone->random.Int(RuleI(Bots, MinJitterTimer), RuleI(Bots, MaxJitterTimer)), true);
}
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() || HasTargetReflection()) { // Taunting/Aggro adjustments
adjustment_needed =
is_too_close ||
los_adjust ||
(is_melee && !input.front_mob);
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;
PlotBotPositionAroundTarget(find_position_input);
}
} 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 (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;
PlotBotPositionAroundTarget(find_position_input);
}
} else { // Regular adjustments
adjustment_needed =
is_too_close ||
los_adjust ||
(behind_mob_set && !input.behind_mob);
if (adjustment_needed && adjustment_allowed) {
find_position_input.distance_min = input.melee_distance_min;
find_position_input.distance_max = input.melee_distance;
PlotBotPositionAroundTarget(find_position_input);
}
}
}
}
if (!adjustment_needed && IsMoving()) {
StopMoving();
}
return adjustment_needed;
}
bool Bot::PlotBotPositionAroundTarget(const FindPositionInput& input) {
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);
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;
}
}
}
else {
if (IsTaunting()) { // Taunting adjustments
bool taunting_adjust = (!front_mob || is_too_close || los_adjust);
if (taunting_adjust) {
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, false, true)) {
RunToGoalWithJitter(Goal);
return;
}
}
}
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;
if (enraged_adjust) {
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, true)) {
RunToGoalWithJitter(Goal);
return;
}
}
}
else { // Regular adjustments
bool regular_adjust =
is_too_close ||
los_adjust ||
(!GetBehindMob() && !front_mob) ||
(GetBehindMob() && !behind_mob);
if (regular_adjust) {
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, GetBehindMob(), !GetBehindMob())) {
RunToGoalWithJitter(Goal);
return;
}
}
}
}
}
}
DoFaceCheckNoJitter(tar);
}
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 Result = false;
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;
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;
while (counter < max_iterations_allowed) {
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);
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);
if (best_z != BEST_Z_INVALID) {
temp_goal.z = best_z;
temp_z = best_z;
}
else {
counter++;
continue;
}
tar_distance = Distance(input.tar->GetPosition(), temp_goal);
temp_m_Position.x = temp_x;
temp_m_Position.y = temp_y;
temp_m_Position.z = temp_z;
if (tar_distance > input.distance_max || tar_distance < std::max(input.distance_min, (input.distance_max * 0.75f))) {
tar_distance = Distance(target->GetPosition(), temp_m_Position);
if (tar_distance > max_distance || tar_distance < min_distance) {
counter++;
continue;
}
if (input.front_only && !InFrontMob(input.tar, temp_goal.x, temp_goal.y)) {
if (front_only && !InFrontMob(target, temp_x, temp_y)) {
counter++;
continue;
}
else if (input.behind_only && !BehindMob(input.tar, temp_goal.x, temp_goal.y)) {
else if (behind_only && !BehindMob(target, temp_x, temp_y)) {
counter++;
continue;
}
if (!input.bypass_los && CastToBot()->RequiresLoSForPositioning() && !CheckPositioningLosFN(input.tar, temp_goal.x, temp_goal.y, temp_goal.z)) {
if (!bypass_los && CastToBot()->RequiresLoSForPositioning() && !CheckPositioningLosFN(target, temp_x, temp_y, temp_z)) {
counter++;
continue;
}
@@ -12117,7 +12161,9 @@ bool Bot::PlotBotPositionAroundTarget(const FindPositionInput& input) {
}
if (Result) {
RunToGoalWithJitter(temp_goal);
x_dest = temp_x;
y_dest = temp_y;
z_dest = temp_z;
}
}
+32 -5
View File
@@ -39,6 +39,9 @@
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;
@@ -229,6 +232,21 @@ 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:
@@ -559,7 +577,7 @@ public:
uint16 GetPetBotSpellType(uint16 spell_type);
// Movement checks
bool PlotBotPositionAroundTarget(const FindPositionInput& input);
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);
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; }
@@ -1084,8 +1102,15 @@ 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();
bool DoCombatPositioning(const CombatPositioningInput& input);
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);
void RunToGoalWithJitter(glm::vec3 Goal);
bool RequiresLoSForPositioning();
bool HasRequiredLoSForPositioning(Mob* tar);
@@ -1093,12 +1118,12 @@ public:
// Try Combat Methods
bool TryEvade(Mob* tar);
bool TryFacingTarget(Mob* tar);
bool TryPursueTarget(float leash_distance);
bool TryPursueTarget(float leash_distance, glm::vec3& Goal);
bool TryMeditate();
bool TryAutoDefend(Client* bot_owner, float leash_distance);
bool TryIdleChecks(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 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 TryBardMovementCasts();
bool BotRangedAttack(Mob* other, bool can_double_attack = false);
bool CheckDoubleRangedAttack();
@@ -1164,6 +1189,8 @@ 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;
+1 -7
View File
@@ -48,10 +48,9 @@ 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->ValidStateCheck(c)) {
if (my_bot->BotPassiveCheck()) {
continue;
}
@@ -69,11 +68,6 @@ 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.");
}
}
-36
View File
@@ -21,7 +21,6 @@
#include "../common/types.h"
#include "../common/timer.h"
#include "mob.h"
#include <sstream>
@@ -152,39 +151,4 @@ 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
+16 -56
View File
@@ -38,11 +38,7 @@ inline void SetupStateZone()
SetupZone("soldungb");
zone->Process();
// depop the zone controller
auto controller = entity_list.GetNPCByNPCTypeID(ZONE_CONTROLLER_NPC_ID);
if (controller != nullptr) {
controller->Depop();
}
entity_list.GetNPCByNPCTypeID(ZONE_CONTROLLER_NPC_ID)->Depop();
entity_list.MobProcess(); // process the depop
}
@@ -356,12 +352,8 @@ 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() == entries;
RunTest(
fmt::format("Spawns > All NPC's killed (0 NPCs) ([{}] Corpses)", entries),
true,
condition
);
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);
std::vector<uint32_t> spawn2_ids = {};
@@ -385,15 +377,11 @@ inline void TestSpawns()
zone->Shutdown();
SetupStateZone();
condition = (int) entity_list.GetNPCList().size() == 0 && (int) entity_list.GetCorpseList().size() == entries;
condition = (int) entity_list.GetNPCList().size() == 0 && (int) entity_list.GetCorpseList().size() == 115;
if (!condition) {
PrintEntityCounts();
}
RunTest(
fmt::format("Spawns > After restore (0 NPCs) ([{}] Corpses)", entries),
true,
condition
);
RunTest("Spawns > After restore (0 NPCs) (115 Corpses)", true, condition);
for (auto &e: entity_list.GetCorpseList()) {
auto c = e.second;
@@ -417,15 +405,11 @@ inline void TestSpawns()
zone->Process();
condition = (int) entity_list.GetNPCList().size() == entries && (int) entity_list.GetCorpseList().size() == entries;
condition = (int) entity_list.GetNPCList().size() == 115 && (int) entity_list.GetCorpseList().size() == 115;
if (!condition) {
PrintEntityCounts();
}
RunTest(
fmt::format("Spawns > After respawn ([{}] NPCs) ([{}] Corpses)", entries, entries),
true,
condition
);
RunTest("Spawns > After respawn (115 NPCs) (115 Corpses)", true, condition);
for (auto &c: entity_list.GetCorpseList()) {
c.second->DepopNPCCorpse();
@@ -433,15 +417,11 @@ inline void TestSpawns()
entity_list.CorpseProcess();
condition = (int) entity_list.GetNPCList().size() == entries && (int) entity_list.GetCorpseList().size() == 0;
condition = (int) entity_list.GetNPCList().size() == 115 && (int) entity_list.GetCorpseList().size() == 0;
if (!condition) {
PrintEntityCounts();
}
RunTest(
fmt::format("Spawns > After respawn ([{}] NPCs) (0 Corpses)", entries),
true,
condition
);
RunTest("Spawns > After respawn (115 NPCs) (0 Corpses)", true, condition);
// lets set NPC's up with a predictable loottable for testing
uint32_t loottable_id = SeedLootTable();
@@ -482,28 +462,20 @@ inline void TestSpawns()
npc->Death(npc, npc->GetHP() + 1, SPELL_UNKNOWN, EQ::skills::SkillHandtoHand);
}
condition = (int) entity_list.GetNPCList().size() == (entries - 10) && (int) entity_list.GetCorpseList().size() == 10;
condition = (int) entity_list.GetNPCList().size() == 105 && (int) entity_list.GetCorpseList().size() == 10;
if (!condition) {
PrintEntityCounts();
}
RunTest(
fmt::format("Spawns > Kill 10 NPC's before save/restore ([{}] NPCs) (10 Corpses)", (entries - 10)),
true,
condition
);
RunTest("Spawns > Kill 10 NPC's before save/restore (105 NPCs) (10 Corpses)", true, condition);
zone->Shutdown();
SetupStateZone();
condition = (int) entity_list.GetNPCList().size() == (entries - 10) && (int) entity_list.GetCorpseList().size() == 10;
condition = (int) entity_list.GetNPCList().size() == 105 && (int) entity_list.GetCorpseList().size() == 10;
if (!condition) {
PrintEntityCounts();
}
RunTest(
fmt::format("Spawns > After restore ([{}] NPCs) (10 Corpses)", (entries - 10)),
true,
condition
);
RunTest("Spawns > After restore (105 NPCs) (10 Corpses)", true, condition);
// validate that all corpses and npc's have cloak of flames
bool test_failed = false;
@@ -600,14 +572,6 @@ 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();
@@ -623,20 +587,16 @@ inline void TestSpawns()
npc->SetEntityVariable("previously_spawned", "true");
}
Timer::RollForward(max_respawn); // longest respawn time in zone
Timer::RollForward(302401); // longest respawn time in zone
zone->Process();
entity_list.MobProcess(); // processing depops
condition = (int) entity_list.GetNPCList().size() == entries && (int) entity_list.GetCorpseList().size() == 10;
condition = (int) entity_list.GetNPCList().size() == 115 && (int) entity_list.GetCorpseList().size() == 10;
if (!condition) {
PrintEntityCounts();
PrintZoneNpcs();
}
RunTest(
fmt::format("Spawns > After respawn, ensure we have expected entity counts ([{}] NPCs) (10 Corpses)", entries),
true,
condition
);
RunTest("Spawns > After respawn, ensure we have expected entity counts (115 NPCs) (10 Corpses)", true, condition);
entity_list.MobProcess(); // processing depops
-60
View File
@@ -995,8 +995,6 @@ 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;
@@ -1024,8 +1022,6 @@ bool Client::Save(uint8 iCommitNow) {
m_pp.endurance = current_endurance;
}
database.TransactionBegin();
/* Save Character Currency */
database.SaveCharacterCurrency(CharacterID(), &m_pp);
@@ -1109,10 +1105,6 @@ bool Client::Save(uint8 iCommitNow) {
database.botdb.SaveBotSettings(this);
}
database.TransactionCommit();
LogInfo("Save for [{}] took [{}]", GetCleanName(), timer.elapsed());
return true;
}
@@ -1217,58 +1209,6 @@ 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();
}
-3
View File
@@ -483,9 +483,6 @@ 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); }
-2
View File
@@ -217,8 +217,6 @@ bool Client::Process() {
GetMerc()->Depop();
}
instalog = true;
camp_timer.Disable();
}
if (IsStunned() && stunned_timer.Check())
+1 -3
View File
@@ -1,7 +1,6 @@
#include "data_bucket.h"
#include "zonedb.h"
#include "mob.h"
#include "client.h"
#include "worldserver.h"
#include <ctime>
#include <cctype>
@@ -360,8 +359,7 @@ bool DataBucket::GetDataBuckets(Mob *mob)
BulkLoadEntitiesToCache(DataBucketLoadType::Bot, {id});
}
else if (mob->IsClient()) {
uint32 account_id = mob->CastToClient()->AccountID();
BulkLoadEntitiesToCache(DataBucketLoadType::Account, {account_id});
BulkLoadEntitiesToCache(DataBucketLoadType::Account, {id});
BulkLoadEntitiesToCache(DataBucketLoadType::Client, {id});
}
-7
View File
@@ -3159,13 +3159,6 @@ 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);
}
-1
View File
@@ -678,7 +678,6 @@ int main(int argc, char **argv)
safe_delete(Config);
if (zone != 0) {
zone->SetSaveZoneState(false);
zone->Shutdown(true);
}
//Fix for Linux world server problem.
+8 -5
View File
@@ -1698,6 +1698,13 @@ void Mob::StopMoving()
void Mob::StopMoving(float new_heading)
{
if (IsBot()) {
auto bot = CastToBot();
bot->SetCombatJitterFlag(false);
bot->SetCombatOutOfRangeJitterFlag(false);
}
StopNavigation();
RotateTo(new_heading);
@@ -4614,12 +4621,8 @@ 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
View File
@@ -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);
bool TrySpellEffectResist(uint16 spell_id);
uint16 GetSpellEffectResistChance(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 ****
+9 -4
View File
@@ -933,11 +933,16 @@ void MobMovementManager::SendCommandToClients(
float MobMovementManager::FixHeading(float in)
{
int h = static_cast<int>(in) % 512;
if (h < 0) {
h += 512;
auto h = in;
while (h > 512.0) {
h -= 512.0;
}
return static_cast<float>(h);
while (h < 0.0) {
h += 512.0;
}
return h;
}
void MobMovementManager::DumpStats(Client *client)
+1 -1
View File
@@ -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 && e->name == timer_name) {
if (e->mob && e->mob == m) {
parse->EventMob(EVENT_TIMER_STOP, m, nullptr, [&]() { return timer_name; });
QTimerList.erase(e);
+1 -7
View File
@@ -277,13 +277,7 @@ bool Spawn2::Process() {
}
}
// 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);
NPC *npc = new NPC(tmp, this, glm::vec4(x, y, z, heading), GravityBehavior::Water);
npcthis = npc;
-2
View File
@@ -79,7 +79,6 @@ 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;
@@ -109,7 +108,6 @@ 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 {
+14 -27
View File
@@ -7465,57 +7465,44 @@ void Mob::DispelMagic(Mob* caster, uint16 spell_id, int effect_value)
}
}
bool Mob::TrySpellEffectResist(uint16 spell_id)
uint16 Mob::GetSpellEffectResistChance(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 (!IsValidSpell(spell_id)) {
return false;
}
if (!aabonuses.SEResist[1] && !spellbonuses.SEResist[1] && !itembonuses.SEResist[1])
return 0;
if (!aabonuses.SEResist[1] && !spellbonuses.SEResist[1] && !itembonuses.SEResist[1]) {
return false;
}
int resist_chance = 0;
uint16 resist_chance = 0;
for(int i = 0; i < EFFECT_COUNT; ++i)
{
if (spells[spell_id].effect_id[i] == SE_Blank) {
continue;
}
bool found = false;
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 (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;
}
if (found)
continue;
}
}
return false;
return resist_chance;
}
bool Mob::TryDispel(uint8 caster_level, uint8 buff_level, int level_modifier){
+16 -11
View File
@@ -5359,17 +5359,16 @@ 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)
if (resist_type != RESIST_NONE && TrySpellEffectResist(spell_id)) {
int se_resist_bonuses = GetSpellEffectResistChance(spell_id);
if (se_resist_bonuses && zone->random.Roll(se_resist_bonuses)) {
return 0;
}
// Check for Chance to Resist Spell bonuses (ie Sanctification Discipline)
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;
}
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;
}
}
@@ -5668,12 +5667,18 @@ int16 Mob::CalcResistChanceBonus()
int16 Mob::CalcFearResistChance()
{
int resist_chance = spellbonuses.ResistFearChance + itembonuses.ResistFearChance + aabonuses.ResistFearChance;
if (spellbonuses.Fearless || itembonuses.Fearless || aabonuses.Fearless) {
resist_chance = 100;
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;
}
return resist_chance;
return resistchance;
}
float Mob::GetAOERange(uint16 spell_id)
+2
View File
@@ -2739,6 +2739,8 @@ 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);
+1 -9
View File
@@ -887,7 +887,7 @@ void Zone::Shutdown(bool quiet)
c.second->WorldKick();
}
if (m_save_zone_state && RuleB(Zone, StateSavingOnShutdown) && zone && zone->IsLoaded()) {
if (RuleB(Zone, StateSavingOnShutdown) && zone && zone->IsLoaded()) {
SaveZoneState();
}
@@ -1010,7 +1010,6 @@ 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;
@@ -1191,11 +1190,6 @@ 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);
@@ -1210,8 +1204,6 @@ 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());
-5
View File
@@ -353,9 +353,6 @@ 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
*
@@ -476,7 +473,6 @@ 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
@@ -520,7 +516,6 @@ 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;
+14 -66
View File
@@ -5,7 +5,6 @@
#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
@@ -382,31 +381,6 @@ 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
@@ -415,19 +389,18 @@ bool Zone::LoadZoneState(
auto spawn_states = ZoneStateSpawnsRepository::GetWhere(
database,
fmt::format(
"zone_id = {} AND instance_id = {} AND is_zone = 0 ORDER BY spawn2_id",
"zone_id = {} AND instance_id = {} 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());
@@ -441,27 +414,15 @@ bool Zone::LoadZoneState(
zone->initgrids_timer.Trigger();
zone->Process();
// load base spawn2 data for spawn locations
std::vector<std::string> spawn2_ids;
// load zone variables first
int count = 0;
for (auto &s: spawn_states) {
if (s.spawn2_id > 0) {
spawn2_ids.push_back(std::to_string(s.spawn2_id));
if (s.is_zone) {
LoadZoneVariables(zone, s.entity_variables);
break;
}
}
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) {
@@ -484,26 +445,13 @@ 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,
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.x,
s.y,
s.z,
s.heading,
s.respawn_time,
s.variance,
spawn_time_left,
@@ -515,14 +463,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();
+2 -6
View File
@@ -528,12 +528,8 @@ void Client::DoZoneSuccess(ZoneChange_Struct *zc, uint16 zone_id, uint32 instanc
m_pp.zone_id = zone_id;
m_pp.zoneInstance = instance_id;
// 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();
//Force a save so its waiting for them when they zone
Save(2);
m_lock_save_position = true;