From 59ad91a1400f8219a59509cf7d3d46393571f49c Mon Sep 17 00:00:00 2001 From: Aeadoin <109764533+Aeadoin@users.noreply.github.com> Date: Thu, 23 Mar 2023 21:42:13 -0400 Subject: [PATCH] [Feature] Add Data Bucket support for scaling of Heroic Stats. (#3058) * [Feature] Add Data Bucket support for scaling of Heroic Stats. * update * fixes, still reworking logic * fixes, still reworking logic * logic done * logic done * fixes * Cleanup * Cleanup * Cleanup naming, verify behaviors * formatting * formatting * fix issue with endurance and mana. * update rule desc * cleanup * DataBucket Struct * Cleanup data_bucket.cpp and add constants * cleanup * changes * formatting * fix from merge * escape keyword `key` * Add `key` to generator, run repository-generator.pl * fix for change to key * cleanup * formatting * formatting * typo --- common/emu_constants.h | 25 +++ .../base/base_data_buckets_repository.h | 24 +-- common/ruletypes.h | 3 + .../generators/repository-generator.pl | 3 +- zone/attack.cpp | 8 +- zone/bonuses.cpp | 143 +++++++++++++++++- zone/bot.cpp | 113 +++----------- zone/bot.h | 19 +-- zone/client.cpp | 37 +---- zone/client.h | 16 +- zone/client_mods.cpp | 38 +---- zone/client_packet.cpp | 3 + zone/client_process.cpp | 10 +- zone/common.h | 18 +++ zone/data_bucket.cpp | 55 ++++++- zone/data_bucket.h | 19 ++- zone/effects.cpp | 77 +++++++--- zone/lua_bot.cpp | 4 +- zone/lua_client.cpp | 6 + zone/lua_client.h | 2 + zone/merc.h | 4 +- zone/mob.cpp | 2 +- zone/mob.h | 25 ++- zone/perl_bot.cpp | 4 +- zone/perl_client.cpp | 6 + zone/tune.cpp | 8 +- zone/zone.cpp | 2 +- zone/zone.h | 2 +- 28 files changed, 433 insertions(+), 243 deletions(-) diff --git a/common/emu_constants.h b/common/emu_constants.h index 0d18f1179..8a432e471 100644 --- a/common/emu_constants.h +++ b/common/emu_constants.h @@ -593,4 +593,29 @@ enum class ApplySpellType { Raid }; + +namespace HeroicBonusBucket +{ + const std::string WisMaxMana = "HWIS-MaxMana"; + const std::string WisManaRegen = "HWIS-ManaRegen"; + const std::string WisHealAmt = "HWIS-HealAmt"; + const std::string IntMaxMana = "HINT-MaxMana"; + const std::string IntManaRegen = "HINT-ManaRegen"; + const std::string IntSpellDmg = "HINT-SpellDmg"; + const std::string StrMeleeDamage = "HSTR-MeleeDamage"; + const std::string StrShieldAC = "HSTR-ShieldAC"; + const std::string StrMaxEndurance = "HSTR-MaxEndurance"; + const std::string StrEnduranceRegen = "HSTR-EnduranceRegen"; + const std::string StaMaxHP = "HSTA-MaxHP"; + const std::string StaHPRegen = "HSTA-HPRegen"; + const std::string StaMaxEndurance = "HSTA-MaxEndurance"; + const std::string StaEnduranceRegen = "HSTA-EnduranceRegen"; + const std::string AgiAvoidance = "HAGI-Avoidance"; + const std::string AgiMaxEndurance = "HAGI-MaxEndurance"; + const std::string AgiEnduranceRegen = "HAGI-EnduranceRegen"; + const std::string DexRangedDamage = "HDEX-RangedDamage"; + const std::string DexMaxEndurance = "HDEX-MaxEndurance"; + const std::string DexEnduranceRegen = "HDEX-EnduranceRegen"; +} + #endif /*COMMON_EMU_CONSTANTS_H*/ diff --git a/common/repositories/base/base_data_buckets_repository.h b/common/repositories/base/base_data_buckets_repository.h index 3c7ad5a0a..0c4709315 100644 --- a/common/repositories/base/base_data_buckets_repository.h +++ b/common/repositories/base/base_data_buckets_repository.h @@ -16,11 +16,12 @@ #include "../../strings.h" #include + class BaseDataBucketsRepository { public: struct DataBuckets { uint64_t id; - std::string key; + std::string key_; std::string value; uint32_t expires; }; @@ -34,7 +35,7 @@ public: { return { "id", - "key", + "`key`", "value", "expires", }; @@ -44,7 +45,7 @@ public: { return { "id", - "key", + "`key`", "value", "expires", }; @@ -88,7 +89,7 @@ public: DataBuckets e{}; e.id = 0; - e.key = ""; + e.key_ = ""; e.value = ""; e.expires = 0; @@ -116,8 +117,9 @@ public: { auto results = db.QueryDatabase( fmt::format( - "{} WHERE id = {} LIMIT 1", + "{} WHERE {} = {} LIMIT 1", BaseSelect(), + PrimaryKey(), data_buckets_id ) ); @@ -127,7 +129,7 @@ public: DataBuckets e{}; e.id = strtoull(row[0], nullptr, 10); - e.key = row[1] ? row[1] : ""; + e.key_ = row[1] ? row[1] : ""; e.value = row[2] ? row[2] : ""; e.expires = static_cast(strtoul(row[3], nullptr, 10)); @@ -163,7 +165,7 @@ public: auto columns = Columns(); - v.push_back(columns[1] + " = '" + Strings::Escape(e.key) + "'"); + 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)); @@ -188,7 +190,7 @@ public: std::vector v; v.push_back(std::to_string(e.id)); - v.push_back("'" + Strings::Escape(e.key) + "'"); + v.push_back("'" + Strings::Escape(e.key_) + "'"); v.push_back("'" + Strings::Escape(e.value) + "'"); v.push_back(std::to_string(e.expires)); @@ -221,7 +223,7 @@ public: std::vector v; v.push_back(std::to_string(e.id)); - v.push_back("'" + Strings::Escape(e.key) + "'"); + v.push_back("'" + Strings::Escape(e.key_) + "'"); v.push_back("'" + Strings::Escape(e.value) + "'"); v.push_back(std::to_string(e.expires)); @@ -258,7 +260,7 @@ public: DataBuckets e{}; e.id = strtoull(row[0], nullptr, 10); - e.key = row[1] ? row[1] : ""; + e.key_ = row[1] ? row[1] : ""; e.value = row[2] ? row[2] : ""; e.expires = static_cast(strtoul(row[3], nullptr, 10)); @@ -286,7 +288,7 @@ public: DataBuckets e{}; e.id = strtoull(row[0], nullptr, 10); - e.key = row[1] ? row[1] : ""; + e.key_ = row[1] ? row[1] : ""; e.value = row[2] ? row[2] : ""; e.expires = static_cast(strtoul(row[3], nullptr, 10)); diff --git a/common/ruletypes.h b/common/ruletypes.h index b597cfc2c..fdef35ecd 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -93,6 +93,9 @@ RULE_INT(Character, ItemEnduranceRegenCap, 15, "Limit on endurance regeneration RULE_INT(Character, ItemExtraDmgCap, 150, "Cap for bonuses to melee skills like Bash, Frenzy, etc.") RULE_INT(Character, HasteCap, 100, "Haste cap for non-v3(over haste) haste") RULE_INT(Character, Hastev3Cap, 25, "Haste cap for v3(over haste) haste") +RULE_BOOL(Character, HeroicStatsUseDataBucketsToScale, false, "Allows scaling the benefits a character receives from Heroic Stats using Data Buckets. Stacks with other Heroic Stats Mulitplier Rules.") +RULE_REAL(Character, HeroicIntelligenceIncreaseSpellDmgMultiplier, 0.00, "Allows Heroic Intelligence to increase a Players Worn Spell Damage Stat from Equipment, for example, setting this rule to 1.00 will always grant 1 Spell Damage per 1 Heroic Intelligence") +RULE_REAL(Character, HeroicWisdomIncreaseHealAmtMultiplier, 0.00, "Allows Heroic Wisdom to increase a Players Worn Heal Amount Stat from Equipment, for example, setting this rule to 1.00 will always grant 1 Heal Amount per 1 Heroic Wisdom") RULE_REAL(Character, HeroicStrengthMultiplier, 1.00, "Multplier scales benefits from Heroic Strength. Grants 25 Base Endurance, 0.05 Endurance Regen, 1 Melee Damage each Hit, and 1 Shield AC per 10 Heroic Strength.") RULE_REAL(Character, HeroicStaminaMultiplier, 1.00, "Multplier scales benefits from Heroic Stamina. Grants 25 Base Endurance, 0.05 Endurance Regen, 100 Base HP, and 0.5 HP Regen per 10 Heroic Stamina.") RULE_REAL(Character, HeroicAgilityMultiplier, 1.00, "Multplier scales benefits from Heroic Agility. Grants 25 Base Endurance, 0.05 Endurance Regen, and 1 Avoidance AC per 10 Heroic Agility. (Rule does not change Dodge Chance)") diff --git a/utils/scripts/generators/repository-generator.pl b/utils/scripts/generators/repository-generator.pl index 438323d66..a943613ba 100644 --- a/utils/scripts/generators/repository-generator.pl +++ b/utils/scripts/generators/repository-generator.pl @@ -585,7 +585,8 @@ sub translate_mysql_data_type_to_c { sub get_reserved_cpp_variable_names { return ( "class", - "int" + "int", + "key" ); } diff --git a/zone/attack.cpp b/zone/attack.cpp index d7978e6d1..d567985b7 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -250,7 +250,7 @@ int Mob::compute_defense() int defense = GetSkill(EQ::skills::SkillDefense) * 400 / 225; defense += (8000 * (GetAGI() - 40)) / 36000; if (IsOfClientBot()) { - defense += GetHeroicAGI() * RuleR(Character, HeroicAgilityMultiplier) / 10; + defense += itembonuses.heroic_agi_avoidance; } //516 SE_AC_Mitigation_Max_Percent @@ -883,7 +883,7 @@ int Mob::ACSum(bool skip_caps) shield_ac = CalcRecommendedLevelBonus(GetLevel(), inst->GetItemRecommendedLevel(true), inst->GetItemArmorClass(true)); } } - shield_ac += GetHeroicSTR() * RuleR(Character, HeroicStrengthMultiplier) / 10; + shield_ac += itembonuses.heroic_str_shield_ac; } // EQ math ac = (ac * 4) / 3; @@ -5909,10 +5909,10 @@ void Mob::CommonOutgoingHitSuccess(Mob* defender, DamageHitInfo &hit, ExtraAttac switch (hit.skill) { case EQ::skills::SkillThrowing: case EQ::skills::SkillArchery: - extra = GetHeroicDEX() * RuleR(Character, HeroicDexterityMultiplier) / 10; + extra = itembonuses.heroic_dex_ranged_damage; break; default: - extra = GetHeroicSTR() * RuleR(Character, HeroicStrengthMultiplier) / 10; + extra = itembonuses.heroic_str_melee_damage; break; } hit.damage_done += extra; diff --git a/zone/bonuses.cpp b/zone/bonuses.cpp index 9ee25918e..fdedc1383 100644 --- a/zone/bonuses.cpp +++ b/zone/bonuses.cpp @@ -77,6 +77,7 @@ void Client::CalcBonuses() { memset(&itembonuses, 0, sizeof(StatBonuses)); CalcItemBonuses(&itembonuses); + CalcHeroicBonuses(&itembonuses); CalcEdibleBonuses(&itembonuses); CalcSpellBonuses(&spellbonuses); CalcAABonuses(&aabonuses); @@ -204,7 +205,7 @@ void Client::CalcItemBonuses(StatBonuses* newbon) { } // These item stat caps depend on spells/AAs so we process them after those are processed -void Client::ProcessItemCaps() +void Mob::ProcessItemCaps() { itembonuses.HPRegen = std::min(itembonuses.HPRegen, CalcHPRegenCap()); itembonuses.ManaRegen = std::min(itembonuses.ManaRegen, CalcManaRegenCap()); @@ -219,6 +220,15 @@ void Client::ProcessItemCaps() } itembonuses.ATK = std::min(itembonuses.ATK, CalcItemATKCap()); + + if (itembonuses.SpellDmg > RuleI(Character, ItemSpellDmgCap)) { + itembonuses.SpellDmg = RuleI(Character, ItemSpellDmgCap); + } + + if (itembonuses.HealAmt > RuleI(Character, ItemHealAmtCap)) { + itembonuses.HealAmt = RuleI(Character, ItemHealAmtCap); + } + } void Client::AddItemBonuses(const EQ::ItemInstance *inst, StatBonuses *newbon, bool isAug, bool isTribute, int rec_override, bool ammo_slot_item) @@ -5634,3 +5644,134 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id) } } } + +void Mob::CalcHeroicBonuses(StatBonuses* newbon) +{ + if (GetHeroicSTR()) { + SetHeroicStrBonuses(newbon); + } + + if (GetHeroicSTA()) { + SetHeroicStaBonuses(newbon); + } + + if (GetHeroicAGI()) { + SetHeroicAgiBonuses(newbon); + } + + if (GetHeroicDEX()) { + SetHeroicDexBonuses(newbon); + } + + if (GetHeroicINT()) { + SetHeroicIntBonuses(newbon); + } + + if (GetHeroicWIS()) { + SetHeroicWisBonuses(newbon); + } +} + +void Mob::SetHeroicWisBonuses(StatBonuses* n) +{ + n->heroic_max_mana += IsWISCasterClass(GetClass()) ? GetHeroicWIS() * RuleR(Character, HeroicWisdomMultiplier) * 10 : 0; + n->heroic_mana_regen += IsWISCasterClass(GetClass()) ? GetHeroicWIS() * RuleR(Character, HeroicWisdomMultiplier) / 25 : 0; + n->HealAmt += GetHeroicWIS() * RuleR(Character, HeroicWisdomIncreaseHealAmtMultiplier); + + if (RuleB(Character, HeroicStatsUseDataBucketsToScale)) { + n->heroic_max_mana += IsWISCasterClass(GetClass()) ? GetHeroicWIS() * CheckHeroicBonusesDataBuckets(HeroicBonusBucket::WisMaxMana) * 10 : 0; + n->heroic_mana_regen += IsWISCasterClass(GetClass()) ? GetHeroicWIS() * CheckHeroicBonusesDataBuckets(HeroicBonusBucket::WisManaRegen) / 25 : 0; + n->HealAmt += GetHeroicWIS() * CheckHeroicBonusesDataBuckets(HeroicBonusBucket::WisHealAmt); + } +} + +void Mob::SetHeroicIntBonuses(StatBonuses* n) +{ + n->heroic_max_mana += IsINTCasterClass(GetClass()) ? GetHeroicINT() * RuleR(Character, HeroicIntelligenceMultiplier) * 10 : 0; + n->heroic_mana_regen += IsINTCasterClass(GetClass()) ? GetHeroicINT() * RuleR(Character, HeroicIntelligenceMultiplier) / 25 : 0; + n->SpellDmg += GetHeroicINT() * RuleR(Character, HeroicIntelligenceIncreaseSpellDmgMultiplier); + + if (RuleB(Character, HeroicStatsUseDataBucketsToScale)) { + n->heroic_max_mana += IsINTCasterClass(GetClass()) ? GetHeroicINT() * CheckHeroicBonusesDataBuckets(HeroicBonusBucket::IntMaxMana) * 10 : 0; + n->heroic_mana_regen += IsINTCasterClass(GetClass()) ? GetHeroicINT() * CheckHeroicBonusesDataBuckets(HeroicBonusBucket::IntManaRegen) / 25 : 0; + n->SpellDmg += GetHeroicINT() * CheckHeroicBonusesDataBuckets(HeroicBonusBucket::IntSpellDmg); + } +} + +void Mob::SetHeroicDexBonuses(StatBonuses* n) +{ + n->heroic_dex_ranged_damage += GetHeroicDEX() * RuleR(Character, HeroicDexterityMultiplier) / 10; + n->heroic_max_end += GetHeroicDEX() * RuleR(Character, HeroicDexterityMultiplier) / 4 * 10.0f; + n->heroic_end_regen += GetHeroicDEX() * RuleR(Character, HeroicDexterityMultiplier) / 4 / 50; + + if (RuleB(Character, HeroicStatsUseDataBucketsToScale)) { + n->heroic_dex_ranged_damage += GetHeroicDEX() * CheckHeroicBonusesDataBuckets(HeroicBonusBucket::DexRangedDamage) / 10; + n->heroic_max_end += GetHeroicDEX() * CheckHeroicBonusesDataBuckets(HeroicBonusBucket::DexMaxEndurance) / 4 * 10.0f; + n->heroic_end_regen += GetHeroicDEX() * CheckHeroicBonusesDataBuckets(HeroicBonusBucket::DexEnduranceRegen) / 4 / 50; + } +} + +void Mob::SetHeroicAgiBonuses(StatBonuses* n) +{ + n->heroic_agi_avoidance += GetHeroicAGI() * RuleR(Character, HeroicAgilityMultiplier) / 10; + n->heroic_max_end += GetHeroicAGI() * RuleR(Character, HeroicAgilityMultiplier) / 4 * 10.0f; + n->heroic_end_regen += GetHeroicAGI() * RuleR(Character, HeroicAgilityMultiplier) / 4 / 50; + + if (RuleB(Character, HeroicStatsUseDataBucketsToScale)) { + n->heroic_agi_avoidance += GetHeroicAGI() * CheckHeroicBonusesDataBuckets(HeroicBonusBucket::AgiAvoidance) / 10; + n->heroic_max_end += GetHeroicAGI() * CheckHeroicBonusesDataBuckets(HeroicBonusBucket::AgiMaxEndurance) / 4 * 10.0f; + n->heroic_end_regen += GetHeroicAGI() * CheckHeroicBonusesDataBuckets(HeroicBonusBucket::AgiEnduranceRegen) / 4 / 50; + } +} + +void Mob::SetHeroicStaBonuses(StatBonuses* n) +{ + n->heroic_max_hp += GetHeroicSTA() * RuleR(Character, HeroicStaminaMultiplier) * 10; + n->heroic_hp_regen += GetHeroicSTA() * RuleR(Character, HeroicStaminaMultiplier) / 20; + n->heroic_max_end += GetHeroicSTA() * RuleR(Character, HeroicStaminaMultiplier) / 4 * 10.0f; + n->heroic_end_regen += GetHeroicSTA() * RuleR(Character, HeroicStaminaMultiplier) / 4 / 50; + + if (RuleB(Character, HeroicStatsUseDataBucketsToScale)) { + n->heroic_max_hp += GetHeroicSTA() * CheckHeroicBonusesDataBuckets(HeroicBonusBucket::StaMaxHP) * 10; + n->heroic_hp_regen += GetHeroicSTA() * CheckHeroicBonusesDataBuckets(HeroicBonusBucket::StaHPRegen) / 20; + n->heroic_max_end += GetHeroicSTA() * CheckHeroicBonusesDataBuckets(HeroicBonusBucket::StaMaxEndurance) / 4 * 10.0f; + n->heroic_end_regen += GetHeroicSTA() * CheckHeroicBonusesDataBuckets(HeroicBonusBucket::StaEnduranceRegen) / 4 / 50; + } +} + +void Mob::SetHeroicStrBonuses(StatBonuses* n) +{ + n->heroic_str_shield_ac += GetHeroicSTR() * RuleR(Character, HeroicStrengthMultiplier) / 10; + n->heroic_str_melee_damage += GetHeroicSTR() * RuleR(Character, HeroicStrengthMultiplier) / 10; + n->heroic_max_end += GetHeroicSTR() * RuleR(Character, HeroicStrengthMultiplier) / 4 * 10.0f; + n->heroic_end_regen += GetHeroicSTR() * RuleR(Character, HeroicStrengthMultiplier) / 4 / 50; + + if (RuleB(Character, HeroicStatsUseDataBucketsToScale)) { + n->heroic_str_shield_ac += GetHeroicSTR() * CheckHeroicBonusesDataBuckets(HeroicBonusBucket::StrShieldAC) / 10; + n->heroic_str_melee_damage += GetHeroicSTR() * CheckHeroicBonusesDataBuckets(HeroicBonusBucket::StrMeleeDamage) / 10; + n->heroic_max_end += GetHeroicSTR() * CheckHeroicBonusesDataBuckets(HeroicBonusBucket::StrMaxEndurance) / 4 * 10.0f; + n->heroic_end_regen += GetHeroicSTR() * CheckHeroicBonusesDataBuckets(HeroicBonusBucket::StrEnduranceRegen) / 4 / 50; + } +} + +float Mob::CheckHeroicBonusesDataBuckets(std::string bucket_name) +{ + std::string bucket_value; + if (!bucket_name.empty()) { + const auto full_name = fmt::format( + "{}-{}", + GetBucketKey(), + bucket_name + ); + + if (IsOfClientBot()) { + bucket_value = DataBucket::CheckBucketKey(this, full_name); + } + + if (bucket_value.empty() || !Strings::IsNumber(bucket_value)) { + return 0.00f; + } + } + + return Strings::ToFloat(bucket_value); +} \ No newline at end of file diff --git a/zone/bot.cpp b/zone/bot.cpp index 44f40c692..af8584f5d 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -3284,8 +3284,7 @@ bool Bot::Spawn(Client* botCharacterOwner) { m_targetable = true; entity_list.AddBot(this, true, true); - GetBotOwnerDataBuckets(); - GetBotDataBuckets(); + DataBucket::GetDataBuckets(this); LoadBotSpellSettings(); if (!AI_AddBotSpells(GetBotSpellID())) { GetBotOwner()->CastToClient()->Message( @@ -5461,10 +5460,10 @@ int64 Bot::CalcMaxMana() { switch(GetCasterClass()) { case 'I': max_mana = (GenerateBaseManaPoints() + itembonuses.Mana + spellbonuses.Mana + GroupLeadershipAAManaEnhancement()); - max_mana += (GetHeroicINT() * 10); + max_mana += itembonuses.heroic_max_mana; case 'W': { max_mana = (GenerateBaseManaPoints() + itembonuses.Mana + spellbonuses.Mana + GroupLeadershipAAManaEnhancement()); - max_mana += (GetHeroicWIS() * 10); + max_mana += itembonuses.heroic_max_mana; break; } case 'N': { @@ -6001,11 +6000,13 @@ void Bot::CalcBonuses() { memset(&itembonuses, 0, sizeof(StatBonuses)); GenerateBaseStats(); CalcItemBonuses(&itembonuses); + CalcHeroicBonuses(&itembonuses); CalcSpellBonuses(&spellbonuses); CalcAABonuses(&aabonuses); SetAttackTimer(); CalcSeeInvisibleLevel(); CalcInvisibleLevel(); + ProcessItemCaps(); CalcATK(); CalcSTR(); CalcSTA(); @@ -6037,15 +6038,7 @@ int64 Bot::CalcHPRegenCap() { } int64 Bot::CalcManaRegenCap() { - int64 cap = RuleI(Character, ItemManaRegenCap) + aabonuses.ItemManaRegenCap; - switch(GetCasterClass()) { - case 'I': - cap += itembonuses.HeroicINT * RuleR(Character, HeroicIntelligenceMultiplier) / 25; - break; - case 'W': - cap += itembonuses.HeroicWIS * RuleR(Character, HeroicWisdomMultiplier) / 25; - break; - } + int64 cap = RuleI(Character, ItemManaRegenCap) + aabonuses.ItemManaRegenCap + itembonuses.heroic_mana_regen; return (cap * RuleI(Character, ManaRegenMultiplier) / 100); } @@ -6435,7 +6428,7 @@ int32 Bot::LevelRegen() { int64 Bot::CalcHPRegen() { int32 regen = (LevelRegen() + itembonuses.HPRegen + spellbonuses.HPRegen); - regen += GetHeroicSTA() * RuleR(Character, HeroicStaminaMultiplier) / 20; + regen += itembonuses.heroic_hp_regen; regen += (aabonuses.HPRegen + GroupLeadershipAAHealthRegeneration()); regen = ((regen * RuleI(Character, HPRegenMultiplier)) / 100); @@ -6456,15 +6449,8 @@ int64 Bot::CalcManaRegen() { } else regen = (2 + spellbonuses.ManaRegen + itembonuses.ManaRegen); - if(GetCasterClass() == 'I') - regen += itembonuses.HeroicINT * RuleR(Character, HeroicIntelligenceMultiplier) / 25; - else if(GetCasterClass() == 'W') - regen += itembonuses.HeroicWIS * RuleR(Character, HeroicWisdomMultiplier) / 25; + regen += aabonuses.ManaRegen + itembonuses.heroic_mana_regen; - else - regen = 0; - - regen += aabonuses.ManaRegen; regen = ((regen * RuleI(Character, ManaRegenMultiplier)) / 100); float mana_regen_rate = RuleR(Bots, ManaRegen); if (mana_regen_rate < 0.0f) @@ -6509,7 +6495,7 @@ int64 Bot::CalcMaxHP() { int32 bot_hp = 0; uint32 nd = 10000; bot_hp += (GenerateBaseHitPoints() + itembonuses.HP); - bot_hp += GetHeroicSTA() * RuleR(Character, HeroicStaminaMultiplier) * 10; + bot_hp += itembonuses.heroic_max_hp; nd += aabonuses.MaxHP; bot_hp = ((float)bot_hp * (float)nd / (float)10000); bot_hp += (spellbonuses.HP + aabonuses.HP); @@ -6553,13 +6539,7 @@ int64 Bot::CalcBaseEndurance() { int32 sta_end = 0; int stats = 0; if (GetOwner() && GetOwner()->CastToClient() && GetOwner()->CastToClient()->ClientVersion() >= EQ::versions::ClientVersion::SoD && RuleB(Character, SoDClientUseSoDHPManaEnd)) { - double heroic_stats = 0; stats = ((GetSTR() + GetSTA() + GetDEX() + GetAGI()) / 4); - double heroic_str = GetHeroicSTR() * RuleR(Character, HeroicStrengthMultiplier); - double heroic_sta = GetHeroicSTA() * RuleR(Character, HeroicStaminaMultiplier); - double heroic_dex = GetHeroicDEX() * RuleR(Character, HeroicDexterityMultiplier); - double heroic_agi = GetHeroicAGI() * RuleR(Character, HeroicAgilityMultiplier); - heroic_stats = (heroic_str + heroic_sta + heroic_dex + heroic_agi) / 4; if (stats > 100) { converted_stats = (((stats - 100) * 5 / 2) + 100); @@ -6580,7 +6560,7 @@ int64 Bot::CalcBaseEndurance() { sta_end = (9 * converted_stats); base_endurance = (1800 + ((GetLevel() - 80) * 18)); } - base_end = (base_endurance + sta_end + (heroic_stats * 10)); + base_end = base_endurance + sta_end + itembonuses.heroic_max_end; } else { stats = (GetSTR() + GetSTA() + GetDEX() + GetAGI()); @@ -6593,7 +6573,8 @@ int64 Bot::CalcBaseEndurance() { int HalfBonus400to800 = 0; int Bonus800plus = 0; int HalfBonus800plus = 0; - int BonusUpto800 = int(at_most_800 / 4) ; + + auto BonusUpto800 = int(at_most_800 / 4) ; if(stats > 400) { Bonus400to800 = int((at_most_800 - 400) / 4); @@ -8768,62 +8749,7 @@ void Bot::OwnerMessage(const std::string& message) ); } -bool Bot::GetBotOwnerDataBuckets() -{ - auto bot_owner = GetBotOwner(); - if (!bot_owner) { - return false; - } - - const auto query = fmt::format( - "SELECT `key`, `value` FROM data_buckets WHERE `key` LIKE '{}-%'", - Strings::Escape(bot_owner->GetBucketKey()) - ); - - auto results = database.QueryDatabase(query); - if (!results.Success()) { - return false; - } - - bot_owner_data_buckets.clear(); - - if (!results.RowCount()) { - return true; - } - - for (auto row : results) { - bot_owner_data_buckets.insert(std::pair(row[0], row[1])); - } - - return true; -} - -bool Bot::GetBotDataBuckets() -{ - const auto query = fmt::format( - "SELECT `key`, `value` FROM data_buckets WHERE `key` LIKE '{}-%'", - Strings::Escape(GetBucketKey()) - ); - - auto results = database.QueryDatabase(query); - if (!results.Success()) { - return false; - } - - bot_data_buckets.clear(); - - if (!results.RowCount()) { - return true; - } - - for (auto row : results) { - bot_data_buckets.insert(std::pair(row[0], row[1])); - } - - return true; -} - -bool Bot::CheckDataBucket(const std::string& bucket_name, const std::string& bucket_value, uint8 bucket_comparison) +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( @@ -8832,7 +8758,7 @@ bool Bot::CheckDataBucket(const std::string& bucket_name, const std::string& buc bucket_name ); - auto player_value = bot_data_buckets[full_name]; + auto player_value = DataBucket::CheckBucketKey(this, full_name); if (player_value.empty() && GetBotOwner()) { full_name = fmt::format( "{}-{}", @@ -8840,13 +8766,13 @@ bool Bot::CheckDataBucket(const std::string& bucket_name, const std::string& buc bucket_name ); - player_value = bot_owner_data_buckets[full_name]; + player_value = DataBucket::CheckBucketKey(GetBotOwner(), full_name); if (player_value.empty()) { return false; } } - if (zone->CheckDataBucket(bucket_comparison, bucket_value, player_value)) { + if (zone->CompareDataBucket(bucket_comparison, bucket_value, player_value)) { return true; } } @@ -9351,6 +9277,12 @@ float Bot::GetBotCasterMaxRange(float melee_distance_max) {// Calculate caster d return caster_distance_max; } + +int32 Bot::CalcItemATKCap() +{ + return RuleI(Character, ItemATKCap) + itembonuses.ItemATKCap + spellbonuses.ItemATKCap + aabonuses.ItemATKCap; +} + bool Bot::CheckSpawnConditions(Client* c) { if (c->GetFeigned()) { @@ -9386,5 +9318,4 @@ bool Bot::CheckSpawnConditions(Client* c) { return true; } - uint8 Bot::spell_casting_chances[SPELL_TYPE_COUNT][PLAYER_CLASS_COUNT][EQ::constants::STANCE_TYPE_COUNT][cntHSND] = { 0 }; diff --git a/zone/bot.h b/zone/bot.h index b77e0f919..3f31f451d 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -269,8 +269,8 @@ public: int32 CalcPR(); int32 CalcCR(); int32 CalcCorrup(); - int64 CalcHPRegenCap(); - int64 CalcManaRegenCap(); + int64 CalcHPRegenCap() final; + int64 CalcManaRegenCap() final; int32 LevelRegen(); int64 CalcHPRegen(); int64 CalcManaRegen(); @@ -280,6 +280,7 @@ public: int GroupLeadershipAAHealthRegeneration(); int GroupLeadershipAAOffenseEnhancement(); void CalcRestState(); + int64 CalcMaxEndurance(); int64 CalcBaseEndurance(); int64 CalcEnduranceRegen(); @@ -382,9 +383,7 @@ public: [[nodiscard]] int GetMaxDiscSlots() const final { return EQ::spells::DISC_BUFFS; } [[nodiscard]] int GetMaxTotalSlots() const final { return EQ::spells::TOTAL_BUFFS; } - bool GetBotOwnerDataBuckets(); - bool GetBotDataBuckets(); - bool CheckDataBucket(const std::string& bucket_name, const std::string& bucket_value, uint8 bucket_comparison); + bool CheckDataBucket(std::string bucket_name, const std::string& bucket_value, uint8 bucket_comparison); // Bot Equipment & Inventory Class Methods void BotTradeAddItem(const EQ::ItemInstance* inst, uint16 slot_id, std::string* error_message, bool save_to_database = true); @@ -571,8 +570,8 @@ public: inline virtual int32 GetCombatEffects() const { return itembonuses.ProcChance; } inline virtual int32 GetDS() const { return itembonuses.DamageShield; } // Mod3 - inline virtual int32 GetHealAmt() const { return itembonuses.HealAmt; } - inline virtual int32 GetSpellDmg() const { return itembonuses.SpellDmg; } + inline int32 GetHealAmt() const override { return itembonuses.HealAmt; } + inline int32 GetSpellDmg() const override { return itembonuses.SpellDmg; } inline virtual int32 GetClair() const { return itembonuses.Clairvoyance; } inline virtual int32 GetDSMit() const { return itembonuses.DSMitigation; } @@ -883,8 +882,6 @@ private: eStandingPetOrder m_previous_pet_order; uint32 m_bot_caster_range; BotCastingRoles m_CastingRoles; - std::map bot_data_buckets; - std::map bot_owner_data_buckets; std::map bot_spell_settings; @@ -937,6 +934,10 @@ private: bool LoadPet(); // Load and spawn bot pet if there is one bool SavePet(); // Save and depop bot pet if there is one bool DeletePet(); + + public: + + int32 CalcItemATKCap() final; }; bool IsSpellInBotList(DBbotspells_Struct* spell_list, uint16 iSpellID); diff --git a/zone/client.cpp b/zone/client.cpp index 5781fce25..d820328df 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -6437,8 +6437,7 @@ void Client::SendStatsWindow(Client* client, bool use_window) regen_row_color = color_red; base_regen_field = itoa(LevelRegen()); - item_regen_field = itoa( - itembonuses.HPRegen +(GetHeroicSTA() * RuleR(Character, HeroicStaminaMultiplier) / 20)); + item_regen_field = itoa(itembonuses.HPRegen + itembonuses.heroic_hp_regen); cap_regen_field = itoa(CalcHPRegenCap()); spell_regen_field = itoa(spellbonuses.HPRegen); aa_regen_field = itoa(aabonuses.HPRegen); @@ -6451,9 +6450,7 @@ void Client::SendStatsWindow(Client* client, bool use_window) regen_row_color = color_blue; base_regen_field = itoa(CalcBaseManaRegen()); - int32 heroic_mana_regen = (GetCasterClass() == 'W') ? - GetHeroicWIS() * RuleR(Character, HeroicWisdomMultiplier) / 25 : - GetHeroicINT() * RuleR(Character, HeroicIntelligenceMultiplier) / 25; + int32 heroic_mana_regen = itembonuses.heroic_mana_regen; item_regen_field = itoa(itembonuses.ManaRegen + heroic_mana_regen); cap_regen_field = itoa(CalcManaRegenCap()); spell_regen_field = itoa(spellbonuses.ManaRegen); @@ -6468,12 +6465,7 @@ void Client::SendStatsWindow(Client* client, bool use_window) regen_row_color = color_green; base_regen_field = itoa(((GetLevel() * 4 / 10) + 2)); - double heroic_str = GetHeroicSTR() * RuleR(Character, HeroicStrengthMultiplier); - double heroic_sta = GetHeroicSTA() * RuleR(Character, HeroicStaminaMultiplier); - double heroic_dex = GetHeroicDEX() * RuleR(Character, HeroicDexterityMultiplier); - double heroic_agi = GetHeroicAGI() * RuleR(Character, HeroicAgilityMultiplier); - double heroic_stats = (heroic_str + heroic_sta + heroic_dex + heroic_agi) / 4; - item_regen_field = itoa(itembonuses.EnduranceRegen + heroic_stats); + item_regen_field = itoa(itembonuses.EnduranceRegen + itembonuses.heroic_end_regen); cap_regen_field = itoa(CalcEnduranceRegenCap()); spell_regen_field = itoa(spellbonuses.EnduranceRegen); aa_regen_field = itoa(aabonuses.EnduranceRegen); @@ -11584,27 +11576,6 @@ void Client::SendReloadCommandMessages() { SendChatLineBreak(); } -std::map Client::GetMerchantDataBuckets() -{ - std::map merchant_data_buckets; - - auto query = fmt::format( - "SELECT `key`, `value` FROM data_buckets WHERE `key` LIKE '{}-%'", - Strings::Escape(GetBucketKey()) - ); - auto results = database.QueryDatabase(query); - - if (!results.Success() || !results.RowCount()) { - return merchant_data_buckets; - } - - for (auto row : results) { - merchant_data_buckets.insert(std::pair(row[0], row[1])); - } - - return merchant_data_buckets; -} - void Client::Undye() { for (uint8 slot = EQ::textures::textureBegin; slot <= EQ::textures::LastTexture; slot++) { @@ -12215,4 +12186,4 @@ void Client::PlayerTradeEventLog(Trade *t, Trade *t2) RecordPlayerEventLogWithClient(trader, PlayerEvent::TRADE, e); RecordPlayerEventLogWithClient(trader2, PlayerEvent::TRADE, e); -} +} \ No newline at end of file diff --git a/zone/client.h b/zone/client.h index 7d02d4fe2..9362bca60 100644 --- a/zone/client.h +++ b/zone/client.h @@ -430,7 +430,7 @@ public: int64 CalcMaxMana(); int64 CalcBaseMana(); const int64& SetMana(int64 amount); - int64 CalcManaRegenCap(); + int64 CalcManaRegenCap() final; // guild pool regen shit. Sends a SpawnAppearance with a value that regens to value * 0.001 void EnableAreaHPRegen(int value); @@ -535,8 +535,8 @@ public: inline virtual int32 GetCombatEffects() const { return itembonuses.ProcChance; } inline virtual int32 GetDS() const { return itembonuses.DamageShield; } // Mod3 - inline virtual int32 GetHealAmt() const { return itembonuses.HealAmt; } - inline virtual int32 GetSpellDmg() const { return itembonuses.SpellDmg; } + inline int32 GetHealAmt() const override { return itembonuses.HealAmt; } + inline int32 GetSpellDmg() const final { return itembonuses.SpellDmg; } inline virtual int32 GetClair() const { return itembonuses.Clairvoyance; } inline virtual int32 GetDSMit() const { return itembonuses.DSMitigation; } @@ -578,8 +578,8 @@ public: int64 CalcEnduranceRegen(bool bCombat = false); //Calculates endurance regen used in DoEnduranceRegen() int64 GetEndurance() const {return current_endurance;} //This gets our current endurance int64 GetMaxEndurance() const {return max_end;} //This gets our endurance from the last CalcMaxEndurance() call - int64 CalcEnduranceRegenCap(); - int64 CalcHPRegenCap(); + int64 CalcEnduranceRegenCap() final; + int64 CalcHPRegenCap() final; inline uint8 GetEndurancePercent() { return (uint8)((float)current_endurance / (float)max_end * 100.0f); } void SetEndurance(int32 newEnd); //This sets the current endurance to the new value void DoEnduranceRegen(); //This Regenerates endurance @@ -1638,19 +1638,17 @@ public: // rate limit Timer m_list_task_timers_rate_limit = {}; - std::map GetMerchantDataBuckets(); - std::string GetGuildPublicNote(); PlayerEvent::PlayerEvent GetPlayerEvent(); void RecordKilledNPCEvent(NPC *n); + protected: friend class Mob; void CalcItemBonuses(StatBonuses* newbon); void AddItemBonuses(const EQ::ItemInstance *inst, StatBonuses* newbon, bool isAug = false, bool isTribute = false, int rec_override = 0, bool ammo_slot_item = false); void AdditiveWornBonuses(const EQ::ItemInstance *inst, StatBonuses* newbon, bool isAug = false); void CalcEdibleBonuses(StatBonuses* newbon); - void ProcessItemCaps(); void MakeBuffFadePacket(uint16 spell_id, int slot_id, bool send_message = true); bool client_data_loaded; @@ -1701,7 +1699,7 @@ private: void HandleTraderPriceUpdate(const EQApplicationPacket *app); - int32 CalcItemATKCap(); + int32 CalcItemATKCap() final; int32 CalcHaste(); int32 CalcAlcoholPhysicalEffect(); diff --git a/zone/client_mods.cpp b/zone/client_mods.cpp index a9b70d453..4adaee619 100644 --- a/zone/client_mods.cpp +++ b/zone/client_mods.cpp @@ -234,7 +234,7 @@ int32 Client::LevelRegen() int64 Client::CalcHPRegen(bool bCombat) { int64 item_regen = itembonuses.HPRegen; // worn spells and +regen, already capped - item_regen += GetHeroicSTA() * RuleR(Character, HeroicStaminaMultiplier) / 20; + item_regen += itembonuses.heroic_hp_regen; item_regen += aabonuses.HPRegen; @@ -491,7 +491,7 @@ int64 Client::CalcBaseHP() auto base_data = database.GetBaseData(GetLevel(), GetClass()); if (base_data) { base_hp += base_data->base_hp + (base_data->hp_factor * stats); - base_hp += GetHeroicSTA() * RuleR(Character, HeroicStaminaMultiplier) * 10; + base_hp += itembonuses.heroic_max_hp; } } else { @@ -624,8 +624,7 @@ int64 Client::CalcBaseMana() auto base_data = database.GetBaseData(GetLevel(), GetClass()); if (base_data) { max_m = base_data->base_mana + - (ConvertedWisInt * base_data->mana_factor) + - (GetHeroicINT() * RuleR(Character, HeroicIntelligenceMultiplier) * 10); + (ConvertedWisInt * base_data->mana_factor) + itembonuses.heroic_max_mana; } } else { @@ -658,8 +657,7 @@ int64 Client::CalcBaseMana() auto base_data = database.GetBaseData(GetLevel(), GetClass()); if (base_data) { max_m = base_data->base_mana + - (ConvertedWisInt * base_data->mana_factor) + - ((GetHeroicWIS() * RuleR(Character, HeroicWisdomMultiplier)) * 10); + (ConvertedWisInt * base_data->mana_factor) + itembonuses.heroic_max_mana; } } else { @@ -752,18 +750,7 @@ int64 Client::CalcManaRegen(bool bCombat) // add in + 1 bonus for SE_CompleteHeal, but we don't do anything for it yet? int item_bonus = itembonuses.ManaRegen; // this is capped already - int heroic_bonus = 0; - - switch (GetCasterClass()) { - case 'W': - heroic_bonus = GetHeroicWIS() * RuleR(Character, HeroicWisdomMultiplier); - break; - default: - heroic_bonus = GetHeroicINT() * RuleR(Character, HeroicIntelligenceMultiplier); - break; - } - - item_bonus += heroic_bonus / 25; + item_bonus += itembonuses.heroic_mana_regen; regen += item_bonus; if (level <= 70 && regen > 65) @@ -1690,11 +1677,6 @@ int64 Client::CalcBaseEndurance() { int64 base_end = 0; if (ClientVersion() >= EQ::versions::ClientVersion::SoF && RuleB(Character, SoDClientUseSoDHPManaEnd)) { - double heroic_str = GetHeroicSTR() * RuleR(Character, HeroicStrengthMultiplier); - double heroic_sta = GetHeroicSTA() * RuleR(Character, HeroicStaminaMultiplier); - double heroic_dex = GetHeroicDEX() * RuleR(Character, HeroicDexterityMultiplier); - double heroic_agi = GetHeroicAGI() * RuleR(Character, HeroicAgilityMultiplier); - double heroic_stats = (heroic_str + heroic_sta + heroic_dex + heroic_agi) / 4; double stats = (GetSTR() + GetSTA() + GetDEX() + GetAGI()) / 4.0f; if (stats > 201.0f) { @@ -1705,7 +1687,7 @@ int64 Client::CalcBaseEndurance() } auto base_data = database.GetBaseData(GetLevel(), GetClass()); if (base_data) { - base_end = base_data->base_end + (heroic_stats * 10.0f) + (base_data->endurance_factor * static_cast(stats)); + base_end = base_data->base_end + itembonuses.heroic_max_end + (base_data->endurance_factor * static_cast(stats)); } } else { @@ -1794,13 +1776,7 @@ int64 Client::CalcEnduranceRegen(bool bCombat) if (encumbered) base += level / -15; - double heroic_str = GetHeroicSTR() * RuleR(Character, HeroicStrengthMultiplier); - double heroic_sta = GetHeroicSTA() * RuleR(Character, HeroicStaminaMultiplier); - double heroic_dex = GetHeroicDEX() * RuleR(Character, HeroicDexterityMultiplier); - double heroic_agi = GetHeroicAGI() * RuleR(Character, HeroicAgilityMultiplier); - int32 item_bonus = heroic_str + heroic_sta + heroic_dex + heroic_agi; - item_bonus = item_bonus / 4 / 50; - item_bonus += itembonuses.EnduranceRegen; // this is capped already + auto item_bonus = itembonuses.EnduranceRegen + itembonuses.heroic_end_regen; // this is capped already base += item_bonus; base = base * AreaEndRegen + 0.5f; diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 1980926c0..1ea1a85c8 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -1380,6 +1380,9 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app) drakkin_tattoo = m_pp.drakkin_tattoo; drakkin_details = m_pp.drakkin_details; + // Load Data Buckets + DataBucket::GetDataBuckets(this); + // Max Level for Character:PerCharacterQglobalMaxLevel and Character:PerCharacterBucketMaxLevel uint8 client_max_level = 0; if (RuleB(Character, PerCharacterQglobalMaxLevel)) { diff --git a/zone/client_process.cpp b/zone/client_process.cpp index 087e05fe5..748fd80f5 100644 --- a/zone/client_process.cpp +++ b/zone/client_process.cpp @@ -845,18 +845,16 @@ void Client::BulkSendMerchantInventory(int merchant_id, int npcid) { } } - auto client_data_buckets = GetMerchantDataBuckets(); - auto temporary_merchant_list = zone->tmpmerchanttable[npcid]; uint32 slot_id = 1; uint8 handy_chance = 0; - for (auto ml : merchant_list) { + for (const auto& ml : merchant_list) { if (slot_id > merchant_slots) { break; } auto bucket_name = ml.bucket_name; - auto bucket_value = ml.bucket_value; + auto const& bucket_value = ml.bucket_value; if (!bucket_name.empty() && !bucket_value.empty()) { auto full_name = fmt::format( "{}-{}", @@ -864,12 +862,12 @@ void Client::BulkSendMerchantInventory(int merchant_id, int npcid) { bucket_name ); - auto player_value = client_data_buckets[full_name]; + auto const& player_value = DataBucket::CheckBucketKey(this, full_name); if (player_value.empty()) { continue; } - if (!zone->CheckDataBucket(ml.bucket_comparison, bucket_value, player_value)) { + if (!zone->CompareDataBucket(ml.bucket_comparison, bucket_value, player_value)) { continue; } } diff --git a/zone/common.h b/zone/common.h index 08c9e078c..f932da9da 100644 --- a/zone/common.h +++ b/zone/common.h @@ -639,6 +639,16 @@ struct StatBonuses { int aura_slots; int trap_slots; bool hunger; // Song of Sustenance -- min caps to 3500 + int64 heroic_max_hp; + int64 heroic_max_mana; + int64 heroic_max_end; + int64 heroic_hp_regen; + int64 heroic_mana_regen; + int64 heroic_end_regen; + int32 heroic_str_shield_ac; + int32 heroic_str_melee_damage; + int32 heroic_agi_avoidance; + int32 heroic_dex_ranged_damage; }; // StatBonus Indexes @@ -915,5 +925,13 @@ struct ExpeditionInvite std::string swap_remove_name; }; +struct DataBucketCache +{ + uint64_t bucket_id; + std::string bucket_key; + std::string bucket_value; + uint32_t bucket_expires; +}; + #endif diff --git a/zone/data_bucket.cpp b/zone/data_bucket.cpp index fd05f8795..c96d417b4 100644 --- a/zone/data_bucket.cpp +++ b/zone/data_bucket.cpp @@ -13,7 +13,7 @@ * @param bucket_value * @param expires_time */ -void DataBucket::SetData(std::string bucket_key, std::string bucket_value, std::string 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); std::string query; @@ -57,7 +57,7 @@ void DataBucket::SetData(std::string bucket_key, std::string bucket_value, std:: * @param bucket_key * @return */ -std::string DataBucket::GetData(std::string bucket_key) { +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(), @@ -82,7 +82,7 @@ std::string DataBucket::GetData(std::string bucket_key) { * @param bucket_key * @return */ -std::string DataBucket::GetDataExpires(std::string bucket_key) { +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(), @@ -102,7 +102,7 @@ std::string DataBucket::GetDataExpires(std::string bucket_key) { return std::string(row[0]); } -std::string DataBucket::GetDataRemaining(std::string bucket_key) { +std::string DataBucket::GetDataRemaining(const std::string& bucket_key) { if (DataBucket::GetDataExpires(bucket_key).empty()) { return "0"; } @@ -130,7 +130,7 @@ std::string DataBucket::GetDataRemaining(std::string bucket_key) { * @param bucket_key * @return */ -uint64 DataBucket::DoesBucketExist(std::string bucket_key) { +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(), @@ -154,7 +154,7 @@ uint64 DataBucket::DoesBucketExist(std::string bucket_key) { * @param bucket_key * @return */ -bool DataBucket::DeleteData(std::string bucket_key) { +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() @@ -164,3 +164,46 @@ bool DataBucket::DeleteData(std::string bucket_key) { return results.Success(); } + +bool DataBucket::GetDataBuckets(Mob* mob) +{ + auto l = BaseDataBucketsRepository::GetWhere( + database, + fmt::format( + "`key` LIKE '{}-%'", + Strings::Escape(mob->GetBucketKey()) + ) + ); + + if (l.empty()) { + 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); + } + + return true; +} + +std::string DataBucket::CheckBucketKey(const Mob* mob, std::string_view full_name) +{ + std::string bucket_value; + for (const auto &d : mob->m_data_bucket_cache) { + if (d.bucket_key == full_name) { + bucket_value = d.bucket_value; + break; + } + } + return bucket_value; +} + diff --git a/zone/data_bucket.h b/zone/data_bucket.h index c06a77c6c..78a55cc16 100644 --- a/zone/data_bucket.h +++ b/zone/data_bucket.h @@ -5,20 +5,23 @@ #ifndef EQEMU_DATABUCKET_H #define EQEMU_DATABUCKET_H - #include #include "../common/types.h" +#include "../common/repositories/data_buckets_repository.h" +#include "mob.h" class DataBucket { public: - static void SetData(std::string bucket_key, std::string bucket_value, std::string expires_time = ""); - static bool DeleteData(std::string bucket_key); - static std::string GetData(std::string bucket_key); - static std::string GetDataExpires(std::string bucket_key); - static std::string GetDataRemaining(std::string bucket_key); + 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); + private: - static uint64 DoesBucketExist(std::string bucket_key); + static uint64 DoesBucketExist(const std::string& bucket_key); }; - #endif //EQEMU_DATABUCKET_H diff --git a/zone/effects.cpp b/zone/effects.cpp index 9883b5368..87374cf3b 100644 --- a/zone/effects.cpp +++ b/zone/effects.cpp @@ -132,6 +132,7 @@ int64 Mob::GetActSpellDamage(uint16 spell_id, int64 value, Mob* target) { if (RuleB(Spells, IgnoreSpellDmgLvlRestriction) && !spells[spell_id].no_heal_damage_item_mod && itembonuses.SpellDmg) { value -= GetExtraSpellAmt(spell_id, itembonuses.SpellDmg, base_value) * ratio / 100; + } else if (!spells[spell_id].no_heal_damage_item_mod && itembonuses.SpellDmg && spells[spell_id].classes[(GetClass() % 17) - 1] >= GetLevel() - 5) { @@ -140,7 +141,7 @@ int64 Mob::GetActSpellDamage(uint16 spell_id, int64 value, Mob* target) { entity_list.FilteredMessageCloseString( this, true, 100, Chat::SpellCrit, FilterSpellCrits, - OTHER_CRIT_BLAST, 0, GetName(), itoa(-value)); + OTHER_CRIT_BLAST, nullptr, GetName(), itoa(-value)); if (IsClient()) MessageString(Chat::SpellCrit, YOU_CRIT_BLAST, itoa(-value)); @@ -175,8 +176,13 @@ int64 Mob::GetActSpellDamage(uint16 spell_id, int64 value, Mob* target) { if (RuleB(Spells, IgnoreSpellDmgLvlRestriction) && !spells[spell_id].no_heal_damage_item_mod && itembonuses.SpellDmg) value -= GetExtraSpellAmt(spell_id, itembonuses.SpellDmg, base_value); - else if (!spells[spell_id].no_heal_damage_item_mod && itembonuses.SpellDmg && spells[spell_id].classes[(GetClass() % 17) - 1] >= GetLevel() - 5) - value -= GetExtraSpellAmt(spell_id, itembonuses.SpellDmg, base_value); + else if ( + !spells[spell_id].no_heal_damage_item_mod && + GetSpellDmg() && + spells[spell_id].classes[(GetClass() % 17) - 1] >= GetLevel() - 5 + ) { + value -= GetExtraSpellAmt(spell_id, GetSpellDmg(), base_value); + } return value; } @@ -260,11 +266,19 @@ int64 Mob::GetActDoTDamage(uint16 spell_id, int64 value, Mob* target, bool from_ GetFocusEffect(focusFcAmplifyAmt, spell_id, nullptr, from_buff_tic); if (RuleB(Spells, DOTsScaleWithSpellDmg)) { - if (RuleB(Spells, IgnoreSpellDmgLvlRestriction) && !spells[spell_id].no_heal_damage_item_mod && itembonuses.SpellDmg) { - extra_dmg += GetExtraSpellAmt(spell_id, itembonuses.SpellDmg, base_value)*ratio/100; + if ( + RuleB(Spells, IgnoreSpellDmgLvlRestriction) && + !spells[spell_id].no_heal_damage_item_mod && + GetSpellDmg() + ) { + extra_dmg += GetExtraSpellAmt(spell_id, GetSpellDmg(), base_value)*ratio/100; } - else if(!spells[spell_id].no_heal_damage_item_mod && itembonuses.SpellDmg && spells[spell_id].classes[(GetClass() % 17) - 1] >= GetLevel() - 5) { - extra_dmg += GetExtraSpellAmt(spell_id, itembonuses.SpellDmg, base_value)*ratio/100; + else if ( + !spells[spell_id].no_heal_damage_item_mod && + GetSpellDmg() && + spells[spell_id].classes[(GetClass() % 17) - 1] >= GetLevel() - 5 + ) { + extra_dmg += GetExtraSpellAmt(spell_id, GetSpellDmg(), base_value)*ratio/100; } } @@ -295,11 +309,19 @@ int64 Mob::GetActDoTDamage(uint16 spell_id, int64 value, Mob* target, bool from_ GetFocusEffect(focusFcAmplifyAmt, spell_id, nullptr, from_buff_tic); if (RuleB(Spells, DOTsScaleWithSpellDmg)) { - if (RuleB(Spells, IgnoreSpellDmgLvlRestriction) && !spells[spell_id].no_heal_damage_item_mod && itembonuses.SpellDmg) { - extra_dmg += GetExtraSpellAmt(spell_id, itembonuses.SpellDmg, base_value); + if ( + RuleB(Spells, IgnoreSpellDmgLvlRestriction) && + !spells[spell_id].no_heal_damage_item_mod && + GetSpellDmg() + ) { + extra_dmg += GetExtraSpellAmt(spell_id, GetSpellDmg(), base_value); } - else if(!spells[spell_id].no_heal_damage_item_mod && itembonuses.SpellDmg && spells[spell_id].classes[(GetClass() % 17) - 1] >= GetLevel() - 5) { - extra_dmg += GetExtraSpellAmt(spell_id, itembonuses.SpellDmg, base_value); + else if ( + !spells[spell_id].no_heal_damage_item_mod && + GetSpellDmg() && + spells[spell_id].classes[(GetClass() % 17) - 1] >= GetLevel() - 5 + ) { + extra_dmg += GetExtraSpellAmt(spell_id, GetSpellDmg(), base_value); } } @@ -321,6 +343,7 @@ int64 Mob::GetActDoTDamage(uint16 spell_id, int64 value, Mob* target, bool from_ int64 Mob::GetExtraSpellAmt(uint16 spell_id, int64 extra_spell_amt, int64 base_spell_dmg) { + if (RuleB(Spells, FlatItemExtraSpellAmt)) { if (RuleB(Spells, ItemExtraSpellAmtCalcAsPercent)) { return std::abs(base_spell_dmg) * extra_spell_amt / 100; @@ -425,11 +448,19 @@ int64 Mob::GetActSpellHealing(uint16 spell_id, int64 value, Mob* target, bool fr value += GetFocusEffect(focusFcHealAmtCrit, spell_id); //SPA 396 Add before critical //Using IgnoreSpellDmgLvlRestriction to also allow healing to scale - if (RuleB(Spells, IgnoreSpellDmgLvlRestriction) && !spells[spell_id].no_heal_damage_item_mod && itembonuses.HealAmt) { - value += GetExtraSpellAmt(spell_id, itembonuses.HealAmt, base_value);//Item Heal Amt Add before critical + if ( + RuleB(Spells, IgnoreSpellDmgLvlRestriction) && + !spells[spell_id].no_heal_damage_item_mod && + GetHealAmt() + ) { + value += GetExtraSpellAmt(spell_id, GetHealAmt(), base_value); //Item Heal Amt Add before critical } - else if (!spells[spell_id].no_heal_damage_item_mod && itembonuses.HealAmt && spells[spell_id].classes[(GetClass() % 17) - 1] >= GetLevel() - 5) { - value += GetExtraSpellAmt(spell_id, itembonuses.HealAmt, base_value);//Item Heal Amt Add before critical + else if ( + !spells[spell_id].no_heal_damage_item_mod && + GetHealAmt() && + spells[spell_id].classes[(GetClass() % 17) - 1] >= GetLevel() - 5 + ) { + value += GetExtraSpellAmt(spell_id, GetHealAmt(), base_value); //Item Heal Amt Add before critical } if (target) { @@ -471,11 +502,19 @@ int64 Mob::GetActSpellHealing(uint16 spell_id, int64 value, Mob* target, bool fr } if (RuleB(Spells, HOTsScaleWithHealAmt)) { - if (RuleB(Spells, IgnoreSpellDmgLvlRestriction) && !spells[spell_id].no_heal_damage_item_mod && itembonuses.HealAmt) { - extra_heal += GetExtraSpellAmt(spell_id, itembonuses.HealAmt, base_value); + if ( + RuleB(Spells, IgnoreSpellDmgLvlRestriction) && + !spells[spell_id].no_heal_damage_item_mod && + GetHealAmt() + ) { + extra_heal += GetExtraSpellAmt(spell_id, GetHealAmt(), base_value); } - else if(!spells[spell_id].no_heal_damage_item_mod && itembonuses.HealAmt && spells[spell_id].classes[(GetClass() % 17) - 1] >= GetLevel() - 5) { - extra_heal += GetExtraSpellAmt(spell_id, itembonuses.HealAmt, base_value); + else if ( + !spells[spell_id].no_heal_damage_item_mod && + GetHealAmt() && + spells[spell_id].classes[(GetClass() % 17) - 1] >= GetLevel() - 5 + ) { + extra_heal += GetExtraSpellAmt(spell_id, GetHealAmt(), base_value); } } diff --git a/zone/lua_bot.cpp b/zone/lua_bot.cpp index 7b63e3844..a5955d207 100644 --- a/zone/lua_bot.cpp +++ b/zone/lua_bot.cpp @@ -112,12 +112,12 @@ void Lua_Bot::SetExpansionBitmask(int expansion_bitmask, bool save) { bool Lua_Bot::ReloadBotDataBuckets() { Lua_Safe_Call_Bool(); - return self->GetBotDataBuckets(); + return DataBucket::GetDataBuckets(self); } bool Lua_Bot::ReloadBotOwnerDataBuckets() { Lua_Safe_Call_Bool(); - return self->GetBotOwnerDataBuckets(); + return self->HasOwner() && DataBucket::GetDataBuckets(self->GetBotOwner()); } bool Lua_Bot::ReloadBotSpells() { diff --git a/zone/lua_client.cpp b/zone/lua_client.cpp index 0ae70ee66..1540bceb4 100644 --- a/zone/lua_client.cpp +++ b/zone/lua_client.cpp @@ -3046,6 +3046,11 @@ bool Lua_Client::IsAutoFireEnabled() return self->AutoFireEnabled(); } +bool Lua_Client::ReloadDataBuckets() { + Lua_Safe_Call_Bool(); + return DataBucket::GetDataBuckets(self); +} + luabind::scope lua_register_client() { return luabind::class_("Client") .def(luabind::constructor<>()) @@ -3394,6 +3399,7 @@ luabind::scope lua_register_client() { .def("QueuePacket", (void(Lua_Client::*)(Lua_Packet,bool,int,int))&Lua_Client::QueuePacket) .def("ReadBookByName", (void(Lua_Client::*)(std::string,uint8))&Lua_Client::ReadBookByName) .def("RefundAA", (void(Lua_Client::*)(void))&Lua_Client::RefundAA) + .def("ReloadDataBuckets", (bool(Lua_Client::*)(void))&Lua_Client::ReloadDataBuckets) .def("RemoveAllExpeditionLockouts", (void(Lua_Client::*)(std::string))&Lua_Client::RemoveAllExpeditionLockouts) .def("RemoveAllExpeditionLockouts", (void(Lua_Client::*)(void))&Lua_Client::RemoveAllExpeditionLockouts) .def("RemoveExpeditionLockout", (void(Lua_Client::*)(std::string, std::string))&Lua_Client::RemoveExpeditionLockout) diff --git a/zone/lua_client.h b/zone/lua_client.h index a5d0c8409..0d5c58bad 100644 --- a/zone/lua_client.h +++ b/zone/lua_client.h @@ -534,6 +534,8 @@ public: void DialogueWindow(std::string markdown); + bool ReloadDataBuckets(); + Lua_Expedition CreateExpedition(luabind::object expedition_info); Lua_Expedition CreateExpedition(std::string zone_name, uint32 version, uint32 duration, std::string expedition_name, uint32 min_players, uint32 max_players); Lua_Expedition CreateExpedition(std::string zone_name, uint32 version, uint32 duration, std::string expedition_name, uint32 min_players, uint32 max_players, bool disable_messages); diff --git a/zone/merc.h b/zone/merc.h index 42fba3098..57ffe3438 100644 --- a/zone/merc.h +++ b/zone/merc.h @@ -226,8 +226,8 @@ public: inline virtual int32 GetCombatEffects() const { return itembonuses.ProcChance; } inline virtual int32 GetDS() const { return itembonuses.DamageShield; } // Mod3 - inline virtual int32 GetHealAmt() const { return itembonuses.HealAmt; } - inline virtual int32 GetSpellDmg() const { return itembonuses.SpellDmg; } + inline int32 GetHealAmt() const override { return itembonuses.HealAmt; } + inline int32 GetSpellDmg() const override { return itembonuses.SpellDmg; } inline virtual int32 GetClair() const { return itembonuses.Clairvoyance; } inline virtual int32 GetDSMit() const { return itembonuses.DSMitigation; } diff --git a/zone/mob.cpp b/zone/mob.cpp index 2c20fc05e..17f680a8a 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -7115,4 +7115,4 @@ int Mob::DispatchZoneControllerEvent( } return ret; -} +} \ No newline at end of file diff --git a/zone/mob.h b/zone/mob.h index efe069058..bbd4ce944 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -20,6 +20,7 @@ #define MOB_H #include "common.h" +#include "data_bucket.h" #include "entity.h" #include "hate_list.h" #include "pathfinder_interface.h" @@ -618,10 +619,13 @@ public: inline int64 GetHP() const { return current_hp; } inline int64 GetMaxHP() const { return max_hp; } virtual int64 CalcMaxHP(); + virtual int64 CalcHPRegenCap() { return 0; } inline int64 GetMaxMana() const { return max_mana; } + virtual int64 CalcManaRegenCap() { return 0; } inline int64 GetMana() const { return current_mana; } virtual int64 GetEndurance() const { return 0; } virtual int64 GetMaxEndurance() const { return 0; } + virtual int64 CalcEnduranceRegenCap() { return 0; } virtual void SetEndurance(int32 newEnd) { return; } int64 GetItemHPBonuses(); int64 GetSpellHPBonuses(); @@ -654,7 +658,10 @@ public: inline int32 GetHeroicStrikethrough() const { return heroic_strikethrough; } inline const bool GetKeepsSoldItems() const { return keeps_sold_items; } inline void SetKeepsSoldItems(bool in_keeps_sold_items) { keeps_sold_items = in_keeps_sold_items; } - + virtual int32 GetHealAmt() const { return 0; } + virtual int32 GetSpellDmg() const { return 0; } + void ProcessItemCaps(); + virtual int32 CalcItemATKCap() { return 0; } virtual bool IsSitting() const { return false; } int CalcRecommendedLevelBonus(uint8 level, uint8 reclevel, int basestat); @@ -1390,6 +1397,9 @@ public: /// this cures timing issues cuz dead animation isn't done but server side feigning is? inline bool GetFeigned() const { return(feigned); } + std::vector 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); @@ -1397,6 +1407,9 @@ public: std::string GetBucketRemaining(std::string bucket_name); void SetBucket(std::string bucket_name, std::string bucket_value, std::string expiration = ""); + // Heroic Stat Benefits + float CheckHeroicBonusesDataBuckets(std::string bucket_name); + int DispatchZoneControllerEvent(QuestEventID evt, Mob* init, const std::string& data, uint32 extra, std::vector* pointers); // Bots HealRotation methods @@ -1418,6 +1431,8 @@ public: void DrawDebugCoordinateNode(std::string node_name, const glm::vec4 vec); + void CalcHeroicBonuses(StatBonuses* newbon); + 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); @@ -1701,6 +1716,7 @@ protected: bool is_boat; CombatRecord m_combat_record{}; + public: const CombatRecord &GetCombatRecord() const; @@ -1833,6 +1849,13 @@ private: std::shared_ptr m_target_of_heal_rotation; bool m_manual_follow; + void SetHeroicStrBonuses(StatBonuses* n); + void SetHeroicStaBonuses(StatBonuses* n); + void SetHeroicAgiBonuses(StatBonuses* n); + void SetHeroicDexBonuses(StatBonuses* n); + void SetHeroicIntBonuses(StatBonuses* n); + void SetHeroicWisBonuses(StatBonuses* n); + void DoSpellInterrupt(uint16 spell_id, int32 mana_cost, int my_curmana); }; diff --git a/zone/perl_bot.cpp b/zone/perl_bot.cpp index 99f37b3ef..f8e0e956e 100644 --- a/zone/perl_bot.cpp +++ b/zone/perl_bot.cpp @@ -337,12 +337,12 @@ void Perl_Bot_SetSpellDurationGroup(Bot* self, int spell_id, int duration, bool bool Perl_Bot_ReloadBotDataBuckets(Bot* self) { - return self->GetBotDataBuckets(); + return DataBucket::GetDataBuckets(self); } bool Perl_Bot_ReloadBotOwnerDataBuckets(Bot* self) { - return self->GetBotOwnerDataBuckets(); + return self->HasOwner() && DataBucket::GetDataBuckets(self->GetBotOwner()); } bool Perl_Bot_ReloadBotSpells(Bot* self) diff --git a/zone/perl_client.cpp b/zone/perl_client.cpp index 2f2ccaa46..16c57f321 100644 --- a/zone/perl_client.cpp +++ b/zone/perl_client.cpp @@ -2902,6 +2902,11 @@ bool Perl_Client_IsAutoFireEnabled(Client* self) return self->AutoFireEnabled(); } +bool Perl_Client_ReloadDataBuckets(Client* self) +{ + return DataBucket::GetDataBuckets(self); +} + void perl_register_client() { perl::interpreter perl(PERL_GET_THX); @@ -3248,6 +3253,7 @@ void perl_register_client() package.add("ReadBook", &Perl_Client_ReadBook); package.add("ReadBookByName", &Perl_Client_ReadBookByName); package.add("RefundAA", &Perl_Client_RefundAA); + package.add("ReloadDataBuckets", &Perl_Client_ReloadDataBuckets); package.add("RemoveAllExpeditionLockouts", (void(*)(Client*))&Perl_Client_RemoveAllExpeditionLockouts); package.add("RemoveAllExpeditionLockouts", (void(*)(Client*, std::string))&Perl_Client_RemoveAllExpeditionLockouts); package.add("RemoveExpeditionLockout", &Perl_Client_RemoveExpeditionLockout); diff --git a/zone/tune.cpp b/zone/tune.cpp index beb428f10..ad427146e 100644 --- a/zone/tune.cpp +++ b/zone/tune.cpp @@ -1049,7 +1049,7 @@ int64 Mob::TuneACSum(bool skip_caps, int ac_override, int add_ac) shield_ac = CalcRecommendedLevelBonus(GetLevel(), inst->GetItemRecommendedLevel(true),inst->GetItemArmorClass(true)); } } - shield_ac += GetHeroicSTR() * RuleR(Character, HeroicStrengthMultiplier) / 10; + shield_ac += itembonuses.heroic_str_shield_ac; } // EQ math ac = (ac * 4) / 3; @@ -1353,7 +1353,7 @@ int64 Mob::Tunecompute_defense(int avoidance_override, int add_avoidance) defense = avoidance_override; } else { - defense += GetHeroicAGI() * RuleR(Character, HeroicAgilityMultiplier) / 10; + defense += itembonuses.heroic_agi_avoidance; } defense += add_avoidance; //1 pt = 10 heroic agi } @@ -1495,10 +1495,10 @@ void Mob::TuneCommonOutgoingHitSuccess(Mob* defender, DamageHitInfo &hit, ExtraA switch (hit.skill) { case EQ::skills::SkillThrowing: case EQ::skills::SkillArchery: - extra = GetHeroicDEX() * RuleR(Character, HeroicDexterityMultiplier) / 10; + extra = itembonuses.heroic_dex_ranged_damage; break; default: - extra = GetHeroicSTR() * RuleR(Character, HeroicStrengthMultiplier) / 10; + extra = itembonuses.heroic_str_melee_damage; break; } hit.damage_done += extra; diff --git a/zone/zone.cpp b/zone/zone.cpp index 287856f82..3ddcc1894 100644 --- a/zone/zone.cpp +++ b/zone/zone.cpp @@ -2986,7 +2986,7 @@ std::string Zone::GetAAName(int aa_id) return std::string(); } -bool Zone::CheckDataBucket(uint8 bucket_comparison, std::string bucket_value, std::string player_value) +bool Zone::CompareDataBucket(uint8 bucket_comparison, const std::string& bucket_value, const std::string& player_value) { std::vector bucket_checks; bool found = false; diff --git a/zone/zone.h b/zone/zone.h index aa95a0e6c..6a6906684 100755 --- a/zone/zone.h +++ b/zone/zone.h @@ -311,7 +311,7 @@ public: bool IsQuestHotReloadQueued() const; void SetQuestHotReloadQueued(bool in_quest_hot_reload_queued); - bool CheckDataBucket(uint8 bucket_comparison, std::string bucket_value, std::string player_value); + bool CompareDataBucket(uint8 bucket_comparison, const std::string& bucket_value, const std::string& player_value); WaterMap *watermap; ZonePoint *GetClosestZonePoint(const glm::vec3 &location, uint32 to, Client *client, float max_distance = 40000.0f);