Compare commits

...

35 Commits

Author SHA1 Message Date
Chris Miles 5f68e4a41a [Release] 24.4.4 (#2989) 2023-02-24 20:51:45 -06:00
Chris Miles e103422ca5 [Pathing] More z-clip improvements, Wurm and Spectral Iksar race adjustments (#2988) 2023-02-24 20:27:28 -06:00
Akkadius 0cbfad975d [Hotfix] Adjust database manifest to include .sql extension 2023-02-24 20:20:21 -06:00
Alex King 2a20c69c69 [Scaling] Add support for zone ID and instance version to NPC Scaling (#2968)
* [Scaling] Add support for zone ID and instance version to NPC Scaling

# Notes
- Adds `zone_id` and `instance_version` to `npc_scale_global_base` table to allow for zone and version-specific scaling.
- Defaults back to `zone_id` of `0` and `instance_version` of `0` for global scaling.
- Scaling load precedence is as follows:
- `zone_id` of current zone and `instance_version` of current instance
- `zone_id` of current zone and `instance_version` of `0`
- `zone_id` of `0` and `instance_version` of `0`

* Remove debug comment.

* Use zone not NPC.

* SQL
2023-02-24 20:17:07 -06:00
Alex de2dfc1a7e [Bug Fix] Fix for undefined MySQL library behavior. (#2834)
* MYSQL objects cannot be copied in a well defined way, this removes the copy and replaces it with another connection

* Change to share underlying pointers.

* Push up mutex changes

* Post rebase

* Formatting

---------

Co-authored-by: KimLS <KimLS@peqtgc.com>
Co-authored-by: Akkadius <akkadius1@gmail.com>
2023-02-24 20:14:55 -06:00
Chris Miles bad631df59 [Player Events] Add QS processing, mutex tweaks (#2984)
* [Player Events] Add QS processing, mutex tweaks

* Update ucs.cpp

* Move the size process check out of the server to server networking thread
2023-02-24 18:01:59 -06:00
Chris Miles e8f1aa253a [Pathing] Smoother pathing z-correction (#2982) 2023-02-24 14:07:44 -06:00
Chris Miles 889e57a5af [Pathing] Improve roambox logic (#2983)
* [Pathing] Improve roambox logic

* Cleanup roambox logic
2023-02-24 14:07:34 -06:00
Aeadoin 5cfdeb928e [Bug Fix] Fix Beneficial Target of Target procs (#2987) 2023-02-24 13:23:29 -06:00
Chris Miles 7519b0225e [Doors] Fix doors triggering invalid zone fetches of dest_zone of "none" (#2985)
* [Doors] Fix doors triggering invalid zone fetches of dest_zone of "none"

* Update doors.cpp

* Tweaks

* PR comments
2023-02-24 13:22:56 -06:00
Chris Miles 04fdc54522 [Quest API] Fix EVENT_TIMER crash when entity is no longer available (#2986)
* [Quest API] Fix EVENT_TIMER crash when entity is no longer available

* Update questmgr.cpp
2023-02-24 13:22:47 -06:00
Aeadoin f39155952f [Tradeskills] Fix for Lore Conflict (#2977)
* [Tradeskills] Fix for Lore Conflict

* Cleanup

* formatting

* it's beautiful

* container fix
2023-02-24 14:14:36 -05:00
Aeadoin 5acc181d64 [Bots] Cleanup BotDatabase::LoadBuffs (#2981)
* [Bots] Cleanup BotDatabae::LoadBuffs

* cleanup formatting/syntax
2023-02-24 12:58:54 -05:00
nytmyr 2ae0b7dd3e [Bug Fix] Correct Mend reuse time and add reduction support. (#2972)
Notes:

Previously, the server-side reuse of Mend was set to 290 seconds rather than 360 seconds (6 minutes).

Mend was not accepting reduction timers from potential items, buffs or AAs (Hastened Mend).

Mend was outputting duplicate success messages on critical mends, it will now only output a regular Mend message upon success (You mend your wounds and heal some damage) or the critical message upon critical success (You magically mend your wounds and heal considerable damage), not both.

Co-authored-by: toxin06 <53322305+toxin06@users.noreply.github.com>
2023-02-23 02:39:45 -06:00
Alex King b0d4f095ef [Commands] Cleanup #peekinv Command (#2969)
* [Commands] Cleanup #peekinv Command

# Notes
- Cleanup messages and logic.

* Update peekinv.cpp
2023-02-23 02:38:37 -06:00
Aeadoin 7c7a88650b [Bots] Verify Bots Group Integrity on join (#2980) 2023-02-23 02:36:43 -06:00
Aeadoin afaa8f4100 [Bots] Add Caster Range Command, and IsValidSpellRange Checks (#2942)
* [Bots] Add Caster Range Command, and IsValidSpellRange Checks

* remove/add exceptions where makes sense like buffs

* fixes

* fixes
2023-02-23 02:36:17 -06:00
Alex King 0d72295cc9 [Quest API] Add IsAutoAttackEnabled() to Perl/Lua (#2979)
# Perl
- Add `$client->IsAutoAttackEnabled()`.

# Lua
- Add `client:IsAutoAttackEnabled()`.

# Notes
- Allows operators to check if a client has auto attack enabled.
2023-02-23 02:31:35 -06:00
Alex King 9d4f231619 [Quest API] Add IsAutoFireEnabled() to Perl/Lua (#2978)
# Perl
- Add `$client->IsAutoFireEnabled()`.

# Lua
- Add `client:IsAutoFireEnabled()`.

# Notes
- Allows operators to check if a client has auto fire enabled.
2023-02-22 23:30:04 -05:00
Chris Miles fcb0a47280 [Release] 22.4.3 (#2976) 2023-02-21 10:42:43 -06:00
Chris Miles 1e50f19f7e [Pathing] Improvements to z-clipping, z-recovery and z-calculations (#2975)
* zclip adjustments

* Remove debug
2023-02-21 10:24:25 -06:00
Chris Miles 33bb5aa8e5 [Database] Address deadlock in player events (#2974)
* DB mutex testing

* Mutex tweaks, native string escaping
2023-02-20 22:32:29 -06:00
Aeadoin 6a668f8aa5 [Crash] Fix crash with EVENT_UNEQUIP_ITEM_BOT (#2973) 2023-02-20 17:31:15 -05:00
Alex King df499b22ab [Bug Fix] Fix MIR LDoN Theme Items on LDoN Merchants (#2971)
# Notes
- These items weren't showing as MIR due to this condition being typo'd in https://github.com/EQEmu/Server/pull/1611/files.
2023-02-19 21:51:37 -05:00
Alex King 7bc00cb466 [Bug Fix] Fix OOCMute not functioning (#2970)
# Notes
- #oocmute was not functioning as the packet wasn't being handled by the server.
2023-02-19 20:39:24 -05:00
Paul Coene 51f6108aab [Pets] Client Pet summoned by NPC should not change guard location. (#2967)
* [Pets] Client Pet summoned by NPC should not change guard location.

* Update mob.cpp

---------

Co-authored-by: Alex King <89047260+Kinglykrab@users.noreply.github.com>
2023-02-19 16:56:34 -05:00
Aeadoin c13f9f80d9 [Bots] Change HasBotItem(item_id) to return slot_id instead of bool. (#2966) 2023-02-19 16:13:28 -05:00
JJ 443abf9199 [SQL] Add date to optional Drakkin Guktan Faction Update (#2965) 2023-02-19 16:11:07 -05:00
Alex King 1556e05b2f [Quest API] Add client->SignalClient() overload to Perl (#2963)
# Notes
- Fixes an issue with Guild Lobby quests not properly using `client` as first parameter in `mob->SignalClient(client, signal_id)` method.
2023-02-19 15:20:28 -05:00
Alex King 1d645aa5f6 [Quest API] Fix Perl SetSimpleRoamBox Overloads (#2961)
# Notes
- These overloads were non-functional as they didn't have a method to actually fall back to.
2023-02-19 11:17:35 -05:00
Chris Miles 9f42da5bad [Crash] Fix world crash in player event processing (#2960)
* [Crash] Fix world crash in player event processing

* Add rule BatchPlayerEventProcessChunkSize
2023-02-18 16:06:16 -06:00
Aeadoin 4a8222f243 [Bots] Fix output of ^spells while ^Enforcespellsettings is enabled (#2959) 2023-02-18 16:06:06 -06:00
Chris Miles db4c515853 [Player Events] Create new event ITEM_CREATION (#2944) 2023-02-18 16:05:23 -06:00
Chris Miles 462656a201 [Reload API] Add world handlers for certain opcodes (#2958) 2023-02-18 15:58:29 -06:00
Aeadoin ddd98be383 [Bot] Change SaveTimers to Replace instead of Insert. (#2951)
* [Bot] Change SaveTimers to Replace instead of Insert.

* [Bot] Change SaveTimers to Replace instead of Insert.

* fix formatting
2023-02-18 16:13:36 -05:00
65 changed files with 1625 additions and 796 deletions
+94
View File
@@ -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
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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;
+5 -3
View File
@@ -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;
+37 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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"
+9
View File
@@ -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);
+13
View File
@@ -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;
}
+1 -1
View File
@@ -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)
+1
View File
@@ -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;
+73
View File
@@ -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);
}
+7
View File
@@ -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
View File
@@ -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
View File
@@ -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);
}
+1
View File
@@ -15,6 +15,7 @@ public:
static void RegisterLoginservers();
static bool DatabaseLoadRoutines(int argc, char **argv);
static void CheckForPossibleConfigurationIssues();
static void Shutdown();
};
+2
View File
@@ -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"
+1
View File
@@ -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);
};
+1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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.");
}
}
+1
View File
@@ -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
View File
@@ -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"; }
+3
View File
@@ -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
View File
@@ -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;
}
+1
View File
@@ -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();
+5 -3
View File
@@ -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,
+1
View File
@@ -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
View File
@@ -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;
}
+5
View File
@@ -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];
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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();
+16
View File
@@ -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)
+2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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",
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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);
}
+18
View File
@@ -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);
+10
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;