[Feature] Add Bot/Client Specific Stat Caps

This commit is contained in:
Kinglykrab
2025-08-20 22:30:54 -04:00
parent 1eb89edbbd
commit aba58172f4
26 changed files with 1470 additions and 78 deletions
+1 -1
View File
@@ -6701,7 +6701,7 @@ void Client::SetAttackTimer()
if (ItemToUse && ItemToUse->ItemType == EQ::item::ItemTypeBow) {
// Live actually had a bug here where they would return the non-modified attack speed
// rather than the cap ...
speed = std::max(speed - GetQuiverHaste(speed), RuleI(Combat, QuiverHasteCap));
speed = std::max(speed - GetQuiverHaste(speed), GetStatCap(StatCap::QuiverHaste));
}
else {
if (RuleB(Spells, Jun182014HundredHandsRevamp))
+136 -20
View File
@@ -246,12 +246,22 @@ void Mob::ProcessItemCaps()
itembonuses.ATK = std::min(itembonuses.ATK, CalcItemATKCap());
if (IsOfClientBotMerc() && itembonuses.SpellDmg > RuleI(Character, ItemSpellDmgCap)) {
itembonuses.SpellDmg = RuleI(Character, ItemSpellDmgCap);
int spell_damage_cap = (
IsOfClientBot() ?
GetStatCap(StatCap::SpellDamage) :
RuleI(Character, ItemSpellDmgCap)
);
if (IsOfClientBotMerc() && itembonuses.SpellDmg > spell_damage_cap) {
itembonuses.SpellDmg = spell_damage_cap;
}
if (IsOfClientBotMerc() && itembonuses.HealAmt > RuleI(Character, ItemHealAmtCap)) {
itembonuses.HealAmt = RuleI(Character, ItemHealAmtCap);
int heal_amount_cap = (
IsOfClientBot() ?
GetStatCap(StatCap::HealAmount) :
RuleI(Character, ItemHealAmtCap)
);
if (IsOfClientBotMerc() && itembonuses.HealAmt > heal_amount_cap) {
itembonuses.HealAmt = heal_amount_cap;
}
}
@@ -351,33 +361,139 @@ void Mob::AddItemBonuses(const EQ::ItemInstance* inst, StatBonuses* b, bool is_a
b->ManaRegen += CalcItemBonus(item->ManaRegen);
b->EnduranceRegen += CalcItemBonus(item->EnduranceRegen);
int accuracy_cap = (
IsOfClientBot() ?
GetStatCap(StatCap::Accuracy) :
RuleI(Character, ItemAccuracyCap)
);
int attack_cap = (
IsOfClientBot() ?
GetStatCap(StatCap::Attack) :
RuleI(Character, ItemATKCap)
);
int avoidance_cap = (
IsOfClientBot() ?
GetStatCap(StatCap::Avoidance) :
RuleI(Character, ItemAvoidanceCap)
);
int clairvoyance_cap = (
IsOfClientBot() ?
GetStatCap(StatCap::Clairvoyance) :
RuleI(Character, ItemClairvoyanceCap)
);
int combat_effects_cap = (
IsOfClientBot() ?
GetStatCap(StatCap::CombatEffects) :
RuleI(Character, ItemCombatEffectsCap)
);
int damage_shield_cap = (
IsOfClientBot() ?
GetStatCap(StatCap::DamageShield) :
RuleI(Character, ItemDamageShieldCap)
);
int dot_shielding_cap = (
IsOfClientBot() ?
GetStatCap(StatCap::DOTShielding) :
RuleI(Character, ItemDoTShieldingCap)
);
int dsmitigation_cap = (
IsOfClientBot() ?
GetStatCap(StatCap::DSMitigation) :
RuleI(Character, ItemDSMitigationCap)
);
int heal_amount_cap = (
IsOfClientBot() ?
GetStatCap(StatCap::HealAmount) :
RuleI(Character, ItemHealAmtCap)
);
int shielding_cap = (
IsOfClientBot() ?
GetStatCap(StatCap::Shielding) :
RuleI(Character, ItemShieldingCap)
);
int spell_damage_cap = (
IsOfClientBot() ?
GetStatCap(StatCap::SpellDamage) :
RuleI(Character, ItemSpellDmgCap)
);
int spell_shielding_cap = (
IsOfClientBot() ?
GetStatCap(StatCap::SpellShielding) :
RuleI(Character, ItemSpellShieldingCap)
);
int strikethrough_cap = (
IsOfClientBot() ?
GetStatCap(StatCap::Strikethrough) :
RuleI(Character, ItemStrikethroughCap)
);
int stun_resist_cap = (
IsOfClientBot() ?
GetStatCap(StatCap::StunResist) :
RuleI(Character, ItemStunResistCap)
);
// These have rule-configured caps.
b->ATK = CalcCappedItemBonus(b->ATK, item->Attack, RuleI(Character, ItemATKCap) + itembonuses.ItemATKCap + spellbonuses.ItemATKCap + aabonuses.ItemATKCap);
b->DamageShield = CalcCappedItemBonus(b->DamageShield, item->DamageShield, RuleI(Character, ItemDamageShieldCap));
b->SpellShield = CalcCappedItemBonus(b->SpellShield, item->SpellShield, RuleI(Character, ItemSpellShieldingCap));
b->MeleeMitigation = CalcCappedItemBonus(b->MeleeMitigation, item->Shielding, RuleI(Character, ItemShieldingCap));
b->StunResist = CalcCappedItemBonus(b->StunResist, item->StunResist, RuleI(Character, ItemStunResistCap));
b->StrikeThrough = CalcCappedItemBonus(b->StrikeThrough, item->StrikeThrough, RuleI(Character, ItemStrikethroughCap));
b->AvoidMeleeChance = CalcCappedItemBonus(b->AvoidMeleeChance, item->Avoidance, RuleI(Character, ItemAvoidanceCap));
b->HitChance = CalcCappedItemBonus(b->HitChance, item->Accuracy, RuleI(Character, ItemAccuracyCap));
b->ProcChance = CalcCappedItemBonus(b->ProcChance, item->CombatEffects, RuleI(Character, ItemCombatEffectsCap));
b->DoTShielding = CalcCappedItemBonus(b->DoTShielding, item->DotShielding, RuleI(Character, ItemDoTShieldingCap));
b->HealAmt = CalcCappedItemBonus(b->HealAmt, item->HealAmt, RuleI(Character, ItemHealAmtCap));
b->SpellDmg = CalcCappedItemBonus(b->SpellDmg, item->SpellDmg, RuleI(Character, ItemSpellDmgCap));
b->Clairvoyance = CalcCappedItemBonus(b->Clairvoyance, item->Clairvoyance, RuleI(Character, ItemClairvoyanceCap));
b->DSMitigation = CalcCappedItemBonus(b->DSMitigation, item->DSMitigation, RuleI(Character, ItemDSMitigationCap));
b->ATK = CalcCappedItemBonus(
b->ATK,
item->Attack,
(
attack_cap +
itembonuses.ItemATKCap +
spellbonuses.ItemATKCap +
aabonuses.ItemATKCap
)
);
b->DamageShield = CalcCappedItemBonus(b->DamageShield, item->DamageShield, damage_shield_cap);
b->SpellShield = CalcCappedItemBonus(b->SpellShield, item->SpellShield, spell_shielding_cap);
b->MeleeMitigation = CalcCappedItemBonus(b->MeleeMitigation, item->Shielding, shielding_cap);
b->StunResist = CalcCappedItemBonus(b->StunResist, item->StunResist, stun_resist_cap);
b->StrikeThrough = CalcCappedItemBonus(b->StrikeThrough, item->StrikeThrough, strikethrough_cap);
b->AvoidMeleeChance = CalcCappedItemBonus(b->AvoidMeleeChance, item->Avoidance, avoidance_cap);
b->HitChance = CalcCappedItemBonus(b->HitChance, item->Accuracy, accuracy_cap);
b->ProcChance = CalcCappedItemBonus(b->ProcChance, item->CombatEffects, combat_effects_cap);
b->DoTShielding = CalcCappedItemBonus(b->DoTShielding, item->DotShielding, dot_shielding_cap);
b->HealAmt = CalcCappedItemBonus(b->HealAmt, item->HealAmt, heal_amount_cap);
b->SpellDmg = CalcCappedItemBonus(b->SpellDmg, item->SpellDmg, spell_damage_cap);
b->Clairvoyance = CalcCappedItemBonus(b->Clairvoyance, item->Clairvoyance, clairvoyance_cap);
b->DSMitigation = CalcCappedItemBonus(b->DSMitigation, item->DSMitigation, dsmitigation_cap);
if (b->haste < item->Haste) {
b->haste = item->Haste;
}
if (item->ExtraDmgAmt != 0 && item->ExtraDmgSkill <= EQ::skills::HIGHEST_SKILL) {
int extra_damage_cap = (
IsOfClientBot() ?
GetStatCap(StatCap::ExtraDamage) :
RuleI(Character, ItemExtraDmgCap)
);
if (item->ExtraDmgSkill == ALL_SKILLS) {
for (const auto &skill_id: EQ::skills::GetExtraDamageSkills()) {
b->SkillDamageAmount[skill_id] = CalcCappedItemBonus(b->SkillDamageAmount[skill_id], item->ExtraDmgAmt, RuleI(Character, ItemExtraDmgCap));
b->SkillDamageAmount[skill_id] = CalcCappedItemBonus(
b->SkillDamageAmount[skill_id],
item->ExtraDmgAmt,
extra_damage_cap
);
}
} else {
b->SkillDamageAmount[item->ExtraDmgSkill] = CalcCappedItemBonus(b->SkillDamageAmount[item->ExtraDmgSkill], item->ExtraDmgAmt, RuleI(Character, ItemExtraDmgCap));
b->SkillDamageAmount[item->ExtraDmgSkill] = CalcCappedItemBonus(
b->SkillDamageAmount[item->ExtraDmgSkill],
item->ExtraDmgAmt,
extra_damage_cap
);
}
}
+3
View File
@@ -268,6 +268,8 @@ Bot::Bot(
LoadDefaultBotSettings();
database.botdb.LoadBotSettings(this);
database.LoadStatCaps(this);
if (RuleB(Bots, AllowBotBlockedBuffs)) {
bot_blocked_buffs.clear();
database.botdb.LoadBotBlockedBuffs(this);
@@ -1420,6 +1422,7 @@ bool Bot::Save()
database.botdb.SaveTimers(this);
database.botdb.SaveStance(this);
database.botdb.SaveBotSettings(this);
database.SaveStatCaps(this);
if (RuleB(Bots, AllowBotBlockedBuffs)) {
database.botdb.SaveBotBlockedBuffs(this);
+6 -7
View File
@@ -101,7 +101,7 @@ namespace BotSettingCategories {
constexpr uint8 SpellTypeMinManaPct = 7;
constexpr uint8 SpellTypeMaxManaPct = 8;
constexpr uint8 SpellTypeMinHPPct = 9;
constexpr uint8 SpellTypeMaxHPPct = 10;
constexpr uint8 SpellTypeMaxHPPct = 10;
constexpr uint8 SpellTypeIdlePriority = 11;
constexpr uint8 SpellTypeEngagedPriority = 12;
constexpr uint8 SpellTypePursuePriority = 13;
@@ -166,7 +166,7 @@ namespace BotBaseSettings {
constexpr uint16 ExpansionBitmask = 0;
constexpr uint16 ShowHelm = 1;
constexpr uint16 FollowDistance = 2;
constexpr uint16 StopMeleeLevel = 3;
constexpr uint16 StopMeleeLevel = 3;
constexpr uint16 EnforceSpellSettings = 4;
constexpr uint16 RangedSetting = 5;
constexpr uint16 PetSetTypeSetting = 6;
@@ -487,7 +487,7 @@ public:
void SetCommandedSpell(bool value) { _commandedSpell = value; }
bool IsPullingSpell() const { return _pullingSpell; }
void SetPullingSpell(bool value) { _pullingSpell = value; }
void SetGuardMode();
void SetHoldMode();
@@ -550,7 +550,7 @@ public:
bool IsValidMezTarget(Mob* owner, Mob* npc, uint16 spell_id);
// Cast checks
bool PrecastChecks(Mob* tar, uint16 spell_type);
bool PrecastChecks(Mob* tar, uint16 spell_type);
bool CastChecks(uint16 spell_id, Mob* tar, uint16 spell_type, bool prechecks = false, bool ae_check = false);
bool IsImmuneToBotSpell(uint16 spell_id, Mob* caster);
bool CanCastSpellType(uint16 spell_type, uint16 spell_id, Mob* tar);
@@ -601,7 +601,7 @@ public:
void ResetBotSpellSettings();
void CopyBotBlockedBuffs(Bot* to);
void CopyBotBlockedPetBuffs(Bot* to);
void CopyBotBlockedPetBuffs(Bot* to);
void CleanBotBlockedBuffs();
void ClearBotBlockedBuffs() { bot_blocked_buffs.clear(); }
bool IsBlockedBuff(int32 spell_id) override;
@@ -658,7 +658,7 @@ public:
bool GetBehindMob() const { return _behindMobStatus; }
void SetBehindMob(bool value) { _behindMobStatus = value; }
bool GetMaxMeleeRange() const { return _maxMeleeRangeStatus; }
void SetMaxMeleeRange(bool value) { _maxMeleeRangeStatus = value; }
void SetMaxMeleeRange(bool value) { _maxMeleeRangeStatus = value; }
uint8 GetStopMeleeLevel() const { return _stopMeleeLevel; }
void SetStopMeleeLevel(uint8 level) { _stopMeleeLevel = level; }
uint32 GetBotDistanceRanged() const { return _distanceRanged; }
@@ -1210,7 +1210,6 @@ private:
bool _hasLoS;
bool _commandedSpell;
bool _pullingSpell;
bool _illusionBlock;
std::vector<BotSpellSettings> m_bot_spell_settings;
std::vector<Mob*> _spell_target_list;
+2
View File
@@ -1103,6 +1103,8 @@ bool Client::Save(uint8 iCommitNow) {
database.SaveCharacterEXPModifier(this);
database.SaveStatCaps(this);
if (RuleB(Bots, Enabled)) {
database.botdb.SaveBotSettings(this);
}
+17 -16
View File
@@ -32,23 +32,24 @@
int32 Client::GetMaxStat() const
{
if ((RuleI(Character, StatCap)) > 0) {
return (RuleI(Character, StatCap));
int character_cap = GetStatCap(StatCap::Stat);
if (character_cap > 0) {
return character_cap;
}
int level = GetLevel();
int32 base = 0;
uint8 level = GetLevel();
int base = 0;
if (level < 61) {
base = 255;
}
else if (ClientVersion() >= EQ::versions::ClientVersion::SoF) {
} else if (ClientVersion() >= EQ::versions::ClientVersion::SoF) {
base = 255 + 5 * (level - 60);
}
else if (level < 71) {
} else if (level < 71) {
base = 255 + 5 * (level - 60);
}
else {
} else {
base = 330;
}
return base;
}
@@ -300,7 +301,7 @@ int64 Client::CalcHPRegen(bool bCombat)
int64 Client::CalcHPRegenCap()
{
int64 cap = RuleI(Character, ItemHealthRegenCap);
int64 cap = GetStatCap(StatCap::HealthRegen);
if (GetLevel() > 60) {
cap = std::max(cap, static_cast<int64>(GetLevel() - 30)); // if the rule is set greater than normal I guess
}
@@ -717,7 +718,7 @@ int64 Client::CalcManaRegen(bool bCombat)
int64 Client::CalcManaRegenCap()
{
int64 cap = RuleI(Character, ItemManaRegenCap) + aabonuses.ItemManaRegenCap + itembonuses.ItemManaRegenCap + spellbonuses.ItemManaRegenCap;
int64 cap = GetStatCap(StatCap::ManaRegen) + aabonuses.ItemManaRegenCap + itembonuses.ItemManaRegenCap + spellbonuses.ItemManaRegenCap;
return (cap * RuleI(Character, ManaRegenMultiplier) / 100);
}
@@ -976,7 +977,7 @@ int Client::CalcHaste()
}
// 60+ 100, 51-59 85, 1-50 level+25
if (level > 59 || RuleB(Character, IgnoreLevelBasedHasteCaps)) { // 60+
cap = RuleI(Character, HasteCap);
cap = GetStatCap(StatCap::Haste);
}
else if (level > 50) { // 51-59
cap = 85;
@@ -989,7 +990,7 @@ int Client::CalcHaste()
}
// 51+ 25 (despite there being higher spells...), 1-50 10
if (level > 50 || RuleB(Character, IgnoreLevelBasedHasteCaps)) { // 51+
cap = RuleI(Character, Hastev3Cap);
cap = GetStatCap(StatCap::HasteV3);
if (spellbonuses.hastetype3 > cap) {
h += cap;
} else {
@@ -1745,13 +1746,13 @@ int64 Client::CalcEnduranceRegen(bool bCombat)
int64 Client::CalcEnduranceRegenCap()
{
int64 cap = RuleI(Character, ItemEnduranceRegenCap) + aabonuses.ItemEnduranceRegenCap + itembonuses.ItemEnduranceRegenCap + spellbonuses.ItemEnduranceRegenCap;
int64 cap = GetStatCap(StatCap::EnduranceRegen) + aabonuses.ItemEnduranceRegenCap + itembonuses.ItemEnduranceRegenCap + spellbonuses.ItemEnduranceRegenCap;
return (cap * RuleI(Character, EnduranceRegenMultiplier) / 100);
}
int32 Client::CalcItemATKCap()
{
int cap = RuleI(Character, ItemATKCap) + itembonuses.ItemATKCap + spellbonuses.ItemATKCap + aabonuses.ItemATKCap;
int cap = GetStatCap(StatCap::Attack) + itembonuses.ItemATKCap + spellbonuses.ItemATKCap + aabonuses.ItemATKCap;
return cap;
}
+1
View File
@@ -1386,6 +1386,7 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app)
database.LoadCharacterTribute(this); /* Load CharacterTribute */
database.LoadCharacterEXPModifier(this); /* Load Character EXP Modifier */
database.LoadCharacterTitleSets(this); /* Load Character Title Sets */
database.LoadStatCaps(this); /* Load Character Stat Caps */
// this pattern is strange
// this is remnants of the old way of doing things
+14
View File
@@ -664,6 +664,18 @@ void Lua_Bot::RaidGroupSay(const char* message) {
self->RaidGroupSay(message);
}
int Lua_Bot::GetStatCap(uint8 stat_id)
{
Lua_Safe_Call_Int();
return self->GetStatCap(stat_id);
}
void Lua_Bot::SetStatCap(uint8 stat_id, int stat_cap)
{
Lua_Safe_Call_Void();
self->SetStatCap(stat_id, stat_cap, true);
}
luabind::scope lua_register_bot() {
return luabind::class_<Lua_Bot, Lua_Mob>("Bot")
.def(luabind::constructor<>())
@@ -747,6 +759,7 @@ luabind::scope lua_register_bot() {
.def("GetSpellDamage", (int(Lua_Bot::*)(void))&Lua_Bot::GetSpellDamage)
.def("GetSpellRecastTimer", (uint32(Lua_Bot::*)())&Lua_Bot::GetSpellRecastTimer)
.def("GetSpellRecastTimer", (uint32(Lua_Bot::*)(uint16))&Lua_Bot::GetSpellRecastTimer)
.def("GetStatCap", (int(Lua_Bot::*)(uint8))&Lua_Bot::GetStatCap)
.def("HasAugmentEquippedByID", (bool(Lua_Bot::*)(uint32))&Lua_Bot::HasAugmentEquippedByID)
.def("HasBotItem", (int16(Lua_Bot::*)(uint32))&Lua_Bot::HasBotItem)
.def("HasBotSpellEntry", (bool(Lua_Bot::*)(uint16))&Lua_Bot::HasBotSpellEntry)
@@ -783,6 +796,7 @@ luabind::scope lua_register_bot() {
.def("SetSpellDurationRaid", (void(Lua_Bot::*)(int,int,int,bool,bool))&Lua_Bot::SetSpellDurationRaid)
.def("SetSpellRecastTimer", (void(Lua_Bot::*)(uint16))&Lua_Bot::SetSpellRecastTimer)
.def("SetSpellRecastTimer", (void(Lua_Bot::*)(uint16, uint32))&Lua_Bot::SetSpellRecastTimer)
.def("SetStatCap", (void(Lua_Bot::*)(uint8,int))&Lua_Bot::SetStatCap)
.def("SendPayload", (void(Lua_Bot::*)(int))&Lua_Bot::SendPayload)
.def("SendPayload", (void(Lua_Bot::*)(int,std::string))&Lua_Bot::SendPayload)
.def("Signal", (void(Lua_Bot::*)(int))&Lua_Bot::Signal)
+2
View File
@@ -126,6 +126,8 @@ public:
void SetItemReuseTimer(uint32 item_id, uint32 reuse_timer);
void SetSpellRecastTimer(uint16 spell_id);
void SetSpellRecastTimer(uint16 spell_id, uint32 reuse_timer);
int GetStatCap(uint8 stat_id);
void SetStatCap(uint8 stat_id, int stat_cap);
uint32 CountAugmentEquippedByID(uint32 item_id);
uint32 CountItemEquippedByID(uint32 item_id);
+14
View File
@@ -3624,6 +3624,18 @@ luabind::object Lua_Client::GetKeyRing(lua_State* L)
return lua_table;
}
int Lua_Client::GetStatCap(uint8 stat_id)
{
Lua_Safe_Call_Int();
return self->GetStatCap(stat_id);
}
void Lua_Client::SetStatCap(uint8 stat_id, int stat_cap)
{
Lua_Safe_Call_Void();
self->SetStatCap(stat_id, stat_cap, true);
}
luabind::scope lua_register_client() {
return luabind::class_<Lua_Client, Lua_Mob>("Client")
.def(luabind::constructor<>())
@@ -3898,6 +3910,7 @@ luabind::scope lua_register_client() {
.def("GetSpellDamage", (int(Lua_Client::*)(void))&Lua_Client::GetSpellDamage)
.def("GetSpellIDByBookSlot", (uint32(Lua_Client::*)(int))&Lua_Client::GetSpellIDByBookSlot)
.def("GetSpentAA", (int(Lua_Client::*)(void))&Lua_Client::GetSpentAA)
.def("GetStatCap", (int(Lua_Client::*)(uint8))&Lua_Client::GetStatCap)
.def("GetStartZone", (int(Lua_Client::*)(void))&Lua_Client::GetStartZone)
.def("GetTargetRingX", (float(Lua_Client::*)(void))&Lua_Client::GetTargetRingX)
.def("GetTargetRingY", (float(Lua_Client::*)(void))&Lua_Client::GetTargetRingY)
@@ -4163,6 +4176,7 @@ luabind::scope lua_register_client() {
.def("SetStartZone", (void(Lua_Client::*)(int,float))&Lua_Client::SetStartZone)
.def("SetStartZone", (void(Lua_Client::*)(int,float,float))&Lua_Client::SetStartZone)
.def("SetStartZone", (void(Lua_Client::*)(int,float,float,float))&Lua_Client::SetStartZone)
.def("SetStatCap", (void(Lua_Client::*)(uint8,int))&Lua_Client::SetStatCap)
.def("SetStats", (void(Lua_Client::*)(int,int))&Lua_Client::SetStats)
.def("SetThirst", (void(Lua_Client::*)(int))&Lua_Client::SetThirst)
.def("SetTint", (void(Lua_Client::*)(int,uint32))&Lua_Client::SetTint)
+2
View File
@@ -607,6 +607,8 @@ public:
bool RemoveAAPoints(uint32 points);
bool RemoveAlternateCurrencyValue(uint32 currency_id, uint32 amount);
bool AreTasksCompleted(luabind::object task_ids);
int GetStatCap(uint8 stat_id);
void SetStatCap(uint8 stat_id, int stat_cap);
void DialogueWindow(std::string markdown);
+177 -20
View File
@@ -22,7 +22,9 @@
#include "../common/misc_functions.h"
#include "../common/repositories/bot_data_repository.h"
#include "../common/repositories/bot_stat_caps_repository.h"
#include "../common/repositories/character_data_repository.h"
#include "../common/repositories/character_stat_caps_repository.h"
#include "../common/data_bucket.h"
#include "quest_parser_collection.h"
@@ -2059,10 +2061,20 @@ void Mob::SendStatsWindow(Client* c, bool use_window)
for (auto mod2_row_counter = 0; mod2_row_counter < mod2_rows; mod2_row_counter++) {
switch (mod2_row_counter) {
case 0: {
int avoidance_cap = (
IsOfClientBot() ?
GetStatCap(StatCap::Avoidance) :
RuleI(Character, ItemAvoidanceCap)
);
int combat_effects_cap = (
IsOfClientBot() ?
GetStatCap(StatCap::CombatEffects) :
RuleI(Character, ItemCombatEffectsCap)
);
mod2a_name = "Avoidance";
mod2b_name = "Combat Effects";
mod2a_cap = Strings::Commify(RuleI(Character, ItemAvoidanceCap));
mod2b_cap = Strings::Commify(RuleI(Character, ItemCombatEffectsCap));
mod2a_cap = Strings::Commify(avoidance_cap);
mod2b_cap = Strings::Commify(combat_effects_cap);
if (IsBot()) {
mod2a = Strings::Commify(CastToBot()->GetAvoidance());
@@ -2079,10 +2091,20 @@ void Mob::SendStatsWindow(Client* c, bool use_window)
break;
}
case 1: {
int accuracy_cap = (
IsOfClientBot() ?
GetStatCap(StatCap::Accuracy) :
RuleI(Character, ItemAccuracyCap)
);
int strikethrough_cap = (
IsOfClientBot() ?
GetStatCap(StatCap::Strikethrough) :
RuleI(Character, ItemStrikethroughCap)
);
mod2a_name = "Accuracy";
mod2b_name = "Strikethrough";
mod2a_cap = Strings::Commify(RuleI(Character, ItemAccuracyCap));
mod2b_cap = Strings::Commify(RuleI(Character, ItemStrikethroughCap));
mod2a_cap = Strings::Commify(accuracy_cap);
mod2b_cap = Strings::Commify(strikethrough_cap);
if (IsBot()) {
mod2a = Strings::Commify(CastToBot()->GetAccuracy());
@@ -2099,10 +2121,20 @@ void Mob::SendStatsWindow(Client* c, bool use_window)
break;
}
case 2: {
int shielding_cap = (
IsOfClientBot() ?
GetStatCap(StatCap::Shielding) :
RuleI(Character, ItemShieldingCap)
);
int spell_shielding_cap = (
IsOfClientBot() ?
GetStatCap(StatCap::SpellShielding) :
RuleI(Character, ItemSpellShieldingCap)
);
mod2a_name = "Shielding";
mod2b_name = "Spell Shielding";
mod2a_cap = Strings::Commify(RuleI(Character, ItemShieldingCap));
mod2b_cap = Strings::Commify(RuleI(Character, ItemSpellShieldingCap));
mod2a_cap = Strings::Commify(shielding_cap);
mod2b_cap = Strings::Commify(spell_shielding_cap);
if (IsBot()) {
mod2a = Strings::Commify(CastToBot()->GetShielding());
@@ -2120,10 +2152,20 @@ void Mob::SendStatsWindow(Client* c, bool use_window)
break;
}
case 3: {
int dot_shielding_cap = (
IsOfClientBot() ?
GetStatCap(StatCap::DOTShielding) :
RuleI(Character, ItemDoTShieldingCap)
);
int stun_resist_cap = (
IsOfClientBot() ?
GetStatCap(StatCap::StunResist) :
RuleI(Character, ItemStunResistCap)
);
mod2a_name = "Stun Resist";
mod2b_name = "DOT Shielding";
mod2a_cap = Strings::Commify(RuleI(Character, ItemStunResistCap));
mod2b_cap = Strings::Commify(RuleI(Character, ItemDoTShieldingCap));
mod2a_cap = Strings::Commify(stun_resist_cap);
mod2b_cap = Strings::Commify(dot_shielding_cap);
if (IsBot()) {
mod2a = Strings::Commify(CastToBot()->GetStunResist());
@@ -2359,6 +2401,11 @@ void Mob::SendStatsWindow(Client* c, bool use_window)
final_string += DialogueWindow::Break(1);
int attack_cap = (
IsOfClientBot() ?
GetStatCap(StatCap::Attack) :
RuleI(Character, ItemATKCap)
);
// Attack 2
final_string += fmt::format(
"Offense: {}{}{}{}",
@@ -2368,7 +2415,7 @@ void Mob::SendStatsWindow(Client* c, bool use_window)
fmt::format(
" | Item: {} / {} | Used: {}",
Strings::Commify(itembonuses.ATK),
Strings::Commify(RuleI(Character, ItemATKCap)),
Strings::Commify(attack_cap),
Strings::Commify(static_cast<int>(itembonuses.ATK * 1.342))
) :
""
@@ -2415,6 +2462,11 @@ void Mob::SendStatsWindow(Client* c, bool use_window)
final_string += DialogueWindow::CenterMessage("Haste");
int haste_cap = (
IsOfClientBot() ?
GetStatCap(StatCap::Haste) :
RuleI(Character, HasteCap)
);
// Haste Table
const auto& haste_table = DialogueWindow::Table(
fmt::format(
@@ -2433,7 +2485,7 @@ void Mob::SendStatsWindow(Client* c, bool use_window)
fmt::format(
"{} ({})",
IsClient() ? Strings::Commify(CastToClient()->GetHaste()) : Strings::Commify(GetHaste()),
Strings::Commify(RuleI(Character, HasteCap))
Strings::Commify(haste_cap)
)
)
)
@@ -2468,26 +2520,41 @@ void Mob::SendStatsWindow(Client* c, bool use_window)
// Heal Amount
if (GetHealAmt()) {
int cap = (
IsOfClientBot() ?
GetStatCap(StatCap::HealAmount) :
RuleI(Character, ItemHealAmtCap)
);
final_string += fmt::format(
"Heal Amount: {} / {}{}",
Strings::Commify(GetHealAmt()),
Strings::Commify(RuleI(Character, ItemHealAmtCap)),
Strings::Commify(cap),
DialogueWindow::Break(1)
);
}
// Heal Amount
// Spell Damage
if (GetSpellDmg()) {
int cap = (
IsOfClientBot() ?
GetStatCap(StatCap::SpellDamage) :
RuleI(Character, ItemSpellDmgCap)
);
final_string += fmt::format(
"Spell Damage: {} / {}{}",
Strings::Commify(GetSpellDmg()),
Strings::Commify(RuleI(Character, ItemSpellDmgCap)),
Strings::Commify(cap),
DialogueWindow::Break(1)
);
}
// Damage Shield
if (itembonuses.DamageShield || spellbonuses.DamageShield) {
int cap = (
IsOfClientBot() ?
GetStatCap(StatCap::DamageShield) :
RuleI(Character, ItemDamageShieldCap)
);
final_string += fmt::format(
"Damage Shield: {}{}{}{}",
Strings::Commify(itembonuses.DamageShield + spellbonuses.DamageShield),
@@ -2501,7 +2568,7 @@ void Mob::SendStatsWindow(Client* c, bool use_window)
fmt::format(
" | Item: {} / {}",
Strings::Commify(itembonuses.DamageShield),
Strings::Commify(RuleI(Character, ItemDamageShieldCap))
Strings::Commify(cap)
) :
""
),
@@ -2510,12 +2577,17 @@ void Mob::SendStatsWindow(Client* c, bool use_window)
}
// Clairvoyance
const auto clairvoyance = IsBot() ? CastToBot()->GetClair() : CastToClient()->GetClair();
const auto clairvoyance = IsBot() ? CastToBot()->GetClair() : CastToClient()->GetClair();
if (clairvoyance) {
int cap = (
IsOfClientBot() ?
GetStatCap(StatCap::Clairvoyance) :
RuleI(Character, ItemClairvoyanceCap)
);
final_string += fmt::format(
"Clairvoyance: {} / {}{}",
Strings::Commify(clairvoyance),
Strings::Commify(RuleI(Character, ItemClairvoyanceCap)),
Strings::Commify(cap),
DialogueWindow::Break(1)
);
}
@@ -2523,10 +2595,15 @@ void Mob::SendStatsWindow(Client* c, bool use_window)
// Damage Shield Mitigation
const auto ds_mitigation = IsBot() ? CastToBot()->GetDSMit() : CastToClient()->GetDSMit();
if (ds_mitigation) {
int cap = (
IsOfClientBot() ?
GetStatCap(StatCap::DSMitigation) :
RuleI(Character, ItemDSMitigationCap)
);
final_string += fmt::format(
"DS Mitigation: {} / {}{}",
Strings::Commify(ds_mitigation),
Strings::Commify(RuleI(Character, ItemDSMitigationCap)),
Strings::Commify(cap),
DialogueWindow::Break(1)
);
}
@@ -2555,6 +2632,12 @@ void Mob::SendStatsWindow(Client* c, bool use_window)
final_string += faction_item_string;
}
int damage_shield_cap = (
IsOfClientBot() ?
GetStatCap(StatCap::DamageShield) :
RuleI(Character, ItemDamageShieldCap)
);
if (use_window) {
if (final_string.size() < 4096) {
const uint32 popup_buttons = (c->ClientVersion() < EQ::versions::ClientVersion::SoD) ? 0 : 1;
@@ -2596,7 +2679,7 @@ void Mob::SendStatsWindow(Client* c, bool use_window)
GetRaceIDName(GetRace()),
GetRace(),
IsBot() ? Strings::Commify(CastToBot()->GetDS()) : Strings::Commify(CastToClient()->GetDS()),
Strings::Commify(RuleI(Character, ItemDamageShieldCap)),
Strings::Commify(damage_shield_cap),
GetSize(),
GetRunspeed(),
IsBot() ? static_cast<float>(CastToBot()->CalcCurrentWeight()) / 10.0f : static_cast<float>(CastToClient()->CalcCurrentWeight()) / 10.0f,
@@ -2694,18 +2777,23 @@ void Mob::SendStatsWindow(Client* c, bool use_window)
"Attack: {} Item and Spell Attack: {}/{} Server Side Attack: {}",
IsBot() ? Strings::Commify(CastToBot()->GetTotalATK()) : Strings::Commify(CastToClient()->GetTotalATK()),
Strings::Commify(GetATKBonus()),
Strings::Commify(RuleI(Character, ItemATKCap)),
Strings::Commify(attack_cap),
Strings::Commify(GetATK())
).c_str()
);
if ((IsClient() && CastToClient()->GetHaste()) || (!IsClient() && GetHaste())) {
int haste_cap = (
IsOfClientBot() ?
GetStatCap(StatCap::Haste) :
RuleI(Character, HasteCap)
);
c->Message(
Chat::White,
fmt::format(
"Haste: {}/{} (Item: {} + Spell: {} + Over: {})",
IsClient() ? Strings::Commify(CastToClient()->GetHaste()) : Strings::Commify(GetHaste()),
Strings::Commify(RuleI(Character, HasteCap)),
Strings::Commify(haste_cap),
Strings::Commify(itembonuses.haste),
Strings::Commify(spellbonuses.haste + spellbonuses.hastetype2),
Strings::Commify(spellbonuses.hastetype3 + extra_haste)
@@ -8793,3 +8881,72 @@ bool Mob::LoadDataBucketsCache()
return true;
}
int Mob::GetStatCap(uint8 stat_id) const
{
if (HasStatCap(stat_id)) {
return m_stat_caps.at(stat_id);
}
static const std::map<uint8, int> default_caps = {
{ StatCap::Accuracy, RuleI(Character, ItemAccuracyCap) },
{ StatCap::Attack, RuleI(Character, ItemATKCap) },
{ StatCap::Avoidance, RuleI(Character, ItemAvoidanceCap) },
{ StatCap::Clairvoyance, RuleI(Character, ItemClairvoyanceCap) },
{ StatCap::CombatEffects, RuleI(Character, ItemCombatEffectsCap) },
{ StatCap::DamageShield, RuleI(Character, ItemDamageShieldCap) },
{ StatCap::DOTShielding, RuleI(Character, ItemDoTShieldingCap) },
{ StatCap::DSMitigation, RuleI(Character, ItemDSMitigationCap) },
{ StatCap::EnduranceRegen, RuleI(Character, ItemEnduranceRegenCap) },
{ StatCap::ExtraDamage, RuleI(Character, ItemExtraDmgCap) },
{ StatCap::Haste, RuleI(Character, HasteCap) },
{ StatCap::HasteV3, RuleI(Character, Hastev3Cap) },
{ StatCap::HealAmount, RuleI(Character, ItemHealAmtCap) },
{ StatCap::HealthRegen, RuleI(Character, ItemHealthRegenCap) },
{ StatCap::ManaRegen, RuleI(Character, ItemManaRegenCap) },
{ StatCap::QuiverHaste, RuleI(Combat, QuiverHasteCap) },
{ StatCap::Shielding, RuleI(Character, ItemShieldingCap) },
{ StatCap::SpellDamage, RuleI(Character, ItemSpellDmgCap) },
{ StatCap::SpellShielding, RuleI(Character, ItemSpellShieldingCap) },
{ StatCap::Stat, RuleI(Character, StatCap) },
{ StatCap::Strikethrough, RuleI(Character, ItemStrikethroughCap) },
{ StatCap::StunResist, RuleI(Character, ItemStunResistCap) },
};
auto it = default_caps.find(stat_id);
if (it != default_caps.end()) {
return it->second;
}
return -1;
}
void Mob::SetStatCap(uint8 stat_id, int stat_cap, bool save)
{
m_stat_caps[stat_id] = stat_cap;
if (!save) {
return;
}
if (IsBot()) {
BotStatCapsRepository::ReplaceOne(
database,
BotStatCapsRepository::BotStatCaps{
.bot_id = CastToBot()->GetBotID(),
.stat_id = stat_id,
.stat_cap = stat_cap
}
);
}
else if (IsClient()) {
CharacterStatCapsRepository::ReplaceOne(
database,
CharacterStatCapsRepository::CharacterStatCaps{
.character_id = CastToClient()->CharacterID(),
.stat_id = stat_id,
.stat_cap = stat_cap
}
);
}
}
+7
View File
@@ -1519,6 +1519,11 @@ public:
bool IsGuildmaster() const;
bool IsDestroying() const { return m_destroying; }
int GetStatCap(uint8 stat_id) const;
std::map<uint8, int> GetStatCaps() { return m_stat_caps; }
bool HasStatCap(uint8 stat_id) const { return m_stat_caps.count(stat_id); }
void SetStatCap(uint8 stat_id, int stat_cap, bool save = false);
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);
@@ -1802,6 +1807,8 @@ protected:
CombatRecord m_combat_record{};
std::map<uint8, int> m_stat_caps;
public:
const CombatRecord &GetCombatRecord() const;
+19 -7
View File
@@ -620,6 +620,16 @@ void Perl_Bot_RaidGroupSay(Bot* self, const char* message) // @categories Script
self->RaidGroupSay(message);
}
int Perl_Bot_GetStatCap(Bot* self, uint8 stat_id)
{
return self->GetStatCap(stat_id);
}
void Perl_Bot_SetStatCap(Bot* self, uint8 stat_id, int stat_cap)
{
self->SetStatCap(stat_id, stat_cap, true);
}
void perl_register_bot()
{
perl::interpreter state(PERL_GET_THX);
@@ -683,27 +693,28 @@ void perl_register_bot()
package.add("GetBotItem", &Perl_Bot_GetBotItem);
package.add("GetBotItemIDBySlot", &Perl_Bot_GetBotItemIDBySlot);
package.add("GetClassAbbreviation", &Perl_Bot_GetClassAbbreviation);
package.add("GetDisciplineReuseTimer", (uint32(*)(Bot*))&Perl_Bot_GetDisciplineReuseTimer);
package.add("GetDisciplineReuseTimer", (uint32(*)(Bot*, uint16))&Perl_Bot_GetDisciplineReuseTimer);
package.add("GetExpansionBitmask", &Perl_Bot_GetExpansionBitmask);
package.add("GetGroup", &Perl_Bot_GetGroup);
package.add("GetHealAmount", &Perl_Bot_GetHealAmount);
package.add("GetInstrumentMod", &Perl_Bot_GetInstrumentMod);
package.add("GetItemAt", &Perl_Bot_GetItemAt);
package.add("GetItemEquippedByID", &Perl_Bot_HasItemEquippedByID);
package.add("GetItemIDAt", &Perl_Bot_GetItemIDAt);
package.add("GetItemReuseTimer", (uint32(*)(Bot*))&Perl_Bot_GetItemReuseTimer);
package.add("GetItemReuseTimer", (uint32(*)(Bot*, uint32))&Perl_Bot_GetItemReuseTimer);
package.add("GetOwner", &Perl_Bot_GetOwner);
package.add("GetRaceAbbreviation", &Perl_Bot_GetRaceAbbreviation);
package.add("GetRawItemAC", &Perl_Bot_GetRawItemAC);
package.add("GetSpellDamage", &Perl_Bot_GetSpellDamage);
package.add("GetSpellRecastTimer", (uint32(*)(Bot*))&Perl_Bot_GetSpellRecastTimer);
package.add("GetSpellRecastTimer", (uint32(*)(Bot*, uint16))&Perl_Bot_GetSpellRecastTimer);
package.add("GetStatCap", &Perl_Bot_GetStatCap);
package.add("HasAugmentEquippedByID", &Perl_Bot_HasAugmentEquippedByID);
package.add("HasBotItem", &Perl_Bot_HasBotItem);
package.add("HasBotSpellEntry", &Perl_Bot_HasBotSpellEntry);
package.add("HasItemEquippedByID", &Perl_Bot_HasItemEquippedByID);
package.add("GetDisciplineReuseTimer", (uint32(*)(Bot*))&Perl_Bot_GetDisciplineReuseTimer);
package.add("GetDisciplineReuseTimer", (uint32(*)(Bot*, uint16))&Perl_Bot_GetDisciplineReuseTimer);
package.add("GetItemEquippedByID", &Perl_Bot_HasItemEquippedByID);
package.add("GetItemReuseTimer", (uint32(*)(Bot*))&Perl_Bot_GetItemReuseTimer);
package.add("GetItemReuseTimer", (uint32(*)(Bot*, uint32))&Perl_Bot_GetItemReuseTimer);
package.add("GetSpellRecastTimer", (uint32(*)(Bot*))&Perl_Bot_GetSpellRecastTimer);
package.add("GetSpellRecastTimer", (uint32(*)(Bot*, uint16))&Perl_Bot_GetSpellRecastTimer);
package.add("IsGrouped", &Perl_Bot_IsGrouped);
package.add("IsSitting", &Perl_Bot_IsSitting);
package.add("IsStanding", &Perl_Bot_IsStanding);
@@ -736,6 +747,7 @@ void perl_register_bot()
package.add("SetSpellDurationRaid", (void(*)(Bot*, int, int, int, bool, bool))&Perl_Bot_SetSpellDurationRaid);
package.add("SetSpellRecastTimer", (void(*)(Bot*, uint16))&Perl_Bot_SetSpellRecastTimer);
package.add("SetSpellRecastTimer", (void(*)(Bot*, uint16, uint32))&Perl_Bot_SetSpellRecastTimer);
package.add("SetStatCap", &Perl_Bot_SetStatCap);
package.add("Signal", &Perl_Bot_Signal);
package.add("Sit", &Perl_Bot_Sit);
package.add("Stand", &Perl_Bot_Stand);
+12
View File
@@ -3363,6 +3363,16 @@ perl::array Perl_Client_GetKeyRing(Client* self)
return result;
}
int Perl_Client_GetStatCap(Client* self, uint8 stat_id)
{
return self->GetStatCap(stat_id);
}
void Perl_Client_SetStatCap(Client* self, uint8 stat_id, int stat_cap)
{
self->SetStatCap(stat_id, stat_cap, true);
}
void perl_register_client()
{
perl::interpreter perl(PERL_GET_THX);
@@ -3635,6 +3645,7 @@ void perl_register_client()
package.add("GetSpellBookSlotBySpellID", &Perl_Client_GetSpellBookSlotBySpellID);
package.add("GetSpellIDByBookSlot", &Perl_Client_GetSpellIDByBookSlot);
package.add("GetSpentAA", &Perl_Client_GetSpentAA);
package.add("GetStatCap", &Perl_Client_GetStatCap);
package.add("GetStartZone", &Perl_Client_GetStartZone);
package.add("GetTargetRingX", &Perl_Client_GetTargetRingX);
package.add("GetTargetRingY", &Perl_Client_GetTargetRingY);
@@ -3899,6 +3910,7 @@ void perl_register_client()
package.add("SetStartZone", (void(*)(Client*, uint32))&Perl_Client_SetStartZone);
package.add("SetStartZone", (void(*)(Client*, uint32, float, float, float))&Perl_Client_SetStartZone);
package.add("SetStartZone", (void(*)(Client*, uint32, float, float, float, float))&Perl_Client_SetStartZone);
package.add("SetStatCap", &Perl_Client_SetStatCap);
package.add("SetStats", &Perl_Client_SetStats);
package.add("SetThirst", &Perl_Client_SetThirst);
package.add("SetTint", &Perl_Client_SetTint);
+82
View File
@@ -50,6 +50,8 @@
#include "../common/repositories/character_corpses_repository.h"
#include "../common/repositories/character_corpse_items_repository.h"
#include "../common/repositories/zone_repository.h"
#include "../common/repositories/bot_stat_caps_repository.h"
#include "../common/repositories/character_stat_caps_repository.h"
#include "../common/repositories/trader_repository.h"
#include "../common/repositories/character_evolving_items_repository.h"
@@ -4297,3 +4299,83 @@ void ZoneDatabase::LoadCharacterTitleSets(Client* c)
c->EnableTitle(e.title_set, false);
}
}
void ZoneDatabase::LoadStatCaps(Mob* m)
{
if (!zone || !m) {
return;
}
if (m->IsBot()) {
const auto& l = BotStatCapsRepository::GetWhere(
*this,
fmt::format(
"`bot_id` = {}",
m->CastToBot()->GetBotID()
)
);
if (l.empty()) {
return;
}
for (const auto& e : l) {
m->SetStatCap(e.stat_id, e.stat_cap);
}
} else if (m->IsClient()) {
const auto& l = CharacterStatCapsRepository::GetWhere(
*this,
fmt::format(
"`character_id` = {}",
m->CastToClient()->CharacterID()
)
);
if (l.empty()) {
return;
}
for (const auto& e : l) {
m->SetStatCap(e.stat_id, e.stat_cap);
}
}
}
void ZoneDatabase::SaveStatCaps(Mob* m)
{
if (m->IsBot()) {
std::vector<BotStatCapsRepository::BotStatCaps> v;
BotStatCapsRepository::BotStatCaps stat_cap;
stat_cap.bot_id = m->CastToBot()->GetBotID();
for (const auto& e: m->GetStatCaps()) {
stat_cap.stat_id = e.first;
stat_cap.stat_cap = e.second;
v.emplace_back(stat_cap);
}
if (!v.empty()) {
BotStatCapsRepository::ReplaceMany(*this, v);
}
} else if (m->IsClient()) {
std::vector<CharacterStatCapsRepository::CharacterStatCaps> v;
CharacterStatCapsRepository::CharacterStatCaps stat_cap;
stat_cap.character_id = m->CastToClient()->CharacterID();
for (const auto& e: m->GetStatCaps()) {
stat_cap.stat_id = e.first;
stat_cap.stat_cap = e.second;
v.emplace_back(stat_cap);
}
if (!v.empty()) {
CharacterStatCapsRepository::ReplaceMany(*this, v);
}
}
}
+4
View File
@@ -465,6 +465,10 @@ public:
void LoadCharacterEXPModifier(Client* c);
void SaveCharacterEXPModifier(Client *c);
/* Stat Caps */
void LoadStatCaps(Mob* m);
void SaveStatCaps(Mob* m);
/* Player Title Sets */
void LoadCharacterTitleSets(Client* c);