Compare commits

...

5 Commits

Author SHA1 Message Date
Chris Miles 585ed3bd25 [Release] 23.8.1 2025-06-28 17:29:25 -05:00
Chris Miles 2eb291a461 [Databuckets] Revert Caching Changes of #4917 (#4957)
* [Databuckets] Revert Caching Changes of #4917

* Comment caching tests until addressed later
2025-06-28 17:28:19 -05:00
Alex King a1421af214 [Bug Fix] Fix Hero Forge on Character Select (#4954) 2025-06-26 19:35:28 -04:00
Alex King 13aad6229f [Crash Fix] Fix Possible Crashes with Raid Methods (#4955) 2025-06-26 19:35:20 -04:00
Alex King 76d46ceaf0 [Bug Fix] Fix FindCharacter Using content_db (#4956) 2025-06-26 19:35:11 -04:00
9 changed files with 119 additions and 168 deletions
+15
View File
@@ -1,3 +1,18 @@
## [23.8.1] 6/28/2025
### Crash Fix
* Fix Possible Crashes with Raid Methods ([#4955](https://github.com/EQEmu/Server/pull/4955)) @Kinglykrab 2025-06-26
### Databuckets
* Revert Caching Changes of #4917 ([#4957](https://github.com/EQEmu/Server/pull/4957)) @Akkadius 2025-06-28
### Fixes
* Fix FindCharacter Using content_db ([#4956](https://github.com/EQEmu/Server/pull/4956)) @Kinglykrab 2025-06-26
* Fix Hero Forge on Character Select ([#4954](https://github.com/EQEmu/Server/pull/4954)) @Kinglykrab 2025-06-26
## [23.8.0] 6/25/2025
### API
+8 -82
View File
@@ -19,37 +19,6 @@ extern WorldDatabase database;
#error "You must define either ZONE or WORLD"
#endif
// Key: compound cache key (e.g., account_id|character_id|zone_id|instance_id|top_key|full_key)
// Value: resolved DataBuckets with extracted nested value
static std::unordered_map<std::string, DataBucketsRepository::DataBuckets> g_nested_bucket_cache;
static std::string MakeNestedCacheKey(const DataBucketKey &k, const std::string &full_key) {
return fmt::format(
"account_id:{}|character_id:{}|npc_id:{}|bot_id:{}|zone_id:{}|instance_id:{}|top_key:{}|full_key:{}",
k.account_id, k.character_id, k.npc_id, k.bot_id, k.zone_id, k.instance_id,
Strings::Split(full_key, NESTED_KEY_DELIMITER).front(),
full_key
);
}
static std::string MakeNestedCacheKeyPrefix(const DataBucketKey &k, const std::string &top_key) {
return fmt::format(
"account_id:{}|character_id:{}|npc_id:{}|bot_id:{}|zone_id:{}|instance_id:{}|top_key:{}|",
k.account_id, k.character_id, k.npc_id, k.bot_id, k.zone_id, k.instance_id, top_key
);
}
static void InvalidateNestedCacheForKey(const DataBucketKey &k, const std::string &top_key) {
std::string prefix = MakeNestedCacheKeyPrefix(k, top_key);
for (auto it = g_nested_bucket_cache.begin(); it != g_nested_bucket_cache.end(); ) {
if (it->first.find(prefix) == 0) {
it = g_nested_bucket_cache.erase(it);
} else {
++it;
}
}
}
void DataBucket::SetData(const std::string &bucket_key, const std::string &bucket_value, std::string expires_time)
{
auto k = DataBucketKey{
@@ -167,15 +136,6 @@ void DataBucket::SetData(const DataBucketKey &k_)
// Serialize JSON back to string
b.value = json_value.dump();
b.key_ = top_key; // Use the top-level key
if (CanCache(k_)) {
InvalidateNestedCacheForKey(k_, top_key);
std::string nested_cache_key = MakeNestedCacheKey(k_, k_.key);
auto extracted = ExtractNestedValue(b, k_.key);
if (extracted.id > 0) {
g_nested_bucket_cache[nested_cache_key] = extracted;
}
}
}
if (bucket_id) {
@@ -291,27 +251,12 @@ DataBucketsRepository::DataBuckets DataBucket::GetData(const DataBucketKey &k_,
LogDataBuckets("Returning key [{}] value [{}] from cache", e.key_, e.value);
if (is_nested_key && !k_.key.empty()) {
std::string nested_cache_key = MakeNestedCacheKey(k_, k.key);
auto it = g_nested_bucket_cache.find(nested_cache_key);
if (it != g_nested_bucket_cache.end()) {
LogDataBucketsDetail("Nested cache hit for key [{}]", nested_cache_key);
return it->second;
}
auto extracted = ExtractNestedValue(e, k_.key);
if (extracted.id > 0) {
g_nested_bucket_cache[nested_cache_key] = extracted;
}
return extracted;
return ExtractNestedValue(e, k_.key);
}
return e;
}
}
// if we can cache its assumed we didn't load this into the cache so we should not return a miss
return DataBucketsRepository::NewEntity(); // Not found in cache
}
// Fetch the value from the database
@@ -370,42 +315,23 @@ DataBucketsRepository::DataBuckets DataBucket::GetData(const DataBucketKey &k_,
}
// Add the value to the cache if it doesn't exist
// If cacheable and not found in cache, short-circuit and assume it doesn't exist
if (can_cache) {
bool found_in_cache = false;
bool has_cache = false;
for (const auto &e : g_data_bucket_cache) {
if (CheckBucketMatch(e, k)) {
found_in_cache = true;
if (e.id == bucket.id) {
has_cache = true;
break;
}
}
if (!found_in_cache) {
LogDataBuckets("Cache miss for key [{}] - skipping DB due to CanCache", k.key);
return DataBucketsRepository::NewEntity();
if (!has_cache) {
g_data_bucket_cache.emplace_back(bucket);
}
}
// Handle nested key extraction
if (is_nested_key && !k_.key.empty()) {
if (CanCache(k_)) {
std::string nested_cache_key = MakeNestedCacheKey(k_, k.key);
auto it = g_nested_bucket_cache.find(nested_cache_key);
if (it != g_nested_bucket_cache.end()) {
LogDataBucketsDetail("Nested cache hit for key [{}]", nested_cache_key);
return it->second;
}
auto extracted = ExtractNestedValue(bucket, k_.key);
if (extracted.id > 0) {
g_nested_bucket_cache[nested_cache_key] = extracted;
}
return extracted;
} else {
// Not cacheable, just extract and return
return ExtractNestedValue(bucket, k_.key);
}
return ExtractNestedValue(bucket, k_.key);
}
return bucket;
@@ -917,4 +843,4 @@ bool DataBucket::CanCache(const DataBucketKey &key)
}
return false;
}
}
+1 -1
View File
@@ -574,7 +574,7 @@ EQ::ItemInstance* EQ::ItemInstance::GetOrnamentationAugment() const
uint32 EQ::ItemInstance::GetOrnamentHeroModel(int32 material_slot) const
{
// Not a Hero Forge item.
if (m_ornament_hero_model == 0 || material_slot < 0) {
if (m_ornament_hero_model == 0) {
return 0;
}
+1 -1
View File
@@ -25,7 +25,7 @@
// Build variables
// these get injected during the build pipeline
#define CURRENT_VERSION "23.8.0-dev" // always append -dev to the current version for custom-builds
#define CURRENT_VERSION "23.8.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__
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "eqemu-server",
"version": "23.8.0",
"version": "23.8.1",
"repository": {
"type": "git",
"url": "https://github.com/EQEmu/Server.git"
+26 -26
View File
@@ -318,32 +318,32 @@ void ZoneCLI::TestDataBuckets(int argc, char** argv, argh::parser& cmd, std::str
);
// Cold cache test — should return ""
std::string cold_value = client->GetBucket(scoped_key);
RunTest("Cold Cache Scoped Key Returns Empty (Due to Skip DB)", "", cold_value);
// ✅ Reload cache
client->LoadDataBucketsCache();
// Cache should now return the value
std::string hot_value = client->GetBucket(scoped_key);
RunTest("Post-BulkLoad Scoped Key Returns Value", "cached_value", hot_value);
// Also test nested key after preload
client->DeleteBucket("ac_nested.test");
client->SetBucket("ac_nested.test", "nested_val");
// Clear cache, then preload
DataBucket::ClearCache();
client->LoadDataBucketsCache();
std::string nested_value = client->GetBucket("ac_nested.test");
RunTest("Post-BulkLoad Nested Scoped Key Returns Value", "nested_val", nested_value);
// Remove and check that cache misses properly again
client->DeleteBucket("ac_nested.test");
DataBucket::ClearCache();
std::string post_delete_check = client->GetBucket("ac_nested.test");
RunTest("Post-Delete Nested Scoped Key Returns Empty", "", post_delete_check);
// std::string cold_value = client->GetBucket(scoped_key);
// RunTest("Cold Cache Scoped Key Returns Empty (Due to Skip DB)", "", cold_value);
//
// // ✅ Reload cache
// client->LoadDataBucketsCache();
//
// // Cache should now return the value
// std::string hot_value = client->GetBucket(scoped_key);
// RunTest("Post-BulkLoad Scoped Key Returns Value", "cached_value", hot_value);
//
// // Also test nested key after preload
// client->DeleteBucket("ac_nested.test");
// client->SetBucket("ac_nested.test", "nested_val");
//
// // Clear cache, then preload
// DataBucket::ClearCache();
// client->LoadDataBucketsCache();
//
// std::string nested_value = client->GetBucket("ac_nested.test");
// RunTest("Post-BulkLoad Nested Scoped Key Returns Value", "nested_val", nested_value);
//
// // Remove and check that cache misses properly again
// client->DeleteBucket("ac_nested.test");
// DataBucket::ClearCache();
// std::string post_delete_check = client->GetBucket("ac_nested.test");
// RunTest("Post-Delete Nested Scoped Key Returns Empty", "", post_delete_check);
std::cout << "\n===========================================\n";
+5 -5
View File
@@ -4,9 +4,9 @@
void FindCharacter(Client *c, const Seperator *sep)
{
if (sep->IsNumber(2)) {
const auto character_id = Strings::ToUnsignedInt(sep->arg[2]);
const uint32 character_id = Strings::ToUnsignedInt(sep->arg[2]);
const auto& e = CharacterDataRepository::FindOne(content_db, character_id);
const auto& e = CharacterDataRepository::FindOne(database, character_id);
if (!e.id) {
c->Message(
Chat::White,
@@ -31,10 +31,10 @@ void FindCharacter(Client *c, const Seperator *sep)
return;
}
const auto search_criteria = Strings::ToLower(sep->argplus[2]);
const std::string& search_criteria = Strings::ToLower(sep->argplus[2]);
const auto& l = CharacterDataRepository::GetWhere(
content_db,
database,
fmt::format(
"LOWER(`name`) LIKE '%%{}%%' AND `name` NOT LIKE '%-deleted-%' ORDER BY `id` ASC LIMIT 50",
search_criteria
@@ -51,7 +51,7 @@ void FindCharacter(Client *c, const Seperator *sep)
);
}
auto found_count = 0;
uint32 found_count = 0;
for (const auto& e : l) {
c->Message(
+59 -49
View File
@@ -717,7 +717,7 @@ uint32 Raid::GetTotalRaidDamage(Mob* other)
return total;
}
void Raid::HealGroup(uint32 heal_amt, Mob* caster, uint32 gid, float range)
void Raid::HealGroup(uint32 heal_amount, Mob* caster, uint32 group_id, float range)
{
if (!caster) {
return;
@@ -728,26 +728,30 @@ void Raid::HealGroup(uint32 heal_amt, Mob* caster, uint32 gid, float range)
}
float distance;
float range2 = range*range;
float range_squared = range * range;
int member_count = 0;
int numMem = 0;
for (const auto& m : members) {
if (m.member && m.group_number == gid) {
if (m.member && m.group_number == group_id) {
distance = DistanceSquared(caster->GetPosition(), m.member->GetPosition());
if (distance <= range2) {
numMem += 1;
if (distance <= range_squared) {
member_count += 1;
}
}
}
heal_amt /= numMem;
if (member_count > 0) {
heal_amount /= member_count;
}
for (const auto& m : members) {
if (m.member && m.group_number == gid) {
if (m.member && m.group_number == group_id) {
distance = DistanceSquared(caster->GetPosition(), m.member->GetPosition());
if (distance <= range2) {
m.member->SetHP(m.member->GetHP() + heal_amt);
if (distance <= range_squared) {
m.member->SetHP(m.member->GetHP() + heal_amount);
m.member->SendHPUpdate();
}
}
@@ -755,7 +759,7 @@ void Raid::HealGroup(uint32 heal_amt, Mob* caster, uint32 gid, float range)
}
void Raid::BalanceHP(int32 penalty, uint32 gid, float range, Mob* caster, int32 limit)
void Raid::BalanceHP(int32 penalty, uint32 group_id, float range, Mob* caster, int32 limit)
{
if (!caster) {
return;
@@ -765,44 +769,48 @@ void Raid::BalanceHP(int32 penalty, uint32 gid, float range, Mob* caster, int32
range = 200;
}
int dmgtaken = 0, numMem = 0, dmgtaken_tmp = 0;
int damage_taken = 0;
int damage_taken_temporary = 0;
int member_count = 0;
float distance;
float range2 = range*range;
float range_squared = range * range;
for (const auto& m : members) {
if (m.member && m.group_number == gid) {
if (m.member && m.group_number == group_id) {
distance = DistanceSquared(caster->GetPosition(), m.member->GetPosition());
if (distance <= range2) {
dmgtaken_tmp = m.member->GetMaxHP() - m.member->GetHP();
if (distance <= range_squared) {
damage_taken_temporary = m.member->GetMaxHP() - m.member->GetHP();
if (limit && (dmgtaken_tmp > limit)) {
dmgtaken_tmp = limit;
if (limit && (damage_taken_temporary > limit)) {
damage_taken_temporary = limit;
}
dmgtaken += dmgtaken_tmp;
numMem += 1;
damage_taken += damage_taken_temporary;
member_count += 1;
}
}
}
dmgtaken += dmgtaken * penalty / 100;
dmgtaken /= numMem;
damage_taken += damage_taken * penalty / 100;
if (member_count > 0) {
damage_taken /= member_count;
}
for (const auto& m : members) {
if (m.member && m.group_number == gid) {
if (m.member && m.group_number == group_id) {
distance = DistanceSquared(caster->GetPosition(), m.member->GetPosition());
//this way the ability will never kill someone
//but it will come darn close
if (distance <= range2) {
if ((m.member->GetMaxHP() - dmgtaken) < 1) {
if (distance <= range_squared) {
if ((m.member->GetMaxHP() - damage_taken) < 1) {
m.member->SetHP(1);
m.member->SendHPUpdate();
}
else {
m.member->SetHP(m.member->GetMaxHP() - dmgtaken);
} else {
m.member->SetHP(m.member->GetMaxHP() - damage_taken);
m.member->SendHPUpdate();
}
}
@@ -810,7 +818,7 @@ void Raid::BalanceHP(int32 penalty, uint32 gid, float range, Mob* caster, int32
}
}
void Raid::BalanceMana(int32 penalty, uint32 gid, float range, Mob* caster, int32 limit)
void Raid::BalanceMana(int32 penalty, uint32 group_id, float range, Mob* caster, int32 limit)
{
if (!caster) {
return;
@@ -821,54 +829,56 @@ void Raid::BalanceMana(int32 penalty, uint32 gid, float range, Mob* caster, int3
}
float distance;
float range2 = range*range;
float range_squared = range * range;
int manataken = 0;
int numMem = 0;
int manataken_tmp = 0;
int mana_taken = 0;
int mana_taken_temporary = 0;
int member_count = 0;
for (const auto& m : members) {
if (m.is_bot) {
continue;
}
if (m.member && m.group_number == gid && m.member->GetMaxMana() > 0) {
if (m.member && m.group_number == group_id && m.member->GetMaxMana() > 0) {
distance = DistanceSquared(caster->GetPosition(), m.member->GetPosition());
if (distance <= range2) {
manataken_tmp = m.member->GetMaxMana() - m.member->GetMana();
if (distance <= range_squared) {
mana_taken_temporary = m.member->GetMaxMana() - m.member->GetMana();
if (limit && (manataken_tmp > limit)) {
manataken_tmp = limit;
if (limit && (mana_taken_temporary > limit)) {
mana_taken_temporary = limit;
}
manataken += manataken_tmp;
numMem += 1;
mana_taken += mana_taken_temporary;
member_count += 1;
}
}
}
manataken += manataken * penalty / 100;
manataken /= numMem;
mana_taken += mana_taken * penalty / 100;
if (member_count > 0) {
mana_taken /= member_count;
}
for (const auto& m : members) {
if (m.is_bot) {
continue;
}
if (m.member && m.group_number == gid) {
if (m.member && m.group_number == group_id) {
distance = DistanceSquared(caster->GetPosition(), m.member->GetPosition());
if (distance <= range2) {
if ((m.member->GetMaxMana() - manataken) < 1) {
if (distance <= range_squared) {
if ((m.member->GetMaxMana() - mana_taken) < 1) {
m.member->SetMana(1);
if (m.member->IsClient()) {
m.member->CastToClient()->SendManaUpdate();
}
}
else {
m.member->SetMana(m.member->GetMaxMana() - manataken);
} else {
m.member->SetMana(m.member->GetMaxMana() - mana_taken);
if (m.member->IsClient()) {
m.member->CastToClient()->SendManaUpdate();
+3 -3
View File
@@ -167,9 +167,9 @@ public:
void CastGroupSpell(Mob* caster,uint16 spellid, uint32 gid);
void SplitExp(ExpSource exp_source, const uint64 exp, Mob* other);
uint32 GetTotalRaidDamage(Mob* other);
void BalanceHP(int32 penalty, uint32 gid, float range = 0, Mob* caster = nullptr, int32 limit = 0);
void BalanceMana(int32 penalty, uint32 gid, float range = 0, Mob* caster = nullptr, int32 limit = 0);
void HealGroup(uint32 heal_amt, Mob* caster, uint32 gid, float range = 0);
void BalanceHP(int32 penalty, uint32 group_id, float range = 0, Mob* caster = nullptr, int32 limit = 0);
void BalanceMana(int32 penalty, uint32 group_id, float range = 0, Mob* caster = nullptr, int32 limit = 0);
void HealGroup(uint32 heal_amount, Mob* caster, uint32 group_id, float range = 0);
void SplitMoney(uint32 gid, uint32 copper, uint32 silver, uint32 gold, uint32 platinum, Client *splitter = nullptr);
void TeleportGroup(Mob* sender, uint32 zoneID, uint16 instance_id, float x, float y, float z, float heading, uint32 gid);