mirror of
https://github.com/EQEmu/Server.git
synced 2026-05-24 22:32:29 +00:00
Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5f68e4a41a | |||
| e103422ca5 | |||
| 0cbfad975d | |||
| 2a20c69c69 | |||
| de2dfc1a7e | |||
| bad631df59 | |||
| e8f1aa253a | |||
| 889e57a5af | |||
| 5cfdeb928e | |||
| 7519b0225e | |||
| 04fdc54522 | |||
| f39155952f | |||
| 5acc181d64 | |||
| 2ae0b7dd3e | |||
| b0d4f095ef | |||
| 7c7a88650b | |||
| afaa8f4100 | |||
| 0d72295cc9 | |||
| 9d4f231619 | |||
| fcb0a47280 | |||
| 1e50f19f7e | |||
| 33bb5aa8e5 | |||
| 6a668f8aa5 | |||
| df499b22ab | |||
| 7bc00cb466 | |||
| 51f6108aab | |||
| c13f9f80d9 | |||
| 443abf9199 | |||
| 1556e05b2f | |||
| 1d645aa5f6 | |||
| 9f42da5bad | |||
| 4a8222f243 | |||
| db4c515853 | |||
| 462656a201 | |||
| ddd98be383 |
@@ -1,3 +1,97 @@
|
||||
## [22.4.4] - 02/24/2023
|
||||
|
||||
### Bots
|
||||
|
||||
* Add Caster Range Command, and IsValidSpellRange Checks ([#2942](https://github.com/EQEmu/Server/pull/2942)) ([Aeadoin](https://github.com/Aeadoin)) 2023-02-23
|
||||
* Cleanup BotDatabase::LoadBuffs ([#2981](https://github.com/EQEmu/Server/pull/2981)) ([Aeadoin](https://github.com/Aeadoin)) 2023-02-24
|
||||
* Verify Bots Group Integrity on join ([#2980](https://github.com/EQEmu/Server/pull/2980)) ([Aeadoin](https://github.com/Aeadoin)) 2023-02-23
|
||||
|
||||
### Commands
|
||||
|
||||
* Cleanup #peekinv Command ([#2969](https://github.com/EQEmu/Server/pull/2969)) ([Kinglykrab](https://github.com/Kinglykrab)) 2023-02-23
|
||||
|
||||
### Doors
|
||||
|
||||
* Fix doors triggering invalid zone fetches of dest_zone of "none" ([#2985](https://github.com/EQEmu/Server/pull/2985)) ([Akkadius](https://github.com/Akkadius)) 2023-02-24
|
||||
|
||||
### Fixes
|
||||
|
||||
* Adjust database manifest to include .sql extension ([Akkadius](https://github.com/Akkadius)) 2023-02-25
|
||||
* Correct Mend reuse time and add reduction support. ([#2972](https://github.com/EQEmu/Server/pull/2972)) ([nytmyr](https://github.com/nytmyr)) 2023-02-23
|
||||
* Fix Beneficial Target of Target procs ([#2987](https://github.com/EQEmu/Server/pull/2987)) ([Aeadoin](https://github.com/Aeadoin)) 2023-02-24
|
||||
* Fix for undefined MySQL library behavior. ([#2834](https://github.com/EQEmu/Server/pull/2834)) ([KimLS](https://github.com/KimLS)) 2023-02-25
|
||||
|
||||
### Pathing
|
||||
|
||||
* Improve roambox logic ([#2983](https://github.com/EQEmu/Server/pull/2983)) ([Akkadius](https://github.com/Akkadius)) 2023-02-24
|
||||
* More z-clip improvements, Wurm and Spectral Iksar race adjustments ([#2988](https://github.com/EQEmu/Server/pull/2988)) ([Akkadius](https://github.com/Akkadius)) 2023-02-25
|
||||
* Smoother pathing z-correction ([#2982](https://github.com/EQEmu/Server/pull/2982)) ([Akkadius](https://github.com/Akkadius)) 2023-02-24
|
||||
|
||||
### Player Events
|
||||
|
||||
* Add QS processing, mutex tweaks ([#2984](https://github.com/EQEmu/Server/pull/2984)) ([Akkadius](https://github.com/Akkadius)) 2023-02-25
|
||||
|
||||
### Quest API
|
||||
|
||||
* Add IsAutoAttackEnabled() to Perl/Lua ([#2979](https://github.com/EQEmu/Server/pull/2979)) ([Kinglykrab](https://github.com/Kinglykrab)) 2023-02-23
|
||||
* Add IsAutoFireEnabled() to Perl/Lua ([#2978](https://github.com/EQEmu/Server/pull/2978)) ([Kinglykrab](https://github.com/Kinglykrab)) 2023-02-23
|
||||
* Fix EVENT_TIMER crash when entity is no longer available ([#2986](https://github.com/EQEmu/Server/pull/2986)) ([Akkadius](https://github.com/Akkadius)) 2023-02-24
|
||||
|
||||
### Scaling
|
||||
|
||||
* Add support for zone ID and instance version to NPC Scaling ([#2968](https://github.com/EQEmu/Server/pull/2968)) ([Kinglykrab](https://github.com/Kinglykrab)) 2023-02-25
|
||||
|
||||
### Tradeskills
|
||||
|
||||
* Fix for Lore Conflict ([#2977](https://github.com/EQEmu/Server/pull/2977)) ([Aeadoin](https://github.com/Aeadoin)) 2023-02-24
|
||||
|
||||
## [22.4.3] - 02/21/2023
|
||||
|
||||
### Bots
|
||||
|
||||
* Change HasBotItem(item_id) to return slot_id instead of bool. ([#2966](https://github.com/EQEmu/Server/pull/2966)) ([Aeadoin](https://github.com/Aeadoin)) 2023-02-19
|
||||
* Change SaveTimers to Replace instead of Insert. ([#2951](https://github.com/EQEmu/Server/pull/2951)) ([Aeadoin](https://github.com/Aeadoin)) 2023-02-18
|
||||
* Fix output of ^spells while ^Enforcespellsettings is enabled ([#2959](https://github.com/EQEmu/Server/pull/2959)) ([Aeadoin](https://github.com/Aeadoin)) 2023-02-18
|
||||
|
||||
### Crash
|
||||
|
||||
* Fix crash with EVENT_UNEQUIP_ITEM_BOT ([#2973](https://github.com/EQEmu/Server/pull/2973)) ([Aeadoin](https://github.com/Aeadoin)) 2023-02-20
|
||||
* Fix world crash in player event processing ([#2960](https://github.com/EQEmu/Server/pull/2960)) ([Akkadius](https://github.com/Akkadius)) 2023-02-18
|
||||
|
||||
### Database
|
||||
|
||||
* Address deadlock in player events ([#2974](https://github.com/EQEmu/Server/pull/2974)) ([Akkadius](https://github.com/Akkadius)) 2023-02-21
|
||||
|
||||
### Fixes
|
||||
|
||||
* Fix MIR LDoN Theme Items on LDoN Merchants ([#2971](https://github.com/EQEmu/Server/pull/2971)) ([Kinglykrab](https://github.com/Kinglykrab)) 2023-02-20
|
||||
* Fix OOCMute not functioning ([#2970](https://github.com/EQEmu/Server/pull/2970)) ([Kinglykrab](https://github.com/Kinglykrab)) 2023-02-20
|
||||
|
||||
### Pathing
|
||||
|
||||
* Improvements to z-clipping, z-recovery and z-calculations ([#2975](https://github.com/EQEmu/Server/pull/2975)) ([Akkadius](https://github.com/Akkadius)) 2023-02-21
|
||||
|
||||
### Pets
|
||||
|
||||
* Client Pet summoned by NPC should not change guard location. ([#2967](https://github.com/EQEmu/Server/pull/2967)) ([noudess](https://github.com/noudess)) 2023-02-19
|
||||
|
||||
### Player Events
|
||||
|
||||
* Create new event ITEM_CREATION ([#2944](https://github.com/EQEmu/Server/pull/2944)) ([Akkadius](https://github.com/Akkadius)) 2023-02-18
|
||||
|
||||
### Quest API
|
||||
|
||||
* Add client->SignalClient() overload to Perl ([#2963](https://github.com/EQEmu/Server/pull/2963)) ([Kinglykrab](https://github.com/Kinglykrab)) 2023-02-19
|
||||
* Fix Perl SetSimpleRoamBox Overloads ([#2961](https://github.com/EQEmu/Server/pull/2961)) ([Kinglykrab](https://github.com/Kinglykrab)) 2023-02-19
|
||||
|
||||
### Reload API
|
||||
|
||||
* Add world handlers for certain opcodes ([#2958](https://github.com/EQEmu/Server/pull/2958)) ([Akkadius](https://github.com/Akkadius)) 2023-02-18
|
||||
|
||||
### SQL
|
||||
|
||||
* Add date to optional Drakkin Guktan Faction Update ([#2965](https://github.com/EQEmu/Server/pull/2965)) ([joligario](https://github.com/joligario)) 2023-02-19
|
||||
|
||||
## [22.4.2] - 02/18/2023
|
||||
|
||||
### Content
|
||||
|
||||
@@ -86,7 +86,7 @@ int main(int argc, char **argv)
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
content_db.SetMysql(database.getMySQL());
|
||||
content_db.SetMySQL(database);
|
||||
}
|
||||
|
||||
LogSys.SetDatabase(&database)
|
||||
|
||||
@@ -83,7 +83,7 @@ int main(int argc, char **argv) {
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
content_db.SetMysql(database.getMySQL());
|
||||
content_db.SetMySQL(database);
|
||||
}
|
||||
|
||||
LogSys.SetDatabase(&database)
|
||||
|
||||
+54
-48
@@ -34,14 +34,16 @@
|
||||
|
||||
DBcore::DBcore()
|
||||
{
|
||||
mysql_init(&mysql);
|
||||
pHost = nullptr;
|
||||
pUser = nullptr;
|
||||
pPassword = nullptr;
|
||||
pDatabase = nullptr;
|
||||
pCompress = false;
|
||||
pSSL = false;
|
||||
pStatus = Closed;
|
||||
mysql = mysql_init(nullptr);
|
||||
mysqlOwner = true;
|
||||
pHost = nullptr;
|
||||
pUser = nullptr;
|
||||
pPassword = nullptr;
|
||||
pDatabase = nullptr;
|
||||
pCompress = false;
|
||||
pSSL = false;
|
||||
pStatus = Closed;
|
||||
m_mutex = new Mutex;
|
||||
}
|
||||
|
||||
DBcore::~DBcore()
|
||||
@@ -51,16 +53,10 @@ DBcore::~DBcore()
|
||||
* are re-using the default database connection pointer when we dont have an
|
||||
* external configuration setup ex: (content_database)
|
||||
*/
|
||||
std::string mysql_connection_host;
|
||||
if (mysql.host) {
|
||||
mysql_connection_host = mysql.host;
|
||||
if (mysqlOwner) {
|
||||
mysql_close(mysql);
|
||||
}
|
||||
|
||||
if (GetOriginHost() != mysql_connection_host) {
|
||||
return;
|
||||
}
|
||||
|
||||
mysql_close(&mysql);
|
||||
safe_delete_array(pHost);
|
||||
safe_delete_array(pUser);
|
||||
safe_delete_array(pPassword);
|
||||
@@ -70,17 +66,18 @@ DBcore::~DBcore()
|
||||
// Sends the MySQL server a keepalive
|
||||
void DBcore::ping()
|
||||
{
|
||||
if (!MDatabase.trylock()) {
|
||||
if (!m_mutex->trylock()) {
|
||||
// well, if's it's locked, someone's using it. If someone's using it, it doesnt need a keepalive
|
||||
return;
|
||||
}
|
||||
mysql_ping(&mysql);
|
||||
MDatabase.unlock();
|
||||
mysql_ping(mysql);
|
||||
m_mutex->unlock();
|
||||
}
|
||||
|
||||
MySQLRequestResult DBcore::QueryDatabase(std::string query, bool retryOnFailureOnce)
|
||||
{
|
||||
return QueryDatabase(query.c_str(), query.length(), retryOnFailureOnce);
|
||||
auto r = QueryDatabase(query.c_str(), query.length(), retryOnFailureOnce);
|
||||
return r;
|
||||
}
|
||||
|
||||
bool DBcore::DoesTableExist(std::string table_name)
|
||||
@@ -95,18 +92,16 @@ MySQLRequestResult DBcore::QueryDatabase(const char *query, uint32 querylen, boo
|
||||
BenchTimer timer;
|
||||
timer.reset();
|
||||
|
||||
LockMutex lock(&MDatabase);
|
||||
LockMutex lock(m_mutex);
|
||||
|
||||
// Reconnect if we are not connected before hand.
|
||||
if (pStatus != Connected) {
|
||||
Open();
|
||||
}
|
||||
|
||||
|
||||
|
||||
// request query. != 0 indicates some kind of error.
|
||||
if (mysql_real_query(&mysql, query, querylen) != 0) {
|
||||
unsigned int errorNumber = mysql_errno(&mysql);
|
||||
if (mysql_real_query(mysql, query, querylen) != 0) {
|
||||
unsigned int errorNumber = mysql_errno(mysql);
|
||||
|
||||
if (errorNumber == CR_SERVER_GONE_ERROR) {
|
||||
pStatus = Error;
|
||||
@@ -130,26 +125,26 @@ MySQLRequestResult DBcore::QueryDatabase(const char *query, uint32 querylen, boo
|
||||
|
||||
auto errorBuffer = new char[MYSQL_ERRMSG_SIZE];
|
||||
|
||||
snprintf(errorBuffer, MYSQL_ERRMSG_SIZE, "#%i: %s", mysql_errno(&mysql), mysql_error(&mysql));
|
||||
snprintf(errorBuffer, MYSQL_ERRMSG_SIZE, "#%i: %s", mysql_errno(mysql), mysql_error(mysql));
|
||||
|
||||
return MySQLRequestResult(nullptr, 0, 0, 0, 0, (uint32) mysql_errno(&mysql), errorBuffer);
|
||||
return MySQLRequestResult(nullptr, 0, 0, 0, 0, (uint32) mysql_errno(mysql), errorBuffer);
|
||||
}
|
||||
|
||||
auto errorBuffer = new char[MYSQL_ERRMSG_SIZE];
|
||||
snprintf(errorBuffer, MYSQL_ERRMSG_SIZE, "#%i: %s", mysql_errno(&mysql), mysql_error(&mysql));
|
||||
snprintf(errorBuffer, MYSQL_ERRMSG_SIZE, "#%i: %s", mysql_errno(mysql), mysql_error(mysql));
|
||||
|
||||
/**
|
||||
* Error logging
|
||||
*/
|
||||
if (mysql_errno(&mysql) > 0 && strlen(query) > 0) {
|
||||
LogMySQLError("[{}] [{}]\n[{}]", mysql_errno(&mysql), mysql_error(&mysql), query);
|
||||
if (mysql_errno(mysql) > 0 && strlen(query) > 0) {
|
||||
LogMySQLError("[{}] [{}]\n[{}]", mysql_errno(mysql), mysql_error(mysql), query);
|
||||
}
|
||||
|
||||
return MySQLRequestResult(nullptr, 0, 0, 0, 0, mysql_errno(&mysql), errorBuffer);
|
||||
return MySQLRequestResult(nullptr, 0, 0, 0, 0, mysql_errno(mysql), errorBuffer);
|
||||
}
|
||||
|
||||
// successful query. get results.
|
||||
MYSQL_RES *res = mysql_store_result(&mysql);
|
||||
MYSQL_RES *res = mysql_store_result(mysql);
|
||||
uint32 rowCount = 0;
|
||||
|
||||
if (res != nullptr) {
|
||||
@@ -158,10 +153,10 @@ MySQLRequestResult DBcore::QueryDatabase(const char *query, uint32 querylen, boo
|
||||
|
||||
MySQLRequestResult requestResult(
|
||||
res,
|
||||
(uint32) mysql_affected_rows(&mysql),
|
||||
(uint32) mysql_affected_rows(mysql),
|
||||
rowCount,
|
||||
(uint32) mysql_field_count(&mysql),
|
||||
(uint32) mysql_insert_id(&mysql)
|
||||
(uint32) mysql_field_count(mysql),
|
||||
(uint32) mysql_insert_id(mysql)
|
||||
);
|
||||
|
||||
if (LogSys.log_settings[Logs::MySQLQuery].is_category_enabled == 1) {
|
||||
@@ -207,7 +202,7 @@ uint32 DBcore::DoEscapeString(char *tobuf, const char *frombuf, uint32 fromlen)
|
||||
{
|
||||
// No good reason to lock the DB, we only need it in the first place to check char encoding.
|
||||
// LockMutex lock(&MDatabase);
|
||||
return mysql_real_escape_string(&mysql, tobuf, frombuf, fromlen);
|
||||
return mysql_real_escape_string(mysql, tobuf, frombuf, fromlen);
|
||||
}
|
||||
|
||||
bool DBcore::Open(
|
||||
@@ -222,7 +217,7 @@ bool DBcore::Open(
|
||||
bool iSSL
|
||||
)
|
||||
{
|
||||
LockMutex lock(&MDatabase);
|
||||
LockMutex lock(m_mutex);
|
||||
safe_delete_array(pHost);
|
||||
safe_delete_array(pUser);
|
||||
safe_delete_array(pPassword);
|
||||
@@ -242,13 +237,13 @@ bool DBcore::Open(uint32 *errnum, char *errbuf)
|
||||
if (errbuf) {
|
||||
errbuf[0] = 0;
|
||||
}
|
||||
LockMutex lock(&MDatabase);
|
||||
LockMutex lock(m_mutex);
|
||||
if (GetStatus() == Connected) {
|
||||
return true;
|
||||
}
|
||||
if (GetStatus() == Error) {
|
||||
mysql_close(&mysql);
|
||||
mysql_init(&mysql); // Initialize structure again
|
||||
mysql_close(mysql);
|
||||
mysql_init(mysql); // Initialize structure again
|
||||
}
|
||||
if (!pHost) {
|
||||
return false;
|
||||
@@ -265,7 +260,7 @@ bool DBcore::Open(uint32 *errnum, char *errbuf)
|
||||
if (pSSL) {
|
||||
flags |= CLIENT_SSL;
|
||||
}
|
||||
if (mysql_real_connect(&mysql, pHost, pUser, pPassword, pDatabase, pPort, 0, flags)) {
|
||||
if (mysql_real_connect(mysql, pHost, pUser, pPassword, pDatabase, pPort, 0, flags)) {
|
||||
pStatus = Connected;
|
||||
|
||||
std::string connected_origin_host = pHost;
|
||||
@@ -275,21 +270,16 @@ bool DBcore::Open(uint32 *errnum, char *errbuf)
|
||||
}
|
||||
else {
|
||||
if (errnum) {
|
||||
*errnum = mysql_errno(&mysql);
|
||||
*errnum = mysql_errno(mysql);
|
||||
}
|
||||
if (errbuf) {
|
||||
snprintf(errbuf, MYSQL_ERRMSG_SIZE, "#%i: %s", mysql_errno(&mysql), mysql_error(&mysql));
|
||||
snprintf(errbuf, MYSQL_ERRMSG_SIZE, "#%i: %s", mysql_errno(mysql), mysql_error(mysql));
|
||||
}
|
||||
pStatus = Error;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void DBcore::SetMysql(MYSQL *mysql)
|
||||
{
|
||||
DBcore::mysql = *mysql;
|
||||
}
|
||||
|
||||
const std::string &DBcore::GetOriginHost() const
|
||||
{
|
||||
return origin_host;
|
||||
@@ -299,3 +289,19 @@ void DBcore::SetOriginHost(const std::string &origin_host)
|
||||
{
|
||||
DBcore::origin_host = origin_host;
|
||||
}
|
||||
|
||||
std::string DBcore::Escape(const std::string& s)
|
||||
{
|
||||
const std::size_t s_len = s.length();
|
||||
std::vector<char> temp((s_len * 2) + 1, '\0');
|
||||
mysql_real_escape_string(mysql, temp.data(), s.c_str(), s_len);
|
||||
|
||||
return temp.data();
|
||||
}
|
||||
|
||||
void DBcore::SetMutex(Mutex *mutex)
|
||||
{
|
||||
safe_delete(m_mutex);
|
||||
|
||||
DBcore::m_mutex = mutex;
|
||||
}
|
||||
|
||||
+14
-4
@@ -12,6 +12,7 @@
|
||||
|
||||
#include <mysql.h>
|
||||
#include <string.h>
|
||||
#include <mutex>
|
||||
|
||||
class DBcore {
|
||||
public:
|
||||
@@ -27,16 +28,22 @@ public:
|
||||
void TransactionBegin();
|
||||
void TransactionCommit();
|
||||
void TransactionRollback();
|
||||
std::string Escape(const std::string& s);
|
||||
uint32 DoEscapeString(char *tobuf, const char *frombuf, uint32 fromlen);
|
||||
void ping();
|
||||
MYSQL *getMySQL() { return &mysql; }
|
||||
void SetMysql(MYSQL *mysql);
|
||||
|
||||
const std::string &GetOriginHost() const;
|
||||
void SetOriginHost(const std::string &origin_host);
|
||||
|
||||
bool DoesTableExist(std::string table_name);
|
||||
|
||||
void SetMySQL(const DBcore &o)
|
||||
{
|
||||
mysql = o.mysql;
|
||||
mysqlOwner = false;
|
||||
}
|
||||
void SetMutex(Mutex *mutex);
|
||||
|
||||
protected:
|
||||
bool Open(
|
||||
const char *iHost,
|
||||
@@ -53,10 +60,13 @@ protected:
|
||||
private:
|
||||
bool Open(uint32 *errnum = nullptr, char *errbuf = nullptr);
|
||||
|
||||
MYSQL mysql;
|
||||
Mutex MDatabase;
|
||||
MYSQL* mysql;
|
||||
bool mysqlOwner;
|
||||
Mutex *m_mutex;
|
||||
eStatus pStatus;
|
||||
|
||||
std::mutex m_query_lock{};
|
||||
|
||||
std::string origin_host;
|
||||
|
||||
char *pHost;
|
||||
|
||||
@@ -113,7 +113,9 @@ bool PlayerEventLogs::IsEventEnabled(PlayerEvent::EventType event)
|
||||
// this processes any current player events on the queue
|
||||
void PlayerEventLogs::ProcessBatchQueue()
|
||||
{
|
||||
m_batch_queue_lock.lock();
|
||||
if (m_record_batch_queue.empty()) {
|
||||
m_batch_queue_lock.unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -128,7 +130,6 @@ void PlayerEventLogs::ProcessBatchQueue()
|
||||
);
|
||||
|
||||
// empty
|
||||
m_batch_queue_lock.lock();
|
||||
m_record_batch_queue = {};
|
||||
m_batch_queue_lock.unlock();
|
||||
}
|
||||
@@ -602,10 +603,10 @@ std::string PlayerEventLogs::GetDiscordPayloadFromEvent(const PlayerEvent::Playe
|
||||
return payload;
|
||||
}
|
||||
|
||||
// general process function, used in world or UCS depending on rule Logging:PlayerEventsQSProcess
|
||||
// general process function, used in world or QS depending on rule Logging:PlayerEventsQSProcess
|
||||
void PlayerEventLogs::Process()
|
||||
{
|
||||
if (m_process_batch_events_timer.Check()) {
|
||||
if (m_process_batch_events_timer.Check() || m_record_batch_queue.size() >= RuleI(Logging, BatchPlayerEventProcessChunkSize)) {
|
||||
ProcessBatchQueue();
|
||||
}
|
||||
|
||||
@@ -699,6 +700,7 @@ void PlayerEventLogs::SetSettingsDefaults()
|
||||
m_settings[PlayerEvent::KILLED_NPC].event_enabled = 0;
|
||||
m_settings[PlayerEvent::KILLED_NAMED_NPC].event_enabled = 1;
|
||||
m_settings[PlayerEvent::KILLED_RAID_NPC].event_enabled = 1;
|
||||
m_settings[PlayerEvent::ITEM_CREATION].event_enabled = 1;
|
||||
|
||||
for (int i = PlayerEvent::GM_COMMAND; i != PlayerEvent::MAX; i++) {
|
||||
m_settings[i].retention_days = RETENTION_DAYS_DEFAULT;
|
||||
|
||||
@@ -55,6 +55,7 @@ namespace PlayerEvent {
|
||||
KILLED_NPC,
|
||||
KILLED_NAMED_NPC,
|
||||
KILLED_RAID_NPC,
|
||||
ITEM_CREATION,
|
||||
MAX // dont remove
|
||||
};
|
||||
|
||||
@@ -110,7 +111,8 @@ namespace PlayerEvent {
|
||||
"Possible Hack",
|
||||
"Killed NPC",
|
||||
"Killed Named NPC",
|
||||
"Killed Raid NPC"
|
||||
"Killed Raid NPC",
|
||||
"Item Creation"
|
||||
};
|
||||
|
||||
// Generic struct used by all events
|
||||
@@ -184,6 +186,40 @@ namespace PlayerEvent {
|
||||
}
|
||||
};
|
||||
|
||||
// used in Trade event
|
||||
struct ItemCreationEvent {
|
||||
int64 item_id;
|
||||
std::string item_name;
|
||||
uint16 to_slot;
|
||||
int16 charges;
|
||||
uint32 aug1;
|
||||
uint32 aug2;
|
||||
uint32 aug3;
|
||||
uint32 aug4;
|
||||
uint32 aug5;
|
||||
uint32 aug6;
|
||||
bool attuned;
|
||||
|
||||
// cereal
|
||||
template<class Archive>
|
||||
void serialize(Archive &ar)
|
||||
{
|
||||
ar(
|
||||
CEREAL_NVP(item_id),
|
||||
CEREAL_NVP(item_name),
|
||||
CEREAL_NVP(to_slot),
|
||||
CEREAL_NVP(charges),
|
||||
CEREAL_NVP(aug1),
|
||||
CEREAL_NVP(aug2),
|
||||
CEREAL_NVP(aug3),
|
||||
CEREAL_NVP(aug4),
|
||||
CEREAL_NVP(aug5),
|
||||
CEREAL_NVP(aug6),
|
||||
CEREAL_NVP(attuned)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// used in Trade event
|
||||
struct TradeItem {
|
||||
int64 item_id;
|
||||
|
||||
+1
-1
@@ -132,7 +132,7 @@ enum { //reuse times
|
||||
InstillDoubtReuseTime = 9,
|
||||
FishingReuseTime = 11,
|
||||
ForagingReuseTime = 50,
|
||||
MendReuseTime = 290,
|
||||
MendReuseTime = 360,
|
||||
BashReuseTime = 5,
|
||||
BackstabReuseTime = 9,
|
||||
KickReuseTime = 5,
|
||||
|
||||
@@ -69,6 +69,7 @@ public:
|
||||
int32_t expansion_bitmask;
|
||||
uint8_t enforce_spell_settings;
|
||||
uint8_t archery_setting;
|
||||
uint32_t caster_range;
|
||||
};
|
||||
|
||||
static std::string PrimaryKey()
|
||||
@@ -129,6 +130,7 @@ public:
|
||||
"expansion_bitmask",
|
||||
"enforce_spell_settings",
|
||||
"archery_setting",
|
||||
"caster_range",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -185,6 +187,7 @@ public:
|
||||
"expansion_bitmask",
|
||||
"enforce_spell_settings",
|
||||
"archery_setting",
|
||||
"caster_range",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -275,6 +278,7 @@ public:
|
||||
e.expansion_bitmask = -1;
|
||||
e.enforce_spell_settings = 0;
|
||||
e.archery_setting = 0;
|
||||
e.caster_range = 0;
|
||||
|
||||
return e;
|
||||
}
|
||||
@@ -361,6 +365,7 @@ public:
|
||||
e.expansion_bitmask = static_cast<int32_t>(atoi(row[47]));
|
||||
e.enforce_spell_settings = static_cast<uint8_t>(strtoul(row[48], nullptr, 10));
|
||||
e.archery_setting = static_cast<uint8_t>(strtoul(row[49], nullptr, 10));
|
||||
e.caster_range = static_cast<uint32_t>(strtoul(row[50], nullptr, 10));
|
||||
|
||||
return e;
|
||||
}
|
||||
@@ -443,6 +448,7 @@ public:
|
||||
v.push_back(columns[47] + " = " + std::to_string(e.expansion_bitmask));
|
||||
v.push_back(columns[48] + " = " + std::to_string(e.enforce_spell_settings));
|
||||
v.push_back(columns[49] + " = " + std::to_string(e.archery_setting));
|
||||
v.push_back(columns[50] + " = " + std::to_string(e.caster_range));
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
@@ -514,6 +520,7 @@ public:
|
||||
v.push_back(std::to_string(e.expansion_bitmask));
|
||||
v.push_back(std::to_string(e.enforce_spell_settings));
|
||||
v.push_back(std::to_string(e.archery_setting));
|
||||
v.push_back(std::to_string(e.caster_range));
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
@@ -593,6 +600,7 @@ public:
|
||||
v.push_back(std::to_string(e.expansion_bitmask));
|
||||
v.push_back(std::to_string(e.enforce_spell_settings));
|
||||
v.push_back(std::to_string(e.archery_setting));
|
||||
v.push_back(std::to_string(e.caster_range));
|
||||
|
||||
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
|
||||
}
|
||||
@@ -676,6 +684,7 @@ public:
|
||||
e.expansion_bitmask = static_cast<int32_t>(atoi(row[47]));
|
||||
e.enforce_spell_settings = static_cast<uint8_t>(strtoul(row[48], nullptr, 10));
|
||||
e.archery_setting = static_cast<uint8_t>(strtoul(row[49], nullptr, 10));
|
||||
e.caster_range = static_cast<uint32_t>(strtoul(row[50], nullptr, 10));
|
||||
|
||||
all_entries.push_back(e);
|
||||
}
|
||||
@@ -750,6 +759,7 @@ public:
|
||||
e.expansion_bitmask = static_cast<int32_t>(atoi(row[47]));
|
||||
e.enforce_spell_settings = static_cast<uint8_t>(strtoul(row[48], nullptr, 10));
|
||||
e.archery_setting = static_cast<uint8_t>(strtoul(row[49], nullptr, 10));
|
||||
e.caster_range = static_cast<uint32_t>(strtoul(row[50], nullptr, 10));
|
||||
|
||||
all_entries.push_back(e);
|
||||
}
|
||||
|
||||
@@ -16,11 +16,14 @@
|
||||
#include "../../strings.h"
|
||||
#include <ctime>
|
||||
|
||||
|
||||
class BaseNpcScaleGlobalBaseRepository {
|
||||
public:
|
||||
struct NpcScaleGlobalBase {
|
||||
int32_t type;
|
||||
int32_t level;
|
||||
uint32_t zone_id;
|
||||
int32_t instance_version;
|
||||
int32_t ac;
|
||||
int32_t hp;
|
||||
int32_t accuracy;
|
||||
@@ -59,6 +62,8 @@ public:
|
||||
return {
|
||||
"type",
|
||||
"level",
|
||||
"zone_id",
|
||||
"instance_version",
|
||||
"ac",
|
||||
"hp",
|
||||
"accuracy",
|
||||
@@ -93,6 +98,8 @@ public:
|
||||
return {
|
||||
"type",
|
||||
"level",
|
||||
"zone_id",
|
||||
"instance_version",
|
||||
"ac",
|
||||
"hp",
|
||||
"accuracy",
|
||||
@@ -161,6 +168,8 @@ public:
|
||||
|
||||
e.type = 0;
|
||||
e.level = 0;
|
||||
e.zone_id = 0;
|
||||
e.instance_version = -1;
|
||||
e.ac = 0;
|
||||
e.hp = 0;
|
||||
e.accuracy = 0;
|
||||
@@ -212,8 +221,9 @@ public:
|
||||
{
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
"{} WHERE id = {} LIMIT 1",
|
||||
"{} WHERE {} = {} LIMIT 1",
|
||||
BaseSelect(),
|
||||
PrimaryKey(),
|
||||
npc_scale_global_base_id
|
||||
)
|
||||
);
|
||||
@@ -224,32 +234,34 @@ public:
|
||||
|
||||
e.type = static_cast<int32_t>(atoi(row[0]));
|
||||
e.level = static_cast<int32_t>(atoi(row[1]));
|
||||
e.ac = static_cast<int32_t>(atoi(row[2]));
|
||||
e.hp = static_cast<int32_t>(atoi(row[3]));
|
||||
e.accuracy = static_cast<int32_t>(atoi(row[4]));
|
||||
e.slow_mitigation = static_cast<int32_t>(atoi(row[5]));
|
||||
e.attack = static_cast<int32_t>(atoi(row[6]));
|
||||
e.strength = static_cast<int32_t>(atoi(row[7]));
|
||||
e.stamina = static_cast<int32_t>(atoi(row[8]));
|
||||
e.dexterity = static_cast<int32_t>(atoi(row[9]));
|
||||
e.agility = static_cast<int32_t>(atoi(row[10]));
|
||||
e.intelligence = static_cast<int32_t>(atoi(row[11]));
|
||||
e.wisdom = static_cast<int32_t>(atoi(row[12]));
|
||||
e.charisma = static_cast<int32_t>(atoi(row[13]));
|
||||
e.magic_resist = static_cast<int32_t>(atoi(row[14]));
|
||||
e.cold_resist = static_cast<int32_t>(atoi(row[15]));
|
||||
e.fire_resist = static_cast<int32_t>(atoi(row[16]));
|
||||
e.poison_resist = static_cast<int32_t>(atoi(row[17]));
|
||||
e.disease_resist = static_cast<int32_t>(atoi(row[18]));
|
||||
e.corruption_resist = static_cast<int32_t>(atoi(row[19]));
|
||||
e.physical_resist = static_cast<int32_t>(atoi(row[20]));
|
||||
e.min_dmg = static_cast<int32_t>(atoi(row[21]));
|
||||
e.max_dmg = static_cast<int32_t>(atoi(row[22]));
|
||||
e.hp_regen_rate = static_cast<int32_t>(atoi(row[23]));
|
||||
e.attack_delay = static_cast<int32_t>(atoi(row[24]));
|
||||
e.spell_scale = static_cast<int32_t>(atoi(row[25]));
|
||||
e.heal_scale = static_cast<int32_t>(atoi(row[26]));
|
||||
e.special_abilities = row[27] ? row[27] : "";
|
||||
e.zone_id = static_cast<uint32_t>(strtoul(row[2], nullptr, 10));
|
||||
e.instance_version = static_cast<int32_t>(atoi(row[3]));
|
||||
e.ac = static_cast<int32_t>(atoi(row[4]));
|
||||
e.hp = static_cast<int32_t>(atoi(row[5]));
|
||||
e.accuracy = static_cast<int32_t>(atoi(row[6]));
|
||||
e.slow_mitigation = static_cast<int32_t>(atoi(row[7]));
|
||||
e.attack = static_cast<int32_t>(atoi(row[8]));
|
||||
e.strength = static_cast<int32_t>(atoi(row[9]));
|
||||
e.stamina = static_cast<int32_t>(atoi(row[10]));
|
||||
e.dexterity = static_cast<int32_t>(atoi(row[11]));
|
||||
e.agility = static_cast<int32_t>(atoi(row[12]));
|
||||
e.intelligence = static_cast<int32_t>(atoi(row[13]));
|
||||
e.wisdom = static_cast<int32_t>(atoi(row[14]));
|
||||
e.charisma = static_cast<int32_t>(atoi(row[15]));
|
||||
e.magic_resist = static_cast<int32_t>(atoi(row[16]));
|
||||
e.cold_resist = static_cast<int32_t>(atoi(row[17]));
|
||||
e.fire_resist = static_cast<int32_t>(atoi(row[18]));
|
||||
e.poison_resist = static_cast<int32_t>(atoi(row[19]));
|
||||
e.disease_resist = static_cast<int32_t>(atoi(row[20]));
|
||||
e.corruption_resist = static_cast<int32_t>(atoi(row[21]));
|
||||
e.physical_resist = static_cast<int32_t>(atoi(row[22]));
|
||||
e.min_dmg = static_cast<int32_t>(atoi(row[23]));
|
||||
e.max_dmg = static_cast<int32_t>(atoi(row[24]));
|
||||
e.hp_regen_rate = static_cast<int32_t>(atoi(row[25]));
|
||||
e.attack_delay = static_cast<int32_t>(atoi(row[26]));
|
||||
e.spell_scale = static_cast<int32_t>(atoi(row[27]));
|
||||
e.heal_scale = static_cast<int32_t>(atoi(row[28]));
|
||||
e.special_abilities = row[29] ? row[29] : "";
|
||||
|
||||
return e;
|
||||
}
|
||||
@@ -285,32 +297,34 @@ public:
|
||||
|
||||
v.push_back(columns[0] + " = " + std::to_string(e.type));
|
||||
v.push_back(columns[1] + " = " + std::to_string(e.level));
|
||||
v.push_back(columns[2] + " = " + std::to_string(e.ac));
|
||||
v.push_back(columns[3] + " = " + std::to_string(e.hp));
|
||||
v.push_back(columns[4] + " = " + std::to_string(e.accuracy));
|
||||
v.push_back(columns[5] + " = " + std::to_string(e.slow_mitigation));
|
||||
v.push_back(columns[6] + " = " + std::to_string(e.attack));
|
||||
v.push_back(columns[7] + " = " + std::to_string(e.strength));
|
||||
v.push_back(columns[8] + " = " + std::to_string(e.stamina));
|
||||
v.push_back(columns[9] + " = " + std::to_string(e.dexterity));
|
||||
v.push_back(columns[10] + " = " + std::to_string(e.agility));
|
||||
v.push_back(columns[11] + " = " + std::to_string(e.intelligence));
|
||||
v.push_back(columns[12] + " = " + std::to_string(e.wisdom));
|
||||
v.push_back(columns[13] + " = " + std::to_string(e.charisma));
|
||||
v.push_back(columns[14] + " = " + std::to_string(e.magic_resist));
|
||||
v.push_back(columns[15] + " = " + std::to_string(e.cold_resist));
|
||||
v.push_back(columns[16] + " = " + std::to_string(e.fire_resist));
|
||||
v.push_back(columns[17] + " = " + std::to_string(e.poison_resist));
|
||||
v.push_back(columns[18] + " = " + std::to_string(e.disease_resist));
|
||||
v.push_back(columns[19] + " = " + std::to_string(e.corruption_resist));
|
||||
v.push_back(columns[20] + " = " + std::to_string(e.physical_resist));
|
||||
v.push_back(columns[21] + " = " + std::to_string(e.min_dmg));
|
||||
v.push_back(columns[22] + " = " + std::to_string(e.max_dmg));
|
||||
v.push_back(columns[23] + " = " + std::to_string(e.hp_regen_rate));
|
||||
v.push_back(columns[24] + " = " + std::to_string(e.attack_delay));
|
||||
v.push_back(columns[25] + " = " + std::to_string(e.spell_scale));
|
||||
v.push_back(columns[26] + " = " + std::to_string(e.heal_scale));
|
||||
v.push_back(columns[27] + " = '" + Strings::Escape(e.special_abilities) + "'");
|
||||
v.push_back(columns[2] + " = " + std::to_string(e.zone_id));
|
||||
v.push_back(columns[3] + " = " + std::to_string(e.instance_version));
|
||||
v.push_back(columns[4] + " = " + std::to_string(e.ac));
|
||||
v.push_back(columns[5] + " = " + std::to_string(e.hp));
|
||||
v.push_back(columns[6] + " = " + std::to_string(e.accuracy));
|
||||
v.push_back(columns[7] + " = " + std::to_string(e.slow_mitigation));
|
||||
v.push_back(columns[8] + " = " + std::to_string(e.attack));
|
||||
v.push_back(columns[9] + " = " + std::to_string(e.strength));
|
||||
v.push_back(columns[10] + " = " + std::to_string(e.stamina));
|
||||
v.push_back(columns[11] + " = " + std::to_string(e.dexterity));
|
||||
v.push_back(columns[12] + " = " + std::to_string(e.agility));
|
||||
v.push_back(columns[13] + " = " + std::to_string(e.intelligence));
|
||||
v.push_back(columns[14] + " = " + std::to_string(e.wisdom));
|
||||
v.push_back(columns[15] + " = " + std::to_string(e.charisma));
|
||||
v.push_back(columns[16] + " = " + std::to_string(e.magic_resist));
|
||||
v.push_back(columns[17] + " = " + std::to_string(e.cold_resist));
|
||||
v.push_back(columns[18] + " = " + std::to_string(e.fire_resist));
|
||||
v.push_back(columns[19] + " = " + std::to_string(e.poison_resist));
|
||||
v.push_back(columns[20] + " = " + std::to_string(e.disease_resist));
|
||||
v.push_back(columns[21] + " = " + std::to_string(e.corruption_resist));
|
||||
v.push_back(columns[22] + " = " + std::to_string(e.physical_resist));
|
||||
v.push_back(columns[23] + " = " + std::to_string(e.min_dmg));
|
||||
v.push_back(columns[24] + " = " + std::to_string(e.max_dmg));
|
||||
v.push_back(columns[25] + " = " + std::to_string(e.hp_regen_rate));
|
||||
v.push_back(columns[26] + " = " + std::to_string(e.attack_delay));
|
||||
v.push_back(columns[27] + " = " + std::to_string(e.spell_scale));
|
||||
v.push_back(columns[28] + " = " + std::to_string(e.heal_scale));
|
||||
v.push_back(columns[29] + " = '" + Strings::Escape(e.special_abilities) + "'");
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
@@ -334,6 +348,8 @@ public:
|
||||
|
||||
v.push_back(std::to_string(e.type));
|
||||
v.push_back(std::to_string(e.level));
|
||||
v.push_back(std::to_string(e.zone_id));
|
||||
v.push_back(std::to_string(e.instance_version));
|
||||
v.push_back(std::to_string(e.ac));
|
||||
v.push_back(std::to_string(e.hp));
|
||||
v.push_back(std::to_string(e.accuracy));
|
||||
@@ -391,6 +407,8 @@ public:
|
||||
|
||||
v.push_back(std::to_string(e.type));
|
||||
v.push_back(std::to_string(e.level));
|
||||
v.push_back(std::to_string(e.zone_id));
|
||||
v.push_back(std::to_string(e.instance_version));
|
||||
v.push_back(std::to_string(e.ac));
|
||||
v.push_back(std::to_string(e.hp));
|
||||
v.push_back(std::to_string(e.accuracy));
|
||||
@@ -452,32 +470,34 @@ public:
|
||||
|
||||
e.type = static_cast<int32_t>(atoi(row[0]));
|
||||
e.level = static_cast<int32_t>(atoi(row[1]));
|
||||
e.ac = static_cast<int32_t>(atoi(row[2]));
|
||||
e.hp = static_cast<int32_t>(atoi(row[3]));
|
||||
e.accuracy = static_cast<int32_t>(atoi(row[4]));
|
||||
e.slow_mitigation = static_cast<int32_t>(atoi(row[5]));
|
||||
e.attack = static_cast<int32_t>(atoi(row[6]));
|
||||
e.strength = static_cast<int32_t>(atoi(row[7]));
|
||||
e.stamina = static_cast<int32_t>(atoi(row[8]));
|
||||
e.dexterity = static_cast<int32_t>(atoi(row[9]));
|
||||
e.agility = static_cast<int32_t>(atoi(row[10]));
|
||||
e.intelligence = static_cast<int32_t>(atoi(row[11]));
|
||||
e.wisdom = static_cast<int32_t>(atoi(row[12]));
|
||||
e.charisma = static_cast<int32_t>(atoi(row[13]));
|
||||
e.magic_resist = static_cast<int32_t>(atoi(row[14]));
|
||||
e.cold_resist = static_cast<int32_t>(atoi(row[15]));
|
||||
e.fire_resist = static_cast<int32_t>(atoi(row[16]));
|
||||
e.poison_resist = static_cast<int32_t>(atoi(row[17]));
|
||||
e.disease_resist = static_cast<int32_t>(atoi(row[18]));
|
||||
e.corruption_resist = static_cast<int32_t>(atoi(row[19]));
|
||||
e.physical_resist = static_cast<int32_t>(atoi(row[20]));
|
||||
e.min_dmg = static_cast<int32_t>(atoi(row[21]));
|
||||
e.max_dmg = static_cast<int32_t>(atoi(row[22]));
|
||||
e.hp_regen_rate = static_cast<int32_t>(atoi(row[23]));
|
||||
e.attack_delay = static_cast<int32_t>(atoi(row[24]));
|
||||
e.spell_scale = static_cast<int32_t>(atoi(row[25]));
|
||||
e.heal_scale = static_cast<int32_t>(atoi(row[26]));
|
||||
e.special_abilities = row[27] ? row[27] : "";
|
||||
e.zone_id = static_cast<uint32_t>(strtoul(row[2], nullptr, 10));
|
||||
e.instance_version = static_cast<int32_t>(atoi(row[3]));
|
||||
e.ac = static_cast<int32_t>(atoi(row[4]));
|
||||
e.hp = static_cast<int32_t>(atoi(row[5]));
|
||||
e.accuracy = static_cast<int32_t>(atoi(row[6]));
|
||||
e.slow_mitigation = static_cast<int32_t>(atoi(row[7]));
|
||||
e.attack = static_cast<int32_t>(atoi(row[8]));
|
||||
e.strength = static_cast<int32_t>(atoi(row[9]));
|
||||
e.stamina = static_cast<int32_t>(atoi(row[10]));
|
||||
e.dexterity = static_cast<int32_t>(atoi(row[11]));
|
||||
e.agility = static_cast<int32_t>(atoi(row[12]));
|
||||
e.intelligence = static_cast<int32_t>(atoi(row[13]));
|
||||
e.wisdom = static_cast<int32_t>(atoi(row[14]));
|
||||
e.charisma = static_cast<int32_t>(atoi(row[15]));
|
||||
e.magic_resist = static_cast<int32_t>(atoi(row[16]));
|
||||
e.cold_resist = static_cast<int32_t>(atoi(row[17]));
|
||||
e.fire_resist = static_cast<int32_t>(atoi(row[18]));
|
||||
e.poison_resist = static_cast<int32_t>(atoi(row[19]));
|
||||
e.disease_resist = static_cast<int32_t>(atoi(row[20]));
|
||||
e.corruption_resist = static_cast<int32_t>(atoi(row[21]));
|
||||
e.physical_resist = static_cast<int32_t>(atoi(row[22]));
|
||||
e.min_dmg = static_cast<int32_t>(atoi(row[23]));
|
||||
e.max_dmg = static_cast<int32_t>(atoi(row[24]));
|
||||
e.hp_regen_rate = static_cast<int32_t>(atoi(row[25]));
|
||||
e.attack_delay = static_cast<int32_t>(atoi(row[26]));
|
||||
e.spell_scale = static_cast<int32_t>(atoi(row[27]));
|
||||
e.heal_scale = static_cast<int32_t>(atoi(row[28]));
|
||||
e.special_abilities = row[29] ? row[29] : "";
|
||||
|
||||
all_entries.push_back(e);
|
||||
}
|
||||
@@ -504,32 +524,34 @@ public:
|
||||
|
||||
e.type = static_cast<int32_t>(atoi(row[0]));
|
||||
e.level = static_cast<int32_t>(atoi(row[1]));
|
||||
e.ac = static_cast<int32_t>(atoi(row[2]));
|
||||
e.hp = static_cast<int32_t>(atoi(row[3]));
|
||||
e.accuracy = static_cast<int32_t>(atoi(row[4]));
|
||||
e.slow_mitigation = static_cast<int32_t>(atoi(row[5]));
|
||||
e.attack = static_cast<int32_t>(atoi(row[6]));
|
||||
e.strength = static_cast<int32_t>(atoi(row[7]));
|
||||
e.stamina = static_cast<int32_t>(atoi(row[8]));
|
||||
e.dexterity = static_cast<int32_t>(atoi(row[9]));
|
||||
e.agility = static_cast<int32_t>(atoi(row[10]));
|
||||
e.intelligence = static_cast<int32_t>(atoi(row[11]));
|
||||
e.wisdom = static_cast<int32_t>(atoi(row[12]));
|
||||
e.charisma = static_cast<int32_t>(atoi(row[13]));
|
||||
e.magic_resist = static_cast<int32_t>(atoi(row[14]));
|
||||
e.cold_resist = static_cast<int32_t>(atoi(row[15]));
|
||||
e.fire_resist = static_cast<int32_t>(atoi(row[16]));
|
||||
e.poison_resist = static_cast<int32_t>(atoi(row[17]));
|
||||
e.disease_resist = static_cast<int32_t>(atoi(row[18]));
|
||||
e.corruption_resist = static_cast<int32_t>(atoi(row[19]));
|
||||
e.physical_resist = static_cast<int32_t>(atoi(row[20]));
|
||||
e.min_dmg = static_cast<int32_t>(atoi(row[21]));
|
||||
e.max_dmg = static_cast<int32_t>(atoi(row[22]));
|
||||
e.hp_regen_rate = static_cast<int32_t>(atoi(row[23]));
|
||||
e.attack_delay = static_cast<int32_t>(atoi(row[24]));
|
||||
e.spell_scale = static_cast<int32_t>(atoi(row[25]));
|
||||
e.heal_scale = static_cast<int32_t>(atoi(row[26]));
|
||||
e.special_abilities = row[27] ? row[27] : "";
|
||||
e.zone_id = static_cast<uint32_t>(strtoul(row[2], nullptr, 10));
|
||||
e.instance_version = static_cast<int32_t>(atoi(row[3]));
|
||||
e.ac = static_cast<int32_t>(atoi(row[4]));
|
||||
e.hp = static_cast<int32_t>(atoi(row[5]));
|
||||
e.accuracy = static_cast<int32_t>(atoi(row[6]));
|
||||
e.slow_mitigation = static_cast<int32_t>(atoi(row[7]));
|
||||
e.attack = static_cast<int32_t>(atoi(row[8]));
|
||||
e.strength = static_cast<int32_t>(atoi(row[9]));
|
||||
e.stamina = static_cast<int32_t>(atoi(row[10]));
|
||||
e.dexterity = static_cast<int32_t>(atoi(row[11]));
|
||||
e.agility = static_cast<int32_t>(atoi(row[12]));
|
||||
e.intelligence = static_cast<int32_t>(atoi(row[13]));
|
||||
e.wisdom = static_cast<int32_t>(atoi(row[14]));
|
||||
e.charisma = static_cast<int32_t>(atoi(row[15]));
|
||||
e.magic_resist = static_cast<int32_t>(atoi(row[16]));
|
||||
e.cold_resist = static_cast<int32_t>(atoi(row[17]));
|
||||
e.fire_resist = static_cast<int32_t>(atoi(row[18]));
|
||||
e.poison_resist = static_cast<int32_t>(atoi(row[19]));
|
||||
e.disease_resist = static_cast<int32_t>(atoi(row[20]));
|
||||
e.corruption_resist = static_cast<int32_t>(atoi(row[21]));
|
||||
e.physical_resist = static_cast<int32_t>(atoi(row[22]));
|
||||
e.min_dmg = static_cast<int32_t>(atoi(row[23]));
|
||||
e.max_dmg = static_cast<int32_t>(atoi(row[24]));
|
||||
e.hp_regen_rate = static_cast<int32_t>(atoi(row[25]));
|
||||
e.attack_delay = static_cast<int32_t>(atoi(row[26]));
|
||||
e.spell_scale = static_cast<int32_t>(atoi(row[27]));
|
||||
e.heal_scale = static_cast<int32_t>(atoi(row[28]));
|
||||
e.special_abilities = row[29] ? row[29] : "";
|
||||
|
||||
all_entries.push_back(e);
|
||||
}
|
||||
|
||||
@@ -240,8 +240,8 @@ public:
|
||||
v.push_back(columns[7] + " = " + std::to_string(e.z));
|
||||
v.push_back(columns[8] + " = " + std::to_string(e.heading));
|
||||
v.push_back(columns[9] + " = " + std::to_string(e.event_type_id));
|
||||
v.push_back(columns[10] + " = '" + Strings::Escape(e.event_type_name) + "'");
|
||||
v.push_back(columns[11] + " = '" + Strings::Escape(e.event_data) + "'");
|
||||
v.push_back(columns[10] + " = '" + db.Escape(e.event_type_name) + "'");
|
||||
v.push_back(columns[11] + " = '" + db.Escape(e.event_data) + "'");
|
||||
v.push_back(columns[12] + " = FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")");
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
@@ -274,8 +274,8 @@ public:
|
||||
v.push_back(std::to_string(e.z));
|
||||
v.push_back(std::to_string(e.heading));
|
||||
v.push_back(std::to_string(e.event_type_id));
|
||||
v.push_back("'" + Strings::Escape(e.event_type_name) + "'");
|
||||
v.push_back("'" + Strings::Escape(e.event_data) + "'");
|
||||
v.push_back("'" + db.Escape(e.event_type_name) + "'");
|
||||
v.push_back("'" + db.Escape(e.event_data) + "'");
|
||||
v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")");
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
@@ -316,8 +316,8 @@ public:
|
||||
v.push_back(std::to_string(e.z));
|
||||
v.push_back(std::to_string(e.heading));
|
||||
v.push_back(std::to_string(e.event_type_id));
|
||||
v.push_back("'" + Strings::Escape(e.event_type_name) + "'");
|
||||
v.push_back("'" + Strings::Escape(e.event_data) + "'");
|
||||
v.push_back("'" + db.Escape(e.event_type_name) + "'");
|
||||
v.push_back("'" + db.Escape(e.event_data) + "'");
|
||||
v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")");
|
||||
|
||||
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
|
||||
|
||||
+1
-1
@@ -319,7 +319,6 @@ RULE_CATEGORY_END()
|
||||
RULE_CATEGORY(Map)
|
||||
RULE_BOOL(Map, FixPathingZOnSendTo, false, "Try to repair Z coordinates in the SendTo routine as well")
|
||||
RULE_BOOL(Map, FixZWhenPathing, true, "Automatically fix NPC Z coordinates when moving/pathing/engaged (Far less CPU intensive than its predecessor)")
|
||||
RULE_REAL(Map, DistanceCanTravelBeforeAdjustment, 10.0, "Distance a mob can path before FixZ is called, depends on FixZWhenPathing")
|
||||
RULE_BOOL(Map, MobZVisualDebug, false, "Displays spell effects determining whether or not NPC is hitting Best Z calcs (blue for hit, red for miss)")
|
||||
RULE_BOOL(Map, MobPathingVisualDebug, false, "Displays nodes in pathing points in realtime to help with visual debugging")
|
||||
RULE_REAL(Map, FixPathingZMaxDeltaSendTo, 20, "At runtime in SendTo: maximum change in Z to allow the BestZ code to apply")
|
||||
@@ -783,6 +782,7 @@ RULE_BOOL(Logging, PrintFileFunctionAndLine, false, "Ex: [World Server] [net.cpp
|
||||
RULE_BOOL(Logging, WorldGMSayLogging, true, "Relay worldserver logging to zone processes via GM say output")
|
||||
RULE_BOOL(Logging, PlayerEventsQSProcess, false, "Have query server process player events instead of world. Useful when wanting to use a dedicated server and database for processing player events on separate disk")
|
||||
RULE_INT(Logging, BatchPlayerEventProcessIntervalSeconds, 5, "This is the interval in which player events are processed in world or qs")
|
||||
RULE_INT(Logging, BatchPlayerEventProcessChunkSize, 10000, "This is the cap of events that can be inserted into the queue before a force flush. This is to keep from hitting MySQL max_allowed_packet and killing the connection")
|
||||
RULE_CATEGORY_END()
|
||||
|
||||
RULE_CATEGORY(HotReload)
|
||||
|
||||
+3
-3
@@ -25,7 +25,7 @@
|
||||
|
||||
// Build variables
|
||||
// these get injected during the build pipeline
|
||||
#define CURRENT_VERSION "22.4.2-dev" // always append -dev to the current version for custom-builds
|
||||
#define CURRENT_VERSION "22.4.4-dev" // always append -dev to the current version for custom-builds
|
||||
#define LOGIN_VERSION "0.8.0"
|
||||
#define COMPILE_DATE __DATE__
|
||||
#define COMPILE_TIME __TIME__
|
||||
@@ -42,8 +42,8 @@
|
||||
* Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt
|
||||
*/
|
||||
|
||||
#define CURRENT_BINARY_DATABASE_VERSION 9220
|
||||
#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9037
|
||||
#define CURRENT_BINARY_DATABASE_VERSION 9221
|
||||
#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9038
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "eqemu-server",
|
||||
"version": "22.4.2",
|
||||
"version": "22.4.4",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/EQEmu/Server.git"
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include "worldserver.h"
|
||||
#include "../common/path_manager.h"
|
||||
#include "../common/zone_store.h"
|
||||
#include "../common/events/player_event_logs.h"
|
||||
#include <list>
|
||||
#include <signal.h>
|
||||
#include <thread>
|
||||
@@ -47,6 +48,7 @@ WorldServer *worldserver = 0;
|
||||
EQEmuLogSys LogSys;
|
||||
PathManager path;
|
||||
ZoneStore zone_store;
|
||||
PlayerEventLogs player_event_logs;
|
||||
|
||||
void CatchSignal(int sig_num)
|
||||
{
|
||||
@@ -106,6 +108,9 @@ int main()
|
||||
/* Load Looking For Guild Manager */
|
||||
lfguildmanager.LoadDatabase();
|
||||
|
||||
Timer player_event_process_timer(1000);
|
||||
player_event_logs.SetDatabase(&database)->Init();
|
||||
|
||||
auto loop_fn = [&](EQ::Timer* t) {
|
||||
Timer::SetCurrentTime();
|
||||
|
||||
@@ -117,6 +122,10 @@ int main()
|
||||
if (LFGuildExpireTimer.Check()) {
|
||||
lfguildmanager.ExpireEntries();
|
||||
}
|
||||
|
||||
if (player_event_process_timer.Check()) {
|
||||
player_event_logs.Process();
|
||||
}
|
||||
};
|
||||
|
||||
EQ::Timer process_timer(loop_fn);
|
||||
|
||||
@@ -29,6 +29,8 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#include "lfguild.h"
|
||||
#include "queryservconfig.h"
|
||||
#include "worldserver.h"
|
||||
#include "../common/events/player_events.h"
|
||||
#include "../common/events/player_event_logs.h"
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <stdarg.h>
|
||||
@@ -89,6 +91,17 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p)
|
||||
case 0: {
|
||||
break;
|
||||
}
|
||||
case ServerOP_PlayerEvent: {
|
||||
auto n = PlayerEvent::PlayerEventContainer{};
|
||||
auto s = (ServerSendPlayerEvent_Struct *) p.Data();
|
||||
EQ::Util::MemoryStreamReader ss(s->cereal_data, s->cereal_size);
|
||||
cereal::BinaryInputArchive archive(ss);
|
||||
archive(n);
|
||||
|
||||
player_event_logs.AddToQueue(n.player_event_log);
|
||||
|
||||
break;
|
||||
}
|
||||
case ServerOP_KeepAlive: {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ int main(int argc, char **argv)
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
content_db.SetMysql(database.getMySQL());
|
||||
content_db.SetMySQL(database);
|
||||
}
|
||||
|
||||
LogSys.SetDatabase(&database)
|
||||
|
||||
@@ -474,6 +474,7 @@
|
||||
9218|2023_01_24_item_recast.sql|show columns from character_item_recast like '%recast_type%'|contains|smallint
|
||||
9219|2023_01_29_merchant_status_requirements.sql|SHOW COLUMNS FROM merchantlist LIKE 'min_status'|empty|
|
||||
9220|2022_12_19_player_events_tables.sql|SHOW TABLES LIKE 'player_event_logs'|empty|
|
||||
9221|2023_02_24_npc_scaling_zone_id_instance_version.sql|SHOW COLUMNS FROM `npc_scale_global_base` LIKE 'zone_id'|empty|
|
||||
|
||||
# Upgrade conditions:
|
||||
# This won't be needed after this system is implemented, but it is used database that are not
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
9035|2022_12_04_bot_archery.sql|SHOW COLUMNS FROM `bot_data` LIKE 'archery_setting'|empty|
|
||||
9036|2023_01_19_drop_bot_views.sql|SHOW TABLES LIKE 'vw_groups'|not_empty|
|
||||
9037|2023_01_22_add_name_index.sql||show index from bot_data WHERE key_name = 'name`|empty|
|
||||
9038|2023_02_16_add_caster_range.sql|SHOW COLUMNS FROM `bot_data` LIKE 'caster_range'|empty|
|
||||
|
||||
# Upgrade conditions:
|
||||
# This won't be needed after this system is implemented, but it is used database that are not
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE `bot_data`
|
||||
ADD COLUMN `caster_range` INT(11) UNSIGNED NOT NULL DEFAULT '300' AFTER `archery_setting`;
|
||||
@@ -0,0 +1,5 @@
|
||||
ALTER TABLE `npc_scale_global_base`
|
||||
ADD COLUMN `zone_id` int(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `level`,
|
||||
ADD COLUMN `instance_version` int(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `zone_id`,
|
||||
DROP PRIMARY KEY,
|
||||
ADD PRIMARY KEY (`type`, `level`, `zone_id`, `instance_version`) USING BTREE;
|
||||
@@ -0,0 +1,73 @@
|
||||
#include <thread>
|
||||
#include "../../common/repositories/zone_repository.h"
|
||||
#include "../../common/eqemu_config.h"
|
||||
#include <signal.h>
|
||||
|
||||
Database db;
|
||||
Database db2;
|
||||
|
||||
volatile sig_atomic_t stop;
|
||||
void inthand(int signum) {
|
||||
stop = 1;
|
||||
}
|
||||
|
||||
[[noreturn]] void DatabaseTest()
|
||||
{
|
||||
while (true) {
|
||||
LogInfo("DatabaseTest Query");
|
||||
db.QueryDatabase("SELECT 1");
|
||||
}
|
||||
}
|
||||
|
||||
[[noreturn]] void DatabaseTestSecondConnection()
|
||||
{
|
||||
while (true) {
|
||||
LogInfo("DatabaseTest Query");
|
||||
db2.QueryDatabase("SELECT 1");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void WorldserverCLI::TestDatabaseConcurrency(int argc, char **argv, argh::parser &cmd, std::string &description)
|
||||
{
|
||||
description = "Test command to test database concurrency";
|
||||
|
||||
if (cmd[{"-h", "--help"}]) {
|
||||
return;
|
||||
}
|
||||
|
||||
signal(SIGINT, inthand);
|
||||
|
||||
LogInfo("Database test");
|
||||
|
||||
auto mutex = new Mutex;
|
||||
|
||||
auto c = EQEmuConfig::get();
|
||||
LogInfo("Connecting to MySQL");
|
||||
if (!db.Connect(
|
||||
c->DatabaseHost.c_str(),
|
||||
c->DatabaseUsername.c_str(),
|
||||
c->DatabasePassword.c_str(),
|
||||
c->DatabaseDB.c_str(),
|
||||
c->DatabasePort
|
||||
)) {
|
||||
LogError("Cannot continue without a database connection");
|
||||
return;
|
||||
}
|
||||
|
||||
db.SetMutex(mutex);
|
||||
|
||||
db2.SetMySQL(db);
|
||||
|
||||
db2.SetMutex(mutex);
|
||||
|
||||
std::thread(DatabaseTest).detach();
|
||||
std::thread(DatabaseTest).detach();
|
||||
std::thread(DatabaseTestSecondConnection).detach();
|
||||
|
||||
while (!stop) {
|
||||
|
||||
}
|
||||
|
||||
safe_delete(mutex);
|
||||
}
|
||||
@@ -224,6 +224,13 @@ void EQEmuApiWorldDataService::reload(Json::Value &r, const std::vector<std::str
|
||||
else {
|
||||
pack = new ServerPacket(c.opcode, 0);
|
||||
message(r, fmt::format("Reloading [{}] globally", c.desc));
|
||||
|
||||
if (c.opcode == ServerOP_ReloadLogs) {
|
||||
LogSys.LoadLogDatabaseSettings();
|
||||
}
|
||||
else if (c.opcode == ServerOP_ReloadRules) {
|
||||
RuleManager::Instance()->LoadRules(&database, RuleManager::Instance()->GetActiveRuleset(), true);
|
||||
}
|
||||
}
|
||||
|
||||
found_command = true;
|
||||
|
||||
+7
-11
@@ -131,12 +131,6 @@ inline void UpdateWindowTitle(std::string new_title)
|
||||
#endif
|
||||
}
|
||||
|
||||
void PlayerEventQueueListener() {
|
||||
while (RunLoops) {
|
||||
player_event_logs.Process();
|
||||
Sleep(1000);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* World process entrypoint
|
||||
@@ -381,13 +375,9 @@ int main(int argc, char **argv)
|
||||
}
|
||||
);
|
||||
|
||||
Timer player_event_process_timer(1000);
|
||||
player_event_logs.SetDatabase(&database)->Init();
|
||||
|
||||
if (!RuleB(Logging, PlayerEventsQSProcess)) {
|
||||
LogInfo("[PlayerEventQueueListener] Booting queue processor");
|
||||
std::thread(PlayerEventQueueListener).detach();
|
||||
}
|
||||
|
||||
auto loop_fn = [&](EQ::Timer* t) {
|
||||
Timer::SetCurrentTime();
|
||||
|
||||
@@ -435,6 +425,10 @@ int main(int argc, char **argv)
|
||||
|
||||
client_list.Process();
|
||||
|
||||
if (player_event_process_timer.Check()) {
|
||||
player_event_logs.Process();
|
||||
}
|
||||
|
||||
if (PurgeInstanceTimer.Check()) {
|
||||
database.PurgeExpiredInstances();
|
||||
database.PurgeAllDeletedDataBuckets();
|
||||
@@ -484,6 +478,8 @@ int main(int argc, char **argv)
|
||||
LogInfo("Signaling HTTP service to stop");
|
||||
LogSys.CloseFileLogs();
|
||||
|
||||
WorldBoot::Shutdown();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
+14
-4
@@ -30,6 +30,8 @@
|
||||
extern ZSList zoneserver_list;
|
||||
extern WorldConfig Config;
|
||||
|
||||
auto mutex = new Mutex;
|
||||
|
||||
void WorldBoot::GMSayHookCallBackProcessWorld(uint16 log_category, const char *func, std::string message)
|
||||
{
|
||||
// we don't want to loop up with chat messages
|
||||
@@ -136,9 +138,7 @@ bool WorldBoot::LoadDatabaseConnections()
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Multi-tenancy: Content database
|
||||
*/
|
||||
// Multi-tenancy - content database
|
||||
if (!c->ContentDbHost.empty()) {
|
||||
if (!content_db.Connect(
|
||||
c->ContentDbHost.c_str(),
|
||||
@@ -153,7 +153,12 @@ bool WorldBoot::LoadDatabaseConnections()
|
||||
}
|
||||
}
|
||||
else {
|
||||
content_db.SetMysql(database.getMySQL());
|
||||
content_db.SetMySQL(database);
|
||||
// when database and content_db share the same underlying mysql connection
|
||||
// it needs to be protected by a shared mutex otherwise we produce concurrency issues
|
||||
// when database actions are occurring in different threads
|
||||
database.SetMutex(mutex);
|
||||
content_db.SetMutex(mutex);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -652,3 +657,8 @@ void WorldBoot::CheckForPossibleConfigurationIssues()
|
||||
}
|
||||
}
|
||||
|
||||
void WorldBoot::Shutdown()
|
||||
{
|
||||
safe_delete(mutex);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ public:
|
||||
static void RegisterLoginservers();
|
||||
static bool DatabaseLoadRoutines(int argc, char **argv);
|
||||
static void CheckForPossibleConfigurationIssues();
|
||||
static void Shutdown();
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -31,10 +31,12 @@ void WorldserverCLI::CommandHandler(int argc, char **argv)
|
||||
function_map["test:expansion"] = &WorldserverCLI::ExpansionTestCommand;
|
||||
function_map["test:repository"] = &WorldserverCLI::TestRepository;
|
||||
function_map["test:repository2"] = &WorldserverCLI::TestRepository2;
|
||||
function_map["test:db-concurrency"] = &WorldserverCLI::TestDatabaseConcurrency;
|
||||
|
||||
EQEmuCommand::HandleMenu(function_map, cmd, argc, argv);
|
||||
}
|
||||
|
||||
#include "cli/database_concurrency.cpp"
|
||||
#include "cli/copy_character.cpp"
|
||||
#include "cli/database_dump.cpp"
|
||||
#include "cli/database_get_schema.cpp"
|
||||
|
||||
@@ -18,6 +18,7 @@ public:
|
||||
static void ExpansionTestCommand(int argc, char **argv, argh::parser &cmd, std::string &description);
|
||||
static void TestRepository(int argc, char **argv, argh::parser &cmd, std::string &description);
|
||||
static void TestRepository2(int argc, char **argv, argh::parser &cmd, std::string &description);
|
||||
static void TestDatabaseConcurrency(int argc, char **argv, argh::parser &cmd, std::string &description);
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -1332,6 +1332,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
|
||||
case ServerOP_ItemStatus:
|
||||
case ServerOP_KickPlayer:
|
||||
case ServerOP_KillPlayer:
|
||||
case ServerOP_OOCMute:
|
||||
case ServerOP_OOZGroupMessage:
|
||||
case ServerOP_Petition:
|
||||
case ServerOP_RaidGroupSay:
|
||||
|
||||
+47
-51
@@ -71,6 +71,7 @@ Bot::Bot(NPCType *npcTypeData, Client* botOwner) : NPC(npcTypeData, nullptr, glm
|
||||
m_enforce_spell_settings = 0;
|
||||
m_bot_archery_setting = 0;
|
||||
m_expansion_bitmask = -1;
|
||||
m_bot_caster_range = 0;
|
||||
SetBotID(0);
|
||||
SetBotSpellID(0);
|
||||
SetSpawnStatus(false);
|
||||
@@ -2240,16 +2241,16 @@ void Bot::AI_Bot_Init()
|
||||
AIautocastspell_timer.reset(nullptr);
|
||||
casting_spell_AIindex = static_cast<uint8>(AIBot_spells.size());
|
||||
|
||||
roambox_max_x = 0;
|
||||
roambox_max_y = 0;
|
||||
roambox_min_x = 0;
|
||||
roambox_min_y = 0;
|
||||
roambox_distance = 0;
|
||||
roambox_destination_x = 0;
|
||||
roambox_destination_y = 0;
|
||||
roambox_destination_z = 0;
|
||||
roambox_min_delay = 2500;
|
||||
roambox_delay = 2500;
|
||||
m_roambox.max_x = 0;
|
||||
m_roambox.max_y = 0;
|
||||
m_roambox.min_x = 0;
|
||||
m_roambox.min_y = 0;
|
||||
m_roambox.distance = 0;
|
||||
m_roambox.dest_x = 0;
|
||||
m_roambox.dest_y = 0;
|
||||
m_roambox.dest_z = 0;
|
||||
m_roambox.delay = 2500;
|
||||
m_roambox.min_delay = 2500;
|
||||
}
|
||||
|
||||
void Bot::SpellProcess() {
|
||||
@@ -3115,26 +3116,7 @@ void Bot::AI_Process()
|
||||
}
|
||||
}
|
||||
float melee_distance_min = melee_distance / 2.0f;
|
||||
|
||||
// Calculate caster distances
|
||||
float caster_distance_max = 0.0f;
|
||||
float caster_distance_min = 0.0f;
|
||||
float caster_distance = 0.0f;
|
||||
{
|
||||
if (GetLevel() >= GetStopMeleeLevel() && GetClass() >= WARRIOR && GetClass() <= BERSERKER) {
|
||||
caster_distance_max = MAX_CASTER_DISTANCE[(GetClass() - 1)];
|
||||
}
|
||||
|
||||
if (caster_distance_max) {
|
||||
|
||||
caster_distance_min = melee_distance_max;
|
||||
if (caster_distance_max <= caster_distance_min) {
|
||||
caster_distance_max = caster_distance_min * 1.25f;
|
||||
}
|
||||
|
||||
caster_distance = ((caster_distance_max + caster_distance_min) / 2);
|
||||
}
|
||||
}
|
||||
float caster_distance_max = GetBotCasterMaxRange(melee_distance_max);
|
||||
|
||||
bool atArcheryRange = IsArcheryRange(tar);
|
||||
|
||||
@@ -3157,11 +3139,11 @@ void Bot::AI_Process()
|
||||
ChangeBotArcherWeapons(IsBotArcher());
|
||||
}
|
||||
}
|
||||
|
||||
bool stop_melee_level = GetLevel() >= GetStopMeleeLevel();
|
||||
if (IsBotArcher() && atArcheryRange) {
|
||||
atCombatRange = true;
|
||||
}
|
||||
else if (caster_distance_max && tar_distance <= caster_distance_max) {
|
||||
else if (caster_distance_max && tar_distance <= caster_distance_max && stop_melee_level) {
|
||||
atCombatRange = true;
|
||||
}
|
||||
else if (tar_distance <= melee_distance) {
|
||||
@@ -4478,23 +4460,19 @@ uint32 Bot::CountBotItem(uint32 item_id) {
|
||||
return item_count;
|
||||
}
|
||||
|
||||
bool Bot::HasBotItem(uint32 item_id) {
|
||||
bool has_item = false;
|
||||
EQ::ItemInstance *inst = nullptr;
|
||||
int16 Bot::HasBotItem(uint32 item_id) {
|
||||
EQ::ItemInstance const *inst = nullptr;
|
||||
|
||||
for (uint16 slot_id = EQ::invslot::EQUIPMENT_BEGIN; slot_id <= EQ::invslot::EQUIPMENT_END; ++slot_id) {
|
||||
inst = GetBotItem(slot_id);
|
||||
if (!inst || !inst->GetItem()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inst->GetID() == item_id) {
|
||||
has_item = true;
|
||||
break;
|
||||
return slot_id;
|
||||
}
|
||||
}
|
||||
|
||||
return has_item;
|
||||
return INVALID_INDEX;
|
||||
}
|
||||
|
||||
void Bot::RemoveBotItem(uint32 item_id) {
|
||||
@@ -4569,6 +4547,7 @@ bool Bot::AddBotToGroup(Bot* bot, Group* group) {
|
||||
group->SendUpdate(groupActUpdate, TempLeader);
|
||||
}
|
||||
}
|
||||
group->VerifyGroup();
|
||||
Result = true;
|
||||
}
|
||||
}
|
||||
@@ -4616,17 +4595,17 @@ void Bot::PerformTradeWithClient(int16 begin_slot_id, int16 end_slot_id, Client*
|
||||
struct ClientTrade {
|
||||
ItemInstance* trade_item_instance;
|
||||
int16 from_client_slot;
|
||||
int16 to_bot_slot;
|
||||
int16 to_bot_slot = invslot::SLOT_INVALID;
|
||||
|
||||
ClientTrade(ItemInstance* item, int16 from) : trade_item_instance(item), from_client_slot(from), to_bot_slot(invslot::SLOT_INVALID) { }
|
||||
ClientTrade(ItemInstance* item, int16 from) : trade_item_instance(item), from_client_slot(from) { }
|
||||
};
|
||||
|
||||
struct ClientReturn {
|
||||
const ItemInstance* return_item_instance;
|
||||
ItemInstance* return_item_instance;
|
||||
int16 from_bot_slot;
|
||||
int16 to_client_slot;
|
||||
int16 to_client_slot = invslot::SLOT_INVALID;
|
||||
|
||||
ClientReturn(const ItemInstance* item, int16 from) : return_item_instance(item), from_bot_slot(from), to_client_slot(invslot::SLOT_INVALID) { }
|
||||
ClientReturn(ItemInstance* item, int16 from) : return_item_instance(item), from_bot_slot(from) { }
|
||||
};
|
||||
|
||||
static const int16 bot_equip_order[invslot::EQUIPMENT_COUNT] = {
|
||||
@@ -4906,7 +4885,7 @@ void Bot::PerformTradeWithClient(int16 begin_slot_id, int16 end_slot_id, Client*
|
||||
if (trade_instance->GetItem()->IsType2HWeapon()) {
|
||||
if (!melee_secondary) {
|
||||
melee_2h_weapon = true;
|
||||
auto equipped_secondary_weapon = m_inv[invslot::slotSecondary];
|
||||
auto equipped_secondary_weapon = GetBotItem(invslot::slotSecondary);
|
||||
if (equipped_secondary_weapon) {
|
||||
client_return.push_back(ClientReturn(equipped_secondary_weapon, invslot::slotSecondary));
|
||||
}
|
||||
@@ -4922,7 +4901,7 @@ void Bot::PerformTradeWithClient(int16 begin_slot_id, int16 end_slot_id, Client*
|
||||
!trade_instance->IsWeapon()
|
||||
) {
|
||||
melee_secondary = true;
|
||||
auto equipped_primary_weapon = m_inv[invslot::slotPrimary];
|
||||
auto equipped_primary_weapon = GetBotItem(invslot::slotPrimary);
|
||||
if (equipped_primary_weapon && equipped_primary_weapon->GetItem()->IsType2HWeapon()) {
|
||||
client_return.push_back(ClientReturn(equipped_primary_weapon, invslot::slotPrimary));
|
||||
}
|
||||
@@ -4937,7 +4916,7 @@ void Bot::PerformTradeWithClient(int16 begin_slot_id, int16 end_slot_id, Client*
|
||||
trade_iterator.to_bot_slot = index;
|
||||
|
||||
if (m_inv[index]) {
|
||||
client_return.push_back(ClientReturn(m_inv[index], index));
|
||||
client_return.push_back(ClientReturn(GetBotItem(index), index));
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -5071,9 +5050,8 @@ void Bot::PerformTradeWithClient(int16 begin_slot_id, int16 end_slot_id, Client*
|
||||
client->DeleteItemInInventory(return_iterator.from_bot_slot);
|
||||
} else { // successful trade returns
|
||||
auto return_instance = m_inv.PopItem(return_iterator.from_bot_slot);
|
||||
//if (*return_instance != *return_iterator.return_item_instance) {
|
||||
|
||||
// // TODO: add logging
|
||||
//}
|
||||
|
||||
if (!database.botdb.DeleteItemBySlot(GetBotID(), return_iterator.from_bot_slot)) {
|
||||
OwnerMessage(
|
||||
@@ -9490,7 +9468,7 @@ void Bot::ListBotSpells(uint8 min_level)
|
||||
auto spell_count = 0;
|
||||
auto spell_number = 1;
|
||||
|
||||
for (const auto& s : (AIBot_spells.size() > AIBot_spells_enforced.size()) ? AIBot_spells : AIBot_spells_enforced) {
|
||||
for (const auto& s : (GetBotEnforceSpellSetting()) ? AIBot_spells_enforced : AIBot_spells) {
|
||||
auto b = bot_spell_settings.find(s.spellid);
|
||||
if (b == bot_spell_settings.end() && s.minlevel >= min_level) {
|
||||
bot_owner->Message(
|
||||
@@ -9830,4 +9808,22 @@ void Bot::SendSpellAnim(uint16 target_id, uint16 spell_id)
|
||||
entity_list.QueueCloseClients(this, &app, false, RuleI(Range, SpellParticles));
|
||||
}
|
||||
|
||||
float Bot::GetBotCasterMaxRange(float melee_distance_max) {// Calculate caster distances
|
||||
float caster_distance_max = 0.0f;
|
||||
float caster_distance_min = 0.0f;
|
||||
float caster_distance = 0.0f;
|
||||
|
||||
caster_distance_max = GetBotCasterRange() * GetBotCasterRange();
|
||||
if (!GetBotCasterRange() && GetLevel() >= GetStopMeleeLevel() && GetClass() >= WARRIOR && GetClass() <= BERSERKER) {
|
||||
caster_distance_max = MAX_CASTER_DISTANCE[GetClass() - 1];
|
||||
}
|
||||
if (caster_distance_max) {
|
||||
caster_distance_min = melee_distance_max;
|
||||
if (caster_distance_max <= caster_distance_min) {
|
||||
caster_distance_max = caster_distance_min * 1.25f;
|
||||
}
|
||||
}
|
||||
return caster_distance_max;
|
||||
}
|
||||
|
||||
uint8 Bot::spell_casting_chances[SPELL_TYPE_COUNT][PLAYER_CLASS_COUNT][EQ::constants::STANCE_TYPE_COUNT][cntHSND] = { 0 };
|
||||
|
||||
+6
-3
@@ -348,6 +348,8 @@ public:
|
||||
void SetStopMeleeLevel(uint8 level);
|
||||
void SetGuardMode();
|
||||
void SetHoldMode();
|
||||
uint32 GetBotCasterRange() { return m_bot_caster_range; }
|
||||
bool IsValidSpellRange(uint16 spell_id, Mob const* tar);
|
||||
|
||||
// Bot AI Methods
|
||||
void AI_Bot_Init();
|
||||
@@ -490,7 +492,7 @@ public:
|
||||
EQ::constants::StanceType GetBotStance() { return _botStance; }
|
||||
uint8 GetChanceToCastBySpellType(uint32 spellType);
|
||||
bool GetBotEnforceSpellSetting() { return m_enforce_spell_settings; }
|
||||
|
||||
float GetBotCasterMaxRange(float melee_distance_max);
|
||||
bool IsGroupHealer() { return m_CastingRoles.GroupHealer; }
|
||||
bool IsGroupSlower() { return m_CastingRoles.GroupSlower; }
|
||||
bool IsGroupNuker() { return m_CastingRoles.GroupNuker; }
|
||||
@@ -623,6 +625,7 @@ public:
|
||||
else
|
||||
_botStance = EQ::constants::stancePassive;
|
||||
}
|
||||
void SetBotCasterRange(uint32 bot_caster_range) { m_bot_caster_range = bot_caster_range; }
|
||||
void SetSpellRecastTimer(int timer_index, int32 recast_delay);
|
||||
void SetDisciplineRecastTimer(int timer_index, int32 recast_delay);
|
||||
void SetAltOutOfCombatBehavior(bool behavior_flag) { _altoutofcombatbehavior = behavior_flag;}
|
||||
@@ -720,7 +723,7 @@ public:
|
||||
uint32 CountBotItem(uint32 item_id);
|
||||
std::map<uint16, uint32> GetBotItemSlots();
|
||||
uint32 GetBotItemBySlot(uint16 slot_id);
|
||||
bool HasBotItem(uint32 item_id);
|
||||
int16 HasBotItem(uint32 item_id);
|
||||
void RemoveBotItem(uint32 item_id);
|
||||
uint32 GetTotalPlayTime();
|
||||
|
||||
@@ -820,7 +823,7 @@ private:
|
||||
bool m_pulling_flag;
|
||||
bool m_returning_flag;
|
||||
eStandingPetOrder m_previous_pet_order;
|
||||
|
||||
uint32 m_bot_caster_range;
|
||||
BotCastingRoles m_CastingRoles;
|
||||
std::map<std::string,std::string> bot_data_buckets;
|
||||
std::map<std::string,std::string> bot_owner_data_buckets;
|
||||
|
||||
+52
-1
@@ -1366,6 +1366,7 @@ int bot_command_init(void)
|
||||
bot_command_add("bottitle", "Sets a bots title", AccountStatus::Player, bot_subcommand_bot_title) ||
|
||||
bot_command_add("botupdate", "Updates a bot to reflect any level changes that you have experienced", AccountStatus::Player, bot_subcommand_bot_update) ||
|
||||
bot_command_add("botwoad", "Changes the Barbarian woad of a bot", AccountStatus::Player, bot_subcommand_bot_woad) ||
|
||||
bot_command_add("casterrange", "Controls the range casters will try to stay away from a mob (if too far, they will skip spells that are out-of-range)", AccountStatus::Player, bot_command_caster_range) ||
|
||||
bot_command_add("charm", "Attempts to have a bot charm your target", AccountStatus::Player, bot_command_charm) ||
|
||||
bot_command_add("circle", "Orders a Druid bot to open a magical doorway to a specified destination", AccountStatus::Player, bot_subcommand_circle) ||
|
||||
bot_command_add("cure", "Orders a bot to remove any ailments", AccountStatus::Player, bot_command_cure) ||
|
||||
@@ -9320,7 +9321,7 @@ void bot_subcommand_inventory_remove(Client *c, const Seperator *sep)
|
||||
return;
|
||||
}
|
||||
|
||||
const auto* inst = my_bot->GetBotItem(slot_id);
|
||||
auto* inst = my_bot->GetBotItem(slot_id);
|
||||
if (!inst) {
|
||||
std::string slot_message = "is";
|
||||
switch (slot_id) {
|
||||
@@ -10848,3 +10849,53 @@ void bot_command_enforce_spell_list(Client* c, const Seperator *sep)
|
||||
).c_str()
|
||||
);
|
||||
}
|
||||
|
||||
void bot_command_caster_range(Client* c, const Seperator* sep)
|
||||
{
|
||||
if (helper_command_alias_fail(c, "bot_command_caster_range", sep->arg[0], "casterrange")) {
|
||||
return;
|
||||
}
|
||||
if (helper_is_help_or_usage(sep->arg[1])) {
|
||||
c->Message(Chat::White, "usage: <target_bot> %s [current | value: 0 - 300].", sep->arg[0]);
|
||||
c->Message(Chat::White, "note: Can only be used for Casters or Hybrids.");
|
||||
c->Message(Chat::White, "note: Use [current] to check the current setting.");
|
||||
c->Message(Chat::White, "note: Set the value to the minimum distance you want your bot to try to remain from its target.");
|
||||
c->Message(Chat::White, "note: If they are too far for a spell, it will be skipped.");
|
||||
return;
|
||||
}
|
||||
|
||||
auto my_bot = ActionableBots::AsTarget_ByBot(c);
|
||||
if (!my_bot) {
|
||||
c->Message(Chat::White, "You must <target> a bot that you own to use this command.");
|
||||
return;
|
||||
}
|
||||
if (!IsCasterClass(my_bot->GetClass()) && !IsHybridClass(my_bot->GetClass())) {
|
||||
c->Message(Chat::White, "You must <target> a caster or hybrid class to use this command.");
|
||||
return;
|
||||
}
|
||||
|
||||
uint32 crange = 0;
|
||||
if (sep->IsNumber(1)) {
|
||||
crange = atoi(sep->arg[1]);
|
||||
if (crange >= 0 && crange <= 300) {
|
||||
my_bot->SetBotCasterRange(crange);
|
||||
if (!database.botdb.SaveBotCasterRange(c->CharacterID(), my_bot->GetBotID(), crange)) {
|
||||
c->Message(Chat::White, "%s for '%s'", BotDatabase::fail::SaveBotCasterRange(), my_bot->GetCleanName());
|
||||
return;
|
||||
}
|
||||
else {
|
||||
c->Message(Chat::White, "Successfully set Caster Range for %s to %u.", my_bot->GetCleanName(), crange);
|
||||
}
|
||||
}
|
||||
else {
|
||||
c->Message(Chat::White, "You must enter a value within the range of 0 - 300.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (!strcasecmp(sep->arg[1], "current")) {
|
||||
c->Message(Chat::White, "My current range is %u.", my_bot->GetBotCasterRange());
|
||||
}
|
||||
else {
|
||||
c->Message(Chat::White, "Incorrect argument, use help for a list of options.");
|
||||
}
|
||||
}
|
||||
@@ -553,6 +553,7 @@ void bot_command_attack(Client *c, const Seperator *sep);
|
||||
void bot_command_bind_affinity(Client *c, const Seperator *sep);
|
||||
void bot_command_bot(Client *c, const Seperator *sep);
|
||||
void bot_command_botgroup(Client *c, const Seperator *sep);
|
||||
void bot_command_caster_range(Client* c, const Seperator* sep);
|
||||
void bot_command_charm(Client *c, const Seperator *sep);
|
||||
void bot_command_cure(Client *c, const Seperator *sep);
|
||||
void bot_command_defensive(Client *c, const Seperator *sep);
|
||||
|
||||
+73
-28
@@ -485,6 +485,8 @@ bool BotDatabase::LoadBot(const uint32 bot_id, Bot*& loaded_bot)
|
||||
loaded_bot->SetBotEnforceSpellSetting((l.enforce_spell_settings ? true : false));
|
||||
|
||||
loaded_bot->SetBotArcherySetting((l.archery_setting ? true : false));
|
||||
|
||||
loaded_bot->SetBotCasterRange(l.caster_range);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -545,6 +547,7 @@ bool BotDatabase::SaveNewBot(Bot* bot_inst, uint32& bot_id)
|
||||
e.expansion_bitmask = bot_inst->GetExpansionBitmask();
|
||||
e.enforce_spell_settings = bot_inst->GetBotEnforceSpellSetting();
|
||||
e.archery_setting = bot_inst->IsBotArcher() ? 1 : 0;
|
||||
e.caster_range = bot_inst->GetBotCasterRange();
|
||||
|
||||
auto b = BotDataRepository::InsertOne(database, e);
|
||||
if (!b.bot_id) {
|
||||
@@ -639,10 +642,11 @@ bool BotDatabase::DeleteBot(const uint32 bot_id)
|
||||
|
||||
bool BotDatabase::LoadBuffs(Bot* bot_inst)
|
||||
{
|
||||
if (!bot_inst)
|
||||
if (!bot_inst) {
|
||||
return false;
|
||||
}
|
||||
|
||||
query = StringFormat(
|
||||
query = fmt::format(
|
||||
"SELECT"
|
||||
" `spell_id`,"
|
||||
" `caster_level`,"
|
||||
@@ -663,45 +667,58 @@ bool BotDatabase::LoadBuffs(Bot* bot_inst)
|
||||
" `extra_di_chance`,"
|
||||
" `instrument_mod`"
|
||||
" FROM `bot_buffs`"
|
||||
" WHERE `bot_id` = '%u'",
|
||||
" WHERE `bot_id` = {}",
|
||||
bot_inst->GetBotID()
|
||||
);
|
||||
auto results = database.QueryDatabase(query);
|
||||
if (!results.Success())
|
||||
|
||||
if (!results.Success()) {
|
||||
return false;
|
||||
if (!results.RowCount())
|
||||
}
|
||||
|
||||
if (!results.RowCount()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Buffs_Struct* bot_buffs = bot_inst->GetBuffs();
|
||||
if (!bot_buffs)
|
||||
|
||||
if (!bot_buffs) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32 max_slots = bot_inst->GetMaxBuffSlots();
|
||||
for (int index = 0; index < max_slots; index++) {
|
||||
bot_buffs[index].spellid = SPELL_UNKNOWN;
|
||||
}
|
||||
|
||||
int buff_count = 0;
|
||||
for (auto row = results.begin(); row != results.end() && buff_count < BUFF_COUNT; ++row) {
|
||||
bot_buffs[buff_count].spellid = atoi(row[0]);
|
||||
bot_buffs[buff_count].casterlevel = atoi(row[1]);
|
||||
bot_buffs[buff_count].spellid = atoul(row[0]);
|
||||
bot_buffs[buff_count].casterlevel = atoul(row[1]);
|
||||
//row[2] (duration_formula) can probably be removed
|
||||
bot_buffs[buff_count].ticsremaining = atoi(row[3]);
|
||||
bot_buffs[buff_count].ticsremaining = Strings::ToInt(row[3]);
|
||||
|
||||
if (CalculatePoisonCounters(bot_buffs[buff_count].spellid) > 0)
|
||||
bot_buffs[buff_count].counters = atoi(row[4]);
|
||||
else if (CalculateDiseaseCounters(bot_buffs[buff_count].spellid) > 0)
|
||||
bot_buffs[buff_count].counters = atoi(row[5]);
|
||||
else if (CalculateCurseCounters(bot_buffs[buff_count].spellid) > 0)
|
||||
bot_buffs[buff_count].counters = atoi(row[6]);
|
||||
else if (CalculateCorruptionCounters(bot_buffs[buff_count].spellid) > 0)
|
||||
bot_buffs[buff_count].counters = atoi(row[7]);
|
||||
bot_buffs[buff_count].counters = 0;
|
||||
if (CalculatePoisonCounters(bot_buffs[buff_count].spellid) > 0) {
|
||||
bot_buffs[buff_count].counters = atoul(row[4]);
|
||||
} else if (CalculateDiseaseCounters(bot_buffs[buff_count].spellid) > 0) {
|
||||
bot_buffs[buff_count].counters = atoul(row[5]);
|
||||
} else if (CalculateCurseCounters(bot_buffs[buff_count].spellid) > 0) {
|
||||
bot_buffs[buff_count].counters = atoul(row[6]);
|
||||
} else if (CalculateCorruptionCounters(bot_buffs[buff_count].spellid) > 0) {
|
||||
bot_buffs[buff_count].counters = atoul(row[7]);
|
||||
}
|
||||
|
||||
bot_buffs[buff_count].hit_number = atoi(row[8]);
|
||||
bot_buffs[buff_count].melee_rune = atoi(row[9]);
|
||||
bot_buffs[buff_count].magic_rune = atoi(row[10]);
|
||||
bot_buffs[buff_count].dot_rune = atoi(row[11]);
|
||||
bot_buffs[buff_count].persistant_buff = ((atoi(row[12])) ? (true) : (false));
|
||||
bot_buffs[buff_count].caston_x = atoi(row[13]);
|
||||
bot_buffs[buff_count].caston_y = atoi(row[14]);
|
||||
bot_buffs[buff_count].caston_z = atoi(row[15]);
|
||||
bot_buffs[buff_count].ExtraDIChance = atoi(row[16]);
|
||||
bot_buffs[buff_count].instrument_mod = atoi(row[17]);
|
||||
bot_buffs[buff_count].hit_number = atoul(row[8]);
|
||||
bot_buffs[buff_count].melee_rune = atoul(row[9]);
|
||||
bot_buffs[buff_count].magic_rune = atoul(row[10]);
|
||||
bot_buffs[buff_count].dot_rune = atoul(row[11]);
|
||||
bot_buffs[buff_count].persistant_buff = (Strings::ToBool(row[12])) != 0;
|
||||
bot_buffs[buff_count].caston_x = Strings::ToInt(row[13]);
|
||||
bot_buffs[buff_count].caston_y = Strings::ToInt(row[14]);
|
||||
bot_buffs[buff_count].caston_z = Strings::ToInt(row[15]);
|
||||
bot_buffs[buff_count].ExtraDIChance = Strings::ToInt(row[16]);
|
||||
bot_buffs[buff_count].instrument_mod = atoul(row[17]);
|
||||
bot_buffs[buff_count].casterid = 0;
|
||||
++buff_count;
|
||||
}
|
||||
@@ -962,7 +979,10 @@ bool BotDatabase::SaveTimers(Bot* bot_inst)
|
||||
if (bot_timers[timer_index] <= Timer::GetCurrentTime())
|
||||
continue;
|
||||
|
||||
query = StringFormat("INSERT INTO `bot_timers` (`bot_id`, `timer_id`, `timer_value`) VALUES ('%u', '%u', '%u')", bot_inst->GetBotID(), (timer_index + 1), bot_timers[timer_index]);
|
||||
query = fmt::format(
|
||||
"REPLACE INTO `bot_timers` (`bot_id`, `timer_id`, `timer_value`) VALUES ('{}', '{}', '{}')",
|
||||
bot_inst->GetBotID(), (timer_index + 1), bot_timers[timer_index]
|
||||
);
|
||||
auto results = database.QueryDatabase(query);
|
||||
if (!results.Success()) {
|
||||
DeleteTimers(bot_inst->GetBotID());
|
||||
@@ -3148,6 +3168,30 @@ std::string BotDatabase::GetBotNameByID(const uint32 bot_id)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool BotDatabase::SaveBotCasterRange(const uint32 owner_id, const uint32 bot_id, const uint32 bot_caster_range_value)
|
||||
{
|
||||
if (!owner_id || !bot_id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
query = fmt::format(
|
||||
"UPDATE `bot_data`"
|
||||
" SET `caster_range` = '{}'"
|
||||
" WHERE `owner_id` = '{}'"
|
||||
" AND `bot_id` = '{}'",
|
||||
bot_caster_range_value,
|
||||
owner_id,
|
||||
bot_id
|
||||
);
|
||||
auto results = database.QueryDatabase(query);
|
||||
|
||||
if (!results.Success()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* fail::Bot functions */
|
||||
const char* BotDatabase::fail::LoadBotsList() { return "Failed to bots list"; }
|
||||
const char* BotDatabase::fail::LoadOwnerID() { return "Failed to load owner ID"; }
|
||||
@@ -3202,6 +3246,7 @@ const char* BotDatabase::fail::ToggleAllHelmAppearances() { return "Failed to sa
|
||||
const char* BotDatabase::fail::SaveFollowDistance() { return "Failed to save follow distance"; }
|
||||
const char* BotDatabase::fail::SaveAllFollowDistances() { return "Failed to save all follow distances"; }
|
||||
const char* BotDatabase::fail::SaveStopMeleeLevel() { return "Failed to save stop melee level"; }
|
||||
const char* BotDatabase::fail::SaveBotCasterRange() { return "Failed to save caster range"; }
|
||||
|
||||
/* fail::Bot heal rotation functions */
|
||||
const char* BotDatabase::fail::LoadHealRotationIDByBotID() { return "Failed to load heal rotation ID by bot ID"; }
|
||||
|
||||
@@ -146,6 +146,8 @@ public:
|
||||
bool SaveOwnerOption(const uint32 owner_id, size_t type, const bool flag);
|
||||
bool SaveOwnerOption(const uint32 owner_id, const std::pair<size_t, size_t> type, const std::pair<bool, bool> flag);
|
||||
|
||||
bool SaveBotCasterRange(const uint32 owner_id, const uint32 bot_id, const uint32 bot_caster_range_value);
|
||||
|
||||
/* Bot bot-group functions */
|
||||
bool QueryBotGroupExistence(const std::string& botgroup_name);
|
||||
|
||||
@@ -250,6 +252,7 @@ public:
|
||||
static const char* SaveFollowDistance();
|
||||
static const char* SaveAllFollowDistances();
|
||||
static const char* SaveStopMeleeLevel();
|
||||
static const char* SaveBotCasterRange();
|
||||
|
||||
/* fail::Bot bot-group functions */
|
||||
static const char* QueryBotGroupExistence();
|
||||
|
||||
+60
-19
@@ -98,8 +98,10 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
|
||||
if (!(!addMob->IsImmuneToSpell(botSpell.SpellId, this) && addMob->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0)) {
|
||||
break;
|
||||
}
|
||||
castedSpell = AIDoSpellCast(botSpell.SpellIndex, addMob, botSpell.ManaCost);
|
||||
|
||||
if (IsValidSpellRange(botSpell.SpellId, addMob)) {
|
||||
castedSpell = AIDoSpellCast(botSpell.SpellIndex, addMob, botSpell.ManaCost);
|
||||
}
|
||||
if (castedSpell) {
|
||||
BotGroupSay(
|
||||
this,
|
||||
@@ -260,7 +262,9 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
|
||||
|
||||
uint32 TempDontHealMeBeforeTime = tar->DontHealMeBefore();
|
||||
|
||||
castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost, &TempDontHealMeBeforeTime);
|
||||
if (IsValidSpellRange(botSpell.SpellId, tar) || botClass == BARD) {
|
||||
castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost, &TempDontHealMeBeforeTime);
|
||||
}
|
||||
|
||||
if (castedSpell) {
|
||||
/*if (TempDontHealMeBeforeTime != tar->DontHealMeBefore())
|
||||
@@ -340,7 +344,9 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
|
||||
}
|
||||
uint32 TempDontRootMeBefore = tar->DontRootMeBefore();
|
||||
|
||||
castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost, &TempDontRootMeBefore);
|
||||
if (IsValidSpellRange(botSpell.SpellId, tar)) {
|
||||
castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost, &TempDontRootMeBefore);
|
||||
}
|
||||
|
||||
if (TempDontRootMeBefore != tar->DontRootMeBefore()) {
|
||||
tar->SetDontRootMeBefore(TempDontRootMeBefore);
|
||||
@@ -488,7 +494,10 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
|
||||
if (IsInvulnerabilitySpell(botSpell.SpellId)) {
|
||||
tar = this; //target self for invul type spells
|
||||
}
|
||||
castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost);
|
||||
|
||||
if (IsValidSpellRange(botSpell.SpellId, tar) || botClass == BARD) {
|
||||
castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -573,7 +582,9 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
|
||||
}
|
||||
}
|
||||
|
||||
castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost);
|
||||
if (IsValidSpellRange(botSpell.SpellId, tar)) {
|
||||
castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -594,7 +605,9 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
|
||||
// TODO: Check target to see if there is anything to dispel
|
||||
|
||||
if (tar->CountDispellableBuffs() > 0) {
|
||||
castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost);
|
||||
if (IsValidSpellRange(botSpell.SpellId, tar)) {
|
||||
castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -768,7 +781,6 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
|
||||
if (CheckSpellRecastTimers(this, itr->SpellIndex)) {
|
||||
uint32 TempDontBuffMeBefore = tar->DontBuffMeBefore();
|
||||
castedSpell = AIDoSpellCast(selectedBotSpell.SpellIndex, tar, selectedBotSpell.ManaCost, &TempDontBuffMeBefore);
|
||||
|
||||
if (TempDontBuffMeBefore != tar->DontBuffMeBefore())
|
||||
tar->SetDontBuffMeBefore(TempDontBuffMeBefore);
|
||||
}
|
||||
@@ -797,7 +809,9 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
|
||||
if (!(!tar->IsImmuneToSpell(botSpell.SpellId, this) && (tar->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0)))
|
||||
break;
|
||||
|
||||
castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost);
|
||||
if (IsValidSpellRange(botSpell.SpellId, tar)) {
|
||||
castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -820,7 +834,9 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
|
||||
|
||||
uint32 TempDontSnareMeBefore = tar->DontSnareMeBefore();
|
||||
|
||||
if (IsValidSpellRange(botSpell.SpellId, tar)) {
|
||||
castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost, &TempDontSnareMeBefore);
|
||||
}
|
||||
|
||||
if (TempDontSnareMeBefore != tar->DontSnareMeBefore())
|
||||
tar->SetDontSnareMeBefore(TempDontSnareMeBefore);
|
||||
@@ -856,7 +872,7 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
|
||||
|
||||
uint32 TempDontDotMeBefore = tar->DontDotMeBefore();
|
||||
|
||||
castedSpell = AIDoSpellCast(selectedBotSpell.SpellIndex, tar, selectedBotSpell.ManaCost, &TempDontDotMeBefore);
|
||||
castedSpell = AIDoSpellCast(selectedBotSpell.SpellIndex, tar, selectedBotSpell.ManaCost, &TempDontDotMeBefore);
|
||||
|
||||
if (TempDontDotMeBefore != tar->DontDotMeBefore())
|
||||
tar->SetDontDotMeBefore(TempDontDotMeBefore);
|
||||
@@ -888,7 +904,9 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
|
||||
|
||||
uint32 TempDontDotMeBefore = tar->DontDotMeBefore();
|
||||
|
||||
castedSpell = AIDoSpellCast(selectedBotSpell.SpellIndex, tar, selectedBotSpell.ManaCost, &TempDontDotMeBefore);
|
||||
if (IsValidSpellRange(botSpell.SpellId, tar)) {
|
||||
castedSpell = AIDoSpellCast(selectedBotSpell.SpellIndex, tar, selectedBotSpell.ManaCost, &TempDontDotMeBefore);
|
||||
}
|
||||
|
||||
if (TempDontDotMeBefore != tar->DontDotMeBefore())
|
||||
tar->SetDontDotMeBefore(TempDontDotMeBefore);
|
||||
@@ -929,7 +947,9 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
|
||||
if (tar->CanBuffStack(iter.SpellId, botLevel, true) < 0)
|
||||
continue;
|
||||
|
||||
castedSpell = AIDoSpellCast(iter.SpellIndex, tar, iter.ManaCost);
|
||||
if (IsValidSpellRange(botSpell.SpellId, tar)) {
|
||||
castedSpell = AIDoSpellCast(iter.SpellIndex, tar, iter.ManaCost);
|
||||
}
|
||||
if (castedSpell)
|
||||
break;
|
||||
}
|
||||
@@ -956,7 +976,9 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
|
||||
if (!(!tar->IsImmuneToSpell(botSpell.SpellId, this) && tar->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0))
|
||||
break;
|
||||
|
||||
castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost);
|
||||
if (IsValidSpellRange(botSpell.SpellId, tar)) {
|
||||
castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost);
|
||||
}
|
||||
|
||||
if (castedSpell && GetClass() != BARD) {
|
||||
BotGroupSay(
|
||||
@@ -992,7 +1014,9 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
|
||||
if (!(!tar->IsImmuneToSpell(botSpell.SpellId, this) && (tar->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0)))
|
||||
break;
|
||||
|
||||
castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost);
|
||||
if (IsValidSpellRange(botSpell.SpellId, tar)) {
|
||||
castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -1006,7 +1030,7 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
|
||||
|
||||
uint32 TempDontCureMeBeforeTime = tar->DontCureMeBefore();
|
||||
|
||||
castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost, &TempDontCureMeBeforeTime);
|
||||
castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost, &TempDontCureMeBeforeTime);
|
||||
|
||||
if (castedSpell) {
|
||||
if (botClass != BARD) {
|
||||
@@ -1054,7 +1078,9 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
|
||||
if (tar->CanBuffStack(iter.SpellId, botLevel, true) < 0)
|
||||
continue;
|
||||
|
||||
castedSpell = AIDoSpellCast(iter.SpellIndex, tar, iter.ManaCost);
|
||||
if (IsValidSpellRange(botSpell.SpellId, tar)) {
|
||||
castedSpell = AIDoSpellCast(iter.SpellIndex, tar, iter.ManaCost);
|
||||
}
|
||||
if (castedSpell) {
|
||||
BotGroupSay(
|
||||
this,
|
||||
@@ -1096,7 +1122,7 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
|
||||
if (tar->CanBuffStack(iter.SpellId, botLevel, true) < 0)
|
||||
continue;
|
||||
|
||||
castedSpell = AIDoSpellCast(iter.SpellIndex, tar, iter.ManaCost);
|
||||
castedSpell = AIDoSpellCast(iter.SpellIndex, tar, iter.ManaCost);
|
||||
if (castedSpell)
|
||||
break;
|
||||
}
|
||||
@@ -1128,7 +1154,7 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
|
||||
if (tar->CanBuffStack(iter.SpellId, botLevel, true) < 0)
|
||||
continue;
|
||||
|
||||
castedSpell = AIDoSpellCast(iter.SpellIndex, tar, iter.ManaCost);
|
||||
castedSpell = AIDoSpellCast(iter.SpellIndex, tar, iter.ManaCost);
|
||||
if (castedSpell)
|
||||
break;
|
||||
}
|
||||
@@ -1764,8 +1790,9 @@ bool Bot::AIHealRotation(Mob* tar, bool useFastHeals) {
|
||||
return false;
|
||||
|
||||
uint32 TempDontHealMeBeforeTime = tar->DontHealMeBefore();
|
||||
|
||||
castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost, &TempDontHealMeBeforeTime);
|
||||
if (IsValidSpellRange(botSpell.SpellId, tar)) {
|
||||
castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost, &TempDontHealMeBeforeTime);
|
||||
}
|
||||
|
||||
if (castedSpell) {
|
||||
BotGroupSay(
|
||||
@@ -3456,3 +3483,17 @@ bool Bot::HasBotSpellEntry(uint16 spellid) {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Bot::IsValidSpellRange(uint16 spell_id, Mob const* tar) {
|
||||
if (!IsValidSpell(spell_id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tar) {
|
||||
int spellrange = (GetActSpellRange(spell_id, spells[spell_id].range) * GetActSpellRange(spell_id, spells[spell_id].range));
|
||||
if (spellrange >= DistanceSquared(m_Position, tar->GetPosition())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -792,6 +792,7 @@ public:
|
||||
void SendTradeskillDetails(uint32 recipe_id);
|
||||
bool TradeskillExecute(DBTradeskillRecipe_Struct *spec);
|
||||
void CheckIncreaseTradeskill(int16 bonusstat, int16 stat_modifier, float skillup_modifier, uint16 success_modifier, EQ::skills::SkillType tradeskill);
|
||||
bool CheckTradeskillLoreConflict(int32 recipe_id);
|
||||
void InitInnates();
|
||||
|
||||
void GMKill();
|
||||
|
||||
@@ -2144,7 +2144,7 @@ void Client::Handle_OP_AdventureMerchantRequest(const EQApplicationPacket *app)
|
||||
theme = LDoNThemes::RUJ;
|
||||
} else if (item->LDoNTheme & LDoNThemeBits::MMCBit) {
|
||||
theme = LDoNThemes::MMC;
|
||||
} else if (item->LDoNTheme & LDoNThemeBits::RUJBit) {
|
||||
} else if (item->LDoNTheme & LDoNThemeBits::MIRBit) {
|
||||
theme = LDoNThemes::MIR;
|
||||
} else if (item->LDoNTheme & LDoNThemeBits::GUKBit) {
|
||||
theme = LDoNThemes::GUK;
|
||||
@@ -9951,7 +9951,7 @@ void Client::Handle_OP_Mend(const EQApplicationPacket *app)
|
||||
Message(Chat::Red, "Ability recovery time not yet met.");
|
||||
return;
|
||||
}
|
||||
p_timers.Start(pTimerMend, MendReuseTime - 1);
|
||||
p_timers.Start(pTimerMend, (MendReuseTime - GetSkillReuseTime(EQ::skills::SkillMend)));
|
||||
|
||||
int mendhp = GetMaxHP() / 4;
|
||||
int currenthp = GetHP();
|
||||
@@ -9963,9 +9963,11 @@ void Client::Handle_OP_Mend(const EQApplicationPacket *app)
|
||||
mendhp *= 2;
|
||||
MessageString(Chat::LightBlue, MEND_CRITICAL);
|
||||
}
|
||||
else {
|
||||
MessageString(Chat::LightBlue, MEND_SUCCESS);
|
||||
}
|
||||
SetHP(GetHP() + mendhp);
|
||||
SendHPUpdate();
|
||||
MessageString(Chat::LightBlue, MEND_SUCCESS);
|
||||
}
|
||||
else {
|
||||
/* the purpose of the following is to make the chance to worsen wounds much less common,
|
||||
|
||||
@@ -33,6 +33,7 @@ std::string GetModifyNPCStatDescription(std::string stat);
|
||||
void SendNPCEditSubCommands(Client *c);
|
||||
void SendRuleSubCommands(Client *c);
|
||||
void SendGuildSubCommands(Client *c);
|
||||
void SendPeekInvSubCommands(Client *c);
|
||||
|
||||
// Commands
|
||||
void command_acceptrules(Client *c, const Seperator *sep);
|
||||
|
||||
+25
-18
@@ -53,6 +53,14 @@ Doors::Doors(const DoorsRepository::Doors &door) :
|
||||
strn0cpy(m_door_name, door.name.c_str(), sizeof(m_door_name));
|
||||
strn0cpy(m_destination_zone_name, door.dest_zone.c_str(), sizeof(m_destination_zone_name));
|
||||
|
||||
// destination helpers
|
||||
if (!door.dest_zone.empty() && Strings::ToLower(door.dest_zone) != "none" && !door.dest_zone.empty()) {
|
||||
m_has_destination_zone = true;
|
||||
}
|
||||
if (!door.dest_zone.empty() && !door.zone.empty() && Strings::EqualFold(door.dest_zone, door.zone)) {
|
||||
m_same_destination_zone = true;
|
||||
}
|
||||
|
||||
m_database_id = door.id;
|
||||
m_door_id = door.doorid;
|
||||
m_incline = door.incline;
|
||||
@@ -450,7 +458,7 @@ void Doors::HandleClick(Client *sender, uint8 trigger)
|
||||
m_close_timer.Start();
|
||||
}
|
||||
|
||||
if (strncmp(m_destination_zone_name, "NONE", strlen("NONE")) == 0) {
|
||||
if (!HasDestinationZone()) {
|
||||
SetOpenState(true);
|
||||
}
|
||||
}
|
||||
@@ -497,19 +505,15 @@ void Doors::HandleClick(Client *sender, uint8 trigger)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Teleport door
|
||||
*/
|
||||
if (((m_open_type == 57) || (m_open_type == 58)) &&
|
||||
(strncmp(m_destination_zone_name, "NONE", strlen("NONE")) != 0)) {
|
||||
// teleport door
|
||||
if (((m_open_type == 57) || (m_open_type == 58)) && HasDestinationZone()) {
|
||||
bool has_key_required = (required_key_item && ((required_key_item == player_key) || sender->GetGM()));
|
||||
|
||||
/**
|
||||
* If click destination is same zone and doesn't require a key
|
||||
*/
|
||||
if ((strncmp(m_destination_zone_name, m_zone_name, strlen(m_zone_name)) == 0) && (!required_key_item)) {
|
||||
if (IsDestinationZoneSame() && (!required_key_item)) {
|
||||
if (!disable_add_to_key_ring) {
|
||||
sender->KeyRingAdd(player_key);
|
||||
}
|
||||
|
||||
sender->MovePC(
|
||||
zone->GetZoneID(),
|
||||
zone->GetInstanceID(),
|
||||
@@ -519,14 +523,7 @@ void Doors::HandleClick(Client *sender, uint8 trigger)
|
||||
m_destination.w
|
||||
);
|
||||
}
|
||||
/**
|
||||
* If requires a key
|
||||
*/
|
||||
else if (
|
||||
(!IsDoorOpen() || m_open_type == 58) &&
|
||||
(required_key_item && ((required_key_item == player_key) || sender->GetGM()))
|
||||
) {
|
||||
|
||||
else if ((!IsDoorOpen() || m_open_type == 58) && has_key_required) {
|
||||
if (!disable_add_to_key_ring) {
|
||||
sender->KeyRingAdd(player_key);
|
||||
}
|
||||
@@ -896,3 +893,13 @@ float Doors::GetHeading()
|
||||
{
|
||||
return m_position.w;
|
||||
}
|
||||
|
||||
bool Doors::HasDestinationZone() const
|
||||
{
|
||||
return m_has_destination_zone;
|
||||
}
|
||||
|
||||
bool Doors::IsDestinationZoneSame() const
|
||||
{
|
||||
return m_same_destination_zone;
|
||||
}
|
||||
|
||||
@@ -67,8 +67,13 @@ public:
|
||||
float GetZ();
|
||||
float GetHeading();
|
||||
|
||||
bool HasDestinationZone() const;
|
||||
bool IsDestinationZoneSame() const;
|
||||
|
||||
private:
|
||||
|
||||
bool m_has_destination_zone = false;
|
||||
bool m_same_destination_zone = false;
|
||||
uint32 m_database_id;
|
||||
uint8 m_door_id;
|
||||
char m_zone_name[32];
|
||||
|
||||
@@ -116,7 +116,7 @@ void command_gearup(Client *c, const Seperator *sep)
|
||||
if (t->IsClient()) {
|
||||
has_item = t->CastToClient()->GetInv().HasItem(item_id, 1, invWhereWorn) != INVALID_INDEX;
|
||||
} else if (t->IsBot()) {
|
||||
has_item = t->CastToBot()->HasBotItem(item_id);
|
||||
has_item = t->CastToBot()->HasBotItem(item_id) != INVALID_INDEX;
|
||||
}
|
||||
|
||||
bool can_wear_item = false;
|
||||
|
||||
+292
-174
@@ -3,6 +3,12 @@
|
||||
|
||||
void command_peekinv(Client *c, const Seperator *sep)
|
||||
{
|
||||
auto arguments = sep->argnum;
|
||||
if (!arguments) {
|
||||
SendPeekInvSubCommands(c);
|
||||
return;
|
||||
}
|
||||
|
||||
// this can be cleaned up once inventory is cleaned up
|
||||
enum {
|
||||
peekNone = 0x0000,
|
||||
@@ -15,63 +21,83 @@ void command_peekinv(Client *c, const Seperator *sep)
|
||||
peekShBank = 0x0040,
|
||||
peekTrade = 0x0080,
|
||||
peekWorld = 0x0100,
|
||||
peekOutOfScope = (peekWorld * 2) // less than
|
||||
peekOutOfScope = (peekWorld * 2)
|
||||
};
|
||||
|
||||
static const char *scope_prefix[] = {"equip", "gen", "cursor", "limbo", "trib", "bank", "shbank", "trade", "world"};
|
||||
|
||||
static const int16 scope_range[][2] = {
|
||||
{EQ::invslot::EQUIPMENT_BEGIN, EQ::invslot::EQUIPMENT_END},
|
||||
{EQ::invslot::GENERAL_BEGIN, EQ::invslot::GENERAL_END},
|
||||
{EQ::invslot::slotCursor, EQ::invslot::slotCursor},
|
||||
{EQ::invslot::SLOT_INVALID, EQ::invslot::SLOT_INVALID},
|
||||
{EQ::invslot::TRIBUTE_BEGIN, EQ::invslot::TRIBUTE_END},
|
||||
{EQ::invslot::BANK_BEGIN, EQ::invslot::BANK_END},
|
||||
{EQ::invslot::SHARED_BANK_BEGIN, EQ::invslot::SHARED_BANK_END},
|
||||
{EQ::invslot::TRADE_BEGIN, EQ::invslot::TRADE_END},
|
||||
{EQ::invslot::SLOT_BEGIN, (EQ::invtype::WORLD_SIZE - 1)}
|
||||
{ EQ::invslot::EQUIPMENT_BEGIN, EQ::invslot::EQUIPMENT_END },
|
||||
{ EQ::invslot::GENERAL_BEGIN, EQ::invslot::GENERAL_END },
|
||||
{ EQ::invslot::slotCursor, EQ::invslot::slotCursor },
|
||||
{ EQ::invslot::SLOT_INVALID, EQ::invslot::SLOT_INVALID },
|
||||
{ EQ::invslot::TRIBUTE_BEGIN, EQ::invslot::TRIBUTE_END },
|
||||
{ EQ::invslot::BANK_BEGIN, EQ::invslot::BANK_END },
|
||||
{ EQ::invslot::SHARED_BANK_BEGIN, EQ::invslot::SHARED_BANK_END },
|
||||
{ EQ::invslot::TRADE_BEGIN, EQ::invslot::TRADE_END },
|
||||
{ EQ::invslot::SLOT_BEGIN, (EQ::invtype::WORLD_SIZE - 1) }
|
||||
};
|
||||
|
||||
static const bool scope_bag[] = {false, true, true, true, false, true, true, true, true};
|
||||
static const bool scope_bag[] = {
|
||||
false, // Equip
|
||||
true, // General
|
||||
true, // Cursor
|
||||
true, // Cursor Limbo
|
||||
false, // Tribute
|
||||
true, // Bank
|
||||
true, // Shared Bank
|
||||
true, // Trade
|
||||
true // World
|
||||
};
|
||||
|
||||
if (!c) {
|
||||
int scope_mask = peekNone;
|
||||
|
||||
const bool is_all = !strcasecmp(sep->arg[1], "all");
|
||||
const bool is_all_bank = !strcasecmp(sep->arg[1], "allbank");
|
||||
const bool is_bank = !strcasecmp(sep->arg[1], "bank");
|
||||
const bool is_cursor = !strcasecmp(sep->arg[1], "cursor");
|
||||
const bool is_cursor_limbo = !strcasecmp(sep->arg[1], "curlimbo");
|
||||
const bool is_equipment = !strcasecmp(sep->arg[1], "equip");
|
||||
const bool is_general = !strcasecmp(sep->arg[1], "gen");
|
||||
const bool is_limbo = !strcasecmp(sep->arg[1], "limbo");
|
||||
const bool is_possessions = !strcasecmp(sep->arg[1], "poss");
|
||||
const bool is_shared_bank = !strcasecmp(sep->arg[1], "shbank");
|
||||
const bool is_trade = !strcasecmp(sep->arg[1], "trade");
|
||||
const bool is_tribute = !strcasecmp(sep->arg[1], "trib");
|
||||
const bool is_world = !strcasecmp(sep->arg[1], "world");
|
||||
|
||||
if (is_all) {
|
||||
scope_mask = (peekOutOfScope - 1);
|
||||
} else if (is_all_bank) {
|
||||
scope_mask |= (peekBank | peekShBank);
|
||||
} else if (is_bank) {
|
||||
scope_mask |= peekBank;
|
||||
} else if (is_cursor) {
|
||||
scope_mask |= peekCursor;
|
||||
} else if (is_cursor_limbo) {
|
||||
scope_mask |= (peekCursor | peekLimbo);
|
||||
} else if (is_equipment) {
|
||||
scope_mask |= peekEquip;
|
||||
} else if (is_general) {
|
||||
scope_mask |= peekGen;
|
||||
} else if (is_limbo) {
|
||||
scope_mask |= peekLimbo;
|
||||
} else if (is_possessions) {
|
||||
scope_mask |= (peekEquip | peekGen | peekCursor);
|
||||
} else if (is_shared_bank) {
|
||||
scope_mask |= peekShBank;
|
||||
} else if (is_tribute) {
|
||||
scope_mask |= peekTrib;
|
||||
} else if (is_trade) {
|
||||
scope_mask |= peekTrade;
|
||||
} else if (is_world) {
|
||||
scope_mask |= peekWorld;
|
||||
} else {
|
||||
SendPeekInvSubCommands(c);
|
||||
return;
|
||||
}
|
||||
|
||||
if (c->GetTarget() && !c->GetTarget()->IsClient()) {
|
||||
c->Message(Chat::White, "You must target a PC for this command.");
|
||||
return;
|
||||
}
|
||||
|
||||
int scopeMask = peekNone;
|
||||
|
||||
if (strcasecmp(sep->arg[1], "all") == 0) { scopeMask = (peekOutOfScope - 1); }
|
||||
else if (strcasecmp(sep->arg[1], "equip") == 0) { scopeMask |= peekEquip; }
|
||||
else if (strcasecmp(sep->arg[1], "gen") == 0) { scopeMask |= peekGen; }
|
||||
else if (strcasecmp(sep->arg[1], "cursor") == 0) { scopeMask |= peekCursor; }
|
||||
else if (strcasecmp(sep->arg[1], "poss") == 0) { scopeMask |= (peekEquip | peekGen | peekCursor); }
|
||||
else if (strcasecmp(sep->arg[1], "limbo") == 0) { scopeMask |= peekLimbo; }
|
||||
else if (strcasecmp(sep->arg[1], "curlim") == 0) { scopeMask |= (peekCursor | peekLimbo); }
|
||||
else if (strcasecmp(sep->arg[1], "trib") == 0) { scopeMask |= peekTrib; }
|
||||
else if (strcasecmp(sep->arg[1], "bank") == 0) { scopeMask |= peekBank; }
|
||||
else if (strcasecmp(sep->arg[1], "shbank") == 0) { scopeMask |= peekShBank; }
|
||||
else if (strcasecmp(sep->arg[1], "allbank") == 0) { scopeMask |= (peekBank | peekShBank); }
|
||||
else if (strcasecmp(sep->arg[1], "trade") == 0) { scopeMask |= peekTrade; }
|
||||
else if (strcasecmp(sep->arg[1], "world") == 0) { scopeMask |= peekWorld; }
|
||||
|
||||
if (!scopeMask) {
|
||||
c->Message(
|
||||
Chat::White,
|
||||
"Usage: #peekinv [equip|gen|cursor|poss|limbo|curlim|trib|bank|shbank|allbank|trade|world|all]"
|
||||
);
|
||||
c->Message(Chat::White, "- Displays a portion of the targeted user's inventory");
|
||||
c->Message(Chat::White, "- Caution: 'all' is a lot of information!");
|
||||
return;
|
||||
}
|
||||
|
||||
Client *targetClient = c;
|
||||
if (c->GetTarget()) {
|
||||
targetClient = c->GetTarget()->CastToClient();
|
||||
auto t = c;
|
||||
if (c->GetTarget() && c->GetTarget()->IsClient()) {
|
||||
t = c->GetTarget()->CastToClient();
|
||||
}
|
||||
|
||||
const EQ::ItemInstance *inst_main = nullptr;
|
||||
@@ -82,62 +108,81 @@ void command_peekinv(Client *c, const Seperator *sep)
|
||||
EQ::SayLinkEngine linker;
|
||||
linker.SetLinkType(EQ::saylink::SayLinkItemInst);
|
||||
|
||||
c->Message(Chat::White, "Displaying inventory for %s...", targetClient->GetName());
|
||||
c->Message(
|
||||
Chat::White,
|
||||
fmt::format(
|
||||
"Displaying inventory of {}.",
|
||||
c->GetTargetDescription(t)
|
||||
).c_str()
|
||||
);
|
||||
|
||||
Object *objectTradeskill = targetClient->GetTradeskillObject();
|
||||
auto o = t->GetTradeskillObject();
|
||||
auto found_items = false;
|
||||
|
||||
bool itemsFound = false;
|
||||
|
||||
for (int scopeIndex = 0, scopeBit = peekEquip; scopeBit < peekOutOfScope; ++scopeIndex, scopeBit <<= 1) {
|
||||
if (scopeBit & ~scopeMask) {
|
||||
for (int scope_index = 0, scope_bit = peekEquip; scope_bit < peekOutOfScope; ++scope_index, scope_bit <<= 1) {
|
||||
if (scope_bit & ~scope_mask) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (scopeBit & peekWorld) {
|
||||
if (objectTradeskill == nullptr) {
|
||||
c->Message(Chat::Default, "No world tradeskill object selected...");
|
||||
if (scope_bit & peekWorld) {
|
||||
if (!o) {
|
||||
c->Message(Chat::White, "No world Tradeskill object selected.");
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
c->Message(
|
||||
Chat::White,
|
||||
"[WorldObject DBID: %i (entityid: %i)]",
|
||||
objectTradeskill->GetDBID(),
|
||||
objectTradeskill->GetID());
|
||||
fmt::format(
|
||||
"[World Object] Database ID: {} Entity ID: {}",
|
||||
o->GetDBID(),
|
||||
o->GetID()
|
||||
).c_str()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for (int16 indexMain = scope_range[scopeIndex][0]; indexMain <= scope_range[scopeIndex][1]; ++indexMain) {
|
||||
if (indexMain == EQ::invslot::SLOT_INVALID) {
|
||||
for (int16 index_main = scope_range[scope_index][0]; index_main <= scope_range[scope_index][1]; ++index_main) {
|
||||
if (index_main == EQ::invslot::SLOT_INVALID) {
|
||||
continue;
|
||||
}
|
||||
|
||||
inst_main = ((scopeBit & peekWorld) ? objectTradeskill->GetItem(indexMain) : targetClient->GetInv().GetItem(
|
||||
indexMain
|
||||
));
|
||||
inst_main = (
|
||||
(scope_bit & peekWorld) ?
|
||||
o->GetItem(index_main) :
|
||||
t->GetInv().GetItem(index_main)
|
||||
);
|
||||
|
||||
if (inst_main) {
|
||||
itemsFound = true;
|
||||
found_items = true;
|
||||
item_data = inst_main->GetItem();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
item_data = nullptr;
|
||||
}
|
||||
|
||||
linker.SetItemInst(inst_main);
|
||||
|
||||
c->Message(
|
||||
(item_data == nullptr),
|
||||
"%sSlot: %i, Item: %i (%s), Charges: %i",
|
||||
scope_prefix[scopeIndex],
|
||||
((scopeBit & peekWorld) ? (EQ::invslot::WORLD_BEGIN + indexMain) : indexMain),
|
||||
((item_data == nullptr) ? 0 : item_data->ID),
|
||||
linker.GenerateLink().c_str(),
|
||||
((inst_main == nullptr) ? 0 : inst_main->GetCharges())
|
||||
);
|
||||
if (item_data) {
|
||||
c->Message(
|
||||
Chat::White,
|
||||
fmt::format(
|
||||
"Slot {} | {} ({}){}",
|
||||
((scope_bit & peekWorld) ? (EQ::invslot::WORLD_BEGIN + index_main) : index_main),
|
||||
linker.GenerateLink(),
|
||||
item_data->ID,
|
||||
(
|
||||
inst_main->IsStackable() && inst_main->GetCharges() > 0 ?
|
||||
fmt::format(
|
||||
" (Stack of {})",
|
||||
inst_main->GetCharges()
|
||||
) :
|
||||
""
|
||||
)
|
||||
).c_str()
|
||||
);
|
||||
}
|
||||
|
||||
if (inst_main && inst_main->IsClassCommon()) {
|
||||
for (uint8 indexAug = EQ::invaug::SOCKET_BEGIN; indexAug <= EQ::invaug::SOCKET_END; ++indexAug) {
|
||||
inst_aug = inst_main->GetItem(indexAug);
|
||||
for (uint8 augment_index = EQ::invaug::SOCKET_BEGIN; augment_index <= EQ::invaug::SOCKET_END; ++augment_index) {
|
||||
inst_aug = inst_main->GetItem(augment_index);
|
||||
if (!inst_aug) { // extant only
|
||||
continue;
|
||||
}
|
||||
@@ -146,25 +191,33 @@ void command_peekinv(Client *c, const Seperator *sep)
|
||||
linker.SetItemInst(inst_aug);
|
||||
|
||||
c->Message(
|
||||
(item_data == nullptr),
|
||||
".%sAugSlot: %i (Slot #%i, Aug idx #%i), Item: %i (%s), Charges: %i",
|
||||
scope_prefix[scopeIndex],
|
||||
INVALID_INDEX,
|
||||
((scopeBit & peekWorld) ? (EQ::invslot::WORLD_BEGIN + indexMain) : indexMain),
|
||||
indexAug,
|
||||
((item_data == nullptr) ? 0 : item_data->ID),
|
||||
linker.GenerateLink().c_str(),
|
||||
((inst_sub == nullptr) ? 0 : inst_sub->GetCharges())
|
||||
Chat::White,
|
||||
fmt::format(
|
||||
"Slot {} (Augment Slot {}) | {} ({}){}",
|
||||
((scope_bit & peekWorld) ? (EQ::invslot::WORLD_BEGIN + index_main) : index_main),
|
||||
augment_index,
|
||||
linker.GenerateLink(),
|
||||
item_data->ID,
|
||||
(
|
||||
inst_aug->IsStackable() && inst_aug->GetCharges() > 0 ?
|
||||
fmt::format(
|
||||
" (Stack of {})",
|
||||
inst_aug->GetCharges()
|
||||
) :
|
||||
""
|
||||
)
|
||||
).c_str()
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (!scope_bag[scopeIndex] || !(inst_main && inst_main->IsClassBag())) {
|
||||
if (!scope_bag[scope_index] || !(inst_main && inst_main->IsClassBag())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (uint8 indexSub = EQ::invbag::SLOT_BEGIN; indexSub <= EQ::invbag::SLOT_END; ++indexSub) {
|
||||
inst_sub = inst_main->GetItem(indexSub);
|
||||
for (uint8 sub_index = EQ::invbag::SLOT_BEGIN; sub_index <= EQ::invbag::SLOT_END; ++sub_index) {
|
||||
inst_sub = inst_main->GetItem(sub_index);
|
||||
if (!inst_sub) { // extant only
|
||||
continue;
|
||||
}
|
||||
@@ -173,20 +226,32 @@ void command_peekinv(Client *c, const Seperator *sep)
|
||||
linker.SetItemInst(inst_sub);
|
||||
|
||||
c->Message(
|
||||
(item_data == nullptr),
|
||||
"..%sBagSlot: %i (Slot #%i, Bag idx #%i), Item: %i (%s), Charges: %i",
|
||||
scope_prefix[scopeIndex],
|
||||
((scopeBit & peekWorld) ? INVALID_INDEX : EQ::InventoryProfile::CalcSlotId(indexMain, indexSub)),
|
||||
((scopeBit & peekWorld) ? (EQ::invslot::WORLD_BEGIN + indexMain) : indexMain),
|
||||
indexSub,
|
||||
((item_data == nullptr) ? 0 : item_data->ID),
|
||||
linker.GenerateLink().c_str(),
|
||||
((inst_sub == nullptr) ? 0 : inst_sub->GetCharges())
|
||||
Chat::White,
|
||||
fmt::format(
|
||||
"Slot {} Bag Slot {} | {} ({}){}",
|
||||
(
|
||||
(scope_bit & peekWorld) ?
|
||||
INVALID_INDEX :
|
||||
EQ::InventoryProfile::CalcSlotId(index_main, sub_index)
|
||||
),
|
||||
((scope_bit & peekWorld) ? (EQ::invslot::WORLD_BEGIN + index_main) : index_main),
|
||||
sub_index,
|
||||
linker.GenerateLink(),
|
||||
item_data->ID,
|
||||
(
|
||||
inst_sub->IsStackable() && inst_sub->GetCharges() > 0 ?
|
||||
fmt::format(
|
||||
" (Stack of {})",
|
||||
inst_sub->GetCharges()
|
||||
) :
|
||||
""
|
||||
)
|
||||
).c_str()
|
||||
);
|
||||
|
||||
if (inst_sub->IsClassCommon()) {
|
||||
for (uint8 indexAug = EQ::invaug::SOCKET_BEGIN; indexAug <= EQ::invaug::SOCKET_END; ++indexAug) {
|
||||
inst_aug = inst_sub->GetItem(indexAug);
|
||||
for (uint8 augment_index = EQ::invaug::SOCKET_BEGIN; augment_index <= EQ::invaug::SOCKET_END; ++augment_index) {
|
||||
inst_aug = inst_sub->GetItem(augment_index);
|
||||
if (!inst_aug) { // extant only
|
||||
continue;
|
||||
}
|
||||
@@ -195,58 +260,73 @@ void command_peekinv(Client *c, const Seperator *sep)
|
||||
linker.SetItemInst(inst_aug);
|
||||
|
||||
c->Message(
|
||||
(item_data == nullptr),
|
||||
"...%sAugSlot: %i (Slot #%i, Sub idx #%i, Aug idx #%i), Item: %i (%s), Charges: %i",
|
||||
scope_prefix[scopeIndex],
|
||||
INVALID_INDEX,
|
||||
((scopeBit & peekWorld) ? INVALID_INDEX : EQ::InventoryProfile::CalcSlotId(
|
||||
indexMain,
|
||||
indexSub
|
||||
)),
|
||||
indexSub,
|
||||
indexAug,
|
||||
((item_data == nullptr) ? 0 : item_data->ID),
|
||||
linker.GenerateLink().c_str(),
|
||||
((inst_sub == nullptr) ? 0 : inst_sub->GetCharges())
|
||||
Chat::White,
|
||||
fmt::format(
|
||||
"Slot {} Bag Slot {} (Augment Slot {}) | {} ({}){}",
|
||||
(
|
||||
(scope_bit & peekWorld) ?
|
||||
INVALID_INDEX :
|
||||
EQ::InventoryProfile::CalcSlotId(index_main,sub_index)
|
||||
),
|
||||
sub_index,
|
||||
augment_index,
|
||||
linker.GenerateLink(),
|
||||
item_data->ID,
|
||||
(
|
||||
inst_sub->IsStackable() && inst_sub->GetCharges() > 0 ?
|
||||
fmt::format(
|
||||
" (Stack of {})",
|
||||
inst_sub->GetCharges()
|
||||
) :
|
||||
""
|
||||
)
|
||||
).c_str()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (scopeBit & peekLimbo) {
|
||||
if (scope_bit & peekLimbo) {
|
||||
int limboIndex = 0;
|
||||
for (auto it = targetClient->GetInv().cursor_cbegin();
|
||||
(it != targetClient->GetInv().cursor_cend());
|
||||
++it, ++limboIndex) {
|
||||
if (it == targetClient->GetInv().cursor_cbegin()) {
|
||||
for (auto it = t->GetInv().cursor_cbegin(); (it != t->GetInv().cursor_cend()); ++it, ++limboIndex) {
|
||||
if (it == t->GetInv().cursor_cbegin()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
inst_main = *it;
|
||||
if (inst_main) {
|
||||
itemsFound = true;
|
||||
found_items = true;
|
||||
item_data = inst_main->GetItem();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
item_data = nullptr;
|
||||
}
|
||||
|
||||
linker.SetItemInst(inst_main);
|
||||
|
||||
c->Message(
|
||||
(item_data == nullptr),
|
||||
"%sSlot: %i, Item: %i (%s), Charges: %i",
|
||||
scope_prefix[scopeIndex],
|
||||
(8000 + limboIndex),
|
||||
((item_data == nullptr) ? 0 : item_data->ID),
|
||||
linker.GenerateLink().c_str(),
|
||||
((inst_main == nullptr) ? 0 : inst_main->GetCharges())
|
||||
);
|
||||
if (item_data) {
|
||||
c->Message(
|
||||
Chat::White,
|
||||
fmt::format(
|
||||
"Slot {} | {} ({}){}",
|
||||
(8000 + limboIndex),
|
||||
item_data->ID,
|
||||
linker.GenerateLink(),
|
||||
(
|
||||
inst_main->IsStackable() && inst_main->GetCharges() > 0 ?
|
||||
fmt::format(
|
||||
" (Stack of {})",
|
||||
inst_main->GetCharges()
|
||||
) :
|
||||
""
|
||||
)
|
||||
).c_str()
|
||||
);
|
||||
}
|
||||
|
||||
if (inst_main && inst_main->IsClassCommon()) {
|
||||
for (uint8 indexAug = EQ::invaug::SOCKET_BEGIN; indexAug <= EQ::invaug::SOCKET_END; ++indexAug) {
|
||||
inst_aug = inst_main->GetItem(indexAug);
|
||||
for (uint8 augment_index = EQ::invaug::SOCKET_BEGIN; augment_index <= EQ::invaug::SOCKET_END; ++augment_index) {
|
||||
inst_aug = inst_main->GetItem(augment_index);
|
||||
if (!inst_aug) { // extant only
|
||||
continue;
|
||||
}
|
||||
@@ -255,25 +335,32 @@ void command_peekinv(Client *c, const Seperator *sep)
|
||||
linker.SetItemInst(inst_aug);
|
||||
|
||||
c->Message(
|
||||
(item_data == nullptr),
|
||||
".%sAugSlot: %i (Slot #%i, Aug idx #%i), Item: %i (%s), Charges: %i",
|
||||
scope_prefix[scopeIndex],
|
||||
INVALID_INDEX,
|
||||
(8000 + limboIndex),
|
||||
indexAug,
|
||||
((item_data == nullptr) ? 0 : item_data->ID),
|
||||
linker.GenerateLink().c_str(),
|
||||
((inst_sub == nullptr) ? 0 : inst_sub->GetCharges())
|
||||
Chat::White,
|
||||
fmt::format(
|
||||
"Slot {} (Augment Slot {}) | {} ({}){}",
|
||||
(8000 + limboIndex),
|
||||
augment_index,
|
||||
linker.GenerateLink(),
|
||||
item_data->ID,
|
||||
(
|
||||
inst_aug->IsStackable() && inst_aug->GetCharges() > 0 ?
|
||||
fmt::format(
|
||||
" (Stack of {})",
|
||||
inst_aug->GetCharges()
|
||||
) :
|
||||
""
|
||||
)
|
||||
).c_str()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!scope_bag[scopeIndex] || !(inst_main && inst_main->IsClassBag())) {
|
||||
if (!scope_bag[scope_index] || !(inst_main && inst_main->IsClassBag())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (uint8 indexSub = EQ::invbag::SLOT_BEGIN; indexSub <= EQ::invbag::SLOT_END; ++indexSub) {
|
||||
inst_sub = inst_main->GetItem(indexSub);
|
||||
for (uint8 sub_index = EQ::invbag::SLOT_BEGIN; sub_index <= EQ::invbag::SLOT_END; ++sub_index) {
|
||||
inst_sub = inst_main->GetItem(sub_index);
|
||||
if (!inst_sub) {
|
||||
continue;
|
||||
}
|
||||
@@ -282,23 +369,32 @@ void command_peekinv(Client *c, const Seperator *sep)
|
||||
|
||||
linker.SetItemInst(inst_sub);
|
||||
|
||||
c->Message(
|
||||
(item_data == nullptr),
|
||||
"..%sBagSlot: %i (Slot #%i, Bag idx #%i), Item: %i (%s), Charges: %i",
|
||||
scope_prefix[scopeIndex],
|
||||
INVALID_INDEX,
|
||||
(8000 + limboIndex),
|
||||
indexSub,
|
||||
((item_data == nullptr) ? 0 : item_data->ID),
|
||||
linker.GenerateLink().c_str(),
|
||||
((inst_sub == nullptr) ? 0 : inst_sub->GetCharges())
|
||||
);
|
||||
if (item_data) {
|
||||
c->Message(
|
||||
Chat::White,
|
||||
fmt::format(
|
||||
"Slot {} Bag Slot {} | {} ({}){}",
|
||||
(8000 + limboIndex),
|
||||
sub_index,
|
||||
linker.GenerateLink(),
|
||||
item_data->ID,
|
||||
(
|
||||
inst_sub->IsStackable() && inst_sub->GetCharges() > 0 ?
|
||||
fmt::format(
|
||||
" (Stack of {})",
|
||||
inst_sub->GetCharges()
|
||||
) :
|
||||
""
|
||||
)
|
||||
).c_str()
|
||||
);
|
||||
}
|
||||
|
||||
if (inst_sub->IsClassCommon()) {
|
||||
for (uint8 indexAug = EQ::invaug::SOCKET_BEGIN;
|
||||
indexAug <= EQ::invaug::SOCKET_END;
|
||||
++indexAug) {
|
||||
inst_aug = inst_sub->GetItem(indexAug);
|
||||
for (uint8 augment_index = EQ::invaug::SOCKET_BEGIN;
|
||||
augment_index <= EQ::invaug::SOCKET_END;
|
||||
++augment_index) {
|
||||
inst_aug = inst_sub->GetItem(augment_index);
|
||||
if (!inst_aug) { // extant only
|
||||
continue;
|
||||
}
|
||||
@@ -307,16 +403,23 @@ void command_peekinv(Client *c, const Seperator *sep)
|
||||
linker.SetItemInst(inst_aug);
|
||||
|
||||
c->Message(
|
||||
(item_data == nullptr),
|
||||
"...%sAugSlot: %i (Slot #%i, Sub idx #%i, Aug idx #%i), Item: %i (%s), Charges: %i",
|
||||
scope_prefix[scopeIndex],
|
||||
INVALID_INDEX,
|
||||
(8000 + limboIndex),
|
||||
indexSub,
|
||||
indexAug,
|
||||
((item_data == nullptr) ? 0 : item_data->ID),
|
||||
linker.GenerateLink().c_str(),
|
||||
((inst_sub == nullptr) ? 0 : inst_sub->GetCharges())
|
||||
Chat::White,
|
||||
fmt::format(
|
||||
"Slot {} Bag Slot {} (Augment Slot {}) | {} ({}){}",
|
||||
(8000 + limboIndex),
|
||||
sub_index,
|
||||
augment_index,
|
||||
linker.GenerateLink(),
|
||||
item_data->ID,
|
||||
(
|
||||
inst_sub->IsStackable() && inst_sub->GetCharges() > 0 ?
|
||||
fmt::format(
|
||||
" (Stack of {})",
|
||||
inst_sub->GetCharges()
|
||||
) :
|
||||
""
|
||||
)
|
||||
).c_str()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -325,8 +428,23 @@ void command_peekinv(Client *c, const Seperator *sep)
|
||||
}
|
||||
}
|
||||
|
||||
if (!itemsFound) {
|
||||
if (!found_items) {
|
||||
c->Message(Chat::White, "No items found.");
|
||||
}
|
||||
}
|
||||
|
||||
void SendPeekInvSubCommands(Client* c) {
|
||||
c->Message(Chat::White, "Usage: #peekinv equip - Shows items in Equipment slots");
|
||||
c->Message(Chat::White, "Usage: #peekinv gen - Shows items in General slots");
|
||||
c->Message(Chat::White, "Usage: #peekinv cursor - Shows items in Cursor slots");
|
||||
c->Message(Chat::White, "Usage: #peekinv poss - Shows items in Equipment, General, and Cursor slots");
|
||||
c->Message(Chat::White, "Usage: #peekinv limbo - Shows items in Limbo slots");
|
||||
c->Message(Chat::White, "Usage: #peekinv curlim - Shows items in Cursor and Limbo slots");
|
||||
c->Message(Chat::White, "Usage: #peekinv trib - Shows items in Tribute slots");
|
||||
c->Message(Chat::White, "Usage: #peekinv bank - Shows items in Bank slots");
|
||||
c->Message(Chat::White, "Usage: #peekinv shbank - Shows items in Shared Bank slots");
|
||||
c->Message(Chat::White, "Usage: #peekinv allbank - Shows items in Bank and Shared Bank slots");
|
||||
c->Message(Chat::White, "Usage: #peekinv trade - Shows items in Trade slots");
|
||||
c->Message(Chat::White, "Usage: #peekinv world - Shows items in World slots");
|
||||
c->Message(Chat::White, "Usage: #peekinv all - Shows items in all slots");
|
||||
}
|
||||
|
||||
+19
-2
@@ -794,6 +794,23 @@ bool Client::SummonItem(uint32 item_id, int16 charges, uint32 aug1, uint32 aug2,
|
||||
}
|
||||
}
|
||||
|
||||
if (player_event_logs.IsEventEnabled(PlayerEvent::ITEM_CREATION)) {
|
||||
auto e = PlayerEvent::ItemCreationEvent{};
|
||||
e.item_id = item->ID;
|
||||
e.item_name = item->Name;
|
||||
e.to_slot = to_slot;
|
||||
e.charges = charges;
|
||||
e.aug1 = aug1;
|
||||
e.aug2 = aug2;
|
||||
e.aug3 = aug3;
|
||||
e.aug4 = aug4;
|
||||
e.aug5 = aug5;
|
||||
e.aug6 = aug6;
|
||||
e.attuned = attuned;
|
||||
|
||||
RecordPlayerEventLog(PlayerEvent::ITEM_CREATION, e);
|
||||
}
|
||||
|
||||
// put item into inventory
|
||||
if (to_slot == EQ::invslot::slotCursor) {
|
||||
PushItemOnCursor(*inst);
|
||||
@@ -848,13 +865,13 @@ void Client::DropItem(int16 slot_id, bool recurse)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::string message = fmt::format(
|
||||
"Tried to drop an item on the ground that was no-drop! item_name [{}] item_id ({})",
|
||||
invalid_drop->GetItem()->Name,
|
||||
invalid_drop->GetItem()->ID
|
||||
);
|
||||
|
||||
|
||||
invalid_drop = nullptr;
|
||||
RecordPlayerEventLog(PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = message});
|
||||
GetInv().DeleteItem(slot_id);
|
||||
|
||||
+3
-3
@@ -65,8 +65,8 @@ Lua_Mob Lua_Bot::GetOwner() {
|
||||
return Lua_Mob(self->GetOwner());
|
||||
}
|
||||
|
||||
bool Lua_Bot::HasBotItem(uint32 item_id) {
|
||||
Lua_Safe_Call_Bool();
|
||||
int16 Lua_Bot::HasBotItem(uint32 item_id) {
|
||||
Lua_Safe_Call_Int();
|
||||
return self->HasBotItem(item_id);
|
||||
}
|
||||
|
||||
@@ -507,7 +507,7 @@ luabind::scope lua_register_bot() {
|
||||
.def("GetRawItemAC", (int(Lua_Bot::*)(void))&Lua_Bot::GetRawItemAC)
|
||||
.def("GetSpellDamage", (int(Lua_Bot::*)(void))&Lua_Bot::GetSpellDamage)
|
||||
.def("HasAugmentEquippedByID", (bool(Lua_Bot::*)(uint32))&Lua_Bot::HasAugmentEquippedByID)
|
||||
.def("HasBotItem", (bool(Lua_Bot::*)(uint32))&Lua_Bot::HasBotItem)
|
||||
.def("HasBotItem", (int16(Lua_Bot::*)(uint32))&Lua_Bot::HasBotItem)
|
||||
.def("HasBotSpellEntry", (bool(Lua_Bot::*)(uint16)) & Lua_Bot::HasBotSpellEntry)
|
||||
.def("HasItemEquippedByID", (bool(Lua_Bot::*)(uint32))&Lua_Bot::HasItemEquippedByID)
|
||||
.def("IsGrouped", (bool(Lua_Bot::*)(void))&Lua_Bot::IsGrouped)
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@ public:
|
||||
uint32 GetBotItemIDBySlot(uint16 slot_id);
|
||||
int GetExpansionBitmask();
|
||||
Lua_Mob GetOwner();
|
||||
bool HasBotItem(uint32 item_id);
|
||||
int16 HasBotItem(uint32 item_id);
|
||||
void OwnerMessage(std::string message);
|
||||
bool ReloadBotDataBuckets();
|
||||
bool ReloadBotOwnerDataBuckets();
|
||||
|
||||
@@ -3038,6 +3038,20 @@ void Lua_Client::UseAugmentContainer(int container_slot)
|
||||
self->UseAugmentContainer(container_slot);
|
||||
}
|
||||
|
||||
|
||||
bool Lua_Client::IsAutoAttackEnabled()
|
||||
{
|
||||
Lua_Safe_Call_Bool();
|
||||
return self->AutoAttackEnabled();
|
||||
}
|
||||
|
||||
bool Lua_Client::IsAutoFireEnabled()
|
||||
{
|
||||
Lua_Safe_Call_Bool();
|
||||
return self->AutoFireEnabled();
|
||||
|
||||
}
|
||||
|
||||
luabind::scope lua_register_client() {
|
||||
return luabind::class_<Lua_Client, Lua_Mob>("Client")
|
||||
.def(luabind::constructor<>())
|
||||
@@ -3296,6 +3310,8 @@ luabind::scope lua_register_client() {
|
||||
.def("IncreaseSkill", (void(Lua_Client::*)(int))&Lua_Client::IncreaseSkill)
|
||||
.def("IncreaseSkill", (void(Lua_Client::*)(int,int))&Lua_Client::IncreaseSkill)
|
||||
.def("IncrementAA", (void(Lua_Client::*)(int))&Lua_Client::IncrementAA)
|
||||
.def("IsAutoAttackEnabled", (bool(Lua_Client::*)(void))&Lua_Client::IsAutoAttackEnabled)
|
||||
.def("IsAutoFireEnabled", (bool(Lua_Client::*)(void))&Lua_Client::IsAutoFireEnabled)
|
||||
.def("IsCrouching", (bool(Lua_Client::*)(void))&Lua_Client::IsCrouching)
|
||||
.def("IsDead", &Lua_Client::IsDead)
|
||||
.def("IsDueling", (bool(Lua_Client::*)(void))&Lua_Client::IsDueling)
|
||||
|
||||
@@ -466,6 +466,8 @@ public:
|
||||
void SetItemCooldown(uint32 item_id, uint32 in_time);
|
||||
uint32 GetItemCooldown(uint32 item_id);
|
||||
void UseAugmentContainer(int container_slot);
|
||||
bool IsAutoAttackEnabled();
|
||||
bool IsAutoFireEnabled();
|
||||
|
||||
void ApplySpell(int spell_id);
|
||||
void ApplySpell(int spell_id, int duration);
|
||||
|
||||
+12
-4
@@ -231,6 +231,8 @@ int main(int argc, char** argv) {
|
||||
worldserver.SetLauncherName("NONE");
|
||||
}
|
||||
|
||||
auto mutex = new Mutex;
|
||||
|
||||
LogInfo("Connecting to MySQL");
|
||||
if (!database.Connect(
|
||||
Config->DatabaseHost.c_str(),
|
||||
@@ -242,9 +244,7 @@ int main(int argc, char** argv) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Multi-tenancy: Content Database
|
||||
*/
|
||||
// Multi-tenancy: Content Database
|
||||
if (!Config->ContentDbHost.empty()) {
|
||||
if (!content_db.Connect(
|
||||
Config->ContentDbHost.c_str() ,
|
||||
@@ -258,7 +258,12 @@ int main(int argc, char** argv) {
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
content_db.SetMysql(database.getMySQL());
|
||||
content_db.SetMySQL(database);
|
||||
// when database and content_db share the same underlying mysql connection
|
||||
// it needs to be protected by a shared mutex otherwise we produce concurrency issues
|
||||
// when database actions are occurring in different threads
|
||||
database.SetMutex(mutex);
|
||||
content_db.SetMutex(mutex);
|
||||
}
|
||||
|
||||
/* Register Log System and Settings */
|
||||
@@ -613,6 +618,9 @@ int main(int argc, char** argv) {
|
||||
safe_delete(parse);
|
||||
LogInfo("Proper zone shutdown complete.");
|
||||
LogSys.CloseFileLogs();
|
||||
|
||||
safe_delete(mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
+35
-8
@@ -103,6 +103,7 @@ Mob::Mob(
|
||||
attack_dw_timer(2000),
|
||||
ranged_timer(2000),
|
||||
hp_regen_per_second_timer(1000),
|
||||
m_z_clip_check_timer(1000),
|
||||
tic_timer(6000),
|
||||
mana_timer(2000),
|
||||
spellend_timer(0),
|
||||
@@ -2374,14 +2375,14 @@ void Mob::ShowBuffList(Client* client) {
|
||||
}
|
||||
}
|
||||
|
||||
void Mob::GMMove(float x, float y, float z, float heading) {
|
||||
void Mob::GMMove(float x, float y, float z, float heading, bool save_guard_spot) {
|
||||
m_Position.x = x;
|
||||
m_Position.y = y;
|
||||
m_Position.z = z;
|
||||
SetHeading(heading);
|
||||
mMovementManager->SendCommandToClients(this, 0.0, 0.0, 0.0, 0.0, 0, ClientRangeAny);
|
||||
|
||||
if (IsNPC()) {
|
||||
if (IsNPC() && save_guard_spot) {
|
||||
CastToNPC()->SaveGuardSpot(glm::vec4(x, y, z, heading));
|
||||
}
|
||||
}
|
||||
@@ -3714,10 +3715,32 @@ bool Mob::HateSummon() {
|
||||
// probably should be like half melee range, but we can't get melee range nicely because reasons :)
|
||||
new_pos = target->TryMoveAlong(new_pos, 5.0f, angle);
|
||||
|
||||
if (target->IsClient())
|
||||
target->CastToClient()->MovePC(zone->GetZoneID(), zone->GetInstanceID(), new_pos.x, new_pos.y, new_pos.z, new_pos.w, 0, SummonPC);
|
||||
else
|
||||
target->GMMove(new_pos.x, new_pos.y, new_pos.z, new_pos.w);
|
||||
if (target->IsClient()) {
|
||||
target->CastToClient()->MovePC(
|
||||
zone->GetZoneID(),
|
||||
zone->GetInstanceID(),
|
||||
new_pos.x,
|
||||
new_pos.y,
|
||||
new_pos.z,
|
||||
new_pos.w,
|
||||
0,
|
||||
SummonPC
|
||||
);
|
||||
} else {
|
||||
bool target_is_client_pet = (
|
||||
target->IsPet() &&
|
||||
target->IsPetOwnerClient()
|
||||
);
|
||||
bool set_new_guard_spot = !(IsNPC() && target_is_client_pet);
|
||||
|
||||
target->GMMove(
|
||||
new_pos.x,
|
||||
new_pos.y,
|
||||
new_pos.z,
|
||||
new_pos.w,
|
||||
set_new_guard_spot
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
} else if(summon_level == 2) {
|
||||
@@ -4166,7 +4189,7 @@ void Mob::ExecWeaponProc(const EQ::ItemInstance *inst, uint16 spell_id, Mob *on,
|
||||
twinproc = true;
|
||||
}
|
||||
|
||||
if (IsBeneficialSpell(spell_id) && (!IsNPC() || (IsNPC() && CastToNPC()->GetInnateProcSpellID() != spell_id))) { // NPC innate procs don't take this path ever
|
||||
if (IsBeneficialSpell(spell_id) && (!IsNPC() || (IsNPC() && CastToNPC()->GetInnateProcSpellID() != spell_id)) && spells[spell_id].target_type != ST_TargetsTarget) { // NPC innate procs don't take this path ever
|
||||
SpellFinished(spell_id, this, EQ::spells::CastingSlot::Item, 0, -1, spells[spell_id].resist_difficulty, true, level_override);
|
||||
if (twinproc) {
|
||||
SpellFinished(spell_id, this, EQ::spells::CastingSlot::Item, 0, -1, spells[spell_id].resist_difficulty, true, level_override);
|
||||
@@ -4182,7 +4205,11 @@ void Mob::ExecWeaponProc(const EQ::ItemInstance *inst, uint16 spell_id, Mob *on,
|
||||
}
|
||||
|
||||
uint32 Mob::GetZoneID() const {
|
||||
return(zone->GetZoneID());
|
||||
return zone->GetZoneID();
|
||||
}
|
||||
|
||||
uint16 Mob::GetInstanceVersion() const {
|
||||
return zone->GetInstanceVersion();
|
||||
}
|
||||
|
||||
int Mob::GetHaste()
|
||||
|
||||
+4
-1
@@ -686,7 +686,7 @@ public:
|
||||
float GetMovespeed() const { return IsRunning() ? GetRunspeed() : GetWalkspeed(); }
|
||||
bool IsRunning() const { return m_is_running; }
|
||||
void SetRunning(bool val) { m_is_running = val; }
|
||||
virtual void GMMove(float x, float y, float z, float heading = 0.01);
|
||||
virtual void GMMove(float x, float y, float z, float heading = 0.01, bool save_guard_spot = true);
|
||||
virtual void GMMove(const glm::vec4 &position);
|
||||
void SetDelta(const glm::vec4& delta);
|
||||
void MakeSpawnUpdateNoDelta(PlayerPositionUpdateServer_Struct* spu);
|
||||
@@ -1239,6 +1239,7 @@ public:
|
||||
bool Charmed() const { return typeofpet == petCharmed; }
|
||||
static uint32 GetLevelHP(uint8 tlevel);
|
||||
uint32 GetZoneID() const; //for perl
|
||||
uint16 GetInstanceVersion() const; //for perl
|
||||
virtual int32 CheckAggroAmount(uint16 spell_id, Mob *target, bool isproc = false);
|
||||
virtual int32 CheckHealAggroAmount(uint16 spell_id, Mob *target, uint32 heal_possible = 0);
|
||||
|
||||
@@ -1427,6 +1428,8 @@ protected:
|
||||
int _GetRunSpeed() const;
|
||||
int _GetFearSpeed() const;
|
||||
|
||||
Timer m_z_clip_check_timer;
|
||||
|
||||
virtual bool AI_EngagedCastCheck() { return(false); }
|
||||
virtual bool AI_PursueCastCheck() { return(false); }
|
||||
virtual bool AI_IdleCastCheck() { return(false); }
|
||||
|
||||
+27
-127
@@ -404,16 +404,16 @@ void NPC::AI_Init()
|
||||
AIautocastspell_timer.reset(nullptr);
|
||||
casting_spell_AIindex = static_cast<uint8>(AIspells.size());
|
||||
|
||||
roambox_max_x = 0;
|
||||
roambox_max_y = 0;
|
||||
roambox_min_x = 0;
|
||||
roambox_min_y = 0;
|
||||
roambox_distance = 0;
|
||||
roambox_destination_x = 0;
|
||||
roambox_destination_y = 0;
|
||||
roambox_destination_z = 0;
|
||||
roambox_min_delay = 2500;
|
||||
roambox_delay = 2500;
|
||||
m_roambox.max_x = 0;
|
||||
m_roambox.max_y = 0;
|
||||
m_roambox.min_x = 0;
|
||||
m_roambox.min_y = 0;
|
||||
m_roambox.distance = 0;
|
||||
m_roambox.dest_x = 0;
|
||||
m_roambox.dest_y = 0;
|
||||
m_roambox.dest_z = 0;
|
||||
m_roambox.delay = 2500;
|
||||
m_roambox.min_delay = 2500;
|
||||
}
|
||||
|
||||
void Client::AI_Init()
|
||||
@@ -1071,6 +1071,20 @@ void Mob::AI_Process() {
|
||||
}
|
||||
|
||||
if (engaged) {
|
||||
if (IsNPC() && m_z_clip_check_timer.Check()) {
|
||||
auto t = GetTarget();
|
||||
if (t) {
|
||||
float self_z = GetZ() - GetZOffset();
|
||||
float target_z = t->GetPosition().z - t->GetZOffset();
|
||||
if (DistanceNoZ(GetPosition(), t->GetPosition()) < 75 &&
|
||||
std::abs(self_z - target_z) >= 25 && !CheckLosFN(t)) {
|
||||
float new_z = FindDestGroundZ(t->GetPosition());
|
||||
GMMove(t->GetPosition().x, t->GetPosition().y, new_z + GetZOffset(), t->GetPosition().w, false);
|
||||
FaceTarget(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!(m_PlayerState & static_cast<uint32>(PlayerState::Aggressive)))
|
||||
SendAddPlayerState(PlayerState::Aggressive);
|
||||
|
||||
@@ -1584,123 +1598,9 @@ void NPC::AI_DoMovement() {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Roambox logic sets precedence
|
||||
*/
|
||||
if (roambox_distance > 0) {
|
||||
|
||||
// Check if we're already moving to a WP
|
||||
// If so, if we're not moving we have arrived and need to set delay
|
||||
|
||||
if (GetCWP() == EQ::WaypointStatus::RoamBoxPauseInProgress && !IsMoving()) {
|
||||
// We have arrived
|
||||
|
||||
int roambox_move_delay = EQ::ClampLower(GetRoamboxDelay(), GetRoamboxMinDelay());
|
||||
int move_delay_max = (roambox_move_delay > 0 ? roambox_move_delay : (int) GetRoamboxMinDelay() * 4);
|
||||
int random_timer = RandomTimer(
|
||||
GetRoamboxMinDelay(),
|
||||
move_delay_max
|
||||
);
|
||||
|
||||
LogNPCRoamBoxDetail(
|
||||
"({}) Timer calc | random_timer [{}] roambox_move_delay [{}] move_min [{}] move_max [{}]",
|
||||
GetCleanName(),
|
||||
random_timer,
|
||||
roambox_move_delay,
|
||||
(int) GetRoamboxMinDelay(),
|
||||
move_delay_max
|
||||
);
|
||||
|
||||
time_until_can_move = Timer::GetCurrentTime() + random_timer;
|
||||
SetCurrentWP(0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Set a new destination
|
||||
if (!IsMoving() && time_until_can_move < Timer::GetCurrentTime()) {
|
||||
auto move_x = static_cast<float>(zone->random.Real(-roambox_distance, roambox_distance));
|
||||
auto move_y = static_cast<float>(zone->random.Real(-roambox_distance, roambox_distance));
|
||||
|
||||
roambox_destination_x = EQ::Clamp((GetX() + move_x), roambox_min_x, roambox_max_x);
|
||||
roambox_destination_y = EQ::Clamp((GetY() + move_y), roambox_min_y, roambox_max_y);
|
||||
|
||||
/**
|
||||
* If our roambox was configured with large distances, chances of hitting the min or max end of
|
||||
* the clamp is high, this causes NPC's to gather on the border of a box, to reduce clustering
|
||||
* either lower the roambox distance or the code will do a simple random between min - max when it
|
||||
* hits the min or max of the clamp
|
||||
*/
|
||||
if (roambox_destination_x == roambox_min_x || roambox_destination_x == roambox_max_x) {
|
||||
roambox_destination_x = static_cast<float>(zone->random.Real(roambox_min_x, roambox_max_x));
|
||||
}
|
||||
|
||||
if (roambox_destination_y == roambox_min_y || roambox_destination_y == roambox_max_y) {
|
||||
roambox_destination_y = static_cast<float>(zone->random.Real(roambox_min_y, roambox_max_y));
|
||||
}
|
||||
|
||||
/**
|
||||
* If mob was not spawned in water, let's not randomly roam them into water
|
||||
* if the roam box was sloppily configured
|
||||
*/
|
||||
if (!GetWasSpawnedInWater()) {
|
||||
roambox_destination_z = GetGroundZ(roambox_destination_x, roambox_destination_y);
|
||||
if (zone->HasMap() && zone->HasWaterMap()) {
|
||||
auto position = glm::vec3(
|
||||
roambox_destination_x,
|
||||
roambox_destination_y,
|
||||
roambox_destination_z
|
||||
);
|
||||
|
||||
/**
|
||||
* If someone brought us into water when we naturally wouldn't path there, return to spawn
|
||||
*/
|
||||
if (zone->watermap->InLiquid(position) && zone->watermap->InLiquid(m_Position)) {
|
||||
roambox_destination_x = m_SpawnPoint.x;
|
||||
roambox_destination_y = m_SpawnPoint.y;
|
||||
}
|
||||
|
||||
if (zone->watermap->InLiquid(position)) {
|
||||
LogNPCRoamBoxDetail("[{}] | My destination is in water and I don't belong there!", GetCleanName());
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else { // Mob was in water, make sure new spot is in water also
|
||||
roambox_destination_z = m_Position.z;
|
||||
auto position = glm::vec3(
|
||||
roambox_destination_x,
|
||||
roambox_destination_y,
|
||||
m_Position.z + 15
|
||||
);
|
||||
if (zone->HasWaterMap() && !zone->watermap->InLiquid(position)) {
|
||||
roambox_destination_x = m_SpawnPoint.x;
|
||||
roambox_destination_y = m_SpawnPoint.y;
|
||||
roambox_destination_z = m_SpawnPoint.z;
|
||||
}
|
||||
}
|
||||
|
||||
LogNPCRoamBox("[{}] | Pathing to [{}] [{}] [{}]", GetCleanName(),
|
||||
roambox_destination_x, roambox_destination_y,
|
||||
roambox_destination_z);
|
||||
|
||||
LogNPCRoamBox(
|
||||
"NPC ({}) distance [{}] X (min/max) [{} / {}] Y (min/max) [{} / {}] | Dest x/y/z [{} / {} / {}]",
|
||||
GetCleanName(),
|
||||
roambox_distance,
|
||||
roambox_min_x,
|
||||
roambox_max_x,
|
||||
roambox_min_y,
|
||||
roambox_max_y,
|
||||
roambox_destination_x,
|
||||
roambox_destination_y,
|
||||
roambox_destination_z
|
||||
);
|
||||
|
||||
SetCurrentWP(EQ::WaypointStatus::RoamBoxPauseInProgress);
|
||||
NavigateTo(roambox_destination_x, roambox_destination_y, roambox_destination_z);
|
||||
}
|
||||
|
||||
// Roambox logic sets precedence
|
||||
if (m_roambox.distance > 0) {
|
||||
HandleRoambox();
|
||||
return;
|
||||
}
|
||||
else if (roamer) {
|
||||
|
||||
+8
-10
@@ -795,21 +795,19 @@ void Mob::DisplayInfo(Mob *mob)
|
||||
window_text += WriteDisplayInfoSection(mob, "Proximity", npc_proximity, 1, true);
|
||||
}
|
||||
|
||||
int8 npc_type = npc_scale_manager->GetNPCScalingType(npc);
|
||||
std::string npc_type_string = npc_scale_manager->GetNPCScalingTypeName(npc);
|
||||
|
||||
client->Message(
|
||||
0,
|
||||
"| # Target: %s Type: %i (%s)",
|
||||
npc->GetCleanName(),
|
||||
npc_type,
|
||||
npc_type_string.c_str());
|
||||
Chat::White,
|
||||
fmt::format(
|
||||
"| # Target: {} Type: {} ({})",
|
||||
npc->GetCleanName(),
|
||||
npc_scale_manager->GetNPCScalingType(npc),
|
||||
npc_scale_manager->GetNPCScalingTypeName(npc)
|
||||
).c_str()
|
||||
);
|
||||
|
||||
NPCCommandsMenu(client, npc);
|
||||
}
|
||||
|
||||
// std::cout << "Window Length: " << window_text.length() << std::endl;
|
||||
|
||||
if (client->GetDisplayMobInfoWindow()) {
|
||||
client->SendFullPopup(
|
||||
"GM: Entity Info",
|
||||
|
||||
@@ -236,7 +236,7 @@ public:
|
||||
|
||||
if (RuleB(Map, FixZWhenPathing)) {
|
||||
m_distance_moved_since_correction += distance_moved;
|
||||
if (m_distance_moved_since_correction > RuleR(Map, DistanceCanTravelBeforeAdjustment)) {
|
||||
if (m_distance_moved_since_correction > (mob->IsEngaged() ? 1 : 10)) {
|
||||
m_distance_moved_since_correction = 0.0;
|
||||
mob->FixZ();
|
||||
}
|
||||
|
||||
+171
-19
@@ -159,6 +159,9 @@ NPC::NPC(const NPCType *npc_type_data, Spawn2 *in_respawn, const glm::vec4 &posi
|
||||
if (race == RACE_LAVA_DRAGON_49) {
|
||||
size = 5;
|
||||
}
|
||||
if (race == RACE_WURM_158) {
|
||||
size = 15;
|
||||
}
|
||||
|
||||
taunting = false;
|
||||
proximity = nullptr;
|
||||
@@ -255,15 +258,18 @@ NPC::NPC(const NPCType *npc_type_data, Spawn2 *in_respawn, const glm::vec4 &posi
|
||||
}
|
||||
|
||||
guard_anim = eaStanding;
|
||||
roambox_distance = 0;
|
||||
roambox_max_x = -2;
|
||||
roambox_max_y = -2;
|
||||
roambox_min_x = -2;
|
||||
roambox_min_y = -2;
|
||||
roambox_destination_x = -2;
|
||||
roambox_destination_y = -2;
|
||||
roambox_min_delay = 1000;
|
||||
roambox_delay = 1000;
|
||||
|
||||
m_roambox.max_x = -2;
|
||||
m_roambox.max_y = -2;
|
||||
m_roambox.min_x = -2;
|
||||
m_roambox.min_y = -2;
|
||||
m_roambox.distance = 0;
|
||||
m_roambox.dest_x = -2;
|
||||
m_roambox.dest_y = -2;
|
||||
m_roambox.dest_z = 0;
|
||||
m_roambox.delay = 1000;
|
||||
m_roambox.min_delay = 1000;
|
||||
|
||||
p_depop = false;
|
||||
loottable_id = npc_type_data->loottable_id;
|
||||
skip_global_loot = npc_type_data->skip_global_loot;
|
||||
@@ -440,52 +446,52 @@ NPC::NPC(const NPCType *npc_type_data, Spawn2 *in_respawn, const glm::vec4 &posi
|
||||
|
||||
float NPC::GetRoamboxMaxX() const
|
||||
{
|
||||
return roambox_max_x;
|
||||
return m_roambox.max_x;
|
||||
}
|
||||
|
||||
float NPC::GetRoamboxMaxY() const
|
||||
{
|
||||
return roambox_max_y;
|
||||
return m_roambox.max_y;
|
||||
}
|
||||
|
||||
float NPC::GetRoamboxMinX() const
|
||||
{
|
||||
return roambox_min_x;
|
||||
return m_roambox.min_x;
|
||||
}
|
||||
|
||||
float NPC::GetRoamboxMinY() const
|
||||
{
|
||||
return roambox_min_y;
|
||||
return m_roambox.min_y;
|
||||
}
|
||||
|
||||
float NPC::GetRoamboxDistance() const
|
||||
{
|
||||
return roambox_distance;
|
||||
return m_roambox.distance;
|
||||
}
|
||||
|
||||
float NPC::GetRoamboxDestinationX() const
|
||||
{
|
||||
return roambox_destination_x;
|
||||
return m_roambox.dest_x;
|
||||
}
|
||||
|
||||
float NPC::GetRoamboxDestinationY() const
|
||||
{
|
||||
return roambox_destination_y;
|
||||
return m_roambox.dest_y;
|
||||
}
|
||||
|
||||
float NPC::GetRoamboxDestinationZ() const
|
||||
{
|
||||
return roambox_destination_z;
|
||||
return m_roambox.dest_z;
|
||||
}
|
||||
|
||||
uint32 NPC::GetRoamboxDelay() const
|
||||
{
|
||||
return roambox_delay;
|
||||
return m_roambox.delay;
|
||||
}
|
||||
|
||||
uint32 NPC::GetRoamboxMinDelay() const
|
||||
{
|
||||
return roambox_min_delay;
|
||||
return m_roambox.min_delay;
|
||||
}
|
||||
|
||||
NPC::~NPC()
|
||||
@@ -3811,3 +3817,149 @@ void NPC::SendPositionToClients()
|
||||
}
|
||||
safe_delete(p);
|
||||
}
|
||||
|
||||
void NPC::HandleRoambox()
|
||||
{
|
||||
bool has_arrived = GetCWP() == EQ::WaypointStatus::RoamBoxPauseInProgress && !IsMoving();
|
||||
if (has_arrived) {
|
||||
int roambox_move_delay = EQ::ClampLower(GetRoamboxDelay(), GetRoamboxMinDelay());
|
||||
int move_delay_max = (roambox_move_delay > 0 ? roambox_move_delay : (int) GetRoamboxMinDelay() * 4);
|
||||
int random_timer = RandomTimer(
|
||||
GetRoamboxMinDelay(),
|
||||
move_delay_max
|
||||
);
|
||||
|
||||
LogNPCRoamBoxDetail(
|
||||
"({}) random_timer [{}] roambox_move_delay [{}] move_min [{}] move_max [{}]",
|
||||
GetCleanName(),
|
||||
random_timer,
|
||||
roambox_move_delay,
|
||||
(int) GetRoamboxMinDelay(),
|
||||
move_delay_max
|
||||
);
|
||||
|
||||
time_until_can_move = Timer::GetCurrentTime() + random_timer;
|
||||
SetCurrentWP(0);
|
||||
return;
|
||||
}
|
||||
|
||||
bool ready_to_set_new_destination = !IsMoving() && time_until_can_move < Timer::GetCurrentTime();
|
||||
if (ready_to_set_new_destination) {
|
||||
// make several attempts to find a valid next move in the box
|
||||
bool can_path = false;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
auto move_x = static_cast<float>(zone->random.Real(-m_roambox.distance, m_roambox.distance));
|
||||
auto move_y = static_cast<float>(zone->random.Real(-m_roambox.distance, m_roambox.distance));
|
||||
auto requested_x = EQ::Clamp((GetX() + move_x), m_roambox.min_x, m_roambox.max_x);
|
||||
auto requested_y = EQ::Clamp((GetY() + move_y), m_roambox.min_y, m_roambox.max_y);
|
||||
auto requested_z = GetGroundZ(requested_x, requested_y);
|
||||
|
||||
std::vector<float> heights = {0, 250, -250};
|
||||
for (auto &h: heights) {
|
||||
if (CheckLosFN(requested_x, requested_y, requested_z + h, GetSize())) {
|
||||
LogNPCRoamBox("[{}] Found line of sight to path attempt [{}] at height [{}]", GetCleanName(), i, h);
|
||||
can_path = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!can_path) {
|
||||
LogNPCRoamBox("[{}] | Failed line of sight to path attempt [{}]", GetCleanName(), i);
|
||||
continue;
|
||||
}
|
||||
|
||||
m_roambox.dest_x = requested_x;
|
||||
m_roambox.dest_y = requested_y;
|
||||
|
||||
/**
|
||||
* If our roambox was configured with large distances, chances of hitting the min or max end of
|
||||
* the clamp is high, this causes NPC's to gather on the border of a box, to reduce clustering
|
||||
* either lower the roambox distance or the code will do a simple random between min - max when it
|
||||
* hits the min or max of the clamp
|
||||
*/
|
||||
if (m_roambox.dest_x == m_roambox.min_x || m_roambox.dest_x == m_roambox.max_x) {
|
||||
m_roambox.dest_x = static_cast<float>(zone->random.Real(m_roambox.min_x, m_roambox.max_x));
|
||||
}
|
||||
|
||||
if (m_roambox.dest_y == m_roambox.min_y || m_roambox.dest_y == m_roambox.max_y) {
|
||||
m_roambox.dest_y = static_cast<float>(zone->random.Real(m_roambox.min_y, m_roambox.max_y));
|
||||
}
|
||||
|
||||
// If mob was not spawned in water, let's not randomly roam them into water
|
||||
// if the roam box was sloppily configured
|
||||
if (!GetWasSpawnedInWater()) {
|
||||
m_roambox.dest_z = GetGroundZ(m_roambox.dest_x, m_roambox.dest_y);
|
||||
if (zone->HasMap() && zone->HasWaterMap()) {
|
||||
auto position = glm::vec3(
|
||||
m_roambox.dest_x,
|
||||
m_roambox.dest_y,
|
||||
m_roambox.dest_z
|
||||
);
|
||||
|
||||
// If someone brought us into water when we naturally wouldn't path there, return to spawn
|
||||
if (zone->watermap->InLiquid(position) && zone->watermap->InLiquid(m_Position)) {
|
||||
m_roambox.dest_x = m_SpawnPoint.x;
|
||||
m_roambox.dest_y = m_SpawnPoint.y;
|
||||
}
|
||||
|
||||
if (zone->watermap->InLiquid(position)) {
|
||||
LogNPCRoamBoxDetail("[{}] | My destination is in water and I don't belong there!", GetCleanName());
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else { // Mob was in water, make sure new spot is in water also
|
||||
m_roambox.dest_z = m_Position.z;
|
||||
auto position = glm::vec3(
|
||||
m_roambox.dest_x,
|
||||
m_roambox.dest_y,
|
||||
m_Position.z + 15
|
||||
);
|
||||
if (zone->HasWaterMap() && !zone->watermap->InLiquid(position)) {
|
||||
m_roambox.dest_x = m_SpawnPoint.x;
|
||||
m_roambox.dest_y = m_SpawnPoint.y;
|
||||
m_roambox.dest_z = m_SpawnPoint.z;
|
||||
}
|
||||
}
|
||||
|
||||
LogNPCRoamBox(
|
||||
"[{}] | Pathing to [{}] [{}] [{}]",
|
||||
GetCleanName(),
|
||||
m_roambox.dest_x,
|
||||
m_roambox.dest_y,
|
||||
m_roambox.dest_z
|
||||
);
|
||||
|
||||
LogNPCRoamBox(
|
||||
"NPC ({}) distance [{}] X (min/max) [{} / {}] Y (min/max) [{} / {}] | Dest x/y/z [{} / {} / {}]",
|
||||
GetCleanName(),
|
||||
m_roambox.distance,
|
||||
m_roambox.min_x,
|
||||
m_roambox.max_x,
|
||||
m_roambox.min_y,
|
||||
m_roambox.max_y,
|
||||
m_roambox.dest_x,
|
||||
m_roambox.dest_y,
|
||||
m_roambox.dest_z
|
||||
);
|
||||
|
||||
if (can_path) {
|
||||
SetCurrentWP(EQ::WaypointStatus::RoamBoxPauseInProgress);
|
||||
NavigateTo(m_roambox.dest_x, m_roambox.dest_y, m_roambox.dest_z);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// failed to find path, reset timer
|
||||
int roambox_move_delay = EQ::ClampLower(GetRoamboxDelay(), GetRoamboxMinDelay());
|
||||
int move_delay_max = (roambox_move_delay > 0 ? roambox_move_delay : (int) GetRoamboxMinDelay() * 4);
|
||||
int random_timer = RandomTimer(
|
||||
GetRoamboxMinDelay(),
|
||||
move_delay_max
|
||||
);
|
||||
time_until_can_move = Timer::GetCurrentTime() + random_timer;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
+17
-10
@@ -80,6 +80,19 @@ struct AISpellsVar_Struct {
|
||||
uint8 idle_beneficial_chance;
|
||||
};
|
||||
|
||||
struct Roambox {
|
||||
float max_x;
|
||||
float max_y;
|
||||
float min_x;
|
||||
float min_y;
|
||||
float distance;
|
||||
float dest_x;
|
||||
float dest_y;
|
||||
float dest_z;
|
||||
uint32 delay;
|
||||
uint32 min_delay;
|
||||
};
|
||||
|
||||
class SwarmPet;
|
||||
class Client;
|
||||
class Group;
|
||||
@@ -538,6 +551,8 @@ public:
|
||||
|
||||
protected:
|
||||
|
||||
void HandleRoambox();
|
||||
|
||||
const NPCType* NPCTypedata;
|
||||
NPCType* NPCTypedata_ours; //special case for npcs with uniquely created data.
|
||||
|
||||
@@ -635,16 +650,8 @@ protected:
|
||||
glm::vec4 m_GuardPoint;
|
||||
glm::vec4 m_GuardPointSaved;
|
||||
EmuAppearance guard_anim;
|
||||
float roambox_max_x;
|
||||
float roambox_max_y;
|
||||
float roambox_min_x;
|
||||
float roambox_min_y;
|
||||
float roambox_distance;
|
||||
float roambox_destination_x;
|
||||
float roambox_destination_y;
|
||||
float roambox_destination_z;
|
||||
uint32 roambox_delay;
|
||||
uint32 roambox_min_delay;
|
||||
|
||||
Roambox m_roambox = {};
|
||||
|
||||
uint16 skills[EQ::skills::HIGHEST_SKILL + 1];
|
||||
|
||||
|
||||
+87
-24
@@ -30,21 +30,27 @@ void NpcScaleManager::ScaleNPC(
|
||||
NPC *npc,
|
||||
bool always_scale,
|
||||
bool override_special_abilities
|
||||
)
|
||||
{
|
||||
) {
|
||||
if (npc->IsSkipAutoScale() || npc->GetNPCTypeID() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int8 npc_type = GetNPCScalingType(npc);
|
||||
int npc_level = npc->GetLevel();
|
||||
bool is_auto_scaled = IsAutoScaled(npc);
|
||||
auto npc_type = GetNPCScalingType(npc);
|
||||
auto npc_level = npc->GetLevel();
|
||||
auto is_auto_scaled = IsAutoScaled(npc);
|
||||
auto zone_id = zone->GetZoneID();
|
||||
auto instance_version = zone->GetInstanceVersion();
|
||||
|
||||
global_npc_scale scale_data = GetGlobalScaleDataForTypeLevel(npc_type, npc_level);
|
||||
global_npc_scale scale_data = GetGlobalScaleDataForTypeLevel(
|
||||
npc_type,
|
||||
npc_level,
|
||||
zone_id,
|
||||
instance_version
|
||||
);
|
||||
|
||||
if (!scale_data.level) {
|
||||
LogNPCScaling(
|
||||
"NPC: [{}] - scaling data not found for type: [{}] level: [{}]",
|
||||
"NPC: [{}] - scaling data not found for type [{}] level [{}]",
|
||||
npc->GetCleanName(),
|
||||
npc_type,
|
||||
npc_level
|
||||
@@ -209,11 +215,13 @@ void NpcScaleManager::ResetNPCScaling(NPC* npc)
|
||||
bool NpcScaleManager::LoadScaleData()
|
||||
{
|
||||
auto rows = NpcScaleGlobalBaseRepository::All(content_db);
|
||||
for (auto &s: rows) {
|
||||
for (const auto &s : rows) {
|
||||
global_npc_scale scale_data;
|
||||
|
||||
scale_data.type = s.type;
|
||||
scale_data.level = s.level;
|
||||
scale_data.zone_id = s.zone_id;
|
||||
scale_data.instance_version = s.instance_version;
|
||||
scale_data.ac = s.ac;
|
||||
scale_data.hp = s.hp;
|
||||
scale_data.accuracy = s.accuracy;
|
||||
@@ -246,7 +254,12 @@ bool NpcScaleManager::LoadScaleData()
|
||||
|
||||
npc_global_base_scaling_data.insert(
|
||||
std::make_pair(
|
||||
std::make_pair(scale_data.type, scale_data.level),
|
||||
std::make_tuple(
|
||||
scale_data.type,
|
||||
scale_data.level,
|
||||
scale_data.zone_id,
|
||||
scale_data.instance_version
|
||||
),
|
||||
scale_data
|
||||
)
|
||||
);
|
||||
@@ -262,9 +275,45 @@ bool NpcScaleManager::LoadScaleData()
|
||||
* @param npc_level
|
||||
* @return NpcScaleManager::global_npc_scale
|
||||
*/
|
||||
NpcScaleManager::global_npc_scale NpcScaleManager::GetGlobalScaleDataForTypeLevel(int8 npc_type, int npc_level)
|
||||
{
|
||||
auto iter = npc_global_base_scaling_data.find(std::make_pair(npc_type, npc_level));
|
||||
NpcScaleManager::global_npc_scale NpcScaleManager::GetGlobalScaleDataForTypeLevel(
|
||||
int8 npc_type,
|
||||
uint8 npc_level,
|
||||
uint32 zone_id,
|
||||
uint16 instance_version
|
||||
) {
|
||||
auto iter = npc_global_base_scaling_data.find(
|
||||
std::make_tuple(
|
||||
npc_type,
|
||||
npc_level,
|
||||
zone_id,
|
||||
instance_version
|
||||
)
|
||||
);
|
||||
|
||||
if (iter != npc_global_base_scaling_data.end()) {
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
iter = npc_global_base_scaling_data.find(
|
||||
std::make_tuple(
|
||||
npc_type,
|
||||
npc_level,
|
||||
zone_id,
|
||||
0
|
||||
)
|
||||
);
|
||||
if (iter != npc_global_base_scaling_data.end()) {
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
iter = npc_global_base_scaling_data.find(
|
||||
std::make_tuple(
|
||||
npc_type,
|
||||
npc_level,
|
||||
0,
|
||||
0
|
||||
)
|
||||
);
|
||||
if (iter != npc_global_base_scaling_data.end()) {
|
||||
return iter->second;
|
||||
}
|
||||
@@ -487,14 +536,21 @@ bool NpcScaleManager::IsAutoScaled(NPC* npc)
|
||||
*/
|
||||
bool NpcScaleManager::ApplyGlobalBaseScalingToNPCStatically(NPC *&npc)
|
||||
{
|
||||
int8 npc_type = GetNPCScalingType(npc);
|
||||
int npc_level = npc->GetLevel();
|
||||
auto npc_type = GetNPCScalingType(npc);
|
||||
auto npc_level = npc->GetLevel();
|
||||
auto zone_id = zone->GetZoneID();
|
||||
auto instance_version = zone->GetInstanceVersion();
|
||||
|
||||
global_npc_scale g = GetGlobalScaleDataForTypeLevel(npc_type, npc_level);
|
||||
global_npc_scale g = GetGlobalScaleDataForTypeLevel(
|
||||
npc_type,
|
||||
npc_level,
|
||||
zone_id,
|
||||
instance_version
|
||||
);
|
||||
|
||||
if (!g.level) {
|
||||
LogNPCScaling(
|
||||
"NPC: [{}] - scaling data not found for type: [{}] level: [{}]",
|
||||
"NPC: [{}] - scaling data not found for type [{}] level [{}]",
|
||||
npc->GetCleanName(),
|
||||
npc_type,
|
||||
npc_level
|
||||
@@ -503,7 +559,7 @@ bool NpcScaleManager::ApplyGlobalBaseScalingToNPCStatically(NPC *&npc)
|
||||
return false;
|
||||
}
|
||||
|
||||
auto n = NpcTypesRepository::FindOne(content_db, (int) npc->GetNPCTypeID());
|
||||
auto n = NpcTypesRepository::FindOne(content_db, static_cast<int>(npc->GetNPCTypeID()));
|
||||
if (n.id > 0) {
|
||||
n.AC = g.ac;
|
||||
n.hp = g.hp;
|
||||
@@ -528,8 +584,8 @@ bool NpcScaleManager::ApplyGlobalBaseScalingToNPCStatically(NPC *&npc)
|
||||
n.maxdmg = g.max_dmg;
|
||||
n.hp_regen_rate = g.hp_regen_rate;
|
||||
n.attack_delay = g.attack_delay;
|
||||
n.spellscale = (float) g.spell_scale;
|
||||
n.healscale = (float) g.heal_scale;
|
||||
n.spellscale = static_cast<float>(g.spell_scale);
|
||||
n.healscale = static_cast<float>(g.heal_scale);
|
||||
n.special_abilities = g.special_abilities;
|
||||
|
||||
return NpcTypesRepository::UpdateOne(content_db, n);
|
||||
@@ -545,14 +601,21 @@ bool NpcScaleManager::ApplyGlobalBaseScalingToNPCStatically(NPC *&npc)
|
||||
*/
|
||||
bool NpcScaleManager::ApplyGlobalBaseScalingToNPCDynamically(NPC *&npc)
|
||||
{
|
||||
int8 npc_type = GetNPCScalingType(npc);
|
||||
int npc_level = npc->GetLevel();
|
||||
auto npc_type = GetNPCScalingType(npc);
|
||||
auto npc_level = npc->GetLevel();
|
||||
auto zone_id = zone->GetZoneID();
|
||||
auto instance_version = zone->GetInstanceVersion();
|
||||
|
||||
global_npc_scale d = GetGlobalScaleDataForTypeLevel(npc_type, npc_level);
|
||||
global_npc_scale d = GetGlobalScaleDataForTypeLevel(
|
||||
npc_type,
|
||||
npc_level,
|
||||
zone_id,
|
||||
instance_version
|
||||
);
|
||||
|
||||
if (!d.level) {
|
||||
LogNPCScaling(
|
||||
"NPC: [{}] - scaling data not found for type: [{}] level: [{}]",
|
||||
"NPC: [{}] - scaling data not found for type [{}] level [{}]",
|
||||
npc->GetCleanName(),
|
||||
npc_type,
|
||||
npc_level
|
||||
@@ -561,7 +624,7 @@ bool NpcScaleManager::ApplyGlobalBaseScalingToNPCDynamically(NPC *&npc)
|
||||
return false;
|
||||
}
|
||||
|
||||
auto n = NpcTypesRepository::FindOne(content_db, (int) npc->GetNPCTypeID());
|
||||
auto n = NpcTypesRepository::FindOne(content_db, static_cast<int>(npc->GetNPCTypeID()));
|
||||
if (n.id > 0) {
|
||||
n.AC = 0;
|
||||
n.hp = 0;
|
||||
|
||||
+38
-29
@@ -22,37 +22,41 @@
|
||||
#define EQEMU_NPC_SCALE_MANAGER_H
|
||||
|
||||
#include "npc.h"
|
||||
#include "zone.h"
|
||||
extern Zone* zone;
|
||||
|
||||
class NpcScaleManager {
|
||||
public:
|
||||
struct global_npc_scale {
|
||||
int type;
|
||||
int level;
|
||||
int ac;
|
||||
int64 hp;
|
||||
int accuracy;
|
||||
int slow_mitigation;
|
||||
int attack;
|
||||
int strength;
|
||||
int stamina;
|
||||
int dexterity;
|
||||
int agility;
|
||||
int intelligence;
|
||||
int wisdom;
|
||||
int charisma;
|
||||
int magic_resist;
|
||||
int cold_resist;
|
||||
int fire_resist;
|
||||
int poison_resist;
|
||||
int disease_resist;
|
||||
int corruption_resist;
|
||||
int physical_resist;
|
||||
int min_dmg;
|
||||
int max_dmg;
|
||||
int64 hp_regen_rate;
|
||||
int attack_delay;
|
||||
int spell_scale;
|
||||
int heal_scale;
|
||||
int8 type;
|
||||
uint8 level;
|
||||
uint32 zone_id;
|
||||
uint16 instance_version;
|
||||
int ac;
|
||||
int64 hp;
|
||||
int accuracy;
|
||||
int slow_mitigation;
|
||||
int attack;
|
||||
int strength;
|
||||
int stamina;
|
||||
int dexterity;
|
||||
int agility;
|
||||
int intelligence;
|
||||
int wisdom;
|
||||
int charisma;
|
||||
int magic_resist;
|
||||
int cold_resist;
|
||||
int fire_resist;
|
||||
int poison_resist;
|
||||
int disease_resist;
|
||||
int corruption_resist;
|
||||
int physical_resist;
|
||||
int min_dmg;
|
||||
int max_dmg;
|
||||
int64 hp_regen_rate;
|
||||
int attack_delay;
|
||||
int spell_scale;
|
||||
int heal_scale;
|
||||
|
||||
std::string special_abilities;
|
||||
};
|
||||
@@ -91,9 +95,14 @@ public:
|
||||
bool IsAutoScaled(NPC* npc);
|
||||
bool LoadScaleData();
|
||||
|
||||
global_npc_scale GetGlobalScaleDataForTypeLevel(int8 npc_type, int npc_level);
|
||||
global_npc_scale GetGlobalScaleDataForTypeLevel(
|
||||
int8 npc_type,
|
||||
uint8 npc_level,
|
||||
uint32 zone_id,
|
||||
uint16 instance_version
|
||||
);
|
||||
|
||||
std::map<std::pair<int, int>, global_npc_scale> npc_global_base_scaling_data;
|
||||
std::map<std::tuple<int8, uint8, uint32, uint16>, global_npc_scale> npc_global_base_scaling_data;
|
||||
|
||||
int8 GetNPCScalingType(NPC * &npc);
|
||||
std::string GetNPCScalingTypeName(NPC * &npc);
|
||||
|
||||
+1
-1
@@ -90,7 +90,7 @@ uint32 Perl_Bot_CountBotItem(Bot* self, uint32 item_id)
|
||||
return self->CountBotItem(item_id);
|
||||
}
|
||||
|
||||
bool Perl_Bot_HasBotItem(Bot* self, uint32 item_id)
|
||||
int16 Perl_Bot_HasBotItem(Bot* self, uint32 item_id)
|
||||
{
|
||||
return self->HasBotItem(item_id);
|
||||
}
|
||||
|
||||
@@ -2755,6 +2755,11 @@ void Perl_Client_Signal(Client* self, int signal_id)
|
||||
self->Signal(signal_id);
|
||||
}
|
||||
|
||||
void Perl_Client_SignalClient(Client* self, int signal_id) // @categories Script Utility
|
||||
{
|
||||
self->Signal(signal_id);
|
||||
}
|
||||
|
||||
std::string Perl_Client_GetGuildPublicNote(Client* self)
|
||||
{
|
||||
return self->GetGuildPublicNote();
|
||||
@@ -2892,6 +2897,16 @@ void Perl_Client_UseAugmentContainer(Client* self, int container_slot)
|
||||
self->UseAugmentContainer(container_slot);
|
||||
}
|
||||
|
||||
bool Perl_Client_IsAutoAttackEnabled(Client* self)
|
||||
{
|
||||
return self->AutoAttackEnabled();
|
||||
}
|
||||
|
||||
bool Perl_Client_IsAutoFireEnabled(Client* self)
|
||||
{
|
||||
return self->AutoFireEnabled();
|
||||
}
|
||||
|
||||
void perl_register_client()
|
||||
{
|
||||
perl::interpreter perl(PERL_GET_THX);
|
||||
@@ -3154,6 +3169,8 @@ void perl_register_client()
|
||||
package.add("IncreaseSkill", (void(*)(Client*, int))&Perl_Client_IncreaseSkill);
|
||||
package.add("IncreaseSkill", (void(*)(Client*, int, int))&Perl_Client_IncreaseSkill);
|
||||
package.add("IncrementAA", &Perl_Client_IncrementAA);
|
||||
package.add("IsAutoAttackEnabled", &Perl_Client_IsAutoAttackEnabled);
|
||||
package.add("IsAutoFireEnabled", &Perl_Client_IsAutoFireEnabled);
|
||||
package.add("IsBecomeNPC", &Perl_Client_IsBecomeNPC);
|
||||
package.add("IsCrouching", &Perl_Client_IsCrouching);
|
||||
package.add("IsDueling", &Perl_Client_IsDueling);
|
||||
@@ -3362,6 +3379,7 @@ void perl_register_client()
|
||||
package.add("SetTitleSuffix", (void(*)(Client*, std::string, bool))&Perl_Client_SetTitleSuffix);
|
||||
package.add("SetZoneFlag", &Perl_Client_SetZoneFlag);
|
||||
package.add("Signal", &Perl_Client_Signal);
|
||||
package.add("SignalClient", &Perl_Client_SignalClient);
|
||||
package.add("SilentMessage", &Perl_Client_SilentMessage);
|
||||
package.add("Sit", &Perl_Client_Sit);
|
||||
package.add("SlotConvert2", &Perl_Client_SlotConvert2);
|
||||
|
||||
@@ -584,6 +584,16 @@ bool Perl_NPC_GetCombatState(NPC* self) // @categories Script Utility
|
||||
return self->GetCombatEvent();
|
||||
}
|
||||
|
||||
void Perl_NPC_SetSimpleRoamBox(NPC* self, float box_size) // @categories Script Utility
|
||||
{
|
||||
self->SetSimpleRoamBox(box_size);
|
||||
}
|
||||
|
||||
void Perl_NPC_SetSimpleRoamBox(NPC* self, float box_size, float move_distance) // @categories Script Utility
|
||||
{
|
||||
self->SetSimpleRoamBox(box_size, move_distance);
|
||||
}
|
||||
|
||||
void Perl_NPC_SetSimpleRoamBox(NPC* self, float box_size, float move_distance, int move_delay) // @categories Script Utility
|
||||
{
|
||||
self->SetSimpleRoamBox(box_size, move_distance, move_delay);
|
||||
|
||||
+21
-9
@@ -88,19 +88,28 @@ void QuestManager::Process() {
|
||||
end = QTimerList.end();
|
||||
while (cur != end) {
|
||||
if (cur->Timer_.Enabled() && cur->Timer_.Check()) {
|
||||
if(entity_list.IsMobInZone(cur->mob)) {
|
||||
if(cur->mob->IsNPC()) {
|
||||
if (cur->mob && entity_list.IsMobInZone(cur->mob)) {
|
||||
if (cur->mob->IsNPC()) {
|
||||
if (parse->HasQuestSub(cur->mob->GetNPCTypeID(), EVENT_TIMER)) {
|
||||
parse->EventNPC(EVENT_TIMER, cur->mob->CastToNPC(), nullptr, cur->name, 0);
|
||||
}
|
||||
} else if (cur->mob->IsEncounter()) {
|
||||
parse->EventEncounter(EVENT_TIMER, cur->mob->CastToEncounter()->GetEncounterName(), cur->name, 0, nullptr);
|
||||
} else if (cur->mob->IsClient()) {
|
||||
}
|
||||
else if (cur->mob->IsEncounter()) {
|
||||
parse->EventEncounter(
|
||||
EVENT_TIMER,
|
||||
cur->mob->CastToEncounter()->GetEncounterName(),
|
||||
cur->name,
|
||||
0,
|
||||
nullptr
|
||||
);
|
||||
}
|
||||
else if (cur->mob->IsClient()) {
|
||||
if (parse->PlayerHasQuestSub(EVENT_TIMER)) {
|
||||
//this is inheriently unsafe if we ever make it so more than npc/client start timers
|
||||
parse->EventPlayer(EVENT_TIMER, cur->mob->CastToClient(), cur->name, 0);
|
||||
}
|
||||
} else if (cur->mob->IsBot()) {
|
||||
}
|
||||
else if (cur->mob->IsBot()) {
|
||||
if (parse->BotHasQuestSub(EVENT_TIMER)) {
|
||||
parse->EventBot(EVENT_TIMER, cur->mob->CastToBot(), nullptr, cur->name, 0);
|
||||
}
|
||||
@@ -109,12 +118,15 @@ void QuestManager::Process() {
|
||||
//we MUST reset our iterator since the quest could have removed/added any
|
||||
//number of timers... worst case we have to check a bunch of timers twice
|
||||
cur = QTimerList.begin();
|
||||
end = QTimerList.end(); //dunno if this is needed, cant hurt...
|
||||
} else {
|
||||
end = QTimerList.end(); //dunno if this is needed, cant hurt...
|
||||
}
|
||||
else {
|
||||
cur = QTimerList.erase(cur);
|
||||
}
|
||||
} else
|
||||
}
|
||||
else {
|
||||
++cur;
|
||||
}
|
||||
}
|
||||
|
||||
auto cur_iter = STimerList.begin();
|
||||
|
||||
+47
-35
@@ -469,22 +469,11 @@ void Object::HandleCombine(Client* user, const NewCombine_Struct* in_combine, Ob
|
||||
}
|
||||
|
||||
// Check if Combine would result in Lore conflict
|
||||
for (const auto& e : spec.onsuccess) {
|
||||
auto success_item_inst = database.GetItem(e.first);
|
||||
if (success_item_inst->LoreGroup > 0) {
|
||||
continue;
|
||||
}
|
||||
if (user->CheckLoreConflict(success_item_inst)) {
|
||||
EQ::SayLinkEngine linker;
|
||||
linker.SetLinkType(EQ::saylink::SayLinkItemData);
|
||||
linker.SetItemData(success_item_inst);
|
||||
auto item_link = linker.GenerateLink();
|
||||
user->MessageString(Chat::Red, TRADESKILL_COMBINE_LORE, item_link.c_str());
|
||||
auto outapp = new EQApplicationPacket(OP_TradeSkillCombine, 0);
|
||||
user->QueuePacket(outapp);
|
||||
safe_delete(outapp);
|
||||
return;
|
||||
}
|
||||
if (user->CheckTradeskillLoreConflict(spec.recipe_id)) {
|
||||
auto outapp = new EQApplicationPacket(OP_TradeSkillCombine, 0);
|
||||
user->QueuePacket(outapp);
|
||||
safe_delete(outapp);
|
||||
return;
|
||||
}
|
||||
|
||||
// final check for any additional quest requirements .. "check_zone" in this case - exported as variable [validate_type]
|
||||
@@ -622,9 +611,9 @@ void Object::HandleAutoCombine(Client* user, const RecipeAutoCombine_Struct* rac
|
||||
}
|
||||
|
||||
//pull the list of components
|
||||
std::string query = StringFormat("SELECT tre.item_id, tre.componentcount "
|
||||
"FROM tradeskill_recipe_entries AS tre "
|
||||
"WHERE tre.componentcount > 0 AND tre.recipe_id = %u",
|
||||
const auto query = fmt::format("SELECT item_id, componentcount "
|
||||
"FROM tradeskill_recipe_entries "
|
||||
"WHERE componentcount > 0 AND recipe_id = {}",
|
||||
rac->recipe_id);
|
||||
auto results = content_db.QueryDatabase(query);
|
||||
if (!results.Success()) {
|
||||
@@ -698,6 +687,13 @@ void Object::HandleAutoCombine(Client* user, const RecipeAutoCombine_Struct* rac
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if Combine would result in Lore conflict
|
||||
if (user->CheckTradeskillLoreConflict(rac->recipe_id)) {
|
||||
user->QueuePacket(outapp);
|
||||
safe_delete(outapp);
|
||||
return;
|
||||
}
|
||||
|
||||
//now we know they have everything...
|
||||
|
||||
//remove all the items from the players inventory, with updates...
|
||||
@@ -727,22 +723,6 @@ void Object::HandleAutoCombine(Client* user, const RecipeAutoCombine_Struct* rac
|
||||
}
|
||||
}
|
||||
|
||||
DBTradeskillRecipe_Struct recipe_struct;
|
||||
|
||||
// Check if Combine would result in Lore conflict
|
||||
for (const auto& e : recipe_struct.onsuccess) {
|
||||
auto success_item_inst = database.GetItem(e.first);
|
||||
if (user->CheckLoreConflict(success_item_inst)) {
|
||||
EQ::SayLinkEngine linker;
|
||||
linker.SetLinkType(EQ::saylink::SayLinkItemData);
|
||||
linker.SetItemData(success_item_inst);
|
||||
auto item_link = linker.GenerateLink();
|
||||
user->MessageString(Chat::Red, TRADESKILL_COMBINE_LORE, item_link.c_str());
|
||||
user->QueuePacket(outapp);
|
||||
safe_delete(outapp);
|
||||
return;
|
||||
}
|
||||
}
|
||||
//otherwise, we found it all...
|
||||
outp->reply_code = 0x00000000; //success for finding it...
|
||||
user->QueuePacket(outapp);
|
||||
@@ -1882,3 +1862,35 @@ bool ZoneDatabase::DisableRecipe(uint32 recipe_id)
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Client::CheckTradeskillLoreConflict(int32 recipe_id)
|
||||
{
|
||||
const auto& recipe_entries = TradeskillRecipeEntriesRepository::GetWhere(
|
||||
content_db,
|
||||
fmt::format(
|
||||
"recipe_id = {} ORDER BY id ASC",
|
||||
recipe_id
|
||||
)
|
||||
);
|
||||
if (recipe_entries.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto& e : recipe_entries) {
|
||||
auto item_inst = database.GetItem(e.item_id);
|
||||
if (item_inst) {
|
||||
if (item_inst->LoreGroup == 0 || e.componentcount > 0 || e.iscontainer) {
|
||||
continue;
|
||||
}
|
||||
if (CheckLoreConflict(item_inst)) {
|
||||
EQ::SayLinkEngine linker;
|
||||
linker.SetLinkType(EQ::saylink::SayLinkItemData);
|
||||
linker.SetItemData(item_inst);
|
||||
auto item_link = linker.GenerateLink();
|
||||
MessageString(Chat::Red, TRADESKILL_COMBINE_LORE, item_link.c_str());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
+22
-23
@@ -66,14 +66,14 @@ void NPC::AI_SetRoambox(
|
||||
uint32 min_delay
|
||||
)
|
||||
{
|
||||
roambox_distance = distance;
|
||||
roambox_max_x = max_x;
|
||||
roambox_min_x = min_x;
|
||||
roambox_max_y = max_y;
|
||||
roambox_min_y = min_y;
|
||||
roambox_destination_x = roambox_max_x + 1; // this will trigger a recalc
|
||||
roambox_delay = delay;
|
||||
roambox_min_delay = min_delay;
|
||||
m_roambox.distance = distance;
|
||||
m_roambox.max_x = max_x;
|
||||
m_roambox.min_x = min_x;
|
||||
m_roambox.max_y = max_y;
|
||||
m_roambox.min_y = min_y;
|
||||
m_roambox.dest_x = max_x + 1; // this will trigger a recalc
|
||||
m_roambox.delay = delay;
|
||||
m_roambox.min_delay = min_delay;
|
||||
}
|
||||
|
||||
void NPC::DisplayWaypointInfo(Client *client) {
|
||||
@@ -781,7 +781,19 @@ float Mob::GetFixedZ(const glm::vec3 &destination, int32 z_find_offset) {
|
||||
return new_z;
|
||||
}
|
||||
|
||||
new_z = FindDestGroundZ(destination, (-GetZOffset() / 2));
|
||||
new_z = FindDestGroundZ(destination, ((-GetZOffset() / 2) + z_find_offset));
|
||||
|
||||
if (RuleB(Map, MobPathingVisualDebug)) {
|
||||
DrawDebugCoordinateNode(
|
||||
fmt::format("{} search z node", GetCleanName()),
|
||||
glm::vec4{
|
||||
m_Position.x,
|
||||
m_Position.y,
|
||||
((-GetZOffset() / 2) + z_find_offset),
|
||||
m_Position.w
|
||||
}
|
||||
);
|
||||
}
|
||||
if (new_z != BEST_Z_INVALID) {
|
||||
new_z += GetZOffset();
|
||||
|
||||
@@ -790,20 +802,6 @@ float Mob::GetFixedZ(const glm::vec3 &destination, int32 z_find_offset) {
|
||||
}
|
||||
}
|
||||
|
||||
// prevent ceiling clipping
|
||||
// if client is close in distance (not counting Z) and we clipped up into a ceiling
|
||||
// this helps us snap back down (or up) if it were to happen
|
||||
// other fixes were put in place to prevent clipping into the ceiling to begin with
|
||||
if (std::abs(new_z - m_Position.z) > 15) {
|
||||
LogFixZ("TRIGGER clipping detection");
|
||||
auto t = GetTarget();
|
||||
if (t && DistanceNoZ(GetPosition(), t->GetPosition()) < 20) {
|
||||
new_z = FindDestGroundZ(t->GetPosition(), -t->GetZOffset());
|
||||
new_z += GetZOffset();
|
||||
GMMove(t->GetPosition().x, t->GetPosition().y, new_z, t->GetPosition().w);
|
||||
}
|
||||
}
|
||||
|
||||
auto duration = timer.elapsed();
|
||||
|
||||
LogFixZ("[{}] returned [{}] at [{}] [{}] [{}] - Took [{}]",
|
||||
@@ -936,6 +934,7 @@ float Mob::GetZOffset() const {
|
||||
case RACE_AMYGDALAN_663:
|
||||
offset = 5.0f;
|
||||
break;
|
||||
case RACE_SPECTRAL_IKSAR_147:
|
||||
case RACE_SANDMAN_664:
|
||||
offset = 4.0f;
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user