#include "StatsCollector.h" #include #include "DBCStores.h" #include "ItemEnchantmentMgr.h" #include "ItemTemplate.h" #include "ObjectMgr.h" #include "PlayerbotAI.h" #include "PlayerbotAIAware.h" #include "SharedDefines.h" #include "SpellAuraDefines.h" #include "SpellInfo.h" #include "SpellMgr.h" #include "UpdateFields.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()) { uint32 val = (proto->Damage[0].DamageMin + proto->Damage[0].DamageMax) * 1000 / 2 / proto->Delay; stats[STATS_TYPE_RANGED_DPS] += val; } else if (proto->IsWeapon()) { uint32 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] += val / 10; break; case ITEM_MOD_HEALTH: stats[STATS_TYPE_STAMINA] += 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; } } int32 StatsCollector::AverageValue(const SpellEffectInfo& effectInfo) { // float basePointsPerLevel = effectInfo.RealPointsPerLevel; //not used, line marked for removal. int32 basePoints = effectInfo.BasePoints; int32 randomPoints = int32(effectInfo.DieSides); switch (randomPoints) { case 0: break; case 1: basePoints += 1; break; default: int32 randvalue = (1 + randomPoints) / 2; 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; }