mirror of
https://github.com/mod-playerbots/mod-playerbots.git
synced 2026-03-11 19:35:09 +00:00
# Pull Request
This is the first in a series of PRs intended to eliminate warnings in
the module. The design intent is to eliminate the calling event when not
needed in the body of the function. Based off of SmashingQuasars work.
---
## How to Test the Changes
- Step-by-step instructions to test the change
- Any required setup (e.g. multiple players, bots, specific
configuration)
- Expected behavior and how to verify it
## Complexity & Impact
- Does this change add new decision branches?
- [x] No
- [ ] Yes (**explain below**)
- Does this change increase per-bot or per-tick processing?
- [x] No
- [ ] Yes (**describe and justify impact**)
- Could this logic scale poorly under load?
- [x] No
- [ ] Yes (**explain why**)
---
## Defaults & Configuration
- Does this change modify default bot behavior?
- [x] No
- [ ] Yes (**explain why**)
If this introduces more advanced or AI-heavy logic:
- [ ] Lightweight mode remains the default
- [ ] More complex behavior is optional and thereby configurable
---
## AI Assistance
- Was AI assistance (e.g. ChatGPT or similar tools) used while working
on this change?
- [x] No
- [ ] Yes (**explain below**)
---
## Final Checklist
- [x] Stability is not compromised
- [x] Performance impact is understood, tested, and acceptable
- [x] Added logic complexity is justified and explained
- [x] Documentation updated if needed
---
## Notes for Reviewers
Anything that significantly improves realism at the cost of stability or
performance should be carefully discussed
before merging.
---------
Co-authored-by: bashermens <31279994+hermensbas@users.noreply.github.com>
834 lines
30 KiB
C++
834 lines
30 KiB
C++
#include "StatsCollector.h"
|
|
|
|
#include "DBCStores.h"
|
|
#include "ItemTemplate.h"
|
|
#include "PlayerbotAI.h"
|
|
#include "PlayerbotAIAware.h"
|
|
#include "SharedDefines.h"
|
|
#include "SpellAuraDefines.h"
|
|
#include "SpellInfo.h"
|
|
#include "SpellMgr.h"
|
|
#include "Util.h"
|
|
|
|
StatsCollector::StatsCollector(CollectorType type, int32 cls) : type_(type), cls_(cls) { Reset(); }
|
|
|
|
void StatsCollector::Reset()
|
|
{
|
|
for (uint32 i = 0; i < STATS_TYPE_MAX; i++)
|
|
{
|
|
stats[i] = 0;
|
|
}
|
|
}
|
|
|
|
void StatsCollector::CollectItemStats(ItemTemplate const* proto)
|
|
{
|
|
if (proto->IsRangedWeapon())
|
|
{
|
|
float val = (proto->Damage[0].DamageMin + proto->Damage[0].DamageMax) * 1000 / 2 / proto->Delay;
|
|
stats[STATS_TYPE_RANGED_DPS] += val;
|
|
}
|
|
else if (proto->IsWeapon())
|
|
{
|
|
float val = (proto->Damage[0].DamageMin + proto->Damage[0].DamageMax) * 1000 / 2 / proto->Delay;
|
|
stats[STATS_TYPE_MELEE_DPS] += val;
|
|
}
|
|
stats[STATS_TYPE_ARMOR] += proto->Armor;
|
|
stats[STATS_TYPE_BLOCK_VALUE] += proto->Block;
|
|
for (int i = 0; i < proto->StatsCount; i++)
|
|
{
|
|
const _ItemStat& stat = proto->ItemStat[i];
|
|
const int32& val = stat.ItemStatValue;
|
|
CollectByItemStatType(stat.ItemStatType, val);
|
|
}
|
|
for (uint8 j = 0; j < MAX_ITEM_PROTO_SPELLS; j++)
|
|
{
|
|
switch (proto->Spells[j].SpellTrigger)
|
|
{
|
|
case ITEM_SPELLTRIGGER_ON_USE:
|
|
CollectSpellStats(proto->Spells[j].SpellId, 1.0f, proto->Spells[j].SpellCooldown);
|
|
break;
|
|
case ITEM_SPELLTRIGGER_ON_EQUIP:
|
|
CollectSpellStats(proto->Spells[j].SpellId, 1.0f, 0);
|
|
break;
|
|
case ITEM_SPELLTRIGGER_CHANCE_ON_HIT:
|
|
if (type_ & CollectorType::MELEE)
|
|
{
|
|
if (proto->Spells[j].SpellPPMRate > 0.01f)
|
|
CollectSpellStats(proto->Spells[j].SpellId, 1.0f, 60000 / proto->Spells[j].SpellPPMRate);
|
|
else
|
|
CollectSpellStats(proto->Spells[j].SpellId, 1.0f, 60000 / 1.8f); // Default PPM = 1.8
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (proto->socketBonus)
|
|
{
|
|
if (const SpellItemEnchantmentEntry* enchant = sSpellItemEnchantmentStore.LookupEntry(proto->socketBonus))
|
|
CollectEnchantStats(enchant);
|
|
}
|
|
}
|
|
|
|
void StatsCollector::CollectSpellStats(uint32 spellId, float multiplier, int32 spellCooldown)
|
|
{
|
|
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
|
|
|
|
if (!spellInfo)
|
|
return;
|
|
|
|
if (SpecialSpellFilter(spellId))
|
|
return;
|
|
|
|
const SpellProcEventEntry* eventEntry = sSpellMgr->GetSpellProcEvent(spellInfo->Id);
|
|
|
|
uint32 triggerCooldown = eventEntry ? eventEntry->cooldown : 0;
|
|
|
|
bool canNextTrigger = true;
|
|
|
|
uint32 procFlags;
|
|
uint32 procChance;
|
|
if (eventEntry && eventEntry->procFlags)
|
|
procFlags = eventEntry->procFlags;
|
|
else
|
|
procFlags = spellInfo->ProcFlags;
|
|
|
|
if (eventEntry && eventEntry->customChance)
|
|
procChance = eventEntry->customChance;
|
|
else
|
|
procChance = spellInfo->ProcChance;
|
|
bool lowChance = procChance <= 5;
|
|
|
|
if (lowChance || (procFlags && !CanBeTriggeredByType(spellInfo, procFlags)))
|
|
canNextTrigger = false;
|
|
|
|
if (spellInfo->StackAmount)
|
|
{
|
|
// Heuristic multiplier for spell with stackAmount since high stackAmount may not be available
|
|
if (spellInfo->StackAmount <= 1)
|
|
multiplier *= spellInfo->StackAmount * 1;
|
|
else if (spellInfo->StackAmount <= 5)
|
|
multiplier *= 1 + (spellInfo->StackAmount - 1) * 0.75;
|
|
else if (spellInfo->StackAmount <= 10)
|
|
multiplier *= 4 + (spellInfo->StackAmount - 5) * 0.6;
|
|
else if (spellInfo->StackAmount <= 20)
|
|
multiplier *= 7 + (spellInfo->StackAmount - 10) * 0.4;
|
|
else
|
|
multiplier *= 11;
|
|
}
|
|
|
|
for (int i = 0; i < MAX_SPELL_EFFECTS; i++)
|
|
{
|
|
const SpellEffectInfo& effectInfo = spellInfo->Effects[i];
|
|
if (!effectInfo.Effect)
|
|
continue;
|
|
switch (effectInfo.Effect)
|
|
{
|
|
case SPELL_EFFECT_APPLY_AURA:
|
|
{
|
|
if (spellInfo->SpellFamilyName && /*effectInfo.ApplyAuraName != SPELL_AURA_DUMMY &&*/
|
|
effectInfo.ApplyAuraName != SPELL_AURA_PROC_TRIGGER_SPELL)
|
|
{
|
|
if (!CheckSpellValidation(spellInfo->SpellFamilyName, effectInfo.SpellClassMask))
|
|
return;
|
|
|
|
// Some dummy effects cannot be recognized, make some bonus to identify
|
|
stats[STATS_TYPE_BONUS] += 1;
|
|
}
|
|
|
|
/// @todo Handle negative spell
|
|
if (!spellInfo->IsPositive())
|
|
break;
|
|
|
|
float coverage;
|
|
if (spellCooldown <= 2000 || spellInfo->GetDuration() == -1)
|
|
coverage = 1.0f;
|
|
else
|
|
coverage =
|
|
std::min(1.0f, (float)spellInfo->GetDuration() / (spellInfo->GetDuration() + spellCooldown));
|
|
|
|
multiplier *= coverage;
|
|
HandleApplyAura(effectInfo, multiplier, canNextTrigger, triggerCooldown);
|
|
break;
|
|
}
|
|
case SPELL_EFFECT_HEAL:
|
|
{
|
|
/// @todo Handle spell without cooldown
|
|
if (!spellCooldown)
|
|
break;
|
|
float normalizedCd = std::max((float)spellCooldown / 1000, 5.0f);
|
|
int32 val = AverageValue(effectInfo);
|
|
float transfer_multiplier = 1;
|
|
stats[STATS_TYPE_HEAL_POWER] += (float)val / normalizedCd * multiplier * transfer_multiplier;
|
|
break;
|
|
}
|
|
case SPELL_EFFECT_ENERGIZE:
|
|
{
|
|
/// @todo Handle spell without cooldown
|
|
if (!spellCooldown)
|
|
break;
|
|
if (effectInfo.MiscValue != POWER_MANA)
|
|
break;
|
|
float normalizedCd = std::max((float)spellCooldown / 1000, 5.0f);
|
|
int32 val = AverageValue(effectInfo);
|
|
float transfer_multiplier = 0.2;
|
|
stats[STATS_TYPE_MANA_REGENERATION] += (float)val / normalizedCd * multiplier * transfer_multiplier;
|
|
break;
|
|
}
|
|
case SPELL_EFFECT_SCHOOL_DAMAGE:
|
|
{
|
|
/// @todo Handle spell without cooldown
|
|
if (!spellCooldown)
|
|
break;
|
|
float normalizedCd = std::max((float)spellCooldown / 1000, 5.0f);
|
|
int32 val = AverageValue(effectInfo);
|
|
if (type_ & (CollectorType::MELEE | CollectorType::RANGED))
|
|
{
|
|
float transfer_multiplier = 1;
|
|
stats[STATS_TYPE_ATTACK_POWER] += (float)val / normalizedCd * multiplier * transfer_multiplier;
|
|
}
|
|
else if (type_ & CollectorType::SPELL_DMG)
|
|
{
|
|
float transfer_multiplier = 0.5;
|
|
stats[STATS_TYPE_SPELL_POWER] += (float)val / normalizedCd * multiplier * transfer_multiplier;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void StatsCollector::CollectEnchantStats(SpellItemEnchantmentEntry const* enchant, uint32 default_enchant_amount)
|
|
{
|
|
for (int s = 0; s < MAX_SPELL_ITEM_ENCHANTMENT_EFFECTS; ++s)
|
|
{
|
|
uint32 enchant_display_type = enchant->type[s];
|
|
uint32 enchant_amount = enchant->amount[s];
|
|
uint32 enchant_spell_id = enchant->spellid[s];
|
|
|
|
if (SpecialEnchantFilter(enchant_spell_id))
|
|
continue;
|
|
|
|
switch (enchant_display_type)
|
|
{
|
|
case ITEM_ENCHANTMENT_TYPE_COMBAT_SPELL:
|
|
{
|
|
if (type_ & CollectorType::MELEE)
|
|
CollectSpellStats(enchant_spell_id, 0.25f);
|
|
break;
|
|
}
|
|
case ITEM_ENCHANTMENT_TYPE_EQUIP_SPELL:
|
|
{
|
|
CollectSpellStats(enchant_spell_id, 1.0f);
|
|
break;
|
|
}
|
|
case ITEM_ENCHANTMENT_TYPE_STAT:
|
|
{
|
|
// for item random suffix
|
|
if (!enchant_amount)
|
|
enchant_amount = default_enchant_amount;
|
|
|
|
if (!enchant_amount)
|
|
{
|
|
break;
|
|
}
|
|
CollectByItemStatType(enchant_spell_id, enchant_amount);
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// @todo Special case for some spell that hard to calculate, like trinket, relic, etc.
|
|
bool StatsCollector::SpecialSpellFilter(uint32 spellId)
|
|
{
|
|
// trinket
|
|
switch (spellId)
|
|
{
|
|
case 60764: // Totem of Splintering
|
|
if (type_ & (CollectorType::SPELL))
|
|
return true;
|
|
break;
|
|
case 27521: // Insightful Earthstorm Diamond
|
|
stats[STATS_TYPE_MANA_REGENERATION] += 20;
|
|
return true;
|
|
case 55381: // Insightful Earthsiege Diamond
|
|
stats[STATS_TYPE_MANA_REGENERATION] += 40;
|
|
return true;
|
|
case 39442: // Darkmoon Card: Wrath
|
|
if (!(type_ & CollectorType::SPELL_HEAL))
|
|
stats[STATS_TYPE_CRIT] += 50;
|
|
return true;
|
|
case 59620: // Berserk
|
|
if (type_ & CollectorType::MELEE)
|
|
stats[STATS_TYPE_ATTACK_POWER] += 120;
|
|
return true;
|
|
case 67702: // Death's Verdict
|
|
stats[STATS_TYPE_ATTACK_POWER] += 225;
|
|
return true;
|
|
case 67771: // Death's Verdict (heroic)
|
|
stats[STATS_TYPE_ATTACK_POWER] += 260;
|
|
return true;
|
|
case 71406: // Tiny Abomination in a Jar
|
|
if (cls_ == CLASS_PALADIN)
|
|
stats[STATS_TYPE_ATTACK_POWER] += 600;
|
|
else
|
|
stats[STATS_TYPE_ATTACK_POWER] += 150;
|
|
return true;
|
|
case 71545: // Tiny Abomination in a Jar (heroic)
|
|
if (cls_ == CLASS_PALADIN)
|
|
stats[STATS_TYPE_ATTACK_POWER] += 800;
|
|
else
|
|
stats[STATS_TYPE_ATTACK_POWER] += 200;
|
|
return true;
|
|
case 71519: // Deathbringer's Will
|
|
stats[STATS_TYPE_ATTACK_POWER] += 350;
|
|
return true;
|
|
case 71562: // Deathbringer's Will (heroic)
|
|
stats[STATS_TYPE_ATTACK_POWER] += 400;
|
|
return true;
|
|
case 71602: // Dislodged Foreign Object
|
|
/// @todo The item can be triggered by heal spell, which mismatch with it's description
|
|
/// Noticing that heroic item can not be triggered, probably a bug to report to AC
|
|
if (type_ & CollectorType::SPELL_HEAL)
|
|
return true;
|
|
break;
|
|
case 71903: // Shadowmourne
|
|
stats[STATS_TYPE_STRENGTH] += 200;
|
|
return true;
|
|
default:
|
|
break;
|
|
}
|
|
// switch (spellId)
|
|
// {
|
|
// case 50457: // Idol of the Lunar Eclipse
|
|
// stats[STATS_TYPE_CRIT] += 150;
|
|
// return true;
|
|
// default:
|
|
// break;
|
|
// }
|
|
return false;
|
|
}
|
|
|
|
bool StatsCollector::SpecialEnchantFilter(uint32 enchantSpellId)
|
|
{
|
|
switch (enchantSpellId)
|
|
{
|
|
case 64440:
|
|
if (type_ & CollectorType::MELEE)
|
|
{
|
|
stats[STATS_TYPE_PARRY] += 50;
|
|
}
|
|
return true;
|
|
case 53365: // Rune of the Fallen Crusader
|
|
if (type_ & CollectorType::MELEE)
|
|
{
|
|
stats[STATS_TYPE_STRENGTH] += 75;
|
|
}
|
|
return true;
|
|
case 62157: // Rune of the Stoneskin Gargoyle
|
|
if (type_ & CollectorType::MELEE)
|
|
{
|
|
stats[STATS_TYPE_DEFENSE] += 25;
|
|
stats[STATS_TYPE_STAMINA] += 50;
|
|
}
|
|
return true;
|
|
case 64571: // Blood draining
|
|
if (type_ & CollectorType::MELEE)
|
|
{
|
|
stats[STATS_TYPE_STAMINA] += 50;
|
|
}
|
|
return true;
|
|
default:
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool StatsCollector::CanBeTriggeredByType(SpellInfo const* spellInfo, uint32 procFlags, bool strict)
|
|
{
|
|
const SpellProcEventEntry* eventEntry = sSpellMgr->GetSpellProcEvent(spellInfo->Id);
|
|
uint32 spellFamilyName = 0;
|
|
if (eventEntry)
|
|
{
|
|
spellFamilyName = eventEntry->spellFamilyName;
|
|
flag96 spellFamilyMask = eventEntry->spellFamilyMask;
|
|
if (spellFamilyName != 0)
|
|
{
|
|
if (!CheckSpellValidation(spellFamilyName, spellFamilyMask, strict))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
uint32 triggerMask = TAKEN_HIT_PROC_FLAG_MASK; // Generic trigger mask
|
|
switch (type_)
|
|
{
|
|
case CollectorType::MELEE_DMG:
|
|
{
|
|
triggerMask |= MELEE_PROC_FLAG_MASK;
|
|
triggerMask |= SPELL_PROC_FLAG_MASK;
|
|
triggerMask |= PROC_FLAG_DONE_PERIODIC;
|
|
if (procFlags & triggerMask)
|
|
return true;
|
|
break;
|
|
}
|
|
case CollectorType::MELEE_TANK:
|
|
{
|
|
triggerMask |= MELEE_PROC_FLAG_MASK;
|
|
triggerMask |= SPELL_PROC_FLAG_MASK;
|
|
triggerMask |= PERIODIC_PROC_FLAG_MASK;
|
|
if (procFlags & triggerMask)
|
|
return true;
|
|
break;
|
|
}
|
|
case CollectorType::RANGED:
|
|
{
|
|
triggerMask |= RANGED_PROC_FLAG_MASK;
|
|
triggerMask |= SPELL_PROC_FLAG_MASK;
|
|
triggerMask |= PROC_FLAG_DONE_PERIODIC;
|
|
if (procFlags & triggerMask)
|
|
return true;
|
|
break;
|
|
}
|
|
case CollectorType::SPELL_DMG:
|
|
{
|
|
triggerMask |= SPELL_PROC_FLAG_MASK;
|
|
triggerMask |= PROC_FLAG_DONE_PERIODIC;
|
|
// Healing spell cannot trigger
|
|
triggerMask &= ~PROC_FLAG_DONE_SPELL_NONE_DMG_CLASS_POS;
|
|
triggerMask &= ~PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_POS;
|
|
if (procFlags & triggerMask)
|
|
return true;
|
|
break;
|
|
}
|
|
case CollectorType::SPELL_HEAL:
|
|
{
|
|
triggerMask |= SPELL_PROC_FLAG_MASK;
|
|
triggerMask |= PROC_FLAG_DONE_PERIODIC;
|
|
// Dmg spell should not trigger
|
|
triggerMask &= ~PROC_FLAG_DONE_SPELL_NONE_DMG_CLASS_NEG;
|
|
triggerMask &= ~PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG;
|
|
if (!spellFamilyName)
|
|
triggerMask &=
|
|
~PROC_FLAG_DONE_PERIODIC; // spellFamilyName = 0 and PROC_FLAG_DONE_PERIODIC -> it is a dmg spell
|
|
if (procFlags & triggerMask)
|
|
return true;
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void StatsCollector::CollectByItemStatType(uint32 itemStatType, int32 val)
|
|
{
|
|
switch (itemStatType)
|
|
{
|
|
case ITEM_MOD_MANA:
|
|
stats[STATS_TYPE_MANA_REGENERATION] += (float)val / 10;
|
|
break;
|
|
case ITEM_MOD_HEALTH:
|
|
stats[STATS_TYPE_STAMINA] += (float)val / 15;
|
|
break;
|
|
case ITEM_MOD_AGILITY:
|
|
stats[STATS_TYPE_AGILITY] += val;
|
|
break;
|
|
case ITEM_MOD_STRENGTH:
|
|
stats[STATS_TYPE_STRENGTH] += val;
|
|
break;
|
|
case ITEM_MOD_INTELLECT:
|
|
stats[STATS_TYPE_INTELLECT] += val;
|
|
break;
|
|
case ITEM_MOD_SPIRIT:
|
|
stats[STATS_TYPE_SPIRIT] += val;
|
|
break;
|
|
case ITEM_MOD_STAMINA:
|
|
stats[STATS_TYPE_STAMINA] += val;
|
|
break;
|
|
case ITEM_MOD_DEFENSE_SKILL_RATING:
|
|
stats[STATS_TYPE_DEFENSE] += val;
|
|
break;
|
|
case ITEM_MOD_DODGE_RATING:
|
|
stats[STATS_TYPE_DODGE] += val;
|
|
break;
|
|
case ITEM_MOD_PARRY_RATING:
|
|
stats[STATS_TYPE_PARRY] += val;
|
|
break;
|
|
case ITEM_MOD_BLOCK_RATING:
|
|
stats[STATS_TYPE_BLOCK_RATING] += val;
|
|
break;
|
|
case ITEM_MOD_HIT_MELEE_RATING:
|
|
if (type_ & CollectorType::MELEE)
|
|
stats[STATS_TYPE_HIT] += val;
|
|
break;
|
|
case ITEM_MOD_HIT_RANGED_RATING:
|
|
if (type_ & CollectorType::RANGED)
|
|
stats[STATS_TYPE_HIT] += val;
|
|
break;
|
|
case ITEM_MOD_HIT_SPELL_RATING:
|
|
if (type_ & CollectorType::SPELL)
|
|
stats[STATS_TYPE_HIT] += val;
|
|
break;
|
|
case ITEM_MOD_CRIT_MELEE_RATING:
|
|
if (type_ & CollectorType::MELEE)
|
|
stats[STATS_TYPE_CRIT] += val;
|
|
break;
|
|
case ITEM_MOD_CRIT_RANGED_RATING:
|
|
if (type_ & CollectorType::RANGED)
|
|
stats[STATS_TYPE_CRIT] += val;
|
|
break;
|
|
case ITEM_MOD_CRIT_SPELL_RATING:
|
|
if (type_ & CollectorType::SPELL)
|
|
stats[STATS_TYPE_CRIT] += val;
|
|
break;
|
|
case ITEM_MOD_HASTE_MELEE_RATING:
|
|
if (type_ & CollectorType::MELEE)
|
|
stats[STATS_TYPE_HASTE] += val;
|
|
break;
|
|
case ITEM_MOD_HASTE_RANGED_RATING:
|
|
if (type_ & CollectorType::RANGED)
|
|
stats[STATS_TYPE_HASTE] += val;
|
|
break;
|
|
case ITEM_MOD_HASTE_SPELL_RATING:
|
|
if (type_ & CollectorType::SPELL)
|
|
stats[STATS_TYPE_HASTE] += val;
|
|
break;
|
|
case ITEM_MOD_HIT_RATING:
|
|
stats[STATS_TYPE_HIT] += val;
|
|
break;
|
|
case ITEM_MOD_CRIT_RATING:
|
|
stats[STATS_TYPE_CRIT] += val;
|
|
break;
|
|
case ITEM_MOD_RESILIENCE_RATING:
|
|
stats[STATS_TYPE_RESILIENCE] += val;
|
|
break;
|
|
case ITEM_MOD_HASTE_RATING:
|
|
stats[STATS_TYPE_HASTE] += val;
|
|
break;
|
|
case ITEM_MOD_EXPERTISE_RATING:
|
|
stats[STATS_TYPE_EXPERTISE] += val;
|
|
break;
|
|
case ITEM_MOD_ATTACK_POWER:
|
|
stats[STATS_TYPE_ATTACK_POWER] += val;
|
|
break;
|
|
case ITEM_MOD_RANGED_ATTACK_POWER:
|
|
if (type_ == CollectorType::RANGED)
|
|
stats[STATS_TYPE_ATTACK_POWER] += val;
|
|
break;
|
|
case ITEM_MOD_MANA_REGENERATION:
|
|
stats[STATS_TYPE_MANA_REGENERATION] += val;
|
|
break;
|
|
case ITEM_MOD_ARMOR_PENETRATION_RATING:
|
|
stats[STATS_TYPE_ARMOR_PENETRATION] += val;
|
|
break;
|
|
case ITEM_MOD_SPELL_POWER:
|
|
stats[STATS_TYPE_SPELL_POWER] += val;
|
|
stats[STATS_TYPE_HEAL_POWER] += val;
|
|
break;
|
|
case ITEM_MOD_HEALTH_REGEN:
|
|
stats[STATS_TYPE_HEALTH_REGENERATION] += val;
|
|
break;
|
|
case ITEM_MOD_SPELL_PENETRATION:
|
|
stats[STATS_TYPE_SPELL_PENETRATION] += val;
|
|
break;
|
|
case ITEM_MOD_BLOCK_VALUE:
|
|
stats[STATS_TYPE_BLOCK_VALUE] += val;
|
|
break;
|
|
case ITEM_MOD_SPELL_HEALING_DONE: // deprecated
|
|
case ITEM_MOD_SPELL_DAMAGE_DONE: // deprecated
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void StatsCollector::HandleApplyAura(const SpellEffectInfo& effectInfo, float multiplier, bool canNextTrigger,
|
|
uint32 triggerCooldown)
|
|
{
|
|
if (effectInfo.Effect != SPELL_EFFECT_APPLY_AURA)
|
|
return;
|
|
|
|
int32 val = AverageValue(effectInfo);
|
|
|
|
switch (effectInfo.ApplyAuraName)
|
|
{
|
|
case SPELL_AURA_MOD_DAMAGE_DONE:
|
|
{
|
|
int32 schoolType = effectInfo.MiscValue;
|
|
if (schoolType & SPELL_SCHOOL_MASK_NORMAL)
|
|
stats[STATS_TYPE_ATTACK_POWER] += val * multiplier;
|
|
if ((schoolType & SPELL_SCHOOL_MASK_MAGIC) == SPELL_SCHOOL_MASK_MAGIC)
|
|
stats[STATS_TYPE_SPELL_POWER] += val * multiplier;
|
|
break;
|
|
}
|
|
case SPELL_AURA_MOD_HEALING_DONE:
|
|
stats[STATS_TYPE_HEAL_POWER] += val * multiplier;
|
|
break;
|
|
case SPELL_AURA_MOD_INCREASE_HEALTH:
|
|
stats[STATS_TYPE_STAMINA] += val * multiplier / 15;
|
|
break;
|
|
case SPELL_AURA_SCHOOL_ABSORB:
|
|
{
|
|
int32 schoolType = effectInfo.MiscValue;
|
|
if (schoolType & SPELL_SCHOOL_MASK_NORMAL)
|
|
stats[STATS_TYPE_STAMINA] += val * multiplier / 15;
|
|
break;
|
|
}
|
|
case SPELL_AURA_MOD_ATTACK_POWER:
|
|
if (type_ & CollectorType::MELEE)
|
|
stats[STATS_TYPE_ATTACK_POWER] += val * multiplier;
|
|
break;
|
|
case SPELL_AURA_MOD_RANGED_ATTACK_POWER:
|
|
if (type_ & CollectorType::RANGED)
|
|
stats[STATS_TYPE_ATTACK_POWER] += val * multiplier;
|
|
break;
|
|
case SPELL_AURA_MOD_SHIELD_BLOCKVALUE:
|
|
stats[STATS_TYPE_BLOCK_VALUE] += val * multiplier;
|
|
break;
|
|
case SPELL_AURA_MOD_STAT:
|
|
{
|
|
int32 statType = effectInfo.MiscValue;
|
|
switch (statType)
|
|
{
|
|
case STAT_STRENGTH:
|
|
stats[STATS_TYPE_STRENGTH] += val * multiplier;
|
|
break;
|
|
case STAT_AGILITY:
|
|
stats[STATS_TYPE_AGILITY] += val * multiplier;
|
|
break;
|
|
case STAT_STAMINA:
|
|
stats[STATS_TYPE_STAMINA] += val * multiplier;
|
|
break;
|
|
case STAT_INTELLECT:
|
|
stats[STATS_TYPE_INTELLECT] += val * multiplier;
|
|
break;
|
|
case STAT_SPIRIT:
|
|
stats[STATS_TYPE_SPIRIT] += val * multiplier;
|
|
break;
|
|
case -1: // Stat all
|
|
stats[STATS_TYPE_STRENGTH] += val * multiplier;
|
|
stats[STATS_TYPE_AGILITY] += val * multiplier;
|
|
stats[STATS_TYPE_STAMINA] += val * multiplier;
|
|
stats[STATS_TYPE_INTELLECT] += val * multiplier;
|
|
stats[STATS_TYPE_SPIRIT] += val * multiplier;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
case SPELL_AURA_MOD_RESISTANCE:
|
|
{
|
|
int32 statType = effectInfo.MiscValue;
|
|
if (statType & SPELL_SCHOOL_MASK_NORMAL) // physical
|
|
stats[STATS_TYPE_ARMOR] += val * multiplier;
|
|
break;
|
|
}
|
|
case SPELL_AURA_MOD_RATING:
|
|
{
|
|
for (uint32 rating = CR_WEAPON_SKILL; rating < MAX_COMBAT_RATING; ++rating)
|
|
{
|
|
if (effectInfo.MiscValue & (1 << rating))
|
|
{
|
|
switch (rating)
|
|
{
|
|
case CR_DEFENSE_SKILL:
|
|
stats[STATS_TYPE_DEFENSE] += val * multiplier;
|
|
break;
|
|
case CR_DODGE:
|
|
stats[STATS_TYPE_DODGE] += val * multiplier;
|
|
break;
|
|
case CR_PARRY:
|
|
stats[STATS_TYPE_PARRY] += val * multiplier;
|
|
break;
|
|
case CR_BLOCK:
|
|
stats[STATS_TYPE_BLOCK_RATING] += val * multiplier;
|
|
break;
|
|
case CR_HIT_MELEE:
|
|
if (type_ & CollectorType::MELEE)
|
|
stats[STATS_TYPE_HIT] += val * multiplier;
|
|
break;
|
|
case CR_HIT_RANGED:
|
|
if (type_ & CollectorType::RANGED)
|
|
stats[STATS_TYPE_HIT] += val * multiplier;
|
|
break;
|
|
case CR_HIT_SPELL:
|
|
if (type_ & CollectorType::SPELL)
|
|
stats[STATS_TYPE_HIT] += val * multiplier;
|
|
break;
|
|
case CR_CRIT_MELEE:
|
|
if (type_ & CollectorType::MELEE)
|
|
stats[STATS_TYPE_CRIT] += val * multiplier;
|
|
break;
|
|
case CR_CRIT_RANGED:
|
|
if (type_ & CollectorType::RANGED)
|
|
stats[STATS_TYPE_CRIT] += val * multiplier;
|
|
break;
|
|
case CR_CRIT_SPELL:
|
|
if (type_ & CollectorType::SPELL)
|
|
stats[STATS_TYPE_CRIT] += val * multiplier;
|
|
break;
|
|
case CR_HASTE_MELEE:
|
|
if (type_ & CollectorType::MELEE)
|
|
stats[STATS_TYPE_HASTE] += val * multiplier;
|
|
break;
|
|
case CR_HASTE_RANGED:
|
|
if (type_ & CollectorType::RANGED)
|
|
stats[STATS_TYPE_HASTE] += val * multiplier;
|
|
break;
|
|
case CR_HASTE_SPELL:
|
|
if (type_ & CollectorType::SPELL)
|
|
stats[STATS_TYPE_HASTE] += val * multiplier;
|
|
break;
|
|
case CR_EXPERTISE:
|
|
stats[STATS_TYPE_EXPERTISE] += val * multiplier;
|
|
break;
|
|
case CR_ARMOR_PENETRATION:
|
|
stats[STATS_TYPE_ARMOR_PENETRATION] += val * multiplier;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
case SPELL_AURA_MOD_POWER_REGEN:
|
|
{
|
|
int32 powerType = effectInfo.MiscValue;
|
|
switch (powerType)
|
|
{
|
|
case POWER_MANA:
|
|
stats[STATS_TYPE_MANA_REGENERATION] += val * multiplier;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
case SPELL_AURA_PROC_TRIGGER_SPELL:
|
|
{
|
|
if (canNextTrigger)
|
|
CollectSpellStats(effectInfo.TriggerSpell, multiplier, triggerCooldown);
|
|
break;
|
|
}
|
|
case SPELL_AURA_PERIODIC_TRIGGER_SPELL:
|
|
{
|
|
if (canNextTrigger)
|
|
CollectSpellStats(effectInfo.TriggerSpell, multiplier, triggerCooldown);
|
|
break;
|
|
}
|
|
case SPELL_AURA_ADD_TARGET_TRIGGER:
|
|
{
|
|
if (canNextTrigger)
|
|
CollectSpellStats(effectInfo.TriggerSpell, multiplier, triggerCooldown);
|
|
break;
|
|
}
|
|
case SPELL_AURA_MOD_CRIT_DAMAGE_BONUS:
|
|
{
|
|
if (type_ != CollectorType::SPELL_HEAL)
|
|
{
|
|
int32 statType = effectInfo.MiscValue;
|
|
if (statType & SPELL_SCHOOL_MASK_NORMAL) // physical
|
|
stats[STATS_TYPE_CRIT] += 30 * val * multiplier;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
float StatsCollector::AverageValue(const SpellEffectInfo& effectInfo)
|
|
{
|
|
// float basePointsPerLevel = effectInfo.RealPointsPerLevel; //not used, line marked for removal.
|
|
float basePoints = effectInfo.BasePoints;
|
|
int32 randomPoints = effectInfo.DieSides;
|
|
|
|
switch (randomPoints)
|
|
{
|
|
case 0:
|
|
break;
|
|
case 1:
|
|
basePoints += 1;
|
|
break;
|
|
default:
|
|
float randvalue = (1 + randomPoints) / 2.0f;
|
|
basePoints += randvalue;
|
|
break;
|
|
}
|
|
return basePoints;
|
|
}
|
|
|
|
bool StatsCollector::CheckSpellValidation(uint32 spellFamilyName, flag96 spelFalimyFlags, bool strict)
|
|
{
|
|
if (PlayerbotAI::Class2SpellFamilyName(cls_) != spellFamilyName)
|
|
return false;
|
|
|
|
bool isHealingSpell = PlayerbotAI::IsHealingSpell(spellFamilyName, spelFalimyFlags);
|
|
// strict to healer
|
|
if (strict && (type_ & CollectorType::SPELL_HEAL))
|
|
{
|
|
return isHealingSpell;
|
|
}
|
|
|
|
if (!(type_ & CollectorType::SPELL_HEAL) && isHealingSpell)
|
|
return false;
|
|
|
|
// spells for caster/melee/tank are ambiguous
|
|
if (cls_ == CLASS_DRUID && spellFamilyName == SPELLFAMILY_DRUID && (type_ & CollectorType::MELEE))
|
|
{
|
|
uint32 castingFlagsA = 0x4 | 0x2 | 0x1 | 0x200000; // starfire | moonfire | wrath | insect swarm
|
|
uint32 castingFlagsB = 0x0;
|
|
uint32 castingFlagsC = 0x0;
|
|
flag96 invalidFlags = {castingFlagsA, castingFlagsB, castingFlagsC};
|
|
if (spelFalimyFlags & invalidFlags)
|
|
return false;
|
|
}
|
|
|
|
if (cls_ == CLASS_PALADIN && spellFamilyName == SPELLFAMILY_PALADIN && (type_ & CollectorType::MELEE_TANK))
|
|
{
|
|
uint32 retributionFlagsA = 0x0;
|
|
uint32 retributionFlagsB = 0x8000; // crusader strike
|
|
uint32 retributionFlagsC = 0x0;
|
|
flag96 invalidFlags = {retributionFlagsA, retributionFlagsB, retributionFlagsC};
|
|
if (spelFalimyFlags & invalidFlags)
|
|
return false;
|
|
}
|
|
|
|
if (cls_ == CLASS_PALADIN && spellFamilyName == SPELLFAMILY_PALADIN && (type_ & CollectorType::MELEE_DMG))
|
|
{
|
|
uint32 retributionFlagsA = 0x0;
|
|
uint32 retributionFlagsB = 0x100000 | 0x40; // shield of righteouness | holy shield
|
|
uint32 retributionFlagsC = 0x0;
|
|
flag96 invalidFlags = {retributionFlagsA, retributionFlagsB, retributionFlagsC};
|
|
if (spelFalimyFlags & invalidFlags)
|
|
return false;
|
|
}
|
|
|
|
if (cls_ == CLASS_SHAMAN && spellFamilyName == SPELLFAMILY_SHAMAN && (type_ & CollectorType::SPELL_DMG))
|
|
{
|
|
uint32 meleeFlagsA = 0x0;
|
|
uint32 meleeFlagsB = 0x1000010; // stromstrike
|
|
uint32 meleeFlagsC = 0x4; // lava lash
|
|
flag96 invalidFlags = {meleeFlagsA, meleeFlagsB, meleeFlagsC};
|
|
if (spelFalimyFlags & invalidFlags)
|
|
return false;
|
|
}
|
|
|
|
if (cls_ == CLASS_SHAMAN && spellFamilyName == SPELLFAMILY_SHAMAN && (type_ & CollectorType::MELEE_DMG))
|
|
{
|
|
uint32 casterFlagsA = 0x0;
|
|
uint32 casterFlagsB = 0x1000; // lava burst
|
|
uint32 casterFlagsC = 0x0;
|
|
flag96 invalidFlags = {casterFlagsA, casterFlagsB, casterFlagsC};
|
|
if (spelFalimyFlags & invalidFlags)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|