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