mirror of
https://github.com/EQEmu/Server.git
synced 2026-05-28 01:26:01 +00:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 585ed3bd25 | |||
| 2eb291a461 | |||
| a1421af214 | |||
| 13aad6229f | |||
| 76d46ceaf0 |
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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"
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user