diff --git a/src/server/game/Miscellaneous/SharedDefines.h b/src/server/game/Miscellaneous/SharedDefines.h index 848f908ba8..4677d5fcad 100644 --- a/src/server/game/Miscellaneous/SharedDefines.h +++ b/src/server/game/Miscellaneous/SharedDefines.h @@ -509,7 +509,7 @@ enum SpellAttr4 SPELL_ATTR4_TRIGGER_ACTIVATE = 0x00000200, // 9 initially disabled / trigger activate from event (Execute, Riposte, Deep Freeze end other) SPELL_ATTR4_SPELL_VS_EXTEND_COST = 0x00000400, // 10 Rogue Shiv have this flag SPELL_ATTR4_UNK11 = 0x00000800, // 11 - SPELL_ATTR4_UNK12 = 0x00001000, // 12 + SPELL_ATTR4_AURA_IS_BUFF = 0x00001000, // 12 Aura Is Buff SPELL_ATTR4_UNK13 = 0x00002000, // 13 SPELL_ATTR4_DAMAGE_DOESNT_BREAK_AURAS = 0x00004000, // 14 doesn't break auras by damage from these spells SPELL_ATTR4_HIDDEN_IN_SPELLBOOK = 0x00008000, // 15 diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp index e309586e01..3f82a44692 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -2911,7 +2911,8 @@ SpellMissInfo Spell::DoSpellHitOnUnit(Unit* unit, uint32 effectMask, bool scaleA { for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) { - if ((effectMask & (1 << i)) && !aurSpellInfo->IsPositiveEffect(i)) + // mod duration only for effects applying aura! + if ((aura_effmask & (1 << i)) && !aurSpellInfo->IsPositiveEffect(i)) { positive = false; break; diff --git a/src/server/game/Spells/SpellInfo.cpp b/src/server/game/Spells/SpellInfo.cpp index 4482658748..689062f822 100644 --- a/src/server/game/Spells/SpellInfo.cpp +++ b/src/server/game/Spells/SpellInfo.cpp @@ -3825,73 +3825,86 @@ void SpellInfo::_InitializeExplicitTargetMask() ExplicitTargetMask = targetMask; } -bool SpellInfo::_IsPositiveEffect(uint8 effIndex, bool deep) const +inline bool _isPositiveTarget(SpellInfo const* spellInfo, uint8 effIndex) { - // not found a single positive spell with this attribute - if (HasAttribute(SPELL_ATTR0_AURA_IS_DEBUFF)) + if (!spellInfo->Effects[effIndex].IsEffect()) + return true; + + return (spellInfo->Effects[effIndex].TargetA.GetCheckType() != TARGET_CHECK_ENEMY && + spellInfo->Effects[effIndex].TargetB.GetCheckType() != TARGET_CHECK_ENEMY); +} + +bool _isPositiveEffectImpl(SpellInfo const* spellInfo, uint8 effIndex, std::unordered_set>& visited) +{ + if (!spellInfo->Effects[effIndex].IsEffect()) + return true; + + // attribute may be already set in DB + if (!spellInfo->IsPositiveEffect(effIndex)) return false; - switch (SpellFamilyName) + // passive auras like talents are all positive + if (spellInfo->IsPassive()) + return true; + + // not found a single positive spell with this attribute + if (spellInfo->HasAttribute(SPELL_ATTR0_AURA_IS_DEBUFF)) + return false; + + if (spellInfo->HasAttribute(SPELL_ATTR4_AURA_IS_BUFF)) + return true; + + visited.insert({ spellInfo->Id, effIndex }); + + int32 bp = spellInfo->Effects[effIndex].CalcValue(); + switch (spellInfo->SpellFamilyName) { case SPELLFAMILY_GENERIC: - switch (Id) + switch (spellInfo->Id) { - case 29214: // Wrath of the Plaguebringer - case 34700: // Allergic Reaction - case 54836: // Wrath of the Plaguebringer - case 41914: // Parasitic Shadowfiend (Illidan) - case 41917: // Parasitic Shadowfiend (Illidan) + case 40268: // Spiritual Vengeance, Teron Gorefiend, Black Temple + case 61987: // Avenging Wrath Marker + case 61988: // Divine Shield exclude aura + case 64412: // Phase Punch, Algalon the Observer, Ulduar + case 72410: // Rune of Blood, Saurfang, Icecrown Citadel + case 71204: // Touch of Insignificance, Lady Deathwhisper, Icecrown Citadel return false; + case 24732: // Bat Costume case 30877: // Tag Murloc case 61716: // Rabbit Costume case 61734: // Noblegarden Bunny case 62344: // Fists of Stone + case 50344: // Dream Funnel case 61819: // Manabonked! (item) case 61834: // Manabonked! (minigob) - return true; - default: - break; - } - break; - case SPELLFAMILY_MAGE: - // Impact - if (SpellIconID == 45) - return true; - // Arcane Missiles - if (SpellFamilyFlags[0] == 0x00000800) - return false; - break; - case SPELLFAMILY_PRIEST: - switch (Id) - { - case 64844: // Divine Hymn - case 64904: // Hymn of Hope - case 47585: // Dispersion + case 73523: // Rigor Mortis return true; default: break; } break; case SPELLFAMILY_ROGUE: - switch (Id) + switch (spellInfo->Id) { // Envenom must be considered as a positive effect even though it deals damage case 32645: // Envenom - // Slice and Dice. Prevents breaking Stealth - case 5171: // Slice and Dice (Rank 1) - case 6774: // Slice and Dice (Rank 2) return true; + case 40251: // Shadow of Death, Teron Gorefiend, Black Temple + return false; default: break; } break; - case SPELLFAMILY_PALADIN: + case SPELLFAMILY_WARRIOR: + // Slam, Execute + if ((spellInfo->SpellFamilyFlags[0] & 0x20200000) != 0) + return false; break; default: break; } - switch (Mechanic) + switch (spellInfo->Mechanic) { case MECHANIC_IMMUNE_SHIELD: return true; @@ -3900,223 +3913,373 @@ bool SpellInfo::_IsPositiveEffect(uint8 effIndex, bool deep) const } // Special case: effects which determine positivity of whole spell + if (spellInfo->HasAttribute(SPELL_ATTR1_DONT_REFRESH_DURATION_ON_RECAST)) // @todo: rename to and handle SPELL_ATTR1_AURA_UNIQUE + { + // check for targets, there seems to be an assortment of dummy triggering spells that should be negative + for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) + if (!_isPositiveTarget(spellInfo, i)) + return false; + } + for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) { - if (Effects[i].IsAura()) + switch (spellInfo->Effects[i].Effect) { - switch (Effects[i].ApplyAuraName) + case SPELL_EFFECT_HEAL: + case SPELL_EFFECT_LEARN_SPELL: + case SPELL_EFFECT_SKILL_STEP: + case SPELL_EFFECT_HEAL_PCT: + case SPELL_EFFECT_ENERGIZE_PCT: + return true; + case SPELL_EFFECT_INSTAKILL: + if (i != effIndex && // for spells like 38044: instakill effect is negative but auras on target must count as buff + spellInfo->Effects[i].TargetA.GetTarget() == spellInfo->Effects[effIndex].TargetA.GetTarget() && + spellInfo->Effects[i].TargetB.GetTarget() == spellInfo->Effects[effIndex].TargetB.GetTarget()) + return false; + break; + default: + break; + } + + if (spellInfo->Effects[i].IsAura()) + { + switch (spellInfo->Effects[i].ApplyAuraName) { case SPELL_AURA_MOD_STEALTH: + case SPELL_AURA_MOD_UNATTACKABLE: return true; + case SPELL_AURA_SCHOOL_HEAL_ABSORB: case SPELL_AURA_CHANNEL_DEATH_ITEM: case SPELL_AURA_EMPATHY: return false; + default: + break; } } } - switch (Effects[effIndex].Effect) + switch (spellInfo->Effects[effIndex].Effect) { - case SPELL_EFFECT_DUMMY: - // some explicitly required dummy effect sets - switch (Id) - { - case 28441: - return false; // AB Effect 000 - default: - break; - } - break; - // always positive effects (check before target checks that provided non-positive result in some case for positive effects) - case SPELL_EFFECT_HEAL: - case SPELL_EFFECT_LEARN_SPELL: - case SPELL_EFFECT_SKILL_STEP: - case SPELL_EFFECT_HEAL_PCT: - case SPELL_EFFECT_ENERGIZE_PCT: - return true; + case SPELL_EFFECT_WEAPON_DAMAGE: + case SPELL_EFFECT_WEAPON_DAMAGE_NOSCHOOL: + case SPELL_EFFECT_NORMALIZED_WEAPON_DMG: + case SPELL_EFFECT_WEAPON_PERCENT_DAMAGE: + case SPELL_EFFECT_SCHOOL_DAMAGE: + case SPELL_EFFECT_ENVIRONMENTAL_DAMAGE: + case SPELL_EFFECT_HEALTH_LEECH: + case SPELL_EFFECT_INSTAKILL: + case SPELL_EFFECT_POWER_DRAIN: + case SPELL_EFFECT_STEAL_BENEFICIAL_BUFF: + case SPELL_EFFECT_INTERRUPT_CAST: + case SPELL_EFFECT_PICKPOCKET: + case SPELL_EFFECT_GAMEOBJECT_DAMAGE: + case SPELL_EFFECT_DURABILITY_DAMAGE: + case SPELL_EFFECT_DURABILITY_DAMAGE_PCT: case SPELL_EFFECT_APPLY_AREA_AURA_ENEMY: + case SPELL_EFFECT_TAMECREATURE: + case SPELL_EFFECT_DISTRACT: return false; - - // non-positive aura use - case SPELL_EFFECT_APPLY_AURA: - case SPELL_EFFECT_APPLY_AURA_2: - case SPELL_EFFECT_APPLY_AREA_AURA_FRIEND: - { - switch (Effects[effIndex].ApplyAuraName) + case SPELL_EFFECT_ENERGIZE: + case SPELL_EFFECT_ENERGIZE_PCT: + case SPELL_EFFECT_HEAL_PCT: + case SPELL_EFFECT_HEAL_MAX_HEALTH: + case SPELL_EFFECT_HEAL_MECHANICAL: + return true; + case SPELL_EFFECT_KNOCK_BACK: + case SPELL_EFFECT_CHARGE: + case SPELL_EFFECT_PERSISTENT_AREA_AURA: + case SPELL_EFFECT_ATTACK_ME: + case SPELL_EFFECT_POWER_BURN: + // check targets + if (!_isPositiveTarget(spellInfo, effIndex)) + return false; + break; + case SPELL_EFFECT_DISPEL: + // non-positive dispel + switch (spellInfo->Effects[effIndex].MiscValue) { - case SPELL_AURA_MOD_DAMAGE_DONE: // dependent from bas point sign (negative -> negative) - case SPELL_AURA_MOD_STAT: - case SPELL_AURA_MOD_SKILL: - case SPELL_AURA_MOD_DODGE_PERCENT: - case SPELL_AURA_MOD_HEALING_PCT: - case SPELL_AURA_MOD_HEALING_DONE: - case SPELL_AURA_MOD_DAMAGE_PERCENT_DONE: - if (Effects[effIndex].CalcValue() < 0) - return false; - break; - case SPELL_AURA_MOD_DAMAGE_TAKEN: // dependent from bas point sign (positive -> negative) - if (Effects[effIndex].CalcValue() > 0) - return false; - break; - case SPELL_AURA_MOD_CRIT_PCT: - case SPELL_AURA_MOD_SPELL_CRIT_CHANCE: - if (Effects[effIndex].CalcValue() > 0) - return true; // some expected positive spells have SPELL_ATTR1_NEGATIVE - break; - case SPELL_AURA_ADD_TARGET_TRIGGER: - return true; - case SPELL_AURA_PERIODIC_TRIGGER_SPELL_WITH_VALUE: - case SPELL_AURA_PERIODIC_TRIGGER_SPELL: - if (!deep) - { - if (SpellInfo const* spellTriggeredProto = sSpellMgr->GetSpellInfo(Effects[effIndex].TriggerSpell)) - { - // negative targets of main spell return early - for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) - { - if (!spellTriggeredProto->Effects[i].Effect) - continue; - // if non-positive trigger cast targeted to positive target this main cast is non-positive - // this will place this spell auras as debuffs - if (_IsPositiveTarget(spellTriggeredProto->Effects[i].TargetA.GetTarget(), spellTriggeredProto->Effects[i].TargetB.GetTarget()) && !spellTriggeredProto->_IsPositiveEffect(i, true)) - return false; - } - } - } - break; - case SPELL_AURA_PROC_TRIGGER_SPELL: - // many positive auras have negative triggered spells at damage for example and this not make it negative (it can be canceled for example) - break; - case SPELL_AURA_MOD_STUN: //have positive and negative spells, we can't sort its correctly at this moment. - if (effIndex == 0 && Effects[1].Effect == 0 && Effects[2].Effect == 0) - return false; // but all single stun aura spells is negative - break; - case SPELL_AURA_MOD_PACIFY_SILENCE: - if (Id == 24740) // Wisp Costume - return true; + case DISPEL_STEALTH: + case DISPEL_INVISIBILITY: + case DISPEL_ENRAGE: return false; - case SPELL_AURA_MOD_ROOT: - case SPELL_AURA_MOD_SILENCE: - case SPELL_AURA_GHOST: - case SPELL_AURA_PERIODIC_LEECH: - case SPELL_AURA_MOD_STALKED: - case SPELL_AURA_PERIODIC_DAMAGE_PERCENT: - case SPELL_AURA_PREVENT_RESURRECTION: - return false; - case SPELL_AURA_PERIODIC_DAMAGE: // used in positive spells also. - // part of negative spell if cast at self (prevent cancel) - if (Effects[effIndex].TargetA.GetTarget() == TARGET_UNIT_CASTER) - return false; - break; - case SPELL_AURA_MOD_DECREASE_SPEED: // used in positive spells also - // part of positive spell if cast at self - if (Effects[effIndex].TargetA.GetTarget() != TARGET_UNIT_CASTER) - return false; - // but not this if this first effect (didn't find better check) - if (HasAttribute(SPELL_ATTR0_AURA_IS_DEBUFF) && effIndex == 0) - return false; - break; - case SPELL_AURA_MECHANIC_IMMUNITY: - { - // non-positive immunities - switch (Effects[effIndex].MiscValue) - { - case MECHANIC_BANDAGE: - case MECHANIC_SHIELD: - case MECHANIC_MOUNT: - case MECHANIC_INVULNERABILITY: - return false; - default: - break; - } - break; - } - case SPELL_AURA_ADD_FLAT_MODIFIER: // mods - case SPELL_AURA_ADD_PCT_MODIFIER: - { - // non-positive mods - switch (Effects[effIndex].MiscValue) - { - case SPELLMOD_COST: // dependent from bas point sign (negative -> positive) - if (Effects[effIndex].CalcValue() > 0) - { - if (!deep) - { - bool negative = true; - for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) - { - if (i != effIndex) - if (_IsPositiveEffect(i, true)) - { - negative = false; - break; - } - } - if (negative) - return false; - } - } - break; - default: - break; - } - break; - } default: break; } + + // also check targets + if (!_isPositiveTarget(spellInfo, effIndex)) + return false; + break; + case SPELL_EFFECT_DISPEL_MECHANIC: + if (!_isPositiveTarget(spellInfo, effIndex)) + { + // non-positive mechanic dispel on negative target + switch (spellInfo->Effects[effIndex].MiscValue) + { + case MECHANIC_BANDAGE: + case MECHANIC_SHIELD: + case MECHANIC_MOUNT: + case MECHANIC_INVULNERABILITY: + return false; + default: + break; + } + } + break; + case SPELL_EFFECT_THREAT: + case SPELL_EFFECT_MODIFY_THREAT_PERCENT: + // check targets AND basepoints + if (!_isPositiveTarget(spellInfo, effIndex) && bp > 0) + return false; break; - } default: break; } - // non-positive targets - if (!_IsPositiveTarget(Effects[effIndex].TargetA.GetTarget(), Effects[effIndex].TargetB.GetTarget())) - return false; - - // negative spell if triggered spell is negative - if (!deep && !Effects[effIndex].ApplyAuraName && Effects[effIndex].TriggerSpell) + if (spellInfo->Effects[effIndex].IsAura()) { - if (SpellInfo const* spellTriggeredProto = sSpellMgr->GetSpellInfo(Effects[effIndex].TriggerSpell)) - if (!spellTriggeredProto->_IsPositiveSpell()) + // non-positive aura use + switch (spellInfo->Effects[effIndex].ApplyAuraName) + { + case SPELL_AURA_MOD_DAMAGE_DONE: // dependent from basepoint sign (negative -> negative) + case SPELL_AURA_MOD_STAT: + case SPELL_AURA_MOD_SKILL: + case SPELL_AURA_MOD_DODGE_PERCENT: + case SPELL_AURA_MOD_HEALING_DONE: + case SPELL_AURA_MOD_DAMAGE_DONE_CREATURE: + case SPELL_AURA_OBS_MOD_HEALTH: + case SPELL_AURA_OBS_MOD_POWER: + case SPELL_AURA_MOD_CRIT_PCT: + case SPELL_AURA_MOD_HIT_CHANCE: + case SPELL_AURA_MOD_SPELL_HIT_CHANCE: + case SPELL_AURA_MOD_SPELL_CRIT_CHANCE: + case SPELL_AURA_MOD_RANGED_HASTE: + case SPELL_AURA_MOD_CASTING_SPEED_NOT_STACK: + case SPELL_AURA_HASTE_SPELLS: + case SPELL_AURA_MOD_RESISTANCE: + case SPELL_AURA_MOD_RESISTANCE_EXCLUSIVE: + case SPELL_AURA_MOD_DETECT_RANGE: + case SPELL_AURA_MOD_INCREASE_HEALTH_PERCENT: + case SPELL_AURA_MOD_TOTAL_STAT_PERCENTAGE: + case SPELL_AURA_MOD_INCREASE_SWIM_SPEED: + if (bp < 0) + return false; + break; + case SPELL_AURA_MOD_ATTACKSPEED: // some buffs have negative bp, check both target and bp + case SPELL_AURA_MOD_MELEE_HASTE: + case SPELL_AURA_MOD_RESISTANCE_PCT: + case SPELL_AURA_MOD_RATING: + case SPELL_AURA_MOD_ATTACK_POWER: + case SPELL_AURA_MOD_RANGED_ATTACK_POWER: + case SPELL_AURA_MOD_DAMAGE_PERCENT_DONE: + case SPELL_AURA_MOD_SPEED_SLOW_ALL: + case SPELL_AURA_MELEE_SLOW: + case SPELL_AURA_MOD_ATTACK_POWER_PCT: + case SPELL_AURA_MOD_HEALING_DONE_PERCENT: + case SPELL_AURA_MOD_HEALING_PCT: + if (!_isPositiveTarget(spellInfo, effIndex) && bp < 0) + return false; + break; + case SPELL_AURA_MOD_DAMAGE_TAKEN: // dependent from basepoint sign (positive -> negative) + case SPELL_AURA_MOD_MELEE_DAMAGE_TAKEN: + case SPELL_AURA_MOD_MELEE_DAMAGE_TAKEN_PCT: + case SPELL_AURA_MOD_SCHOOL_CRIT_DMG_TAKEN: + case SPELL_AURA_MOD_ATTACKER_SPELL_CRIT_CHANCE: + case SPELL_AURA_MOD_POWER_COST_SCHOOL: + case SPELL_AURA_MOD_POWER_COST_SCHOOL_PCT: + if (bp > 0) + return false; + break; + case SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN: // check targets and basepoints (ex Recklessness) + if (!_isPositiveTarget(spellInfo, effIndex) && bp > 0) + return false; + break; + case SPELL_AURA_ADD_TARGET_TRIGGER: + return true; + case SPELL_AURA_PERIODIC_TRIGGER_SPELL_WITH_VALUE: + case SPELL_AURA_PERIODIC_TRIGGER_SPELL: + if (!_isPositiveTarget(spellInfo, effIndex)) + { + if (SpellInfo const* spellTriggeredProto = sSpellMgr->GetSpellInfo(spellInfo->Effects[effIndex].TriggerSpell)) + { + // negative targets of main spell return early + for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) + { + // already seen this + if (visited.count({ spellTriggeredProto->Id, i }) > 0) + continue; + + if (!spellTriggeredProto->Effects[i].Effect) + continue; + + // if non-positive trigger cast targeted to positive target this main cast is non-positive + // this will place this spell auras as debuffs + if (_isPositiveTarget(spellTriggeredProto, i) && !_isPositiveEffectImpl(spellTriggeredProto, i, visited)) + return false; + } + } + } + break; + case SPELL_AURA_MOD_STUN: + case SPELL_AURA_TRANSFORM: + case SPELL_AURA_MOD_DECREASE_SPEED: + case SPELL_AURA_MOD_FEAR: + case SPELL_AURA_MOD_TAUNT: + // special auras: they may have non negative target but still need to be marked as debuff + // checked again after all effects (SpellInfo::_InitializeSpellPositivity) + case SPELL_AURA_MOD_PACIFY: + case SPELL_AURA_MOD_PACIFY_SILENCE: + case SPELL_AURA_MOD_DISARM: + case SPELL_AURA_MOD_DISARM_OFFHAND: + case SPELL_AURA_MOD_DISARM_RANGED: + case SPELL_AURA_MOD_CHARM: + case SPELL_AURA_AOE_CHARM: + case SPELL_AURA_MOD_POSSESS: + case SPELL_AURA_MOD_LANGUAGE: + case SPELL_AURA_DAMAGE_SHIELD: + case SPELL_AURA_PROC_TRIGGER_SPELL: + case SPELL_AURA_MOD_ATTACKER_MELEE_HIT_CHANCE: + case SPELL_AURA_MOD_ATTACKER_RANGED_HIT_CHANCE: + case SPELL_AURA_MOD_ATTACKER_SPELL_HIT_CHANCE: + case SPELL_AURA_MOD_ATTACKER_MELEE_CRIT_CHANCE: + case SPELL_AURA_MOD_ATTACKER_RANGED_CRIT_CHANCE: + case SPELL_AURA_MOD_ATTACKER_MELEE_CRIT_DAMAGE: + case SPELL_AURA_MOD_ATTACKER_RANGED_CRIT_DAMAGE: + case SPELL_AURA_MOD_ATTACKER_SPELL_AND_WEAPON_CRIT_CHANCE: + // have positive and negative spells, check target + if (!_isPositiveTarget(spellInfo, effIndex)) + return false; + break; + case SPELL_AURA_MOD_CONFUSE: + case SPELL_AURA_MOD_ROOT: + case SPELL_AURA_MOD_SILENCE: + case SPELL_AURA_MOD_DETAUNT: + case SPELL_AURA_GHOST: + case SPELL_AURA_PERIODIC_LEECH: + case SPELL_AURA_PERIODIC_MANA_LEECH: + case SPELL_AURA_MOD_STALKED: + case SPELL_AURA_PREVENT_RESURRECTION: + case SPELL_AURA_PERIODIC_DAMAGE: + case SPELL_AURA_PERIODIC_DAMAGE_PERCENT: + case SPELL_AURA_MELEE_ATTACK_POWER_ATTACKER_BONUS: + case SPELL_AURA_RANGED_ATTACK_POWER_ATTACKER_BONUS: return false; + case SPELL_AURA_MECHANIC_IMMUNITY: + { + // non-positive immunities + switch (spellInfo->Effects[effIndex].MiscValue) + { + case MECHANIC_BANDAGE: + case MECHANIC_SHIELD: + case MECHANIC_MOUNT: + case MECHANIC_INVULNERABILITY: + return false; + default: + break; + } + break; + } + case SPELL_AURA_ADD_FLAT_MODIFIER: // mods + case SPELL_AURA_ADD_PCT_MODIFIER: + { + switch (spellInfo->Effects[effIndex].MiscValue) + { + case SPELLMOD_CASTING_TIME: // dependent from basepoint sign (positive -> negative) + case SPELLMOD_ACTIVATION_TIME: + case SPELLMOD_SPELL_COST_REFUND_ON_FAIL: + case SPELLMOD_GLOBAL_COOLDOWN: + if (bp > 0) + return false; + break; + case SPELLMOD_COOLDOWN: + case SPELLMOD_COST: + if (!spellInfo->IsPositive() && bp > 0) // dependent on prev effects too (ex Arcane Power) + return false; + break; + case SPELLMOD_EFFECT1: // always positive + case SPELLMOD_EFFECT2: + case SPELLMOD_EFFECT3: + case SPELLMOD_ALL_EFFECTS: + case SPELLMOD_THREAT: + case SPELLMOD_DAMAGE_MULTIPLIER: + case SPELLMOD_VALUE_MULTIPLIER: + return true; + case SPELLMOD_DURATION: + case SPELLMOD_CRITICAL_CHANCE: + case SPELLMOD_DAMAGE: + case SPELLMOD_JUMP_TARGETS: + if (!spellInfo->IsPositive() && bp < 0) // dependent on prev effects too + return false; + break; + default: // dependent from basepoint sign (negative -> negative) + if (bp < 0) + return false; + break; + } + break; + } + default: + break; + } + } + // negative spell if triggered spell is negative + if (!spellInfo->Effects[effIndex].ApplyAuraName && spellInfo->Effects[effIndex].TriggerSpell) + { + if (SpellInfo const* spellTriggeredProto = sSpellMgr->GetSpellInfo(spellInfo->Effects[effIndex].TriggerSpell)) + { + // spells with at least one negative effect are considered negative + // some self-applied spells have negative effects but in self casting case negative check ignored. + for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) + { + // already seen this + if (visited.count({ spellTriggeredProto->Id, i }) > 0) + continue; + + if (!spellTriggeredProto->Effects[i].IsEffect()) + continue; + + if (!_isPositiveEffectImpl(spellTriggeredProto, i, visited)) + return false; + } + } } // ok, positive return true; } -bool SpellInfo::_IsPositiveSpell() const +void SpellInfo::_InitializeSpellPositivity() { - // spells with at least one negative effect are considered negative - // some self-applied spells have negative effects but in self casting case negative check ignored. - for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) - if (!_IsPositiveEffect(i, true)) - return false; - return true; -} + std::unordered_set> visited; -bool SpellInfo::_IsPositiveTarget(uint32 targetA, uint32 targetB) -{ - // non-positive targets - switch (targetA) + for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) + if (!_isPositiveEffectImpl(this, i, visited)) + AttributesCu |= (SPELL_ATTR0_CU_NEGATIVE_EFF0 << i); + + // additional checks after effects marked + for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) { - case TARGET_UNIT_NEARBY_ENEMY: - case TARGET_UNIT_TARGET_ENEMY: - case TARGET_UNIT_SRC_AREA_ENEMY: - case TARGET_UNIT_DEST_AREA_ENEMY: - case TARGET_UNIT_CONE_ENEMY_24: - case TARGET_UNIT_CONE_CASTER_TO_DEST_ENEMY: - case TARGET_DEST_DYNOBJ_ENEMY: - case TARGET_DEST_TARGET_ENEMY: - return false; - default: - break; + if (!Effects[i].IsEffect() || !IsPositiveEffect(i)) + continue; + + switch (Effects[i].ApplyAuraName) + { + // has other non positive effect? + // then it should be marked negative despite of targets (ex 8510, 8511, 8893, 10267) + case SPELL_AURA_DUMMY: + case SPELL_AURA_MOD_STUN: + case SPELL_AURA_MOD_FEAR: + case SPELL_AURA_MOD_TAUNT: + case SPELL_AURA_TRANSFORM: + case SPELL_AURA_MOD_ATTACKSPEED: + case SPELL_AURA_MOD_DECREASE_SPEED: + if (!IsPositive()) + AttributesCu |= (SPELL_ATTR0_CU_NEGATIVE_EFF0 << i); + break; + default: + break; + } } - if (targetB) - return _IsPositiveTarget(targetB, 0); - return true; } SpellTargetRestrictionsEntry const* SpellInfo::GetSpellTargetRestrictions() const diff --git a/src/server/game/Spells/SpellInfo.h b/src/server/game/Spells/SpellInfo.h index fe443147e4..bf8b46b258 100644 --- a/src/server/game/Spells/SpellInfo.h +++ b/src/server/game/Spells/SpellInfo.h @@ -628,9 +628,7 @@ class TC_GAME_API SpellInfo private: // loading helpers void _InitializeExplicitTargetMask(); - bool _IsPositiveEffect(uint8 effIndex, bool deep) const; - bool _IsPositiveSpell() const; - static bool _IsPositiveTarget(uint32 targetA, uint32 targetB); + void _InitializeSpellPositivity(); void _LoadSpellSpecific(); void _LoadAuraState(); void _LoadSpellDiminishInfo(); diff --git a/src/server/game/Spells/SpellMgr.cpp b/src/server/game/Spells/SpellMgr.cpp index 46e066afde..13fbd86846 100644 --- a/src/server/game/Spells/SpellMgr.cpp +++ b/src/server/game/Spells/SpellMgr.cpp @@ -2649,7 +2649,20 @@ void SpellMgr::LoadSpellInfoCustomAttributes() continue; } - // TODO: validate attributes + if ((attributes & SPELL_ATTR0_CU_NEGATIVE) != 0) + { + for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) + { + if (spellInfo->Effects[i].IsEffect()) + continue; + + if ((attributes & (SPELL_ATTR0_CU_NEGATIVE_EFF0 << i)) != 0) + { + TC_LOG_ERROR("sql.sql", "Table `spell_custom_attr` has attribute SPELL_ATTR0_CU_NEGATIVE_EFF%u for spell %u with no EFFECT_%u", uint32(i), spellId, uint32(i)); + continue; + } + } + } spellInfo->AttributesCu |= attributes; ++count; @@ -2849,14 +2862,7 @@ void SpellMgr::LoadSpellInfoCustomAttributes() spellInfo->AttributesCu |= SPELL_ATTR0_CU_SCHOOLMASK_NORMAL_WITH_MAGIC; } - if (!spellInfo->_IsPositiveEffect(EFFECT_0, false)) - spellInfo->AttributesCu |= SPELL_ATTR0_CU_NEGATIVE_EFF0; - - if (!spellInfo->_IsPositiveEffect(EFFECT_1, false)) - spellInfo->AttributesCu |= SPELL_ATTR0_CU_NEGATIVE_EFF1; - - if (!spellInfo->_IsPositiveEffect(EFFECT_2, false)) - spellInfo->AttributesCu |= SPELL_ATTR0_CU_NEGATIVE_EFF2; + spellInfo->_InitializeSpellPositivity(); switch (spellInfo->SpellFamilyName) {