Compare commits

...

45 Commits

Author SHA1 Message Date
Chris Miles d3ca636a70 [Release] 22.4.5 (#3022) 2023-03-03 17:09:39 -06:00
Vayle 01855d40df [Bug Fix] Fix log messages when players join channel (#2992)
* Fix log message when players join channels

* Formatting

* More formatting

* Update CurrentPlayerChannels to use vector of channel names

* Put log statement back in place

* Remove channel limit in db query

* Formatting tweak
2023-03-03 11:54:56 -06:00
Paul Coene 748602b04e [Bug Fix] Fix an issue where EVENT_TIMER timers would not be cleaned up after zone (#3018)
* [Bug Fix] Dangling client timers fixed.

* Remove all timers from mob in destructor instead of in QuestMgr::Process
2023-03-03 11:54:14 -06:00
Alex King a97a9a0d1c [Bug Fix] Fix npcfeature and playerfeature (#3017) 2023-03-01 20:42:37 -05:00
Aeadoin a78c754c0e [Cleanup] Remove unused iterator from LoadCharacterDisciplines (#3012) 2023-03-01 20:35:28 -05:00
Alex King ef214f91e9 [Bug Fix] Fix DoAnim quest method default speed (#3016)
# Notes
- Sets default speed to `0` which makes the animations run at normal speed instead of `1` that makes them run is slow motion.
2023-03-01 17:47:27 -05:00
Aeadoin 04a74df0b2 [Bots] Add additional Heroic Sta/Wis/Int bonuses for Bots. (#3013) 2023-03-01 10:58:04 -05:00
Chris Miles c15bfe12eb [Fix] Fix issue where quest saylink responses would occur before the NPC's response (#3010)
* [Fix] Fix issue where quest saylink responses would occur before the NPC's response

* Update client_packet.cpp

* Revert "[Fix] Fix issue where quest saylink responses would occur before the NPC's response"

This reverts commit a09e1bbbe9.
2023-02-28 21:27:05 -05:00
Alex King 5702f7bcd1 [Quest API] Add IsFindable() and IsTrackable() to Perl/Lua (#2996)
# Perl
- Add `$mob->IsFindable()`.
- Add `$mob->IsTrackable()`.

# Lua
- Add `mob:IsFindable()`.
- Add `mob:IsTrackable()`.

# Notes
- Allows operators to see if a mob is findable or trackable.
2023-02-28 21:26:11 -05:00
Alex King 9a5bf53e11 [Quest API] Add IsUnderwaterOnly() to Perl/Lua (#2995)
# Perl
- Add `$npc->IsUnderwaterOnly()`.

# Lua
- Add `npc:IsUnderwaterOnly()`.

# Notes
- Allows operators to chec k if an NPC is underwater only.
2023-02-28 21:13:43 -05:00
Alex King 69c6a7b89a [Quest API] Add IsBerserk() to Perl/Lua (#2997)
* [Quest API] Add IsBerserk() to Perl/Lua

# Perl
- Add `$client->IsBerserk()`.

# Lua
- Add `client:IsBerserk()`.
- Remove `mob:IsBerserk()` to move to client.

# Notes
- Allows operators to check if a client is berserk.

* Move to Mob.

* Update lua_client.cpp
2023-02-28 20:31:20 -05:00
Aeadoin 2f0dbc5d15 [Bug Fix] Fix for Discipline Loading from Database causing issues with slot_ids (#3008)
* [Bug Fix] Fix for Discipline Loading from Database causing issues with slot_ids

* cleanup per comments
2023-02-28 16:55:22 -05:00
Aeadoin 93c79817cd [Crash] Fix crash in CheckTradeskillLoreConflict (#3009) 2023-02-27 19:24:05 -06:00
Aeadoin 3296287d70 [Bug Fix] Fix for Lore Components where component is returned. (#3005)
* [Bug Fix] Fix for Lore Components where component is returned.

* Refactor, and take into account loregroups above 0 properly

* Update tradeskills.cpp

* formatting for suggestions.

* commenting, update formatting.

---------

Co-authored-by: Alex King <89047260+Kinglykrab@users.noreply.github.com>
2023-02-27 12:36:35 -06:00
Alex King 774a7fa779 [Cleanup] Delete unused zone/skills.h (#3007)
# Notes
- This is unused.
2023-02-26 21:35:49 -05:00
Alex King 1ff4541a9f [Cleanup] Remove NumberOfAvailableTitles() from titles.h (#3006)
# Notes
- This is unused.
2023-02-26 21:35:43 -05:00
Alex King 3448758c03 [Quest API] Add HasSpecialAbilities() to Perl/Lua (#2994)
* [Quest API] Add HasSpecialAbilities() to Perl/Lua

# Perl
- Add `$mob->HasSpecialAbilities()`.

# Lua
- Add `mob:HasSpecialAbilities()`

# Notes
- Allows operators to check if a mob has special abilities

* Move to NPC.

* Update lua_mob.cpp
2023-02-26 21:35:10 -05:00
Alex King ff4ccfa98f [Quest API] Add GetDefaultRaceSize() to Perl/Lua (#2993)
# Perl
- Add `$mob->GetDefaultRaceSize()`.

# Lua
- Add `mob:GetDefaultRaceSize()`.

# Notes
- Allows operators to get the default race size of a race if they want to use it in a script.
2023-02-26 21:35:03 -05:00
Aeadoin d2c3c14ae0 [Bots] Cleanup AI_IdleCastCheck Logic (#3004)
* [Bots] Cleanup AI_IdleCastCheck Logic

* cleanup logic
2023-02-25 20:16:09 -05:00
Alex King a470931fdd [Cleanup] Remove class EGNode from mob.h (#3003)
# Notes
- This is unused.
2023-02-25 20:10:23 -05:00
Alex King 078db3460d [Cleanup] Remove ReturnItemPacket from client.h/inventory.cpp (#3002)
# Notes
- This is unused.
2023-02-25 20:10:08 -05:00
Alex King 0980a780d0 [Cleanup] Remove GetDamageMultiplier() from client.h (#3001)
# Notes
- This is unused.
2023-02-25 19:50:42 -05:00
Alex King 7f01bb509c [Cleanup] Remove DumpPacketProfile() from client.h (#3000)
# Notes
- This is unused.
2023-02-25 19:38:09 -05:00
Alex King 4bb189cbf4 [Cleanup] Remove GetCombinedAC_TEST() from client.h (#2999)
# Notes
- This is unused.
2023-02-25 19:38:03 -05:00
Alex King b03e8ff0fb [Cleanup] Remove unused ClientFactory in client.h (#2998)
# Notes
- This is unused.
2023-02-25 19:37:57 -05:00
Aeadoin 6179b7481e [Bug Fix] Account for bad data in Tradeskill Recipe Entries (#2991) 2023-02-25 10:52:17 -06:00
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
72 changed files with 1642 additions and 960 deletions
+89
View File
@@ -1,3 +1,92 @@
## [22.4.5] - 03/03/2023
### Bots
* Add additional Heroic Sta/Wis/Int bonuses for Bots. ([#3013](https://github.com/EQEmu/Server/pull/3013)) @Aeadoin 2023-03-01
* Cleanup AI_IdleCastCheck Logic ([#3004](https://github.com/EQEmu/Server/pull/3004)) @Aeadoin 2023-02-26
### Code
* Delete unused zone/skills.h ([#3007](https://github.com/EQEmu/Server/pull/3007)) @Kinglykrab 2023-02-27
* Remove DumpPacketProfile() from client.h ([#3000](https://github.com/EQEmu/Server/pull/3000)) @Kinglykrab 2023-02-26
* Remove GetCombinedAC_TEST() from client.h ([#2999](https://github.com/EQEmu/Server/pull/2999)) @Kinglykrab 2023-02-26
* Remove GetDamageMultiplier() from client.h ([#3001](https://github.com/EQEmu/Server/pull/3001)) @Kinglykrab 2023-02-26
* Remove NumberOfAvailableTitles() from titles.h ([#3006](https://github.com/EQEmu/Server/pull/3006)) @Kinglykrab 2023-02-27
* Remove ReturnItemPacket from client.h/inventory.cpp ([#3002](https://github.com/EQEmu/Server/pull/3002)) @Kinglykrab 2023-02-26
* Remove class EGNode from mob.h ([#3003](https://github.com/EQEmu/Server/pull/3003)) @Kinglykrab 2023-02-26
* Remove unused ClientFactory in client.h ([#2998](https://github.com/EQEmu/Server/pull/2998)) @Kinglykrab 2023-02-26
* Remove unused iterator from LoadCharacterDisciplines ([#3012](https://github.com/EQEmu/Server/pull/3012)) @Aeadoin 2023-03-02
### Crash
* Fix crash in CheckTradeskillLoreConflict ([#3009](https://github.com/EQEmu/Server/pull/3009)) @Aeadoin 2023-02-28
### Fixes
* Account for bad data in Tradeskill Recipe Entries ([#2991](https://github.com/EQEmu/Server/pull/2991)) @Aeadoin 2023-02-25
* Fix DoAnim quest method default speed ([#3016](https://github.com/EQEmu/Server/pull/3016)) @Kinglykrab 2023-03-01
* Fix an issue where EVENT_TIMER timers would not be cleaned up after zone ([#3018](https://github.com/EQEmu/Server/pull/3018)) @noudess 2023-03-03
* Fix for Discipline Loading from Database causing issues with slot_ids ([#3008](https://github.com/EQEmu/Server/pull/3008)) @Aeadoin 2023-02-28
* Fix for Lore Components where component is returned. ([#3005](https://github.com/EQEmu/Server/pull/3005)) @Aeadoin 2023-02-27
* Fix issue where quest saylink responses would occur before the NPC's response ([#3010](https://github.com/EQEmu/Server/pull/3010)) @Akkadius 2023-03-01
* Fix log messages when players join channel ([#2992](https://github.com/EQEmu/Server/pull/2992)) @Valorith 2023-03-03
* Fix npcfeature and playerfeature ([#3017](https://github.com/EQEmu/Server/pull/3017)) @Kinglykrab 2023-03-02
### Quest API
* Add GetDefaultRaceSize() to Perl/Lua ([#2993](https://github.com/EQEmu/Server/pull/2993)) @Kinglykrab 2023-02-27
* Add HasSpecialAbilities() to Perl/Lua ([#2994](https://github.com/EQEmu/Server/pull/2994)) @Kinglykrab 2023-02-27
* Add IsBerserk() to Perl/Lua ([#2997](https://github.com/EQEmu/Server/pull/2997)) @Kinglykrab 2023-03-01
* Add IsFindable() and IsTrackable() to Perl/Lua ([#2996](https://github.com/EQEmu/Server/pull/2996)) @Kinglykrab 2023-03-01
* Add IsUnderwaterOnly() to Perl/Lua ([#2995](https://github.com/EQEmu/Server/pull/2995)) @Kinglykrab 2023-03-01
## [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
+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)
+45 -47
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,19 +66,17 @@ DBcore::~DBcore()
// Sends the MySQL server a keepalive
void DBcore::ping()
{
if (!m_query_lock.try_lock()) {
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);
m_query_lock.unlock();
mysql_ping(mysql);
m_mutex->unlock();
}
MySQLRequestResult DBcore::QueryDatabase(std::string query, bool retryOnFailureOnce)
{
m_query_lock.lock();
auto r = QueryDatabase(query.c_str(), query.length(), retryOnFailureOnce);
m_query_lock.unlock();
return r;
}
@@ -98,14 +92,16 @@ MySQLRequestResult DBcore::QueryDatabase(const char *query, uint32 querylen, boo
BenchTimer timer;
timer.reset();
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;
@@ -129,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) {
@@ -157,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) {
@@ -206,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(
@@ -221,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);
@@ -241,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;
@@ -264,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;
@@ -274,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;
@@ -303,7 +294,14 @@ 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);
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;
}
+10 -4
View File
@@ -31,14 +31,19 @@ public:
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,
@@ -55,8 +60,9 @@ 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{};
+2 -6
View File
@@ -140,10 +140,6 @@ void PlayerEventLogs::AddToQueue(const PlayerEventLogsRepository::PlayerEventLog
m_batch_queue_lock.lock();
m_record_batch_queue.emplace_back(log);
m_batch_queue_lock.unlock();
if (m_record_batch_queue.size() >= RuleI(Logging, BatchPlayerEventProcessChunkSize)) {
ProcessBatchQueue();
}
}
// fills common event data in the SendEvent function
@@ -607,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();
}
+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);
}
-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")
+3 -3
View File
@@ -25,7 +25,7 @@
// Build variables
// these get injected during the build pipeline
#define CURRENT_VERSION "22.4.3-dev" // always append -dev to the current version for custom-builds
#define CURRENT_VERSION "22.4.5-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.3",
"version": "22.4.5",
"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)
+5 -5
View File
@@ -48,10 +48,10 @@ ChatChannel::ChatChannel(std::string inName, std::string inOwner, std::string in
m_moderated = false;
LogDebug(
"New ChatChannel created: Name: [{}], Owner: [{}], Password: [{}], MinStatus: [{}]",
m_name.c_str(),
m_owner.c_str(),
m_password.c_str(),
"New ChatChannel created: Name: [{}] Owner: [{}] Password: [{}] MinStatus: [{}]",
m_name,
m_owner,
m_password,
m_minimum_status
);
@@ -667,7 +667,7 @@ ChatChannel *ChatChannelList::RemoveClientFromChannel(const std::string& in_chan
}
LogDebug("Client [{}] removed from channel [{}]. Channel is owned by {}. Command directed: {}", c->GetName(), channel_name, required_channel->GetOwnerName(), command_directed);
if (c->GetName() == required_channel->GetOwnerName() && command_directed) { // Check if the client that is leaving is the the channel owner
if (c->GetName() == required_channel->GetOwnerName() && command_directed) { // Check if the client that is leaving is the channel owner
LogDebug("Owner left the channel [{}], removing channel from database...", channel_name);
database.DeleteChatChannel(channel_name); // Remove the channel from the database.
LogDebug("Flagging [{}] channel as temporary...", channel_name);
+4 -1
View File
@@ -793,7 +793,10 @@ void Clientlist::ProcessOPMailCommand(Client *c, std::string command_string, boo
case CommandJoin:
if (!command_directed) {
//Append saved channels to params
parameters = parameters + ", " + database.CurrentPlayerChannels(c->GetName());
const auto saved_channels = database.CurrentPlayerChannels(c->GetName());
if (!saved_channels.empty()) {
parameters += fmt::format(", {}", Strings::Join(saved_channels, ", "));
}
parameters = RemoveDuplicateChannels(parameters);
}
c->JoinChannels(parameters, command_directed);
+10 -9
View File
@@ -336,16 +336,17 @@ void UCSDatabase::DeleteChatChannel(const std::string& channel_name)
LogInfo("Deleting channel [{}] from the database.", channel_name);
}
std::string UCSDatabase::CurrentPlayerChannels(const std::string& player_name) {
int current_player_channel_count = CurrentPlayerChannelCount(player_name);
if (current_player_channel_count == 0) {
return "";
std::vector<std::string> UCSDatabase::CurrentPlayerChannels(const std::string& player_name) {
auto rows = ChatchannelsRepository::GetWhere(*this, fmt::format("`owner` = '{}'", Strings::Escape(player_name)));
if (rows.empty()) {
return {};
}
const auto rquery = fmt::format("SELECT GROUP_CONCAT(`name` SEPARATOR ', ') FROM chatchannels WHERE `owner` = '{}'; ", Strings::Escape(player_name));
auto results = QueryDatabase(rquery);
auto row = results.begin();
std::string channels = row[0];
LogDebug("Player [{}] has the following permanent channels saved to the database: [{}].", player_name, channels);
std::vector<std::string> channels = {};
channels.reserve(rows.size());
for (auto &e: rows) {
channels.emplace_back(e.name);
}
LogDebug("Player [{}] has the following [{}] permanent channels saved to the database: [{}].", player_name, rows.size(), Strings::Join(channels, ", "));
return channels;
}
+1 -1
View File
@@ -52,7 +52,7 @@ public:
void SaveChatChannel(const std::string& channel_name, const std::string& channel_owner, const std::string& channel_password, const uint16& min_status);
void DeleteChatChannel(const std::string& channel_name);
int CurrentPlayerChannelCount(const std::string& player_name);
std::string CurrentPlayerChannels(const std::string& player_name);
std::vector<std::string> CurrentPlayerChannels(const std::string& player_name);
void GetAccountStatus(Client *c);
void SetChannelPassword(const std::string& channel_name, const std::string& password);
void SetChannelOwner(const std::string& channel_name, const std::string& owner);
+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 -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
@@ -255,7 +255,6 @@ SET(zone_headers
quest_parser_collection.h
raids.h
raycast_mesh.h
skills.h
shared_task_zone_messaging.h
spawn2.cpp
spawn2.h
+39 -32
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() {
@@ -2469,6 +2470,7 @@ void Bot::AI_Process()
}
// We also need a leash owner and follow mob (subset of primary AI criteria)
bot_group->VerifyGroup();
Client* leash_owner = (bot_group->GetLeader() && bot_group->GetLeader()->IsClient() ? bot_group->GetLeader()->CastToClient() : bot_owner);
if (!leash_owner) {
return;
@@ -3115,26 +3117,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 +3140,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) {
@@ -4565,6 +4548,7 @@ bool Bot::AddBotToGroup(Bot* bot, Group* group) {
group->SendUpdate(groupActUpdate, TempLeader);
}
}
group->VerifyGroup();
Result = true;
}
}
@@ -6150,8 +6134,11 @@ void Bot::ProcessBotOwnerRefDelete(Mob* botOwner) {
int64 Bot::CalcMaxMana() {
switch(GetCasterClass()) {
case 'I':
max_mana = (GenerateBaseManaPoints() + itembonuses.Mana + spellbonuses.Mana + GroupLeadershipAAManaEnhancement());
max_mana += (GetHeroicINT() * 10);
case 'W': {
max_mana = (GenerateBaseManaPoints() + itembonuses.Mana + spellbonuses.Mana + GroupLeadershipAAManaEnhancement());
max_mana += (GetHeroicWIS() * 10);
break;
}
case 'N': {
@@ -7101,6 +7088,7 @@ int32 Bot::LevelRegen() {
int64 Bot::CalcHPRegen() {
int32 regen = (LevelRegen() + itembonuses.HPRegen + spellbonuses.HPRegen);
regen += GetHeroicSTA() / 20;
regen += (aabonuses.HPRegen + GroupLeadershipAAHealthRegeneration());
regen = ((regen * RuleI(Character, HPRegenMultiplier)) / 100);
return regen;
@@ -7172,6 +7160,7 @@ int64 Bot::CalcMaxHP() {
int32 bot_hp = 0;
uint32 nd = 10000;
bot_hp += (GenerateBaseHitPoints() + itembonuses.HP);
bot_hp += (GetHeroicSTA() * 10);
nd += aabonuses.MaxHP;
bot_hp = ((float)bot_hp * (float)nd / (float)10000);
bot_hp += (spellbonuses.HP + aabonuses.HP);
@@ -9825,4 +9814,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 };
+5 -2
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;}
@@ -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;
+51
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) ||
@@ -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);
+69 -27
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;
}
@@ -3151,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"; }
@@ -3205,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();
+106 -46
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;
}
@@ -1277,8 +1303,7 @@ bool Bot::AI_IdleCastCheck() {
if (HasGroup() && GetGroup()->GetLeader() && GetGroup()->GetLeader()->IsClient()) {
test_against = GetGroup()->GetLeader()->CastToClient();
}
else if (GetOwner() && GetOwner()->IsClient()) {
} else if (GetOwner() && GetOwner()->IsClient()) {
test_against = GetOwner()->CastToClient();
}
@@ -1291,17 +1316,12 @@ bool Bot::AI_IdleCastCheck() {
// Healers WITHOUT pets will check if a heal is needed before buffing.
case CLERIC:
case PALADIN:
case RANGER:
case MONK:
case ROGUE:
case WARRIOR:
case BERSERKER: {
case RANGER: {
if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, 100, BotAISpellRange, SpellType_Cure)) {
if (!AICastSpell(this, 100, SpellType_Heal)) {
if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, 100, BotAISpellRange, SpellType_Heal)) {
if (!AICastSpell(this, 100, SpellType_Buff)) {
if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, 100, BotAISpellRange, SpellType_Buff)) {
//
}
}
}
@@ -1311,13 +1331,46 @@ bool Bot::AI_IdleCastCheck() {
result = true;
break;
}
case MONK:
case ROGUE:
case WARRIOR:
case BERSERKER: {
if (!AICastSpell(this, 100, SpellType_Cure)) {
if (!AICastSpell(this, 100, SpellType_Heal)) {
if (!AICastSpell(this, 100, SpellType_Buff)) {
if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, 100, BotAISpellRange, SpellType_Buff)) {
}
}
}
}
result = true;
break;
}
// Pets class will first cast their pet, then buffs
case DRUID:
case MAGICIAN:
case SHADOWKNIGHT:
case SHAMAN:
case NECROMANCER:
case ENCHANTER:
case ENCHANTER: {
if (!AICastSpell(this, 100, SpellType_Pet)) {
if (!AICastSpell(this, 100, SpellType_Cure)) {
if (!AICastSpell(GetPet(), 100, SpellType_Cure)) {
if (!AICastSpell(this, 100, SpellType_Buff)) {
if (!AICastSpell(GetPet(), 100, SpellType_Heal)) {
if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, 100, BotAISpellRange, SpellType_Buff)) {
}
}
}
}
}
}
result = true;
break;
}
case DRUID:
case SHAMAN:
case BEASTLORD: {
if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, 100, BotAISpellRange, SpellType_Cure)) {
if (!AICastSpell(this, 100, SpellType_Pet)) {
@@ -1339,16 +1392,12 @@ bool Bot::AI_IdleCastCheck() {
}
case WIZARD: { // This can eventually be move into the BEASTLORD case handler once pre-combat is fully implemented
if (pre_combat) {
if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, 100, BotAISpellRange, SpellType_Cure)) {
if (!AICastSpell(this, 100, SpellType_Pet)) {
if (!AICastSpell(this, 100, SpellType_Pet)) {
if (!AICastSpell(this, 100, SpellType_Cure)) {
if (!AICastSpell(this, 100, SpellType_Heal)) {
if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, 100, BotAISpellRange, SpellType_Heal)) {
if (!AICastSpell(this, 100, SpellType_Buff)) {
if (!AICastSpell(GetPet(), 100, SpellType_Heal)) {
if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, 100, BotAISpellRange, SpellType_PreCombatBuff)) {
if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, 100, BotAISpellRange, SpellType_Buff)) {
}
}
if (!AICastSpell(this, 100, SpellType_Buff)) {
if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, 100, BotAISpellRange, SpellType_PreCombatBuff)) {
if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, 100, BotAISpellRange, SpellType_Buff)) {
}
}
}
@@ -1357,15 +1406,11 @@ bool Bot::AI_IdleCastCheck() {
}
}
else {
if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, 100, BotAISpellRange, SpellType_Cure)) {
if (!AICastSpell(this, 100, SpellType_Cure)) {
if (!AICastSpell(this, 100, SpellType_Pet)) {
if (!AICastSpell(this, 100, SpellType_Heal)) {
if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, 100, BotAISpellRange, SpellType_Heal)) {
if (!AICastSpell(this, 100, SpellType_Buff)) {
if (!AICastSpell(GetPet(), 100, SpellType_Heal)) {
if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, 100, BotAISpellRange, SpellType_Buff)) {
}
}
if (!AICastSpell(this, 100, SpellType_Buff)) {
if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, 100, BotAISpellRange, SpellType_Buff)) {
}
}
}
@@ -1764,8 +1809,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 +3502,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 -12
View File
@@ -218,11 +218,6 @@ struct ClientReward
uint32 amount;
};
class ClientFactory {
public:
Client *MakeClient(std::shared_ptr<EQStreamInterface> ieqs);
};
class Client : public Mob
{
public:
@@ -343,8 +338,6 @@ public:
bool HasRecipeLearned(uint32 recipe_id);
bool CanIncreaseTradeskill(EQ::skills::SkillType tradeskill);
EQApplicationPacket* ReturnItemPacket(int16 slot_id, const EQ::ItemInstance* inst, ItemPacketType packet_type);
bool GetRevoked() const { return revoked; }
void SetRevoked(bool rev) { revoked = rev; }
inline uint32 GetIP() const { return ip; }
@@ -697,7 +690,6 @@ public:
void SetFactionLevel(uint32 char_id, uint32 npc_id, uint8 char_class, uint8 char_race, uint8 char_deity, bool quest = false);
void SetFactionLevel2(uint32 char_id, int32 faction_id, uint8 char_class, uint8 char_race, uint8 char_deity, int32 value, uint8 temp);
int32 GetRawItemAC();
uint16 GetCombinedAC_TEST();
inline uint32 LSAccountID() const { return lsaccountid; }
inline uint32 GetWID() const { return WID; }
@@ -792,6 +784,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();
@@ -850,9 +843,6 @@ public:
inline void SetBecomeNPC(bool flag) { npcflag = flag; }
inline void SetBecomeNPCLevel(uint8 level) { npclevel = level; }
EQStreamInterface* Connection() { return eqs; }
#ifdef PACKET_PROFILER
void DumpPacketProfile() { if(eqs) eqs->DumpPacketProfile(); }
#endif
uint32 GetEquippedItemFromTextureSlot(uint8 material_slot) const; // returns item id
uint32 GetEquipmentColor(uint8 material_slot) const;
virtual void UpdateEquipmentLight() { m_Light.Type[EQ::lightsource::LightEquipment] = m_inv.FindBrightestLightType(); m_Light.Level[EQ::lightsource::LightEquipment] = EQ::lightsource::TypeToLevel(m_Light.Type[EQ::lightsource::LightEquipment]); }
@@ -1594,7 +1584,6 @@ public:
void SetAccountFlag(std::string flag, std::string val);
std::string GetAccountFlag(std::string flag);
void SetGMStatus(int16 new_status);
float GetDamageMultiplier(EQ::skills::SkillType how_long_has_this_been_missing);
void Consume(const EQ::ItemData *item, uint8 type, int16 slot, bool auto_consume);
void PlayMP3(const char* fname);
void ExpeditionSay(const char *str, int ExpID);
+6 -4
View File
@@ -8737,12 +8737,12 @@ void Client::Handle_OP_ItemLinkClick(const EQApplicationPacket *app)
}
if (!response.empty()) {
ChannelMessageReceived(ChatChannel_Say, 0, 100, response.c_str(), nullptr, true);
if (!silentsaylink) {
Message(Chat::LightGray, "You say, '%s'", response.c_str());
}
ChannelMessageReceived(ChatChannel_Say, 0, 100, response.c_str(), nullptr, true);
return;
}
else {
@@ -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];
+2 -2
View File
@@ -1063,12 +1063,12 @@ void Perl__playertexture(int texture_id)
quest_manager.playertexture(texture_id);
}
void Perl__playerfeature(char* feature, int value)
void Perl__playerfeature(const char* feature, int value)
{
quest_manager.playerfeature(feature, value);
}
void Perl__npcfeature(char* feature, int value)
void Perl__npcfeature(const char* feature, int value)
{
quest_manager.npcfeature(feature, value);
}
+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");
}
-25
View File
@@ -3376,31 +3376,6 @@ void Client::SendItemPacket(int16 slot_id, const EQ::ItemInstance* inst, ItemPac
FastQueuePacket(&outapp);
}
EQApplicationPacket* Client::ReturnItemPacket(int16 slot_id, const EQ::ItemInstance* inst, ItemPacketType packet_type)
{
if (!inst)
return nullptr;
// Serialize item into |-delimited string
std::string packet = inst->Serialize(slot_id);
EmuOpcode opcode = OP_Unknown;
EQApplicationPacket* outapp = nullptr;
BulkItemPacket_Struct* itempacket = nullptr;
// Construct packet
opcode = OP_ItemPacket;
outapp = new EQApplicationPacket(opcode, packet.length()+1);
itempacket = (BulkItemPacket_Struct*)outapp->pBuffer;
memcpy(itempacket->SerializedItem, packet.c_str(), packet.length());
#if EQDEBUG >= 9
DumpPacket(outapp);
#endif
return outapp;
}
static int16 BandolierSlotToWeaponSlot(int BandolierSlot)
{
switch (BandolierSlot)
+15
View File
@@ -3038,6 +3038,19 @@ 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 +3309,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);
+19
View File
@@ -2823,6 +2823,22 @@ Lua_HateList Lua_Mob::GetHateListBots(uint32 distance) {
return ret;
}
bool Lua_Mob::IsFindable() {
Lua_Safe_Call_Bool();
return self->IsFindable();
}
bool Lua_Mob::IsTrackable() {
Lua_Safe_Call_Bool();
return self->IsTrackable();
}
float Lua_Mob::GetDefaultRaceSize() {
Lua_Safe_Call_Real();
return self->GetDefaultRaceSize();
}
luabind::scope lua_register_mob() {
return luabind::class_<Lua_Mob, Lua_Entity>("Mob")
.def(luabind::constructor<>())
@@ -3022,6 +3038,7 @@ luabind::scope lua_register_mob() {
.def("GetDEX", &Lua_Mob::GetDEX)
.def("GetDR", &Lua_Mob::GetDR)
.def("GetDamageAmount", (uint32(Lua_Mob::*)(Lua_Mob))&Lua_Mob::GetDamageAmount)
.def("GetDefaultRaceSize", &Lua_Mob::GetDefaultRaceSize)
.def("GetDeity", &Lua_Mob::GetDeity)
.def("GetDisplayAC", &Lua_Mob::GetDisplayAC)
.def("GetDrakkinDetails", &Lua_Mob::GetDrakkinDetails)
@@ -3164,6 +3181,7 @@ luabind::scope lua_register_mob() {
.def("IsEngaged", (bool(Lua_Mob::*)(void))&Lua_Mob::IsEngaged)
.def("IsEnraged", (bool(Lua_Mob::*)(void))&Lua_Mob::IsEnraged)
.def("IsFeared", (bool(Lua_Mob::*)(void))&Lua_Mob::IsFeared)
.def("IsFindable", (bool(Lua_Mob::*)(void))&Lua_Mob::IsFindable)
.def("IsHorse", &Lua_Mob::IsHorse)
.def("IsImmuneToSpell", (bool(Lua_Mob::*)(int,Lua_Mob))&Lua_Mob::IsImmuneToSpell)
.def("IsInvisible", (bool(Lua_Mob::*)(Lua_Mob))&Lua_Mob::IsInvisible)
@@ -3179,6 +3197,7 @@ luabind::scope lua_register_mob() {
.def("IsStunned", (bool(Lua_Mob::*)(void))&Lua_Mob::IsStunned)
.def("IsTargetable", (bool(Lua_Mob::*)(void))&Lua_Mob::IsTargetable)
.def("IsTargeted", &Lua_Mob::IsTargeted)
.def("IsTrackable", (bool(Lua_Mob::*)(void))&Lua_Mob::IsTrackable)
.def("IsWarriorClass", &Lua_Mob::IsWarriorClass)
.def("Kill", (void(Lua_Mob::*)(void))&Lua_Mob::Kill)
.def("Mesmerize", (void(Lua_Mob::*)(void))&Lua_Mob::Mesmerize)
+3
View File
@@ -513,6 +513,9 @@ public:
void CopyHateList(Lua_Mob to);
bool IsAttackAllowed(Lua_Mob target);
bool IsAttackAllowed(Lua_Mob target, bool is_spell_attack);
bool IsFindable();
bool IsTrackable();
float GetDefaultRaceSize();
};
#endif
+11
View File
@@ -767,6 +767,16 @@ void Lua_NPC::ScaleNPC(uint8 npc_level, bool override_special_abilities)
self->ScaleNPC(npc_level, true, override_special_abilities);
}
bool Lua_NPC::IsUnderwaterOnly() {
Lua_Safe_Call_Bool();
return self->IsUnderwaterOnly();
}
bool Lua_NPC::HasSpecialAbilities() {
Lua_Safe_Call_Bool();
return self->HasSpecialAbilities();
}
luabind::scope lua_register_npc() {
return luabind::class_<Lua_NPC, Lua_Mob>("NPC")
.def(luabind::constructor<>())
@@ -860,6 +870,7 @@ luabind::scope lua_register_npc() {
.def("IsRaidTarget", (bool(Lua_NPC::*)(void))&Lua_NPC::IsRaidTarget)
.def("IsRareSpawn", (bool(Lua_NPC::*)(void))&Lua_NPC::IsRareSpawn)
.def("IsTaunting", (bool(Lua_NPC::*)(void))&Lua_NPC::IsTaunting)
.def("IsUnderwaterOnly", (bool(Lua_NPC::*)(void))&Lua_NPC::IsUnderwaterOnly)
.def("MerchantCloseShop", (void(Lua_NPC::*)(void))&Lua_NPC::MerchantCloseShop)
.def("MerchantOpenShop", (void(Lua_NPC::*)(void))&Lua_NPC::MerchantOpenShop)
.def("ModifyNPCStat", (void(Lua_NPC::*)(std::string,std::string))&Lua_NPC::ModifyNPCStat)
+2
View File
@@ -174,6 +174,8 @@ public:
void SetLDoNTrapDetected(bool is_detected);
void ScaleNPC(uint8 npc_level);
void ScaleNPC(uint8 npc_level, bool override_special_abilities);
bool IsUnderwaterOnly();
bool HasSpecialAbilities();
};
#endif
+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;
}
+9 -3
View File
@@ -518,7 +518,9 @@ Mob::Mob(
}
Mob::~Mob()
{
{
quest_manager.stopalltimers(this);
mMovementManager->RemoveMob(this);
AI_Stop();
@@ -4189,7 +4191,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);
@@ -4205,7 +4207,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()
+1 -1
View File
@@ -42,7 +42,6 @@ char* strn0cpy(char* dest, const char* source, uint32 size);
#define MAX_SPECIAL_ATTACK_PARAMS 8
class EGNode;
class Client;
class EQApplicationPacket;
class Group;
@@ -1239,6 +1238,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);
+22 -130
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()
@@ -1073,9 +1073,15 @@ void Mob::AI_Process() {
if (engaged) {
if (IsNPC() && m_z_clip_check_timer.Check()) {
auto t = GetTarget();
if (t && DistanceNoZ(GetPosition(), t->GetPosition()) < 75 && std::abs(GetPosition().z - t->GetPosition().z) > 15 && !CheckLosFN(t)) {
GMMove(t->GetPosition().x, t->GetPosition().y, t->GetPosition().z, t->GetPosition().w);
FaceTarget(t);
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);
}
}
}
@@ -1592,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);
+12
View File
@@ -2897,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);
@@ -3159,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);
+24
View File
@@ -2786,6 +2786,26 @@ Bot* Perl_Mob_GetHateRandomBot(Mob* self) // @categories Hate and Aggro
return self->GetHateRandomBot();
}
bool Perl_Mob_IsFindable(Mob* self) // @categories Script Utility
{
return self->IsFindable();
}
bool Perl_Mob_IsTrackable(Mob* self) // @categories Script Utility
{
return self->IsTrackable();
}
bool Perl_Mob_IsBerserk(Mob* self) // @categories Script Utility
{
return self->IsBerserk();
}
float Perl_Mob_GetDefaultRaceSize(Mob* self) // @categories Script Utility
{
return self->GetDefaultRaceSize();
}
void perl_register_mob()
{
perl::interpreter perl(PERL_GET_THX);
@@ -2970,6 +2990,7 @@ void perl_register_mob()
package.add("GetClassName", &Perl_Mob_GetClassName);
package.add("GetCleanName", &Perl_Mob_GetCleanName);
package.add("GetCorruption", &Perl_Mob_GetCorruption);
package.add("GetDefaultRaceSize", &Perl_Mob_GetDefaultRaceSize);
package.add("GetDEX", &Perl_Mob_GetDEX);
package.add("GetDR", &Perl_Mob_GetDR);
package.add("GetDamageAmount", &Perl_Mob_GetDamageAmount);
@@ -3112,6 +3133,7 @@ void perl_register_mob()
package.add("IsAttackAllowed", (bool(*)(Mob*, Mob*, bool))&Perl_Mob_IsAttackAllowed);
package.add("IsBeacon", &Perl_Mob_IsBeacon);
package.add("IsBeneficialAllowed", &Perl_Mob_IsBeneficialAllowed);
package.add("IsBerserk", &Perl_Mob_IsBerserk);
package.add("IsBlind", &Perl_Mob_IsBlind);
package.add("IsBot", &Perl_Mob_IsBot);
package.add("IsCasting", &Perl_Mob_IsCasting);
@@ -3122,6 +3144,7 @@ void perl_register_mob()
package.add("IsEngaged", &Perl_Mob_IsEngaged);
package.add("IsEnraged", &Perl_Mob_IsEnraged);
package.add("IsFeared", &Perl_Mob_IsFeared);
package.add("IsFindable", &Perl_Mob_IsFindable);
package.add("IsHorse", &Perl_Mob_IsHorse);
package.add("IsImmuneToSpell", &Perl_Mob_IsImmuneToSpell);
package.add("IsInvisible", (bool(*)(Mob*))&Perl_Mob_IsInvisible);
@@ -3142,6 +3165,7 @@ void perl_register_mob()
package.add("IsStunned", &Perl_Mob_IsStunned);
package.add("IsTargetable", &Perl_Mob_IsTargetable);
package.add("IsTargeted", &Perl_Mob_IsTargeted);
package.add("IsTrackable", &Perl_Mob_IsTrackable);
package.add("IsTrap", &Perl_Mob_IsTrap);
package.add("IsWarriorClass", &Perl_Mob_IsWarriorClass);
package.add("Kill", &Perl_Mob_Kill);
+12
View File
@@ -765,6 +765,16 @@ void Perl_NPC_ScaleNPC(NPC* self, uint8 npc_level, bool override_special_abiliti
return self->ScaleNPC(npc_level, override_special_abilities);
}
bool Perl_NPC_IsUnderwaterOnly(NPC* self) // @categories Script Utility
{
return self->IsUnderwaterOnly();
}
bool Perl_NPC_HasSpecialAbilities(NPC* self) // @categories Script Utility
{
return self->HasSpecialAbilities();
}
void perl_register_npc()
{
perl::interpreter perl(PERL_GET_THX);
@@ -852,6 +862,7 @@ void perl_register_npc()
package.add("GetSwarmTarget", &Perl_NPC_GetSwarmTarget);
package.add("GetWaypointMax", &Perl_NPC_GetWaypointMax);
package.add("HasAISpellEffect", &Perl_NPC_HasAISpellEffect);
package.add("HasSpecialAbilities", &Perl_NPC_HasSpecialAbilities);
package.add("HasItem", &Perl_NPC_HasItem);
package.add("IsAnimal", &Perl_NPC_IsAnimal);
package.add("IsGuarding", &Perl_NPC_IsGuarding);
@@ -862,6 +873,7 @@ void perl_register_npc()
package.add("IsRaidTarget", &Perl_NPC_IsRaidTarget);
package.add("IsRareSpawn", &Perl_NPC_IsRareSpawn);
package.add("IsTaunting", &Perl_NPC_IsTaunting);
package.add("IsUnderwaterOnly", (bool(*)(NPC*))&Perl_NPC_IsUnderwaterOnly);
package.add("MerchantCloseShop", &Perl_NPC_MerchantCloseShop);
package.add("MerchantOpenShop", &Perl_NPC_MerchantOpenShop);
package.add("ModifyNPCStat", &Perl_NPC_ModifyNPCStat);
+23 -11
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) {
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();
@@ -2118,7 +2130,7 @@ void QuestManager::playertexture(int newtexture)
initiator->SendIllusionPacket(initiator->GetRace(), 0xFF, newtexture);
}
void QuestManager::playerfeature(char *feature, int setting)
void QuestManager::playerfeature(const char* feature, int setting)
{
QuestManagerCurrentQuestVars();
uint16 Race = initiator->GetRace();
@@ -2175,7 +2187,7 @@ void QuestManager::playerfeature(char *feature, int setting)
DrakkinHeritage, DrakkinTattoo, DrakkinDetails, Size);
}
void QuestManager::npcfeature(char *feature, int setting)
void QuestManager::npcfeature(const char* feature, int setting)
{
QuestManagerCurrentQuestVars();
uint16 Race = owner->GetRace();
+3 -3
View File
@@ -140,7 +140,7 @@ public:
void movepc(int zone_id, float x, float y, float z, float heading);
void gmmove(float x, float y, float z);
void movegrp(int zoneid, float x, float y, float z);
void doanim(int animation_id, int animation_speed = 1, bool ackreq = true, eqFilterType filter = FilterNone);
void doanim(int animation_id, int animation_speed = 0, bool ackreq = true, eqFilterType filter = FilterNone);
void addskill(int skill_id, int value);
void setlanguage(int skill_id, int value);
void setskill(int skill_id, int value);
@@ -208,8 +208,8 @@ public:
void playergender(int gender_id);
void playersize(int newsize);
void playertexture(int newtexture);
void playerfeature(char *feature, int setting);
void npcfeature(char *feature, int setting);
void playerfeature(const char* feature, int setting);
void npcfeature(const char* feature, int setting);
void popup(const char *title, const char *text, uint32 popupid, uint32 buttons, uint32 Duration);
void taskselector(const std::vector<int>& tasks, bool ignore_cooldown = false);
void tasksetselector(int tasksettid, bool ignore_cooldown = false);
-101
View File
@@ -1,101 +0,0 @@
/* EQEMu: Everquest Server Emulator
Copyright (C) 2001-2002 EQEMu Development Team (http://eqemu.org)
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY except by those people which sell it, which
are required to give you total support for your newly bought product;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef SKILLS_H
#define SKILLS_H
#define HIGHEST_SKILL_UNUSED 74
// Correct Skill Numbers as of 4-14-2002
#define _1H_BLUNT_UNUSED 0
#define _1H_SLASHING_UNUSED 1
#define _2H_BLUNT_UNUSED 2
#define _2H_SLASHING_UNUSED 3
#define ABJURE_UNUSED 4
#define ALTERATION_UNUSED 5
#define APPLY_POISON_UNUSED 6
#define ARCHERY_UNUSED 7
#define BACKSTAB_UNUSED 8
#define BIND_WOUND_UNUSED 9
#define BASH_UNUSED 10
#define BLOCKSKILL_UNUSED 11
#define BRASS_INSTRUMENTS_UNUSED 12
#define CHANNELING_UNUSED 13
#define CONJURATION_UNUSED 14
#define DEFENSE_UNUSED 15
#define DISARM_UNUSED 16
#define DISARM_TRAPS_UNUSED 17
#define DIVINATION_UNUSED 18
#define DODGE_UNUSED 19
#define DOUBLE_ATTACK_UNUSED 20
#define DRAGON_PUNCH_UNUSED 21
#define DUAL_WIELD_UNUSED 22
#define EAGLE_STRIKE_UNUSED 23
#define EVOCATION_UNUSED 24
#define FEIGN_DEATH_UNUSED 25
#define FLYING_KICK_UNUSED 26
#define FORAGE_UNUSED 27
#define HAND_TO_HAND_UNUSED 28
#define HIDE_UNUSED 29
#define KICK_UNUSED 30
#define MEDITATE_UNUSED 31
#define MEND_UNUSED 32
#define OFFENSE_UNUSED 33
#define PARRY_UNUSED 34
#define PICK_LOCK_UNUSED 35
#define PIERCING_UNUSED 36
#define RIPOSTE_UNUSED 37
#define ROUND_KICK_UNUSED 38
#define SAFE_FALL_UNUSED 39
#define SENSE_HEADING_UNUSED 40
#define SINGING_UNUSED 41
#define SNEAK_UNUSED 42
#define SPECIALIZE_ABJURE_UNUSED 43
#define SPECIALIZE_ALTERATION_UNUSED 44
#define SPECIALIZE_CONJURATION_UNUSED 45
#define SPECIALIZE_DIVINATION_UNUSED 46
#define SPECIALIZE_EVOCATION_UNUSED 47
#define PICK_POCKETS_UNUSED 48
#define STRINGED_INSTRUMENTS_UNUSED 49
#define SWIMMING_UNUSED 50
#define THROWING_UNUSED 51
#define TIGER_CLAW_UNUSED 52
#define TRACKING_UNUSED 53
#define WIND_INSTRUMENTS_UNUSED 54
#define FISHING_UNUSED 55
#define MAKE_POISON_UNUSED 56
#define TINKERING_UNUSED 57
#define RESEARCH_UNUSED 58
#define ALCHEMY_UNUSED 59
#define BAKING_UNUSED 60
#define TAILORING_UNUSED 61
#define SENSE_TRAPS_UNUSED 62
#define BLACKSMITHING_UNUSED 63
#define FLETCHING_UNUSED 64
#define BREWING_UNUSED 65
#define ALCOHOL_TOLERANCE_UNUSED 66
#define BEGGING_UNUSED 67
#define JEWELRY_MAKING_UNUSED 68
#define POTTERY_UNUSED 69
#define PERCUSSION_INSTRUMENTS_UNUSED 70
#define INTIMIDATION_UNUSED 71
#define BERSERKING_UNUSED 72
#define TAUNT_UNUSED 73
#define FRENZY_UNUSED 74
#endif
-12
View File
@@ -93,18 +93,6 @@ EQApplicationPacket *TitleManager::MakeTitlesPacket(Client *client)
return(outapp);
}
int TitleManager::NumberOfAvailableTitles(Client *client)
{
int count = 0;
for (const auto& title : titles) {
if (IsClientEligibleForTitle(client, title)) {
++count;
}
}
return count;
}
std::string TitleManager::GetPrefix(int title_id)
{
if (!title_id) {
-1
View File
@@ -51,7 +51,6 @@ public:
EQApplicationPacket *MakeTitlesPacket(Client *client);
std::string GetPrefix(int title_id);
std::string GetSuffix(int title_id);
int NumberOfAvailableTitles(Client *client);
bool IsClientEligibleForTitle(Client *client, TitleEntry title);
bool IsNewAATitleAvailable(int aa_points, int class_id);
bool IsNewTradeSkillTitleAvailable(int skill_id, int skill_value);
+78 -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,66 @@ bool ZoneDatabase::DisableRecipe(uint32 recipe_id)
return false;
}
bool Client::CheckTradeskillLoreConflict(int32 recipe_id)
{
auto recipe_entries = TradeskillRecipeEntriesRepository::GetWhere(
content_db,
fmt::format(
"recipe_id = {} ORDER BY componentcount DESC",
recipe_id
)
);
if (recipe_entries.empty()) {
return false;
}
// validate which items from the recipe we will call CheckLoreConflict on
for (const auto &tre : recipe_entries) {
if (tre.item_id) {
auto tre_inst = database.GetItem(tre.item_id);
// To compare items we iterate against each item in the recipe that have a loregroup.
for (auto &tre_update_item : recipe_entries) {
bool fi_is_valid = tre_update_item.item_id && tre_inst && tre_inst->LoreGroup != 0;
if (fi_is_valid) {
auto tre_update_item_inst = database.GetItem(tre_update_item.item_id);
bool ei_is_valid = tre_update_item_inst && tre_update_item_inst->LoreGroup != 0;
if (ei_is_valid) {
bool unique_lore_group_match = tre_inst->LoreGroup > 0 && tre_inst->LoreGroup == tre_update_item_inst->LoreGroup;
bool component_count_is_valid = tre_update_item.componentcount == 0 && tre.componentcount > 0;
// If the recipe item is a component, and matches a unique lore group (> 0) or the item_id matches another entry in the recipe
// zero out the item_id, this will prevent us from doing a lore check inadvertently where
// the item is a component, and returned on success, fail, salvage.
// or uses an item that is part of a unique loregroup that returns an item of the same unique loregroup
if (ei_is_valid && (tre_update_item.item_id == tre.item_id || unique_lore_group_match) && component_count_is_valid) {
tre_update_item.item_id = 0;
}
}
}
}
if (tre_inst) {
if (tre_inst->LoreGroup == 0 || tre.componentcount > 0 || tre.iscontainer) {
continue;
}
if (CheckLoreConflict(tre_inst)) {
EQ::SayLinkEngine linker;
linker.SetLinkType(EQ::saylink::SayLinkItemData);
linker.SetItemData(tre_inst);
auto item_link = linker.GenerateLink();
MessageString(Chat::Red, TRADESKILL_COMBINE_LORE, item_link.c_str());
return true;
}
}
}
}
return false;
}
+9 -8
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) {
@@ -934,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;
+17 -13
View File
@@ -13,6 +13,7 @@
#include "zonedb.h"
#include "aura.h"
#include "../common/repositories/criteria/content_filter_criteria.h"
#include "../common/repositories/character_disciplines_repository.h"
#include "../common/repositories/npc_types_repository.h"
#include <ctime>
@@ -814,22 +815,25 @@ bool ZoneDatabase::LoadCharacterLeadershipAA(uint32 character_id, PlayerProfile_
}
bool ZoneDatabase::LoadCharacterDisciplines(uint32 character_id, PlayerProfile_Struct* pp){
std::string query = StringFormat(
"SELECT "
"disc_id "
"FROM "
"`character_disciplines`"
"WHERE `id` = %u ORDER BY `slot_id`", character_id);
auto results = database.QueryDatabase(query);
int i = 0;
auto character_disciplines = CharacterDisciplinesRepository::GetWhere(
database, fmt::format(
"`id` = {} ORDER BY `slot_id`",
character_id
)
);
if (character_disciplines.empty()) {
return false;
}
/* Initialize Disciplines */
memset(pp->disciplines.values, 0, (sizeof(pp->disciplines.values[0]) * MAX_PP_DISCIPLINES));
for (auto& row = results.begin(); row != results.end(); ++row) {
if (i < MAX_PP_DISCIPLINES)
pp->disciplines.values[i] = atoi(row[0]);
++i;
}
for (auto& row : character_disciplines) {
if (row.slot_id < MAX_PP_DISCIPLINES && IsValidSpell(row.disc_id)) {
pp->disciplines.values[row.slot_id] = row.disc_id;
}
}
return true;
}