Compare commits

..

26 Commits

Author SHA1 Message Date
Akkadius c25cb0cc23 [Release] 22.22.1 2023-07-30 01:36:49 -05:00
Chris Miles ddac326239 [Doors] Add door blacklist (#3516)
* [Doors] Add door blacklist

* Renaming to simplify
2023-07-30 01:35:44 -05:00
Akkadius 14fe396510 [Database] Hotfix: Add command_subsettings to server tables 2023-07-28 22:22:43 -05:00
Chris Miles c968a0acdc [Release] 22.22.0 (#3513)
* [Release] 22.22.0

* Version other areas
2023-07-28 11:35:40 -05:00
Alex King 8c4cd34e01 [Quest API] Add GetMobTypeIdentifier() to Perl/Lua (#3512)
# Perl
- Add `$mob->GetMobTypeIdentifier()`.

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

# Notes
- Gets unique identifier independent of mob type.
2023-07-27 23:16:41 -05:00
Akkadius 0dbcf83a11 [Database] Fix console output in database:dump --dump-output-to-console 2023-07-25 11:45:26 -05:00
Alex King a75648f73f [Data Buckets] Distributed Databucket Caching (#3500)
* [Data Buckets] Zone-Based Data Bucket Caching

# Notes
- Adds a data bucket cache so we're not needlessly hitting the database every time we need to read a data bucket value.

* Cleanup and unify GetData access patterns

* Cache work

* Push

* Add to cache when we fetch and do a db hit

* Handle bucket misses in cache

* Formatting

* Logging

* [Data Buckets] Zone-Based Data Bucket Caching

- Adds a data bucket cache so we're not needlessly hitting the database every time we need to read a data bucket value.

* Cleanup and unify GetData access patterns

* Cache work

* Push

* Add to cache when we fetch and do a db hit

* Handle bucket misses in cache

* Formatting

* Remove redundant fetches from cache since GetData does the same thing

* Push progress

* Distributed cache work

* Logging

* Fix issue with scoping where same named keys could return overlapping results

* Misses cache tweak, logging, comments

* Add bot, client, and NPC bucket methods to Lua.

---------

Co-authored-by: Akkadius <akkadius1@gmail.com>
2023-07-24 12:22:50 -05:00
Alex King 6c2886a71d [Cleanup] Fix casing in corpse money and decay time. (#3511)
# Notes
- These were uppercase and should be lowercase.
2023-07-23 16:19:07 -04:00
Alex King 1d96ddb60d [Bug Fix] Escape search string in #find item (#3510) 2023-07-22 18:20:00 -04:00
Vayle c30074be66 [Crash Fix] Guard against Spells:MaxTotalSlotsPET being set above client allowed maximum. (#3507)
* Guard against MaxTotalSlotsPET being set too high

This prevents a crash from MaxTotalSlotsPET being set too high.

* Tweak
2023-07-22 10:20:22 -04:00
Chris Miles b5652e6010 [Saylink] Fix cases where saylinks were not being cached (#3508)
* [Saylink] Fix cases where saylinks were not being cached

* Update say_link.cpp
2023-07-19 23:57:04 -05:00
Akkadius 202d2ed496 [Release] 22.21.2 2023-07-19 23:50:47 -05:00
Chris Miles 81cee49ea1 [Databuckets] Fix rarer same bucket name scoping overlap issue (#3509) 2023-07-19 23:49:11 -05:00
Akkadius 2d61cd2b9a [Release] 22.21.1 2023-07-18 20:50:56 -05:00
Vayle b06505b80a [Cleanup] Remove arbitrary teleport blocking in Tutorial and Load zones (#3506) 2023-07-18 20:56:21 -04:00
Chris Miles 4c2f9a4423 [Databuckets] Fix issue with expired databuckets not being expired and returned properly (#3504) 2023-07-18 17:18:36 -05:00
Alex King fb3159b657 [Bug Fix] #set title_suffix Argument Position (#3505)
# Notes
- Off by one on argument index.
2023-07-18 18:02:25 -04:00
Michael 8ebf5bbb78 [Bug] Show Petition and Show Petition_Info fix (#3503)
Where Statement was using incorrect field name.
2023-07-18 17:15:21 -04:00
Akkadius d2aae4d79c [Release] 22.21.0 2023-07-18 02:54:20 -05:00
Chris Miles f9dc9da42b [Pathing] Improvements to roambox logic, pathing (#3502)
* [Roambox] Improvements to roambox logic

* Update npc.cpp

* Update npc.cpp

* More pathing fixes
2023-07-18 02:52:04 -05:00
Chris Miles 3f3bbe98b5 [Data Buckets] Implement scoped data buckets (#3498)
* [Data Buckets] Implement scoped data buckets

* Update database_update_manifest.cpp

* Update data_bucket.cpp

* Update data_bucket.cpp

* Update data_bucket.cpp

* Update data_bucket.h

* Update database_update_manifest.cpp

* Add GetScopedDbFilters references

* Scope transfer
2023-07-16 14:52:28 -04:00
Chris Miles 59537ae977 [Z Clipping] Don't issue zclip adjustments when NPC is not moving (#3499) 2023-07-16 14:52:17 -04:00
Alex King ee45a28efe [Quest API] Add SetLDoNPoints() to Perl/Lua (#3496)
# Perl
- Add `$client->SetLDoNPoints(theme_id, points)`.

# Lua
- Add `client:SetLDoNPoints(theme_id, points)`.

# Notes
- Allows operators to directly set LDoN Points.
2023-07-15 21:46:49 -05:00
Jonathan Sider 70ce81fb0a [Bug Fix] Fix rule check and add rule for pickpocket command (#3492)
* Fix bugs in skills

-Add rule for allowing pickpocket
-Fix method that is supposed to check rule
-Changed Z axis range for pickpocket (was failing on giants)

* Add zoffset to account for taller models
2023-07-15 21:46:26 -05:00
Akkadius e06d28ad20 [Release] 22.20.1 2023-07-15 13:30:36 -05:00
Mitch Freeman d57489781c [Database] Fix database manifest entry for #3443
Updates the syntax for mariadb 10.1
2023-07-15 13:29:34 -05:00
49 changed files with 1365 additions and 382 deletions
+92
View File
@@ -1,3 +1,95 @@
## [22.22.1] - 07/30/2023
### Database
* Hotfix: Add command_subsettings to server tables @Akkadius 2023-07-29
### Doors
* Add door blacklist ([#3516](https://github.com/EQEmu/Server/pull/3516)) @Akkadius 2023-07-30
## [22.22.0] - 07/27/2023
### Code
* Fix casing in corpse money and decay time. ([#3511](https://github.com/EQEmu/Server/pull/3511)) @Kinglykrab 2023-07-23
### Crash Fix
* Guard against Spells:MaxTotalSlotsPET being set above client allowed maximum. ([#3507](https://github.com/EQEmu/Server/pull/3507)) @Valorith 2023-07-22
### Data Buckets
* Distributed Databucket Caching ([#3500](https://github.com/EQEmu/Server/pull/3500)) @Kinglykrab 2023-07-24
### Database
* Fix console output in database:dump --dump-output-to-console @Akkadius 2023-07-25
### Fixes
* Escape search string in #find item ([#3510](https://github.com/EQEmu/Server/pull/3510)) @Kinglykrab 2023-07-22
### Quest API
* Add GetMobTypeIdentifier() to Perl/Lua ([#3512](https://github.com/EQEmu/Server/pull/3512)) @Kinglykrab 2023-07-28
### Saylink
* Fix cases where saylinks were not being cached ([#3508](https://github.com/EQEmu/Server/pull/3508)) @Akkadius 2023-07-20
## [22.21.2] - 07/19/2023
### Databuckets
* Fix rarer same bucket name scoping overlap issue ([#3509](https://github.com/EQEmu/Server/pull/3509)) @Akkadius 2023-07-20
## [22.21.1] - 07/18/2023
### Bug
* Show Petition and Show Petition_Info fix ([#3503](https://github.com/EQEmu/Server/pull/3503)) @fryguy503 2023-07-18
### Code
* Remove arbitrary teleport blocking in Tutorial and Load zones ([#3506](https://github.com/EQEmu/Server/pull/3506)) @Valorith 2023-07-19
### Databuckets
* Fix issue with expired databuckets not being expired and returned properly ([#3504](https://github.com/EQEmu/Server/pull/3504)) @Akkadius 2023-07-18
### Fixes
* #set title_suffix Argument Position ([#3505](https://github.com/EQEmu/Server/pull/3505)) @Kinglykrab 2023-07-18
## [22.21.0] - 07/18/2023
### Data Buckets
* Implement scoped data buckets ([#3498](https://github.com/EQEmu/Server/pull/3498)) @Akkadius 2023-07-16
### Fixes
* Fix rule check and add rule for pickpocket command ([#3492](https://github.com/EQEmu/Server/pull/3492)) @tuday2 2023-07-16
### Pathing
* Improvements to roambox logic, pathing ([#3502](https://github.com/EQEmu/Server/pull/3502)) @Akkadius 2023-07-18
### Quest API
* Add SetLDoNPoints() to Perl/Lua ([#3496](https://github.com/EQEmu/Server/pull/3496)) @Kinglykrab 2023-07-16
### Z Clipping
* Don't issue zclip adjustments when NPC is not moving ([#3499](https://github.com/EQEmu/Server/pull/3499)) @Akkadius 2023-07-16
## [22.20.1] - 07/15/2023
### Database
* Fix database manifest entry for #3443 @neckkola 2023-07-15
## [22.20.0] - 07/15/2023
### Bots
+3 -1
View File
@@ -324,7 +324,9 @@ void DatabaseDumpService::DatabaseDump()
}
}
LogSys.LoadLogSettingsDefaults();
if (!IsDumpOutputToConsole()) {
LogSys.LoadLogSettingsDefaults();
}
if (!pipe_file.empty()) {
std::string file = fmt::format("{}.sql", GetDumpFileNameWithPath());
+27 -6
View File
@@ -4768,14 +4768,14 @@ UNIQUE KEY `name` (`name`)
.match = "",
.sql = R"(
ALTER TABLE `raid_members`
ADD COLUMN `is_marker` TINYINT UNSIGNED DEFAULT(0) NOT NULL AFTER `islooter`,
ADD COLUMN `is_assister` TINYINT UNSIGNED DEFAULT(0) NOT NULL AFTER `is_marker`,
ADD COLUMN `note` VARCHAR(64) DEFAULT("") NOT NULL AFTER `is_assister`;
ADD COLUMN `is_marker` TINYINT UNSIGNED DEFAULT 0 NOT NULL AFTER `islooter`,
ADD COLUMN `is_assister` TINYINT UNSIGNED DEFAULT 0 NOT NULL AFTER `is_marker`,
ADD COLUMN `note` VARCHAR(64) DEFAULT '' NOT NULL AFTER `is_assister`;
ALTER TABLE `raid_details`
ADD COLUMN `marked_npc_1` SMALLINT UNSIGNED DEFAULT(0) NOT NULL AFTER `motd`,
ADD COLUMN `marked_npc_2` SMALLINT UNSIGNED DEFAULT(0) NOT NULL AFTER `marked_npc_1`,
ADD COLUMN `marked_npc_3` SMALLINT UNSIGNED DEFAULT(0) NOT NULL AFTER `marked_npc_2`;
ADD COLUMN `marked_npc_1` SMALLINT UNSIGNED DEFAULT 0 NOT NULL AFTER `motd`,
ADD COLUMN `marked_npc_2` SMALLINT UNSIGNED DEFAULT 0 NOT NULL AFTER `marked_npc_1`,
ADD COLUMN `marked_npc_3` SMALLINT UNSIGNED DEFAULT 0 NOT NULL AFTER `marked_npc_2`;
)",
},
ManifestEntry{
@@ -4804,6 +4804,27 @@ UNIQUE KEY `name` (`name`)
PRIMARY KEY (`id`),
UNIQUE INDEX `command`(`parent_command`, `sub_command`)
)
)"
},
ManifestEntry{
.version = 9233,
.description = "2023_07_16_scoped_data_buckets.sql",
.check = "SHOW COLUMNS FROM `data_buckets` LIKE 'character_id'",
.condition = "empty",
.match = "",
.sql = R"(
ALTER TABLE `data_buckets`
ADD COLUMN `character_id` bigint(11) NOT NULL DEFAULT 0 AFTER `expires`,
ADD COLUMN `npc_id` bigint(11) NOT NULL DEFAULT 0 AFTER `character_id`,
ADD COLUMN `bot_id` bigint(11) NOT NULL DEFAULT 0 AFTER `npc_id`,
DROP INDEX `key_index`,
ADD UNIQUE INDEX `keys`(`key`,`character_id`,`npc_id`,`bot_id`);
UPDATE data_buckets SET character_id = SUBSTRING_INDEX(SUBSTRING_INDEX( `key`, '-', 2 ), '-', -1), `key` = SUBSTR(SUBSTRING_INDEX(`key`, SUBSTRING_INDEX( `key`, '-', 2 ), -1), 2) WHERE `key` LIKE 'character-%';
UPDATE data_buckets SET npc_id = SUBSTRING_INDEX(SUBSTRING_INDEX( `key`, '-', 2 ), '-', -1), `key` = SUBSTR(SUBSTRING_INDEX(`key`, SUBSTRING_INDEX( `key`, '-', 2 ), -1), 2) WHERE `key` LIKE 'npc-%';
UPDATE data_buckets SET bot_id = SUBSTRING_INDEX(SUBSTRING_INDEX( `key`, '-', 2 ), '-', -1), `key` = SUBSTR(SUBSTRING_INDEX(`key`, SUBSTRING_INDEX( `key`, '-', 2 ), -1), 2) WHERE `key` LIKE 'bot-%';
)"
},
+1
View File
@@ -258,6 +258,7 @@ namespace DatabaseSchema {
"chatchannels",
"chatchannel_reserved_names",
"command_settings",
"command_subsettings",
"content_flags",
"db_str",
"eqtime",
+2
View File
@@ -137,6 +137,7 @@ namespace Logs {
Bugs,
QuestErrors,
PlayerEvents,
DataBuckets,
MaxCategoryID /* Don't Remove this */
};
@@ -233,6 +234,7 @@ namespace Logs {
"Bugs",
"QuestErrors",
"PlayerEvents",
"DataBuckets",
};
}
+10
View File
@@ -794,6 +794,16 @@
OutF(LogSys, Logs::Detail, Logs::PlayerEvents, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define LogDataBuckets(message, ...) do {\
if (LogSys.IsLogEnabled(Logs::General, Logs::DataBuckets))\
OutF(LogSys, Logs::General, Logs::DataBuckets, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define LogDataBucketsDetail(message, ...) do {\
if (LogSys.IsLogEnabled(Logs::Detail, Logs::DataBuckets))\
OutF(LogSys, Logs::Detail, Logs::DataBuckets, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define Log(debug_level, log_category, message, ...) do {\
if (LogSys.IsLogEnabled(debug_level, log_category))\
LogSys.Out(debug_level, log_category, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
@@ -15,7 +15,7 @@
#include "../../database.h"
#include "../../strings.h"
#include <ctime>
#include <cereal/cereal.hpp>
class BaseDataBucketsRepository {
public:
@@ -24,6 +24,24 @@ public:
std::string key_;
std::string value;
uint32_t expires;
int64_t character_id;
int64_t npc_id;
int64_t bot_id;
// cereal
template<class Archive>
void serialize(Archive &ar)
{
ar(
CEREAL_NVP(id),
CEREAL_NVP(key_),
CEREAL_NVP(value),
CEREAL_NVP(expires),
CEREAL_NVP(character_id),
CEREAL_NVP(npc_id),
CEREAL_NVP(bot_id)
);
}
};
static std::string PrimaryKey()
@@ -38,6 +56,9 @@ public:
"`key`",
"value",
"expires",
"character_id",
"npc_id",
"bot_id",
};
}
@@ -48,6 +69,9 @@ public:
"`key`",
"value",
"expires",
"character_id",
"npc_id",
"bot_id",
};
}
@@ -88,10 +112,13 @@ public:
{
DataBuckets e{};
e.id = 0;
e.key_ = "";
e.value = "";
e.expires = 0;
e.id = 0;
e.key_ = "";
e.value = "";
e.expires = 0;
e.character_id = 0;
e.npc_id = 0;
e.bot_id = 0;
return e;
}
@@ -128,10 +155,13 @@ public:
if (results.RowCount() == 1) {
DataBuckets e{};
e.id = strtoull(row[0], nullptr, 10);
e.key_ = row[1] ? row[1] : "";
e.value = row[2] ? row[2] : "";
e.expires = static_cast<uint32_t>(strtoul(row[3], nullptr, 10));
e.id = strtoull(row[0], nullptr, 10);
e.key_ = row[1] ? row[1] : "";
e.value = row[2] ? row[2] : "";
e.expires = static_cast<uint32_t>(strtoul(row[3], nullptr, 10));
e.character_id = strtoll(row[4], nullptr, 10);
e.npc_id = strtoll(row[5], nullptr, 10);
e.bot_id = strtoll(row[6], nullptr, 10);
return e;
}
@@ -168,6 +198,9 @@ public:
v.push_back(columns[1] + " = '" + Strings::Escape(e.key_) + "'");
v.push_back(columns[2] + " = '" + Strings::Escape(e.value) + "'");
v.push_back(columns[3] + " = " + std::to_string(e.expires));
v.push_back(columns[4] + " = " + std::to_string(e.character_id));
v.push_back(columns[5] + " = " + std::to_string(e.npc_id));
v.push_back(columns[6] + " = " + std::to_string(e.bot_id));
auto results = db.QueryDatabase(
fmt::format(
@@ -193,6 +226,9 @@ public:
v.push_back("'" + Strings::Escape(e.key_) + "'");
v.push_back("'" + Strings::Escape(e.value) + "'");
v.push_back(std::to_string(e.expires));
v.push_back(std::to_string(e.character_id));
v.push_back(std::to_string(e.npc_id));
v.push_back(std::to_string(e.bot_id));
auto results = db.QueryDatabase(
fmt::format(
@@ -226,6 +262,9 @@ public:
v.push_back("'" + Strings::Escape(e.key_) + "'");
v.push_back("'" + Strings::Escape(e.value) + "'");
v.push_back(std::to_string(e.expires));
v.push_back(std::to_string(e.character_id));
v.push_back(std::to_string(e.npc_id));
v.push_back(std::to_string(e.bot_id));
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
}
@@ -259,10 +298,13 @@ public:
for (auto row = results.begin(); row != results.end(); ++row) {
DataBuckets e{};
e.id = strtoull(row[0], nullptr, 10);
e.key_ = row[1] ? row[1] : "";
e.value = row[2] ? row[2] : "";
e.expires = static_cast<uint32_t>(strtoul(row[3], nullptr, 10));
e.id = strtoull(row[0], nullptr, 10);
e.key_ = row[1] ? row[1] : "";
e.value = row[2] ? row[2] : "";
e.expires = static_cast<uint32_t>(strtoul(row[3], nullptr, 10));
e.character_id = strtoll(row[4], nullptr, 10);
e.npc_id = strtoll(row[5], nullptr, 10);
e.bot_id = strtoll(row[6], nullptr, 10);
all_entries.push_back(e);
}
@@ -287,10 +329,13 @@ public:
for (auto row = results.begin(); row != results.end(); ++row) {
DataBuckets e{};
e.id = strtoull(row[0], nullptr, 10);
e.key_ = row[1] ? row[1] : "";
e.value = row[2] ? row[2] : "";
e.expires = static_cast<uint32_t>(strtoul(row[3], nullptr, 10));
e.id = strtoull(row[0], nullptr, 10);
e.key_ = row[1] ? row[1] : "";
e.value = row[2] ? row[2] : "";
e.expires = static_cast<uint32_t>(strtoul(row[3], nullptr, 10));
e.character_id = strtoll(row[4], nullptr, 10);
e.npc_id = strtoll(row[5], nullptr, 10);
e.bot_id = strtoll(row[6], nullptr, 10);
all_entries.push_back(e);
}
+1 -1
View File
@@ -53,7 +53,7 @@ public:
auto query = fmt::format(
"SELECT `id` FROM {} WHERE LOWER(`name`) LIKE '%%{}%%' ORDER BY id ASC",
TableName(),
search_string
Strings::Escape(search_string)
);
if (query_limit >= 1) {
+1
View File
@@ -637,6 +637,7 @@ RULE_BOOL(Bots, OldRaceRezEffects, false, "Older clients had ID 757 for races wi
RULE_BOOL(Bots, ResurrectionSickness, true, "Use Resurrection Sickness based on Resurrection spell cast, set to false to disable Resurrection Sickness.")
RULE_INT(Bots, OldResurrectionSicknessSpell, 757, "757 is Default Old Resurrection Sickness Spell")
RULE_INT(Bots, ResurrectionSicknessSpell, 756, "756 is Default Resurrection Sickness Spell")
RULE_BOOL(Bots, AllowPickpocketCommand, true, "Allows the use of the bot command 'pickpocket'")
RULE_CATEGORY_END()
RULE_CATEGORY(Chat)
+2 -1
View File
@@ -376,7 +376,7 @@ std::string EQ::SayLinkEngine::InjectSaylinksIfNotExist(const char *message)
void EQ::SayLinkEngine::LoadCachedSaylinks()
{
auto saylinks = SaylinkRepository::GetWhere(database, "phrase not like '%#%'");
auto saylinks = SaylinkRepository::GetWhere(database, "phrase not REGEXP BINARY '[A-Z]' and phrase not REGEXP '[0-9]'");
LogSaylink("Loaded [{}] saylinks into cache", saylinks.size());
g_cached_saylinks = saylinks;
}
@@ -399,6 +399,7 @@ SaylinkRepository::Saylink EQ::SayLinkEngine::GetOrSaveSaylink(std::string sayli
// return if found from the database
if (!saylinks.empty()) {
g_cached_saylinks.emplace_back(saylinks[0]);
return saylinks[0];
}
+7
View File
@@ -286,6 +286,8 @@
// player events
#define ServerOP_PlayerEvent 0x5100
#define ServerOP_DataBucketCacheUpdate 0x5200
enum {
CZUpdateType_Character,
CZUpdateType_Group,
@@ -1820,6 +1822,11 @@ struct ServerSendPlayerEvent_Struct {
char cereal_data[0];
};
struct ServerDataBucketCacheUpdate_Struct {
uint32_t cereal_size;
char cereal_data[0];
};
struct ServerFlagUpdate_Struct {
uint32 account_id;
int16 admin;
+2 -2
View File
@@ -25,7 +25,7 @@
// Build variables
// these get injected during the build pipeline
#define CURRENT_VERSION "22.20.0-dev" // always append -dev to the current version for custom-builds
#define CURRENT_VERSION "22.22.1-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,7 +42,7 @@
* Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt
*/
#define CURRENT_BINARY_DATABASE_VERSION 9232
#define CURRENT_BINARY_DATABASE_VERSION 9233
#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9039
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "eqemu-server",
"version": "22.20.0",
"version": "22.22.1",
"repository": {
"type": "git",
"url": "https://github.com/EQEmu/Server.git"
@@ -112,6 +112,7 @@ if ($requested_table_to_generate ne "all") {
}
my @cereal_enabled_tables = (
"data_buckets",
"player_event_logs"
);
+6
View File
@@ -46,6 +46,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#include "../common/repositories/player_event_logs_repository.h"
#include "../common/events/player_event_logs.h"
#include "../common/patches/patches.h"
#include "../zone/data_bucket.h"
extern ClientList client_list;
extern GroupLFPList LFPGroupList;
@@ -1468,6 +1469,11 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
DynamicZone::HandleZoneMessage(pack);
break;
}
case ServerOP_DataBucketCacheUpdate: {
zoneserver_list.SendPacket(pack);
break;
}
default: {
LogInfo("Unknown ServerOPcode from zone {:#04x}, size [{}]", pack->opcode, pack->size);
DumpPacket(pack->pBuffer, pack->size);
+3 -7
View File
@@ -6033,14 +6033,10 @@ float Mob::CheckHeroicBonusesDataBuckets(std::string bucket_name)
{
std::string bucket_value;
if (!bucket_name.empty()) {
const auto full_name = fmt::format(
"{}-{}",
GetBucketKey(),
bucket_name
);
DataBucketKey k = GetScopedBucketKeys();
k.key = bucket_name;
if (IsOfClientBot()) {
bucket_value = DataBucket::CheckBucketKey(this, full_name);
bucket_value = DataBucket::GetData(k).value;
}
if (bucket_value.empty() || !Strings::IsNumber(bucket_value)) {
+11 -15
View File
@@ -427,6 +427,7 @@ Bot::Bot(uint32 botID, uint32 botOwnerCharacterID, uint32 botSpellsID, double to
Bot::~Bot() {
AI_Stop();
LeaveHealRotationMemberPool();
DataBucket::DeleteCachedBuckets(DataBucketLoadType::Bot, GetBotID());
if (HasPet()) {
GetPet()->Depop();
@@ -8193,27 +8194,22 @@ void Bot::OwnerMessage(const std::string& message)
bool Bot::CheckDataBucket(std::string bucket_name, const std::string& bucket_value, uint8 bucket_comparison)
{
if (!bucket_name.empty() && !bucket_value.empty()) {
auto full_name = fmt::format(
"{}-{}",
GetBucketKey(),
bucket_name
);
// try to fetch from bot first
DataBucketKey k = GetScopedBucketKeys();
k.key = bucket_name;
auto player_value = DataBucket::CheckBucketKey(this, full_name);
if (player_value.empty() && GetBotOwner()) {
full_name = fmt::format(
"{}-{}",
GetBotOwner()->GetBucketKey(),
bucket_name
);
auto b = DataBucket::GetData(k);
if (b.value.empty() && GetBotOwner()) {
// fetch from owner
k = GetBotOwner()->GetScopedBucketKeys();
player_value = DataBucket::CheckBucketKey(GetBotOwner(), full_name);
if (player_value.empty()) {
b = DataBucket::GetData(k);
if (b.value.empty()) {
return false;
}
}
if (zone->CompareDataBucket(bucket_comparison, bucket_value, player_value)) {
if (zone->CompareDataBucket(bucket_comparison, bucket_value, b.value)) {
return true;
}
}
+8 -2
View File
@@ -9170,7 +9170,7 @@ bool helper_cast_standard_spell(Bot* casting_bot, Mob* target_mob, int spell_id,
bool helper_command_disabled(Client* bot_owner, bool rule_value, const char* command)
{
if (rule_value) {
if (!rule_value) {
bot_owner->Message(Chat::White, "Bot command %s is not enabled on this server.", command);
return true;
}
@@ -9999,6 +9999,10 @@ void bot_command_caster_range(Client* c, const Seperator* sep)
void bot_command_pickpocket(Client *c, const Seperator *sep)
{
if (helper_command_disabled(c, RuleB(Bots, AllowPickpocketCommand), "pickpocket")) {
return;
}
if (helper_command_alias_fail(c, "bot_command_pickpocket", sep->arg[0], "pickpocket")) {
return;
}
@@ -10032,7 +10036,9 @@ void bot_command_pickpocket(Client *c, const Seperator *sep)
glm::vec4 mob_distance = (c->GetPosition() - target_mob->GetPosition());
float mob_xy_distance = ((mob_distance.x * mob_distance.x) + (mob_distance.y * mob_distance.y));
float mob_z_distance = (mob_distance.z * mob_distance.z);
if (mob_z_distance >= 25 || mob_xy_distance > 250) {
float z_offset_diff = target_mob->GetZOffset() - c->GetZOffset();
if (mob_z_distance >= (35-z_offset_diff) || mob_xy_distance > 250) {
c->Message(Chat::White, "You must be closer to an enemy to use this command");
return;
}
+9 -1
View File
@@ -381,6 +381,8 @@ Client::Client(EQStreamInterface *ieqs) : Mob(
Client::~Client() {
mMovementManager->RemoveClient(this);
DataBucket::DeleteCachedBuckets(DataBucketLoadType::Client, CharacterID());
if (RuleB(Bots, Enabled)) {
Bot::ProcessBotOwnerRefDelete(this);
}
@@ -1558,7 +1560,13 @@ void Client::SetLDoNPoints(uint32 theme_id, uint32 points)
}
}
m_pp.ldon_points_available += points;
m_pp.ldon_points_available = (
m_pp.ldon_points_guk +
m_pp.ldon_points_mir +
m_pp.ldon_points_mmc +
m_pp.ldon_points_ruj +
m_pp.ldon_points_tak
);
auto outapp = new EQApplicationPacket(OP_AdventurePointsUpdate, sizeof(AdventurePoints_Update_Struct));
+2 -2
View File
@@ -5224,12 +5224,12 @@ void Client::Handle_OP_ConsiderCorpse(const EQApplicationPacket *app)
uint32 decay_time = t->GetDecayTime();
if (decay_time) {
auto time_string = Strings::SecondsToTime(decay_time, true);
const std::string& time_string = Strings::SecondsToTime(decay_time, true);
Message(
Chat::NPCQuestSay,
fmt::format(
"This corpse will decay in {}.",
time_string
Strings::ToLower(time_string)
).c_str()
);
+6 -8
View File
@@ -853,18 +853,16 @@ void Client::BulkSendMerchantInventory(int merchant_id, int npcid) {
auto bucket_name = ml.bucket_name;
auto const& bucket_value = ml.bucket_value;
if (!bucket_name.empty() && !bucket_value.empty()) {
auto full_name = fmt::format(
"{}-{}",
GetBucketKey(),
bucket_name
);
auto const& player_value = DataBucket::CheckBucketKey(this, full_name);
if (player_value.empty()) {
DataBucketKey k = GetScopedBucketKeys();
k.key = bucket_name;
auto b = DataBucket::GetData(k);
if (b.value.empty()) {
continue;
}
if (!zone->CompareDataBucket(ml.bucket_comparison, bucket_value, player_value)) {
if (!zone->CompareDataBucket(ml.bucket_comparison, bucket_value, b.value)) {
continue;
}
}
+2 -2
View File
@@ -1207,7 +1207,7 @@ void Corpse::MakeLootRequestPackets(Client* client, const EQApplicationPacket* a
client->Message(
Chat::Yellow,
fmt::format(
"This corpse Contains {}.",
"This corpse contains {}.",
Strings::Money(
GetPlatinum(),
GetGold(),
@@ -1217,7 +1217,7 @@ void Corpse::MakeLootRequestPackets(Client* client, const EQApplicationPacket* a
).c_str()
);
} else {
client->Message(Chat::Yellow, "This corpse Contains no money.");
client->Message(Chat::Yellow, "This corpse contains no money.");
}
auto outapp = new EQApplicationPacket(OP_MoneyOnCorpse, sizeof(moneyOnCorpseStruct));
+628 -168
View File
@@ -1,205 +1,665 @@
#include "data_bucket.h"
#include "entity.h"
#include "zonedb.h"
#include "mob.h"
#include "worldserver.h"
#include <ctime>
#include <cctype>
/**
* Persists data via bucket_name as key
* @param bucket_key
* @param bucket_value
* @param expires_time
*/
void DataBucket::SetData(const std::string& bucket_key, const std::string& bucket_value, std::string expires_time) {
uint64 bucket_id = DataBucket::DoesBucketExist(bucket_key);
extern WorldServer worldserver;
std::string query;
long long expires_time_unix = 0;
std::vector<DataBucketCacheEntry> g_data_bucket_cache = {};
if (!expires_time.empty()) {
if (isalpha(expires_time[0]) || isalpha(expires_time[expires_time.length() - 1])) {
expires_time_unix = (long long) std::time(nullptr) + Strings::TimeToSeconds(expires_time);
} else {
expires_time_unix = (long long) std::time(nullptr) + Strings::ToInt(expires_time);
void DataBucket::SetData(const std::string &bucket_key, const std::string &bucket_value, std::string expires_time)
{
auto k = DataBucketKey{
.key = bucket_key,
.value = bucket_value,
.expires = expires_time,
.character_id = 0,
.npc_id = 0,
.bot_id = 0
};
DataBucket::SetData(k);
}
void DataBucket::SetData(const DataBucketKey &k)
{
auto b = DataBucketsRepository::NewEntity();
auto r = GetData(k, true);
// if we have an entry, use it
if (r.id > 0) {
b = r;
}
// add scoping to bucket
if (k.character_id > 0) {
b.character_id = k.character_id;
}
else if (k.npc_id > 0) {
b.npc_id = k.npc_id;
}
else if (k.bot_id > 0) {
b.bot_id = k.bot_id;
}
const uint64 bucket_id = b.id;
int64 expires_time_unix = 0;
if (!k.expires.empty()) {
expires_time_unix = static_cast<int64>(std::time(nullptr)) + Strings::ToInt(k.expires);
if (isalpha(k.expires[0]) || isalpha(k.expires[k.expires.length() - 1])) {
expires_time_unix = static_cast<int64>(std::time(nullptr)) + Strings::TimeToSeconds(k.expires);
}
}
if (bucket_id > 0) {
std::string update_expired_time;
if (expires_time_unix > 0) {
update_expired_time = StringFormat(", `expires` = %lld ", expires_time_unix);
b.expires = expires_time_unix;
b.value = k.value;
if (bucket_id) {
// loop cache and update cache value and timestamp
for (auto &ce: g_data_bucket_cache) {
if (CheckBucketMatch(ce.e, k)) {
ce.e = b;
ce.updated_time = GetCurrentTimeUNIX();
ce.update_action = DataBucketCacheUpdateAction::Upsert;
SendDataBucketCacheUpdate(ce);
break;
}
}
query = StringFormat(
"UPDATE `data_buckets` SET `value` = '%s' %s WHERE `id` = %i",
Strings::Escape(bucket_value).c_str(),
Strings::Escape(update_expired_time).c_str(),
bucket_id
);
DataBucketsRepository::UpdateOne(database, b);
}
else {
query = StringFormat(
"INSERT INTO `data_buckets` (`key`, `value`, `expires`) VALUES ('%s', '%s', '%lld')",
Strings::Escape(bucket_key).c_str(),
Strings::Escape(bucket_value).c_str(),
expires_time_unix
);
}
b.key_ = k.key;
b = DataBucketsRepository::InsertOne(database, b);
if (!ExistsInCache(b)) {
// add data bucket and timestamp to cache
auto ce = DataBucketCacheEntry{
.e = b,
.updated_time = DataBucket::GetCurrentTimeUNIX(),
.update_action = DataBucketCacheUpdateAction::Upsert
};
database.QueryDatabase(query);
g_data_bucket_cache.emplace_back(ce);
SendDataBucketCacheUpdate(ce);
DeleteFromMissesCache(b);
}
}
}
/**
* Retrieves data via bucket_name as key
* @param bucket_key
* @return
*/
std::string DataBucket::GetData(const std::string& bucket_key) {
std::string query = StringFormat(
"SELECT `value` from `data_buckets` WHERE `key` = '%s' AND (`expires` > %lld OR `expires` = 0) LIMIT 1",
bucket_key.c_str(),
(long long) std::time(nullptr)
);
auto results = database.QueryDatabase(query);
if (!results.Success()) {
return std::string();
}
if (results.RowCount() != 1)
return std::string();
auto row = results.begin();
return std::string(row[0]);
}
/**
* Retrieves data expires time via bucket_name as key
* @param bucket_key
* @return
*/
std::string DataBucket::GetDataExpires(const std::string& bucket_key) {
std::string query = StringFormat(
"SELECT `expires` from `data_buckets` WHERE `key` = '%s' AND (`expires` > %lld OR `expires` = 0) LIMIT 1",
bucket_key.c_str(),
(long long) std::time(nullptr)
);
auto results = database.QueryDatabase(query);
if (!results.Success()) {
return std::string();
}
if (results.RowCount() != 1)
return std::string();
auto row = results.begin();
return std::string(row[0]);
}
std::string DataBucket::GetDataRemaining(const std::string& bucket_key) {
if (DataBucket::GetDataExpires(bucket_key).empty()) {
return "0";
}
std::string query = fmt::format(
"SELECT (`expires` - UNIX_TIMESTAMP()) AS `remaining` from `data_buckets` WHERE `key` = '{}' AND (`expires` > {} OR `expires` = 0) LIMIT 1",
bucket_key.c_str(),
(long long) std::time(nullptr)
);
auto results = database.QueryDatabase(query);
if (!results.Success()) {
return std::string();
}
if (results.RowCount() != 1)
return std::string();
auto row = results.begin();
return std::string(row[0]);
}
/**
* Checks for bucket existence by bucket_name key
* @param bucket_key
* @return
*/
uint64 DataBucket::DoesBucketExist(const std::string& bucket_key) {
std::string query = StringFormat(
"SELECT `id` from `data_buckets` WHERE `key` = '%s' AND (`expires` > %lld OR `expires` = 0) LIMIT 1",
Strings::Escape(bucket_key).c_str(),
(long long) std::time(nullptr)
);
auto results = database.QueryDatabase(query);
if (!results.Success()) {
return 0;
}
auto row = results.begin();
if (results.RowCount() != 1)
return 0;
return Strings::ToUnsignedBigInt(row[0]);
}
/**
* Deletes data bucket by key
* @param bucket_key
* @return
*/
bool DataBucket::DeleteData(const std::string& bucket_key) {
std::string query = StringFormat(
"DELETE FROM `data_buckets` WHERE `key` = '%s'",
Strings::Escape(bucket_key).c_str()
);
auto results = database.QueryDatabase(query);
return results.Success();
}
bool DataBucket::GetDataBuckets(Mob* mob)
std::string DataBucket::GetData(const std::string &bucket_key)
{
auto l = BaseDataBucketsRepository::GetWhere(
DataBucketKey k = {};
k.key = bucket_key;
return GetData(k).value;
}
// GetData fetches bucket data from the database or cache if it exists
// if the bucket doesn't exist, it will be added to the cache as a miss
// if ignore_misses_cache is true, the bucket will not be added to the cache as a miss
// the only place we should be ignoring the misses cache is on the initial read during SetData
DataBucketsRepository::DataBuckets DataBucket::GetData(const DataBucketKey &k, bool ignore_misses_cache)
{
LogDataBuckets(
"Getting bucket key [{}] bot_id [{}] character_id [{}] npc_id [{}]",
k.key,
k.bot_id,
k.character_id,
k.npc_id
);
for (const auto &ce: g_data_bucket_cache) {
if (CheckBucketMatch(ce.e, k)) {
if (ce.e.expires > 0 && ce.e.expires < std::time(nullptr)) {
LogDataBuckets("Attempted to read expired key [{}] removing from cache", ce.e.key_);
DeleteData(k);
return DataBucketsRepository::NewEntity();
}
// this is a bucket miss, return empty entity
// we still cache bucket misses, so we don't have to hit the database
if (ce.e.id == 0) {
return DataBucketsRepository::NewEntity();
}
LogDataBuckets("Returning key [{}] value [{}] from cache", ce.e.key_, ce.e.value);
return ce.e;
}
}
auto r = DataBucketsRepository::GetWhere(
database,
fmt::format(
"`key` LIKE '{}-%'",
Strings::Escape(mob->GetBucketKey())
"{} `key` = '{}' LIMIT 1",
DataBucket::GetScopedDbFilters(k),
k.key
)
);
if (l.empty()) {
if (r.empty()) {
// if we're ignoring the misses cache, don't add to the cache
// the only place this is ignored is during the initial read of SetData
if (!ignore_misses_cache) {
size_t size_before = g_data_bucket_cache.size();
// cache bucket misses, so we don't have to hit the database
// when scripts try to read a bucket that doesn't exist
g_data_bucket_cache.emplace_back(
DataBucketCacheEntry{
.e = DataBucketsRepository::DataBuckets{
.id = 0,
.key_ = k.key,
.value = "",
.expires = 0,
.character_id = k.character_id,
.npc_id = k.npc_id,
.bot_id = k.bot_id
},
.updated_time = DataBucket::GetCurrentTimeUNIX()
}
);
LogDataBuckets(
"Key [{}] not found in database, adding to cache as a miss character_id [{}] npc_id [{}] bot_id [{}] cache size before [{}] after [{}]",
k.key,
k.character_id,
k.npc_id,
k.bot_id,
size_before,
g_data_bucket_cache.size()
);
}
return {};
}
// if the entry has expired, delete it
if (r[0].expires > 0 && r[0].expires < (long long) std::time(nullptr)) {
DeleteData(k);
return {};
}
bool has_cache = false;
for (auto &ce: g_data_bucket_cache) {
if (ce.e.id == r[0].id) {
has_cache = true;
break;
}
}
if (!has_cache) {
// add data bucket and timestamp to cache
g_data_bucket_cache.emplace_back(
DataBucketCacheEntry{
.e = r[0],
.updated_time = DataBucket::GetCurrentTimeUNIX()
}
);
}
return r[0];
}
std::string DataBucket::GetDataExpires(const std::string &bucket_key)
{
DataBucketKey k = {};
k.key = bucket_key;
return GetDataExpires(k);
}
std::string DataBucket::GetDataRemaining(const std::string &bucket_key)
{
DataBucketKey k = {};
k.key = bucket_key;
return GetDataRemaining(k);
}
bool DataBucket::DeleteData(const std::string &bucket_key)
{
DataBucketKey k = {};
k.key = bucket_key;
return DeleteData(k);
}
// GetDataBuckets bulk loads all data buckets for a mob
bool DataBucket::GetDataBuckets(Mob *mob)
{
DataBucketLoadType::Type t;
const uint32 id = mob->GetMobTypeIdentifier();
if (!id) {
return false;
}
mob->m_data_bucket_cache.clear();
DataBucketCache d;
for (const auto& e : l) {
d.bucket_id = e.id;
d.bucket_key = e.key_;
d.bucket_value = e.value;
d.bucket_expires = e.expires;
mob->m_data_bucket_cache.emplace_back(d);
if (mob->IsBot()) {
t = DataBucketLoadType::Bot;
}
else if (mob->IsClient()) {
t = DataBucketLoadType::Client;
}
else if (mob->IsNPC()) {
t = DataBucketLoadType::NPC;
}
BulkLoadEntities(t, {id});
return true;
}
std::string DataBucket::CheckBucketKey(const Mob* mob, std::string_view full_name)
bool DataBucket::DeleteData(const DataBucketKey &k)
{
std::string bucket_value;
for (const auto &d : mob->m_data_bucket_cache) {
if (d.bucket_key == full_name) {
bucket_value = d.bucket_value;
size_t size_before = g_data_bucket_cache.size();
// delete from cache where contents match
g_data_bucket_cache.erase(
std::remove_if(
g_data_bucket_cache.begin(),
g_data_bucket_cache.end(),
[&](DataBucketCacheEntry &ce) {
bool match = CheckBucketMatch(ce.e, k);
if (match) {
ce.update_action = DataBucketCacheUpdateAction::Delete;
SendDataBucketCacheUpdate(ce);
}
return match;
}
),
g_data_bucket_cache.end()
);
LogDataBuckets(
"Deleting bucket key [{}] bot_id [{}] character_id [{}] npc_id [{}] cache size before [{}] after [{}]",
k.key,
k.bot_id,
k.character_id,
k.npc_id,
size_before,
g_data_bucket_cache.size()
);
return DataBucketsRepository::DeleteWhere(
database,
fmt::format(
"{} `key` = '{}'",
DataBucket::GetScopedDbFilters(k),
k.key
)
);
}
std::string DataBucket::GetDataExpires(const DataBucketKey &k)
{
LogDataBuckets(
"Getting bucket expiration key [{}] bot_id [{}] character_id [{}] npc_id [{}]",
k.key,
k.bot_id,
k.character_id,
k.npc_id
);
auto r = GetData(k);
if (r.id == 0) {
return {};
}
return std::to_string(r.expires);
}
std::string DataBucket::GetDataRemaining(const DataBucketKey &k)
{
LogDataBuckets(
"Getting bucket remaining key [{}] bot_id [{}] character_id [{}] npc_id [{}]",
k.key,
k.bot_id,
k.character_id,
k.npc_id
);
auto r = GetData(k);
if (r.id == 0) {
return "0";
}
return fmt::format("{}", r.expires - (long long) std::time(nullptr));
}
std::string DataBucket::GetScopedDbFilters(const DataBucketKey &k)
{
std::vector<std::string> query = {};
if (k.character_id > 0) {
query.emplace_back(fmt::format("character_id = {}", k.character_id));
}
else {
query.emplace_back("character_id = 0");
}
if (k.npc_id > 0) {
query.emplace_back(fmt::format("npc_id = {}", k.npc_id));
}
else {
query.emplace_back("npc_id = 0");
}
if (k.bot_id > 0) {
query.emplace_back(fmt::format("bot_id = {}", k.bot_id));
}
else {
query.emplace_back("bot_id = 0");
}
return fmt::format(
"{} {}",
Strings::Join(query, " AND "),
!query.empty() ? "AND" : ""
);
}
bool DataBucket::CheckBucketMatch(const DataBucketsRepository::DataBuckets &dbe, const DataBucketKey &k)
{
return (
dbe.key_ == k.key &&
dbe.bot_id == k.bot_id &&
dbe.character_id == k.character_id &&
dbe.npc_id == k.npc_id
);
}
void DataBucket::BulkLoadEntities(DataBucketLoadType::Type t, std::vector<uint32> ids)
{
if (ids.empty()) {
return;
}
if (ids.size() == 1) {
bool has_cache = false;
for (const auto &ce: g_data_bucket_cache) {
if (t == DataBucketLoadType::Bot) {
has_cache = ce.e.bot_id == ids[0];
}
else if (t == DataBucketLoadType::Client) {
has_cache = ce.e.character_id == ids[0];
}
else if (t == DataBucketLoadType::NPC) {
has_cache = ce.e.npc_id == ids[0];
}
}
if (has_cache) {
LogDataBucketsDetail("LoadType [{}] ID [{}] has cache", DataBucketLoadType::Name[t], ids[0]);
return;
}
}
std::string column;
switch (t) {
case DataBucketLoadType::Bot:
column = "bot_id";
break;
case DataBucketLoadType::Client:
column = "character_id";
break;
case DataBucketLoadType::NPC:
column = "npc_id";
break;
default:
LogError("Incorrect LoadType [{}]", t);
break;
}
const auto &l = DataBucketsRepository::GetWhere(
database,
fmt::format(
"{} IN ({}) AND (`expires` > {} OR `expires` = 0)",
column,
Strings::Join(ids, ", "),
(long long) std::time(nullptr)
)
);
if (l.empty()) {
return;
}
size_t size_before = g_data_bucket_cache.size();
LogDataBucketsDetail("cache size before [{}] l size [{}]", g_data_bucket_cache.size(), l.size());
uint32 added_count = 0;
for (const auto &e: l) {
if (!ExistsInCache(e)) {
added_count++;
}
}
g_data_bucket_cache.reserve(g_data_bucket_cache.size() + added_count);
for (const auto &e: l) {
if (!ExistsInCache(e)) {
LogDataBucketsDetail("bucket id [{}] bucket key [{}] bucket value [{}]", e.id, e.key_, e.value);
g_data_bucket_cache.emplace_back(
DataBucketCacheEntry{
.e = e,
.updated_time = GetCurrentTimeUNIX()
}
);
}
}
LogDataBucketsDetail("cache size after [{}]", g_data_bucket_cache.size());
LogDataBuckets(
"Bulk Loaded ids [{}] column [{}] new cache size is [{}]",
ids.size(),
column,
g_data_bucket_cache.size()
);
}
void DataBucket::DeleteCachedBuckets(DataBucketLoadType::Type t, uint32 id)
{
size_t size_before = g_data_bucket_cache.size();
g_data_bucket_cache.erase(
std::remove_if(
g_data_bucket_cache.begin(),
g_data_bucket_cache.end(),
[&](DataBucketCacheEntry &ce) {
return (
(t == DataBucketLoadType::Bot && ce.e.bot_id == id) ||
(t == DataBucketLoadType::Client && ce.e.character_id == id) ||
(t == DataBucketLoadType::NPC && ce.e.npc_id == id)
);
}
),
g_data_bucket_cache.end()
);
LogDataBuckets(
"LoadType [{}] id [{}] cache size before [{}] after [{}]",
DataBucketLoadType::Name[t],
id,
size_before,
g_data_bucket_cache.size()
);
}
int64_t DataBucket::GetCurrentTimeUNIX()
{
return std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::system_clock::now().time_since_epoch()
).count();
}
bool DataBucket::ExistsInCache(const DataBucketsRepository::DataBuckets &e)
{
for (const auto &ce: g_data_bucket_cache) {
if (ce.e.id == e.id) {
return true;
}
}
return false;
}
bool DataBucket::SendDataBucketCacheUpdate(const DataBucketCacheEntry &e)
{
if (!e.e.id) {
return false;
}
EQ::Net::DynamicPacket p;
p.PutSerialize(0, e);
auto pack_size = sizeof(ServerDataBucketCacheUpdate_Struct) + p.Length();
auto pack = new ServerPacket(ServerOP_DataBucketCacheUpdate, static_cast<uint32_t>(pack_size));
auto buf = reinterpret_cast<ServerDataBucketCacheUpdate_Struct *>(pack->pBuffer);
buf->cereal_size = static_cast<uint32_t>(p.Length());
memcpy(buf->cereal_data, p.Data(), p.Length());
worldserver.SendPacket(pack);
return true;
}
void DataBucket::HandleWorldMessage(ServerPacket *p)
{
DataBucketCacheEntry n;
auto s = (ServerDataBucketCacheUpdate_Struct *) p->pBuffer;
EQ::Util::MemoryStreamReader ss(s->cereal_data, s->cereal_size);
cereal::BinaryInputArchive archive(ss);
archive(n);
LogDataBucketsDetail(
"Received cache packet for id [{}] key [{}] value [{}] action [{}]",
n.e.id,
n.e.key_,
n.e.value,
n.update_action
);
// delete
if (n.update_action == DataBucketCacheUpdateAction::Delete) {
g_data_bucket_cache.erase(
std::remove_if(
g_data_bucket_cache.begin(),
g_data_bucket_cache.end(),
[&](DataBucketCacheEntry &ce) {
bool match = n.e.id > 0 && ce.e.id == n.e.id;
if (match) {
LogDataBuckets(
"[delete] cache key [{}] id [{}] cache_size before [{}] after [{}]",
ce.e.key_,
ce.e.id,
g_data_bucket_cache.size(),
g_data_bucket_cache.size() - 1
);
}
return match;
}
),
g_data_bucket_cache.end()
);
return;
}
// update
bool has_key = false;
for (auto &ce: g_data_bucket_cache) {
int64 time_delta = ce.updated_time - n.updated_time;
if (ce.updated_time >= n.updated_time) {
LogDataBuckets(
"Attempted to update older cache key [{}] rejecting old time [{}] new time [{}] delta [{}] cache_size [{}]",
ce.e.key_,
ce.updated_time,
n.updated_time,
time_delta,
g_data_bucket_cache.size()
);
return;
}
// update cache
if (ce.e.id == n.e.id) {
DeleteFromMissesCache(n.e);
LogDataBuckets(
"[update] cache id [{}] key [{}] value [{}] old time [{}] new time [{}] delta [{}] cache_size [{}]",
ce.e.id,
ce.e.key_,
n.e.value,
ce.updated_time,
n.updated_time,
time_delta,
g_data_bucket_cache.size()
);
ce.e = n.e;
ce.updated_time = n.updated_time;
has_key = true;
break;
}
}
return bucket_value;
// create
if (!has_key) {
DeleteFromMissesCache(n.e);
size_t size_before = g_data_bucket_cache.size();
g_data_bucket_cache.emplace_back(
DataBucketCacheEntry{
.e = n.e,
.updated_time = GetCurrentTimeUNIX()
}
);
LogDataBuckets(
"[create] Adding new cache id [{}] key [{}] value [{}] cache size before [{}] after [{}]",
n.e.id,
n.e.key_,
n.e.value,
size_before,
g_data_bucket_cache.size()
);
}
}
void DataBucket::DeleteFromMissesCache(DataBucketsRepository::DataBuckets e)
{
// delete from cache where there might have been a written bucket miss to the cache
// this is to prevent the cache from growing too large
size_t size_before = g_data_bucket_cache.size();
g_data_bucket_cache.erase(
std::remove_if(
g_data_bucket_cache.begin(),
g_data_bucket_cache.end(),
[&](DataBucketCacheEntry &ce) {
return ce.e.id == 0 && ce.e.key_ == e.key_ &&
ce.e.character_id == e.character_id &&
ce.e.npc_id == e.npc_id &&
ce.e.bot_id == e.bot_id;
}
),
g_data_bucket_cache.end()
);
LogDataBucketsDetail(
"Deleted bucket misses from cache where key [{}] size before [{}] after [{}]",
e.key_,
size_before,
g_data_bucket_cache.size()
);
}
+75 -9
View File
@@ -9,19 +9,85 @@
#include "../common/types.h"
#include "../common/repositories/data_buckets_repository.h"
#include "mob.h"
#include "../common/json/json_archive_single_line.h"
#include "../common/servertalk.h"
enum DataBucketCacheUpdateAction : uint8 {
Upsert,
Delete
};
struct DataBucketCacheEntry {
DataBucketsRepository::DataBuckets e;
int64_t updated_time{};
DataBucketCacheUpdateAction update_action{};
template<class Archive>
void serialize(Archive &ar)
{
ar(
CEREAL_NVP(e),
CEREAL_NVP(updated_time),
CEREAL_NVP(update_action)
);
}
};
struct DataBucketKey {
std::string key;
std::string value;
std::string expires;
int64_t character_id;
int64_t npc_id;
int64_t bot_id;
};
namespace DataBucketLoadType {
enum Type : uint8 {
Bot,
Client,
NPC,
MaxType
};
static const std::string Name[Type::MaxType] = {
"Bot",
"Client",
"NPC",
};
}
class DataBucket {
public:
static void SetData(const std::string& bucket_key, const std::string& bucket_value, std::string expires_time = "");
static bool DeleteData(const std::string& bucket_key);
static std::string GetData(const std::string& bucket_key);
static std::string GetDataExpires(const std::string& bucket_key);
static std::string GetDataRemaining(const std::string& bucket_key);
static bool GetDataBuckets(Mob* mob);
static std::string CheckBucketKey(const Mob* mob, std::string_view full_name);
// non-scoped bucket methods (for global buckets)
static void SetData(const std::string &bucket_key, const std::string &bucket_value, std::string expires_time = "");
static bool DeleteData(const std::string &bucket_key);
static std::string GetData(const std::string &bucket_key);
static std::string GetDataExpires(const std::string &bucket_key);
static std::string GetDataRemaining(const std::string &bucket_key);
private:
static uint64 DoesBucketExist(const std::string& bucket_key);
static bool GetDataBuckets(Mob *mob);
static int64_t GetCurrentTimeUNIX();
// scoped bucket methods
static void SetData(const DataBucketKey &k);
static bool DeleteData(const DataBucketKey &k);
static DataBucketsRepository::DataBuckets GetData(const DataBucketKey &k, bool ignore_misses_cache = false);
static std::string GetDataExpires(const DataBucketKey &k);
static std::string GetDataRemaining(const DataBucketKey &k);
static std::string GetScopedDbFilters(const DataBucketKey &k);
// bucket repository versus key matching
static bool CheckBucketMatch(const DataBucketsRepository::DataBuckets &dbe, const DataBucketKey &k);
static bool ExistsInCache(const DataBucketsRepository::DataBuckets &e);
static void BulkLoadEntities(DataBucketLoadType::Type t, std::vector<uint32> ids);
static void DeleteCachedBuckets(DataBucketLoadType::Type t, uint32 id);
static bool SendDataBucketCacheUpdate(const DataBucketCacheEntry &e);
static void HandleWorldMessage(ServerPacket *p);
static void DeleteFromMissesCache(DataBucketsRepository::DataBuckets e);
};
#endif //EQEMU_DATABUCKET_H
+26
View File
@@ -83,6 +83,8 @@ Doors::Doors(const DoorsRepository::Doors &door) :
m_close_timer.Disable();
m_disable_timer = (door.disable_timer == 1 ? true : false);
m_is_blacklisted_to_open = GetIsDoorBlacklisted();
}
Doors::Doors(const char *model, const glm::vec4 &position, uint8 open_type, uint16 size) :
@@ -901,3 +903,27 @@ bool Doors::IsDestinationZoneSame() const
{
return m_same_destination_zone;
}
// IsDoorBlacklisted has a static list of doors that are blacklisted
// from being opened by NPCs. This is used to prevent NPCs from opening
// doors that are not meant to be opened by NPCs.
bool Doors::GetIsDoorBlacklisted()
{
std::vector<std::string> blacklist = {
"TOGGLE",
"PNDRESSER101",
};
for (auto& name : blacklist) {
std::string door_name = GetDoorName();
if (name == door_name) {
return true;
}
}
return false;
}
bool Doors::IsDoorBlacklisted() {
return m_is_blacklisted_to_open;
}
+4
View File
@@ -69,7 +69,10 @@ public:
bool HasDestinationZone() const;
bool IsDestinationZoneSame() const;
bool IsDoorBlacklisted();
private:
bool GetIsDoorBlacklisted();
bool m_has_destination_zone = false;
bool m_same_destination_zone = false;
@@ -99,5 +102,6 @@ private:
uint8 m_is_ldon_door;
int m_dz_switch_id = 0;
uint32 m_client_version_mask;
bool m_is_blacklisted_to_open = false; // is door blacklisted to open by npcs
};
#endif
+6 -20
View File
@@ -1273,27 +1273,13 @@ uint8 Client::GetCharMaxLevelFromQGlobal() {
uint8 Client::GetCharMaxLevelFromBucket()
{
auto new_bucket_name = fmt::format(
"{}-CharMaxLevel",
GetBucketKey()
);
DataBucketKey k = GetScopedBucketKeys();
k.key = "CharMaxLevel";
auto bucket_value = DataBucket::GetData(new_bucket_name);
if (!bucket_value.empty()) {
if (Strings::IsNumber(bucket_value)) {
return static_cast<uint8>(Strings::ToUnsignedInt(bucket_value));
}
}
auto old_bucket_name = fmt::format(
"{}-CharMaxLevel",
CharacterID()
);
bucket_value = DataBucket::GetData(old_bucket_name);
if (!bucket_value.empty()) {
if (Strings::IsNumber(bucket_value)) {
return static_cast<uint8>(Strings::ToUnsignedInt(bucket_value));
auto b = DataBucket::GetData(k);
if (!b.value.empty()) {
if (Strings::IsNumber(b.value)) {
return static_cast<uint8>(Strings::ToUnsignedInt(b.value));
}
}
+2 -2
View File
@@ -10,8 +10,8 @@ void SetTitleSuffix(Client *c, const Seperator *sep)
return;
}
const bool is_remove = !strcasecmp(sep->argplus[1], "-1");
std::string suffix = is_remove ? "" : sep->argplus[1];
const bool is_remove = !strcasecmp(sep->argplus[2], "-1");
std::string suffix = is_remove ? "" : sep->argplus[2];
auto t = c;
if (c->GetTarget() && c->GetTarget()->IsClient()) {
+1 -1
View File
@@ -44,7 +44,7 @@ void ShowPetition(Client *c, const Seperator *sep)
const uint32 petition_id = Strings::ToUnsignedInt(sep->arg[2]);
const auto& l = PetitionsRepository::GetWhere(database, fmt::format("petition_id = {}", petition_id));
const auto& l = PetitionsRepository::GetWhere(database, fmt::format("petid = {}", petition_id));
if (l.empty()) {
c->Message(
Chat::White,
+1 -1
View File
@@ -49,7 +49,7 @@ void ShowPetitionInfo(Client *c, const Seperator *sep)
const uint32 petition_id = Strings::ToUnsignedInt(sep->arg[2]);
const auto& l = PetitionsRepository::GetWhere(database, fmt::format("petition_id = {}", petition_id));
const auto& l = PetitionsRepository::GetWhere(database, fmt::format("petid = {}", petition_id));
if (l.empty()) {
c->Message(
Chat::White,
+42
View File
@@ -504,6 +504,42 @@ std::string Lua_Bot::GetRaceAbbreviation() {
return GetPlayerRaceAbbreviation(self->GetBaseRace());
}
void Lua_Bot::DeleteBucket(std::string bucket_name)
{
Lua_Safe_Call_Void();
self->DeleteBucket(bucket_name);
}
std::string Lua_Bot::GetBucket(std::string bucket_name)
{
Lua_Safe_Call_String();
return self->GetBucket(bucket_name);
}
std::string Lua_Bot::GetBucketExpires(std::string bucket_name)
{
Lua_Safe_Call_String();
return self->GetBucketExpires(bucket_name);
}
std::string Lua_Bot::GetBucketRemaining(std::string bucket_name)
{
Lua_Safe_Call_String();
return self->GetBucketRemaining(bucket_name);
}
void Lua_Bot::SetBucket(std::string bucket_name, std::string bucket_value)
{
Lua_Safe_Call_Void();
self->SetBucket(bucket_name, bucket_value);
}
void Lua_Bot::SetBucket(std::string bucket_name, std::string bucket_value, std::string expiration)
{
Lua_Safe_Call_Void();
self->SetBucket(bucket_name, bucket_value, expiration);
}
luabind::scope lua_register_bot() {
return luabind::class_<Lua_Bot, Lua_Mob>("Bot")
.def(luabind::constructor<>())
@@ -531,6 +567,7 @@ luabind::scope lua_register_bot() {
.def("Camp", (void(Lua_Bot::*)(bool))&Lua_Bot::Camp)
.def("CountBotItem", (uint32(Lua_Bot::*)(uint32))&Lua_Bot::CountBotItem)
.def("CountItemEquippedByID", (int(Lua_Bot::*)(uint32))&Lua_Bot::CountItemEquippedByID)
.def("DeleteBucket", (void(Lua_Bot::*)(std::string))&Lua_Bot::DeleteBucket)
.def("Escape", (void(Lua_Bot::*)(void))&Lua_Bot::Escape)
.def("Fling", (void(Lua_Bot::*)(float,float,float))&Lua_Bot::Fling)
.def("Fling", (void(Lua_Bot::*)(float,float,float,bool))&Lua_Bot::Fling)
@@ -551,6 +588,9 @@ luabind::scope lua_register_bot() {
.def("GetBotID", (uint32(Lua_Bot::*)(void))&Lua_Bot::GetBotID)
.def("GetBotItem", (Lua_ItemInst(Lua_Bot::*)(uint16))&Lua_Bot::GetBotItem)
.def("GetBotItemIDBySlot", (uint32(Lua_Bot::*)(uint16))&Lua_Bot::GetBotItemIDBySlot)
.def("GetBucket", (std::string(Lua_Bot::*)(std::string))&Lua_Bot::GetBucket)
.def("GetBucketExpires", (std::string(Lua_Bot::*)(std::string))&Lua_Bot::GetBucketExpires)
.def("GetBucketRemaining", (std::string(Lua_Bot::*)(std::string))&Lua_Bot::GetBucketRemaining)
.def("GetClassAbbreviation", (std::string(Lua_Bot::*)(void))&Lua_Bot::GetClassAbbreviation)
.def("GetExpansionBitmask", (int(Lua_Bot::*)(void))&Lua_Bot::GetExpansionBitmask)
.def("GetGroup", (Lua_Group(Lua_Bot::*)(void))&Lua_Bot::GetGroup)
@@ -576,6 +616,8 @@ luabind::scope lua_register_bot() {
.def("ReloadBotSpellSettings", (void(Lua_Bot::*)(void))&Lua_Bot::ReloadBotSpellSettings)
.def("RemoveBotItem", (void(Lua_Bot::*)(uint32))&Lua_Bot::RemoveBotItem)
.def("SendSpellAnim", (void(Lua_Bot::*)(uint16,uint16))&Lua_Bot::SendSpellAnim)
.def("SetBucket", (void(Lua_Bot::*)(std::string,std::string))&Lua_Bot::SetBucket)
.def("SetBucket", (void(Lua_Bot::*)(std::string,std::string,std::string))&Lua_Bot::SetBucket)
.def("SetExpansionBitmask", (void(Lua_Bot::*)(int))&Lua_Bot::SetExpansionBitmask)
.def("SetExpansionBitmask", (void(Lua_Bot::*)(int,bool))&Lua_Bot::SetExpansionBitmask)
.def("SetSpellDuration", (void(Lua_Bot::*)(int))&Lua_Bot::SetSpellDuration)
+6
View File
@@ -68,6 +68,12 @@ public:
void SendSpellAnim(uint16 target_id, uint16 spell_id);
std::string GetClassAbbreviation();
std::string GetRaceAbbreviation();
void DeleteBucket(std::string bucket_name);
std::string GetBucket(std::string bucket_name);
std::string GetBucketExpires(std::string bucket_name);
std::string GetBucketRemaining(std::string bucket_name);
void SetBucket(std::string bucket_name, std::string bucket_value);
void SetBucket(std::string bucket_name, std::string bucket_value, std::string expiration);
void ApplySpell(int spell_id);
void ApplySpell(int spell_id, int duration);
+49
View File
@@ -3092,6 +3092,48 @@ std::string Lua_Client::GetRaceAbbreviation()
return GetPlayerRaceAbbreviation(self->GetBaseRace());
}
void Lua_Client::SetLDoNPoints(uint32 theme_id, uint32 points)
{
Lua_Safe_Call_Void();
self->SetLDoNPoints(theme_id, points);
}
void Lua_Client::DeleteBucket(std::string bucket_name)
{
Lua_Safe_Call_Void();
self->DeleteBucket(bucket_name);
}
std::string Lua_Client::GetBucket(std::string bucket_name)
{
Lua_Safe_Call_String();
return self->GetBucket(bucket_name);
}
std::string Lua_Client::GetBucketExpires(std::string bucket_name)
{
Lua_Safe_Call_String();
return self->GetBucketExpires(bucket_name);
}
std::string Lua_Client::GetBucketRemaining(std::string bucket_name)
{
Lua_Safe_Call_String();
return self->GetBucketRemaining(bucket_name);
}
void Lua_Client::SetBucket(std::string bucket_name, std::string bucket_value)
{
Lua_Safe_Call_Void();
self->SetBucket(bucket_name, bucket_value);
}
void Lua_Client::SetBucket(std::string bucket_name, std::string bucket_value, std::string expiration)
{
Lua_Safe_Call_Void();
self->SetBucket(bucket_name, bucket_value, expiration);
}
luabind::scope lua_register_client() {
return luabind::class_<Lua_Client, Lua_Mob>("Client")
.def(luabind::constructor<>())
@@ -3169,6 +3211,7 @@ luabind::scope lua_register_client() {
.def("CreateExpeditionFromTemplate", &Lua_Client::CreateExpeditionFromTemplate)
.def("CreateTaskDynamicZone", &Lua_Client::CreateTaskDynamicZone)
.def("DecreaseByID", (bool(Lua_Client::*)(uint32,int))&Lua_Client::DecreaseByID)
.def("DeleteBucket", (void(Lua_Client::*)(std::string))&Lua_Client::DeleteBucket)
.def("DeleteItemInInventory", (void(Lua_Client::*)(int,int))&Lua_Client::DeleteItemInInventory)
.def("DeleteItemInInventory", (void(Lua_Client::*)(int,int,bool))&Lua_Client::DeleteItemInInventory)
.def("DiaWind", (void(Lua_Client::*)(std::string))&Lua_Client::DialogueWindow)
@@ -3243,6 +3286,9 @@ luabind::scope lua_register_client() {
.def("GetBotRequiredLevel", (int(Lua_Client::*)(uint8))&Lua_Client::GetBotRequiredLevel)
.def("GetBotSpawnLimit", (int(Lua_Client::*)(void))&Lua_Client::GetBotSpawnLimit)
.def("GetBotSpawnLimit", (int(Lua_Client::*)(uint8))&Lua_Client::GetBotSpawnLimit)
.def("GetBucket", (std::string(Lua_Client::*)(std::string))&Lua_Client::GetBucket)
.def("GetBucketExpires", (std::string(Lua_Client::*)(std::string))&Lua_Client::GetBucketExpires)
.def("GetBucketRemaining", (std::string(Lua_Client::*)(std::string))&Lua_Client::GetBucketRemaining)
.def("GetCarriedMoney", (uint64(Lua_Client::*)(void))&Lua_Client::GetCarriedMoney)
.def("GetCarriedPlatinum", (uint32(Lua_Client::*)(void))&Lua_Client::GetCarriedPlatinum)
.def("GetCharacterFactionLevel", (int(Lua_Client::*)(int))&Lua_Client::GetCharacterFactionLevel)
@@ -3511,6 +3557,8 @@ luabind::scope lua_register_client() {
.def("SetBotRequiredLevel", (void(Lua_Client::*)(int,uint8))&Lua_Client::SetBotRequiredLevel)
.def("SetBotSpawnLimit", (void(Lua_Client::*)(int))&Lua_Client::SetBotSpawnLimit)
.def("SetBotSpawnLimit", (void(Lua_Client::*)(int,uint8))&Lua_Client::SetBotSpawnLimit)
.def("SetBucket", (void(Lua_Client::*)(std::string,std::string))&Lua_Client::SetBucket)
.def("SetBucketExpires", (void(Lua_Client::*)(std::string,std::string,std::string))&Lua_Client::SetBucket)
.def("SetClientMaxLevel", (void(Lua_Client::*)(int))&Lua_Client::SetClientMaxLevel)
.def("SetConsumption", (void(Lua_Client::*)(int, int))&Lua_Client::SetConsumption)
.def("SetDeity", (void(Lua_Client::*)(int))&Lua_Client::SetDeity)
@@ -3536,6 +3584,7 @@ luabind::scope lua_register_client() {
.def("SetIPExemption", (void(Lua_Client::*)(int))&Lua_Client::SetIPExemption)
.def("SetItemCooldown", (void(Lua_Client::*)(uint32,uint32))&Lua_Client::SetItemCooldown)
.def("SetLanguageSkill", (void(Lua_Client::*)(int,int))&Lua_Client::SetLanguageSkill)
.def("SetLDoNPoints", (void(Lua_Client::*)(uint32,uint32))&Lua_Client::SetLDoNPoints)
.def("SetMaterial", (void(Lua_Client::*)(int,uint32))&Lua_Client::SetMaterial)
.def("SetPEQZoneFlag", (void(Lua_Client::*)(uint32))&Lua_Client::SetPEQZoneFlag)
.def("SetPVP", (void(Lua_Client::*)(bool))&Lua_Client::SetPVP)
+7
View File
@@ -473,6 +473,13 @@ public:
uint32 GetEXPForLevel(uint16 check_level);
std::string GetClassAbbreviation();
std::string GetRaceAbbreviation();
void SetLDoNPoints(uint32 theme_id, uint32 points);
void DeleteBucket(std::string bucket_name);
std::string GetBucket(std::string bucket_name);
std::string GetBucketExpires(std::string bucket_name);
std::string GetBucketRemaining(std::string bucket_name);
void SetBucket(std::string bucket_name, std::string bucket_value);
void SetBucket(std::string bucket_name, std::string bucket_value, std::string expiration);
void ApplySpell(int spell_id);
void ApplySpell(int spell_id, int duration);
+8 -1
View File
@@ -2353,7 +2353,7 @@ std::string Lua_Mob::GetBucketExpires(std::string bucket_name)
std::string Lua_Mob::GetBucketKey()
{
Lua_Safe_Call_String();
return self->GetBucketKey();
return {};
}
std::string Lua_Mob::GetBucketRemaining(std::string bucket_name)
@@ -3133,6 +3133,12 @@ bool Lua_Mob::IsTemporaryPet()
return self->IsTempPet();
}
uint32 Lua_Mob::GetMobTypeIdentifier()
{
Lua_Safe_Call_Int();
return self->GetMobTypeIdentifier();
}
luabind::scope lua_register_mob() {
return luabind::class_<Lua_Mob, Lua_Entity>("Mob")
.def(luabind::constructor<>())
@@ -3429,6 +3435,7 @@ luabind::scope lua_register_mob() {
.def("GetMeleeDamageMod_SE", &Lua_Mob::GetMeleeDamageMod_SE)
.def("GetMeleeMinDamageMod_SE", &Lua_Mob::GetMeleeMinDamageMod_SE)
.def("GetMeleeMitigation", (int32(Lua_Mob::*)(void))&Lua_Mob::GetMeleeMitigation)
.def("GetMobTypeIdentifier", (uint32(Lua_Mob::*)(void))&Lua_Mob::GetMobTypeIdentifier)
.def("GetModSkillDmgTaken", (int(Lua_Mob::*)(int))&Lua_Mob::GetModSkillDmgTaken)
.def("GetModVulnerability", (int(Lua_Mob::*)(int))&Lua_Mob::GetModVulnerability)
.def("GetNPCTypeID", &Lua_Mob::GetNPCTypeID)
+1
View File
@@ -557,6 +557,7 @@ public:
std::string GetClassPlural();
std::string GetRacePlural();
bool IsTemporaryPet();
uint32 GetMobTypeIdentifier();
};
#endif
+42
View File
@@ -777,6 +777,42 @@ bool Lua_NPC::HasSpecialAbilities() {
return self->HasSpecialAbilities();
}
void Lua_NPC::DeleteBucket(std::string bucket_name)
{
Lua_Safe_Call_Void();
self->DeleteBucket(bucket_name);
}
std::string Lua_NPC::GetBucket(std::string bucket_name)
{
Lua_Safe_Call_String();
return self->GetBucket(bucket_name);
}
std::string Lua_NPC::GetBucketExpires(std::string bucket_name)
{
Lua_Safe_Call_String();
return self->GetBucketExpires(bucket_name);
}
std::string Lua_NPC::GetBucketRemaining(std::string bucket_name)
{
Lua_Safe_Call_String();
return self->GetBucketRemaining(bucket_name);
}
void Lua_NPC::SetBucket(std::string bucket_name, std::string bucket_value)
{
Lua_Safe_Call_Void();
self->SetBucket(bucket_name, bucket_value);
}
void Lua_NPC::SetBucket(std::string bucket_name, std::string bucket_value, std::string expiration)
{
Lua_Safe_Call_Void();
self->SetBucket(bucket_name, bucket_value, expiration);
}
luabind::scope lua_register_npc() {
return luabind::class_<Lua_NPC, Lua_Mob>("NPC")
.def(luabind::constructor<>())
@@ -804,12 +840,16 @@ luabind::scope lua_register_npc() {
.def("ClearLastName", (void(Lua_NPC::*)(void))&Lua_NPC::ClearLastName)
.def("CountItem", (uint16(Lua_NPC::*)(uint32))&Lua_NPC::CountItem)
.def("CountLoot", (int(Lua_NPC::*)(void))&Lua_NPC::CountLoot)
.def("DeleteBucket", (void(Lua_NPC::*)(std::string))&Lua_NPC::DeleteBucket)
.def("DisplayWaypointInfo", (void(Lua_NPC::*)(Lua_Client))&Lua_NPC::DisplayWaypointInfo)
.def("DoClassAttacks", (void(Lua_NPC::*)(Lua_Mob))&Lua_NPC::DoClassAttacks)
.def("GetAccuracyRating", (int(Lua_NPC::*)(void))&Lua_NPC::GetAccuracyRating)
.def("GetAttackDelay", (int(Lua_NPC::*)(void))&Lua_NPC::GetAttackDelay)
.def("GetAttackSpeed", (float(Lua_NPC::*)(void))&Lua_NPC::GetAttackSpeed)
.def("GetAvoidanceRating", &Lua_NPC::GetAvoidanceRating)
.def("GetBucket", (std::string(Lua_NPC::*)(std::string))&Lua_NPC::GetBucket)
.def("GetBucketExpires", (std::string(Lua_NPC::*)(std::string))&Lua_NPC::GetBucketExpires)
.def("GetBucketRemaining", (std::string(Lua_NPC::*)(std::string))&Lua_NPC::GetBucketRemaining)
.def("GetCopper", (uint32(Lua_NPC::*)(void))&Lua_NPC::GetCopper)
.def("GetFirstSlotByItemID", (uint16(Lua_NPC::*)(uint32))&Lua_NPC::GetFirstSlotByItemID)
.def("GetFollowCanRun", (bool(Lua_NPC::*)(void))&Lua_NPC::GetFollowCanRun)
@@ -894,6 +934,8 @@ luabind::scope lua_register_npc() {
.def("ScaleNPC", (void(Lua_NPC::*)(uint8,bool))&Lua_NPC::ScaleNPC)
.def("SendPayload", (void(Lua_NPC::*)(int))&Lua_NPC::SendPayload)
.def("SendPayload", (void(Lua_NPC::*)(int,std::string))&Lua_NPC::SendPayload)
.def("SetBucket", (void(Lua_NPC::*)(std::string,std::string))&Lua_NPC::SetBucket)
.def("SetBucket", (void(Lua_NPC::*)(std::string,std::string,std::string))&Lua_NPC::SetBucket)
.def("SetCopper", (void(Lua_NPC::*)(uint32))&Lua_NPC::SetCopper)
.def("SetFollowCanRun", (void(Lua_NPC::*)(bool))&Lua_NPC::SetFollowCanRun)
.def("SetFollowDistance", (void(Lua_NPC::*)(int))&Lua_NPC::SetFollowDistance)
+6
View File
@@ -176,6 +176,12 @@ public:
void ScaleNPC(uint8 npc_level, bool override_special_abilities);
bool IsUnderwaterOnly();
bool HasSpecialAbilities();
void DeleteBucket(std::string bucket_name);
std::string GetBucket(std::string bucket_name);
std::string GetBucketExpires(std::string bucket_name);
std::string GetBucketRemaining(std::string bucket_name);
void SetBucket(std::string bucket_name, std::string bucket_value);
void SetBucket(std::string bucket_name, std::string bucket_value, std::string expiration);
};
#endif
+112 -31
View File
@@ -8049,54 +8049,63 @@ void Mob::SetCanOpenDoors(bool can_open)
m_can_open_doors = can_open;
}
void Mob::DeleteBucket(std::string bucket_name) {
std::string full_bucket_name = fmt::format("{}-{}", GetBucketKey(), bucket_name);
DataBucket::DeleteData(full_bucket_name);
void Mob::DeleteBucket(std::string bucket_name)
{
DataBucketKey k = GetScopedBucketKeys();
k.key = bucket_name;
DataBucket::DeleteData(k);
}
std::string Mob::GetBucket(std::string bucket_name) {
std::string full_bucket_name = fmt::format("{}-{}", GetBucketKey(), bucket_name);
std::string bucket_value = DataBucket::GetData(full_bucket_name);
if (!bucket_value.empty()) {
return bucket_value;
std::string Mob::GetBucket(std::string bucket_name)
{
DataBucketKey k = GetScopedBucketKeys();
k.key = bucket_name;
auto b = DataBucket::GetData(k);
if (!b.value.empty()) {
return b.value;
}
return std::string();
return {};
}
std::string Mob::GetBucketExpires(std::string bucket_name) {
std::string full_bucket_name = fmt::format("{}-{}", GetBucketKey(), bucket_name);
std::string bucket_expiration = DataBucket::GetDataExpires(full_bucket_name);
std::string Mob::GetBucketExpires(std::string bucket_name)
{
DataBucketKey k = GetScopedBucketKeys();
k.key = bucket_name;
std::string bucket_expiration = DataBucket::GetDataExpires(k);
if (!bucket_expiration.empty()) {
return bucket_expiration;
}
return std::string();
return {};
}
std::string Mob::GetBucketKey() {
if (IsClient()) {
return fmt::format("character-{}", CastToClient()->CharacterID());
} else if (IsNPC()) {
return fmt::format("npc-{}", GetNPCTypeID());
} else if (IsBot()) {
return fmt::format("bot-{}", CastToBot()->GetBotID());
}
return std::string();
}
std::string Mob::GetBucketRemaining(std::string bucket_name)
{
DataBucketKey k = GetScopedBucketKeys();
k.key = bucket_name;
std::string Mob::GetBucketRemaining(std::string bucket_name) {
std::string full_bucket_name = fmt::format("{}-{}", GetBucketKey(), bucket_name);
std::string bucket_remaining = DataBucket::GetDataRemaining(full_bucket_name);
std::string bucket_remaining = DataBucket::GetDataRemaining(k);
if (!bucket_remaining.empty() && Strings::ToInt(bucket_remaining) > 0) {
return bucket_remaining;
} else if (Strings::ToInt(bucket_remaining) == 0) {
}
else if (Strings::ToInt(bucket_remaining) == 0) {
return "0";
}
return std::string();
return {};
}
void Mob::SetBucket(std::string bucket_name, std::string bucket_value, std::string expiration) {
std::string full_bucket_name = fmt::format("{}-{}", GetBucketKey(), bucket_name);
DataBucket::SetData(full_bucket_name, bucket_value, expiration);
void Mob::SetBucket(std::string bucket_name, std::string bucket_value, std::string expiration)
{
DataBucketKey k = GetScopedBucketKeys();
k.key = bucket_name;
k.expires = expiration;
k.value = bucket_value;
DataBucket::SetData(k);
}
std::string Mob::GetMobDescription()
@@ -8329,3 +8338,75 @@ std::string Mob::GetClassPlural()
return "Classes";
}
}
DataBucketKey Mob::GetScopedBucketKeys()
{
DataBucketKey k = {};
if (IsClient()) {
k.character_id = CastToClient()->CharacterID();
} else if (IsNPC()) {
k.npc_id = GetNPCTypeID();
} else if (IsBot()) {
k.bot_id = CastToBot()->GetBotID();
}
return k;
}
uint32 Mob::GetMobTypeIdentifier()
{
if (IsClient()) {
return CastToClient()->CharacterID();
} else if (IsNPC()) {
return GetNPCTypeID();
} else if (IsBot()) {
return CastToBot()->GetBotID();
}
return 0;
}
void Mob::HandleDoorOpen()
{
for (auto e : entity_list.GetDoorsList()) {
Doors *d = e.second;
if (d->GetKeyItem()) {
continue;
}
if (d->GetLockpick()) {
continue;
}
if (d->IsDoorOpen()) {
continue;
}
if (d->IsDoorBlacklisted()) {
continue;
}
// If the door is a trigger door, check if the trigger door is open
if (d->GetTriggerDoorID() > 0) {
auto td = entity_list.GetDoorsByDoorID(d->GetTriggerDoorID());
if (td) {
if (Strings::RemoveNumbers(d->GetDoorName()) != Strings::RemoveNumbers(td->GetDoorName())) {
continue;
}
}
}
if (d->GetDoorParam() > 0) {
continue;
}
float distance = DistanceSquared(m_Position, d->GetPosition());
float distance_scan_door_open = 20;
if (distance <= (distance_scan_door_open * distance_scan_door_open)) {
// Make sure we're opening a door within height relevance and not platforms above or below us
if (std::abs(m_Position.z - d->GetPosition().z) > 10) {
continue;
}
d->ForceOpen(this);
}
}
}
+6 -3
View File
@@ -69,6 +69,7 @@ enum class eSpecialAttacks : int {
ChaoticStab
};
class DataBucketKey;
class Mob : public Entity {
public:
enum CLIENT_CONN_STATUS { CLIENT_CONNECTING, CLIENT_CONNECTED, CLIENT_LINKDEAD,
@@ -1407,16 +1408,15 @@ public:
/// this cures timing issues cuz dead animation isn't done but server side feigning is?
inline bool GetFeigned() const { return(feigned); }
std::vector<DataBucketCache> m_data_bucket_cache;
// Data Bucket Methods
void DeleteBucket(std::string bucket_name);
std::string GetBucket(std::string bucket_name);
std::string GetBucketExpires(std::string bucket_name);
std::string GetBucketKey();
std::string GetBucketRemaining(std::string bucket_name);
void SetBucket(std::string bucket_name, std::string bucket_value, std::string expiration = "");
uint32 GetMobTypeIdentifier();
// Heroic Stat Benefits
float CheckHeroicBonusesDataBuckets(std::string bucket_name);
@@ -1443,6 +1443,8 @@ public:
void CalcHeroicBonuses(StatBonuses* newbon);
DataBucketKey GetScopedBucketKeys();
protected:
void CommonDamage(Mob* other, int64 &damage, const uint16 spell_id, const EQ::skills::SkillType attack_skill, bool &avoidable, const int8 buffslot, const bool iBuffTic, eSpecialAttacks specal = eSpecialAttacks::None);
static uint16 GetProcID(uint16 spell_id, uint8 effect_index);
@@ -1864,6 +1866,7 @@ private:
void SetHeroicWisBonuses(StatBonuses* n);
void DoSpellInterrupt(uint16 spell_id, int32 mana_cost, int my_curmana);
void HandleDoorOpen();
};
#endif
+10 -52
View File
@@ -981,52 +981,7 @@ void Mob::AI_Process() {
if (moving && CanOpenDoors()) {
if (AI_scan_door_open_timer->Check()) {
auto &door_list = entity_list.GetDoorsList();
for (auto itr : door_list) {
Doors *door = itr.second;
if (door->GetKeyItem()) {
continue;
}
if (door->GetLockpick()) {
continue;
}
if (door->IsDoorOpen()) {
continue;
}
if (door->GetTriggerDoorID() > 0) {
auto trigger_door = entity_list.GetDoorsByDoorID(door->GetTriggerDoorID());
if (trigger_door) {
if (Strings::RemoveNumbers(door->GetDoorName()) !=
Strings::RemoveNumbers(trigger_door->GetDoorName())) {
continue;
}
}
}
if (door->GetDoorParam() > 0) {
continue;
}
float distance = DistanceSquared(m_Position, door->GetPosition());
float distance_scan_door_open = 20;
if (distance <= (distance_scan_door_open * distance_scan_door_open)) {
/**
* Make sure we're opening a door within height relevance and not platforms
* above or below
*/
if (std::abs(m_Position.z - door->GetPosition().z) > 10) {
continue;
}
door->ForceOpen(this);
}
}
HandleDoorOpen();
}
}
@@ -1071,12 +1026,15 @@ void Mob::AI_Process() {
if (engaged) {
if (IsNPC() && m_z_clip_check_timer.Check()) {
auto t = GetTarget();
if (t) {
float self_z = GetZ() - GetZOffset();
float target_z = t->GetPosition().z - t->GetZOffset();
if (DistanceNoZ(GetPosition(), t->GetPosition()) < 75 &&
std::abs(self_z - target_z) >= 25 && !CheckLosFN(t)) {
bool is_moving = IsMoving() && !(IsRooted() || IsStunned() || IsMezzed());
auto t = GetTarget();
if (is_moving && t) {
float self_z = GetZ() - GetZOffset();
float target_z = t->GetPosition().z - t->GetZOffset();
bool can_path_to = CastToNPC()->CanPathTo(t->GetX(), t->GetY(), t->GetZ());
bool within_distance = DistanceNoZ(GetPosition(), t->GetPosition()) < 75;
bool within_z_distance = std::abs(self_z - target_z) >= 25;
if (within_distance && within_z_distance && !can_path_to) {
float new_z = FindDestGroundZ(t->GetPosition());
GMMove(t->GetPosition().x, t->GetPosition().y, new_z + GetZOffset(), t->GetPosition().w, false);
FaceTarget(t);
+27 -1
View File
@@ -3825,9 +3825,14 @@ void NPC::HandleRoambox()
auto requested_y = EQ::Clamp((GetY() + move_y), m_roambox.min_y, m_roambox.max_y);
auto requested_z = GetGroundZ(requested_x, requested_y);
if (std::abs(requested_z - GetZ()) > 100) {
LogNPCRoamBox("[{}] | Failed to find reasonable ground [{}]", GetCleanName(), i);
continue;
}
std::vector<float> heights = {0, 250, -250};
for (auto &h: heights) {
if (CheckLosFN(requested_x, requested_y, requested_z + h, GetSize())) {
if (CanPathTo(requested_x, requested_y, requested_z + h)) {
LogNPCRoamBox("[{}] Found line of sight to path attempt [{}] at height [{}]", GetCleanName(), i, h);
can_path = true;
break;
@@ -3942,3 +3947,24 @@ void NPC::SetTaunting(bool is_taunting) {
GetOwner()->CastToClient()->SetPetCommandState(PET_BUTTON_TAUNT, is_taunting);
}
}
bool NPC::CanPathTo(float x, float y, float z)
{
PathfinderOptions opts;
opts.smooth_path = true;
opts.step_size = RuleR(Pathing, NavmeshStepSize);
opts.offset = GetZOffset();
opts.flags = PathingNotDisabled ^ PathingZoneLine;
bool partial = false;
bool stuck = false;
auto route = zone->pathing->FindPath(
glm::vec3(GetX(), GetY(), GetZ()),
glm::vec3(x, y, z),
partial,
stuck,
opts
);
return !route.empty();
}
+2
View File
@@ -541,6 +541,8 @@ public:
static LootDropEntries_Struct NewLootDropEntry();
bool CanPathTo(float x, float y, float z);
protected:
void HandleRoambox();
+6
View File
@@ -2947,6 +2947,11 @@ std::string Perl_Client_GetRaceAbbreviation(Client* self)
return GetPlayerRaceAbbreviation(self->GetBaseRace());
}
void Perl_Client_SetLDoNPoints(Client* self, uint32 theme_id, uint32 points)
{
self->SetLDoNPoints(theme_id, points);
}
void perl_register_client()
{
perl::interpreter perl(PERL_GET_THX);
@@ -3394,6 +3399,7 @@ void perl_register_client()
package.add("SetInvulnerableEnvironmentDamage", &Perl_Client_SetInvulnerableEnvironmentDamage);
package.add("SetItemCooldown", &Perl_Client_SetItemCooldown);
package.add("SetLanguageSkill", &Perl_Client_SetLanguageSkill);
package.add("SetLDoNPoints", &Perl_Client_SetLDoNPoints);
package.add("SetMaterial", &Perl_Client_SetMaterial);
package.add("SetPEQZoneFlag", &Perl_Client_SetPEQZoneFlag);
package.add("SetPVP", &Perl_Client_SetPVP);
+7 -1
View File
@@ -2420,7 +2420,7 @@ std::string Perl_Mob_GetBucketExpires(Mob* self, std::string bucket_name) // @ca
std::string Perl_Mob_GetBucketKey(Mob* self) // @categories Script Utility
{
return self->GetBucketKey();
return {};
}
std::string Perl_Mob_GetBucketRemaining(Mob* self, std::string bucket_name) // @categories Script Utility
@@ -3113,6 +3113,11 @@ std::string Perl_Mob_GetClassPlural(Mob* self)
return self->GetClassPlural();
}
uint32 Perl_Mob_GetMobTypeIdentifier(Mob* self)
{
return self->GetMobTypeIdentifier();
}
std::string Perl_Mob_GetRacePlural(Mob* self)
{
return self->GetRacePlural();
@@ -3399,6 +3404,7 @@ void perl_register_mob()
package.add("GetMaxSTR", &Perl_Mob_GetMaxSTR);
package.add("GetMaxWIS", &Perl_Mob_GetMaxWIS);
package.add("GetMeleeMitigation", &Perl_Mob_GetMeleeMitigation);
package.add("GetMobTypeIdentifier", &Perl_Mob_GetMobTypeIdentifier);
package.add("GetModSkillDmgTaken", &Perl_Mob_GetModSkillDmgTaken);
package.add("GetModVulnerability", &Perl_Mob_GetModVulnerability);
package.add("GetNPCTypeID", &Perl_Mob_GetNPCTypeID);
+8 -24
View File
@@ -714,19 +714,6 @@ bool Mob::DoCastingChecksZoneRestrictions(bool check_on_casting, int32 spell_id)
return false;
}
}
/*
Zones where you can not gate.
*/
if (IsClient() &&
(zone->GetZoneID() == Zones::TUTORIAL || zone->GetZoneID() == Zones::LOAD) &&
CastToClient()->Admin() < AccountStatus::QuestTroupe) {
if (IsEffectInSpell(spell_id, SE_Gate) ||
IsEffectInSpell(spell_id, SE_Translocate) ||
IsEffectInSpell(spell_id, SE_Teleport)) {
Message(Chat::White, "The Gods brought you here, only they can send you away.");
return false;
}
}
}
return true;
@@ -5877,20 +5864,17 @@ bool Client::SpellBucketCheck(uint16 spell_id, uint32 character_id) {
return true; // If the entry in the spell_buckets table has nothing set for the qglobal name, allow scribing.
}
auto new_bucket_name = fmt::format(
"{}-{}",
GetBucketKey(),
spell_bucket_name
);
DataBucketKey k = GetScopedBucketKeys();
k.key = spell_bucket_name;
auto bucket_value = DataBucket::GetData(new_bucket_name);
if (!bucket_value.empty()) {
if (Strings::IsNumber(bucket_value) && Strings::IsNumber(spell_bucket_value)) {
if (Strings::ToInt(bucket_value) >= Strings::ToInt(spell_bucket_value)) {
auto b = DataBucket::GetData(k);
if (!b.value.empty()) {
if (Strings::IsNumber(b.value) && Strings::IsNumber(spell_bucket_value)) {
if (Strings::ToInt(b.value) >= Strings::ToInt(spell_bucket_value)) {
return true; // If value is greater than or equal to spell bucket value, allow scribing.
}
} else {
if (bucket_value == spell_bucket_value) {
if (b.value == spell_bucket_value) {
return true; // If value is equal to spell bucket value, allow scribing.
}
}
@@ -5902,7 +5886,7 @@ bool Client::SpellBucketCheck(uint16 spell_id, uint32 character_id) {
spell_bucket_name
);
bucket_value = DataBucket::GetData(old_bucket_name);
std::string bucket_value = DataBucket::GetData(old_bucket_name);
if (!bucket_value.empty()) {
if (Strings::IsNumber(bucket_value) && Strings::IsNumber(spell_bucket_value)) {
if (Strings::ToInt(bucket_value) >= Strings::ToInt(spell_bucket_value)) {
+8 -1
View File
@@ -846,8 +846,15 @@ void Mob::FixZ(int32 z_find_offset /*= 5*/, bool fix_client_z /*= false*/) {
glm::vec3 current_loc(m_Position);
float new_z = GetFixedZ(current_loc, z_find_offset);
if (new_z == m_Position.z)
// reject z if it is too far from the current z
if (std::abs(new_z - m_Position.z) > 100) {
return;
}
// reject if it's the same as the current z
if (new_z == m_Position.z) {
return;
}
if ((new_z > -2000) && new_z != BEST_Z_INVALID) {
if (RuleB(Map, MobZVisualDebug)) {
+5
View File
@@ -3329,6 +3329,11 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p)
SharedTaskZoneMessaging::HandleWorldMessage(pack);
break;
}
case ServerOP_DataBucketCacheUpdate:
{
DataBucket::HandleWorldMessage(pack);
break;
}
default: {
LogInfo("Unknown ZS Opcode [{}] size [{}]", (int)pack->opcode, pack->size);
break;
+11 -1
View File
@@ -1927,6 +1927,8 @@ const NPCType *ZoneDatabase::LoadNPCTypesData(uint32 npc_type_id, bool bulk_load
);
}
std::vector<uint32> npc_ids;
for (NpcTypesRepository::NpcTypes &n : NpcTypesRepository::GetWhere((Database &) content_db, filter)) {
NPCType *t;
t = new NPCType;
@@ -2137,8 +2139,15 @@ const NPCType *ZoneDatabase::LoadNPCTypesData(uint32 npc_type_id, bool bulk_load
zone->npctable[t->npc_id] = t;
npc = t;
// If NPC ID is not in npc_ids, add to vector
if (!std::count(npc_ids.begin(), npc_ids.end(), t->npc_id)) {
npc_ids.emplace_back(t->npc_id);
}
}
DataBucket::BulkLoadEntities(DataBucketLoadType::NPC, npc_ids);
return npc;
}
@@ -3303,7 +3312,8 @@ void ZoneDatabase::SavePetInfo(Client *client)
// build pet buffs into struct
int pet_buff_count = 0;
int max_slots = RuleI(Spells, MaxTotalSlotsPET);
// Guard against setting the maximum pet slots above the client allowed maximum.
int max_slots = RuleI(Spells, MaxTotalSlotsPET) > PET_BUFF_COUNT ? PET_BUFF_COUNT : RuleI(Spells, MaxTotalSlotsPET);
// count pet buffs
for (int index = 0; index < max_slots; index++) {