diff --git a/src/server/game/Entities/Object/Object.cpp b/src/server/game/Entities/Object/Object.cpp index 2411741148..6c6f7e8336 100644 --- a/src/server/game/Entities/Object/Object.cpp +++ b/src/server/game/Entities/Object/Object.cpp @@ -2946,12 +2946,20 @@ void WorldObject::CastSpell(Position const& dest, uint32 spellId, CastSpellExtra } // function based on function Unit::CanAttack from 13850 client -bool WorldObject::IsValidAttackTarget(WorldObject const* target, SpellInfo const* bySpell /*= nullptr*/, bool spellCheck /*= true*/) const +bool WorldObject::IsValidAttackTarget(WorldObject const* target, SpellInfo const* bySpell /*= nullptr*/) const { ASSERT(target); - // can't attack self - if (this == target) + // some positive spells can be casted at hostile target + bool isPositiveSpell = bySpell && bySpell->IsPositive(); + + // can't attack self (spells can, attribute check) + if (!bySpell && this == target) + return false; + + // can't attack unattackable units + Unit const* unitTarget = target->ToUnit(); + if (unitTarget && unitTarget->HasUnitState(UNIT_STATE_UNATTACKABLE)) return false; // can't attack GMs @@ -2959,10 +2967,53 @@ bool WorldObject::IsValidAttackTarget(WorldObject const* target, SpellInfo const return false; Unit const* unit = ToUnit(); - Unit const* targetUnit = target->ToUnit(); + // visibility checks (only units) + if (unit) + { + // can't attack invisible + if (!bySpell || !bySpell->HasAttribute(SPELL_ATTR6_CAN_TARGET_INVISIBLE)) + { + if (!unit->CanSeeOrDetect(target, bySpell && bySpell->IsAffectingArea())) + return false; + } + } + + // can't attack dead + if ((!bySpell || !bySpell->IsAllowingDeadTarget()) && unitTarget && !unitTarget->IsAlive()) + return false; + + // can't attack untargetable + if ((!bySpell || !bySpell->HasAttribute(SPELL_ATTR6_CAN_TARGET_UNTARGETABLE)) && unitTarget && unitTarget->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE)) + return false; + + if (Player const* playerAttacker = ToPlayer()) + { + if (playerAttacker->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_UBER)) + return false; + } + + // check flags + if (unitTarget && unitTarget->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_TAXI_FLIGHT | UNIT_FLAG_NOT_ATTACKABLE_1 | UNIT_FLAG_NOT_ATTACKABLE_2)) + return false; + + // ignore immunity flags when assisting + if (isPositiveSpell && !bySpell->HasAttribute(SPELL_ATTR6_ASSIST_IGNORE_IMMUNE_FLAG)) + { + if (unit && !unit->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED) && unitTarget && unitTarget->IsImmuneToNPC()) + return false; + + if (unitTarget && !unitTarget->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED) && unit && unit->IsImmuneToNPC()) + return false; + + if (unit && unit->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED) && unitTarget && unitTarget->IsImmuneToPC()) + return false; + + if (unitTarget && unitTarget->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED) && unit && unit->IsImmuneToPC()) + return false; + } // CvC case - can attack each other only when one of them is hostile - if (unit && !unit->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED) && targetUnit && !targetUnit->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED)) + if (ToUnit() && !HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED) && target->ToUnit() && !target->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED)) return IsHostileTo(target) || target->IsHostileTo(this); // PvP, PvC, CvP case @@ -2970,15 +3021,15 @@ bool WorldObject::IsValidAttackTarget(WorldObject const* target, SpellInfo const if (IsFriendlyTo(target) || target->IsFriendlyTo(this)) return false; - Player const* playerAffectingAttacker = unit && unit->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED) ? GetAffectingPlayer() : nullptr; - Player const* playerAffectingTarget = targetUnit && targetUnit->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED) ? target->GetAffectingPlayer() : nullptr; + Player const* playerAffectingAttacker = ToUnit() && HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED) ? GetAffectingPlayer() : nullptr; + Player const* playerAffectingTarget = target->ToUnit() && target->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED) ? target->GetAffectingPlayer() : nullptr; // Not all neutral creatures can be attacked (even some unfriendly faction does not react aggresive to you, like Sporaggar) if ((playerAffectingAttacker && !playerAffectingTarget) || (!playerAffectingAttacker && playerAffectingTarget)) { Player const* player = playerAffectingAttacker ? playerAffectingAttacker : playerAffectingTarget; - if (Unit const* creature = playerAffectingAttacker ? targetUnit : unit) + if (Unit const* creature = playerAffectingAttacker ? target->ToUnit() : ToUnit()) { if (creature->IsContestedGuard() && player->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_CONTESTED_PVP)) return true; @@ -2998,83 +3049,7 @@ bool WorldObject::IsValidAttackTarget(WorldObject const* target, SpellInfo const if (creatureAttacker && (creatureAttacker->GetCreatureTemplate()->type_flags & CREATURE_TYPE_FLAG_TREAT_AS_RAID_UNIT)) return false; - if (!bySpell) - spellCheck = false; - if (spellCheck && !IsValidSpellAttackTarget(target, bySpell)) - return false; - - return true; -} - -bool WorldObject::IsValidSpellAttackTarget(WorldObject const* target, SpellInfo const* bySpell) const -{ - ASSERT(target); - ASSERT(bySpell); - - // can't attack unattackable units - Unit const* unitTarget = target->ToUnit(); - if (unitTarget && unitTarget->HasUnitState(UNIT_STATE_UNATTACKABLE)) - return false; - - Unit const* unit = ToUnit(); - // visibility checks (only units) - if (unit) - { - // can't attack invisible - if (!bySpell->HasAttribute(SPELL_ATTR6_CAN_TARGET_INVISIBLE)) - { - if (!unit->CanSeeOrDetect(target, bySpell->IsAffectingArea())) - return false; - - /* - else if (!obj) - { - // ignore stealth for aoe spells. Ignore stealth if target is player and unit in combat with same player - bool const ignoreStealthCheck = (bySpell && bySpell->IsAffectingArea()) || - (target->GetTypeId() == TYPEID_PLAYER && target->HasStealthAura() && IsInCombatWith(target)); - - if (!CanSeeOrDetect(target, ignoreStealthCheck)) - return false; - } - */ - } - } - - // can't attack dead - if ((!bySpell || !bySpell->IsAllowingDeadTarget()) && unitTarget && !unitTarget->IsAlive()) - return false; - - // can't attack untargetable - if ((!bySpell || !bySpell->HasAttribute(SPELL_ATTR6_CAN_TARGET_UNTARGETABLE)) - && unitTarget && unitTarget->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE)) - return false; - - if (Player const* playerAttacker = ToPlayer()) - { - if (playerAttacker->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_UBER)) - return false; - } - - // check flags - if (unitTarget && unitTarget->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_TAXI_FLIGHT | UNIT_FLAG_NOT_ATTACKABLE_1 | UNIT_FLAG_NOT_ATTACKABLE_2)) - return false; - - if (unit && !unit->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED) && unitTarget->IsImmuneToNPC()) - return false; - - if (!bySpell->HasAttribute(SPELL_ATTR8_ATTACK_IGNORE_IMMUNE_TO_PC_FLAG)) - { - if (unitTarget && !unitTarget->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED) && unit && unit->IsImmuneToNPC()) - return false; - - if (unit && unit->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED) && unitTarget->IsImmuneToPC()) - return false; - } - - // check duel - before sanctuary checks - Player const* playerAffectingAttacker = unit && unit->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED) ? GetAffectingPlayer() : nullptr; - Player const* playerAffectingTarget = unitTarget && unitTarget->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED) ? target->GetAffectingPlayer() : nullptr; if (playerAffectingAttacker && playerAffectingTarget) if (playerAffectingAttacker->duel && playerAffectingAttacker->duel->opponent == playerAffectingTarget && playerAffectingAttacker->duel->startTime != 0) return true; @@ -3101,36 +3076,17 @@ bool WorldObject::IsValidSpellAttackTarget(WorldObject const* target, SpellInfo } // function based on function Unit::CanAssist from 13850 client -bool WorldObject::IsValidAssistTarget(WorldObject const* target, SpellInfo const* bySpell /*= nullptr*/, bool spellCheck /*= true*/) const +bool WorldObject::IsValidAssistTarget(WorldObject const* target, SpellInfo const* bySpell /*= nullptr*/) const { ASSERT(target); + // some negative spells can be casted at friendly target + bool isNegativeSpell = bySpell && !bySpell->IsPositive(); + // can assist to self if (this == target) return true; - // can't assist GMs - if (target->GetTypeId() == TYPEID_PLAYER && target->ToPlayer()->IsGameMaster()) - return false; - - // can't assist non-friendly targets - if (GetReactionTo(target) < REP_NEUTRAL && target->GetReactionTo(this) < REP_NEUTRAL && (!ToCreature() || !(ToCreature()->GetCreatureTemplate()->type_flags & CREATURE_TYPE_FLAG_TREAT_AS_RAID_UNIT))) - return false; - - if (!bySpell) - spellCheck = false; - - if (spellCheck && !IsValidSpellAssistTarget(target, bySpell)) - return false; - - return true; -} - -bool WorldObject::IsValidSpellAssistTarget(WorldObject const* target, SpellInfo const* bySpell) const -{ - ASSERT(target); - ASSERT(bySpell); - // can't assist unattackable units Unit const* unitTarget = target->ToUnit(); @@ -3138,6 +3094,10 @@ bool WorldObject::IsValidSpellAssistTarget(WorldObject const* target, SpellInfo if (unitTarget && unitTarget->HasUnitState(UNIT_STATE_UNATTACKABLE)) return false; + // can't assist GMs + if (target->GetTypeId() == TYPEID_PLAYER && target->ToPlayer()->IsGameMaster()) + return false; + // can't assist own vehicle or passenger Unit const* unit = ToUnit(); if (unit && unitTarget && unit->GetVehicle()) @@ -3150,18 +3110,22 @@ bool WorldObject::IsValidSpellAssistTarget(WorldObject const* target, SpellInfo } // can't assist invisible - if (!bySpell->HasAttribute(SPELL_ATTR6_CAN_TARGET_INVISIBLE) && !CanSeeOrDetect(target, bySpell->IsAffectingArea())) + if ((!bySpell || !bySpell->HasAttribute(SPELL_ATTR6_CAN_TARGET_INVISIBLE)) && !CanSeeOrDetect(target, bySpell && bySpell->IsAffectingArea())) return false; // can't assist dead - if (!bySpell->IsAllowingDeadTarget() && unitTarget && !unitTarget->IsAlive()) + if ((!bySpell || !bySpell->IsAllowingDeadTarget()) && unitTarget && !unitTarget->IsAlive()) return false; // can't assist untargetable if ((!bySpell || !bySpell->HasAttribute(SPELL_ATTR6_CAN_TARGET_UNTARGETABLE)) && unitTarget && unitTarget->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE)) return false; - if (!bySpell || !bySpell->HasAttribute(SPELL_ATTR6_ASSIST_IGNORE_IMMUNE_FLAG)) + // check flags for negative spells + if (isNegativeSpell && unitTarget && unitTarget->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_TAXI_FLIGHT | UNIT_FLAG_NOT_ATTACKABLE_1 | UNIT_FLAG_NOT_ATTACKABLE_2)) + return false; + + if (isNegativeSpell || !bySpell || !bySpell->HasAttribute(SPELL_ATTR6_ASSIST_IGNORE_IMMUNE_FLAG)) { if (HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED)) { @@ -3175,6 +3139,10 @@ bool WorldObject::IsValidSpellAssistTarget(WorldObject const* target, SpellInfo } } + // can't assist non-friendly targets + if (GetReactionTo(target) < REP_NEUTRAL && target->GetReactionTo(this) < REP_NEUTRAL && (!ToCreature() || !(ToCreature()->GetCreatureTemplate()->type_flags & CREATURE_TYPE_FLAG_TREAT_AS_RAID_UNIT))) + return false; + // PvP case if (unitTarget && unitTarget->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED)) { @@ -3202,7 +3170,7 @@ bool WorldObject::IsValidSpellAssistTarget(WorldObject const* target, SpellInfo // !target->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PVP_ATTACKABLE) && else if (unit && unit->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED)) { - if (!bySpell->HasAttribute(SPELL_ATTR6_ASSIST_IGNORE_IMMUNE_FLAG)) + if (!bySpell || !bySpell->HasAttribute(SPELL_ATTR6_ASSIST_IGNORE_IMMUNE_FLAG)) if (unitTarget && !unitTarget->IsPvP()) if (Creature const* creatureTarget = target->ToCreature()) return ((creatureTarget->GetCreatureTemplate()->type_flags & CREATURE_TYPE_FLAG_TREAT_AS_RAID_UNIT) || (creatureTarget->GetCreatureTemplate()->type_flags & CREATURE_TYPE_FLAG_CAN_ASSIST)); diff --git a/src/server/game/Entities/Object/Object.h b/src/server/game/Entities/Object/Object.h index c2e2c9dcb6..39b4b81169 100644 --- a/src/server/game/Entities/Object/Object.h +++ b/src/server/game/Entities/Object/Object.h @@ -468,11 +468,8 @@ class TC_GAME_API WorldObject : public Object, public WorldLocation void CastSpell(WorldObject* target, uint32 spellId, CastSpellExtraArgs const& args = { }); void CastSpell(Position const& dest, uint32 spellId, CastSpellExtraArgs const& args = { }); - bool IsValidAttackTarget(WorldObject const* target, SpellInfo const* bySpell = nullptr, bool spellCheck = true) const; - bool IsValidSpellAttackTarget(WorldObject const* target, SpellInfo const* bySpell) const; - - bool IsValidAssistTarget(WorldObject const* target, SpellInfo const* bySpell = nullptr, bool spellCheck = true) const; - bool IsValidSpellAssistTarget(WorldObject const* target, SpellInfo const* bySpell) const; + bool IsValidAttackTarget(WorldObject const* target, SpellInfo const* bySpell = nullptr) const; + bool IsValidAssistTarget(WorldObject const* target, SpellInfo const* bySpell = nullptr) const; Unit* GetMagicHitRedirectTarget(Unit* victim, SpellInfo const* spellInfo); diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp index 3c11200712..3b461b4b77 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -2452,6 +2452,7 @@ void Spell::TargetInfo::PreprocessTarget(Spell* spell) if (missInfo != SPELL_MISS_MISS) spell->m_caster->ToUnit()->SendSpellMiss(unit, spell->m_spellInfo->Id, missInfo); spell->m_damage = 0; + spell->m_healing = 0; _spellHitTarget = nullptr; } } @@ -2736,6 +2737,20 @@ void Spell::TargetInfo::DoDamageAndTriggers(Spell* spell) else if (spell->m_caster->GetTypeId() == TYPEID_GAMEOBJECT && spell->m_caster->ToGameObject()->AI()) spell->m_caster->ToGameObject()->AI()->SpellHitTarget(_spellHitTarget, spell->m_spellInfo); + if (spell->_spellAura) + { + if (AuraApplication* aurApp = spell->_spellAura->GetApplicationOfTarget(_spellHitTarget->GetGUID())) + { + // only apply unapplied effects (for reapply case) + uint8 effMask = EffectMask & aurApp->GetEffectsToApply(); + for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) + if ((effMask & (1 << i)) && aurApp->HasEffect(i)) + effMask &= ~(1 << i); + + _spellHitTarget->_ApplyAura(aurApp, effMask); + } + } + // Needs to be called after dealing damage/healing to not remove breaking on damage auras spell->DoTriggersOnSpellHit(_spellHitTarget, EffectMask); @@ -2838,7 +2853,7 @@ SpellMissInfo Spell::PreprocessSpellHit(Unit* unit, bool scaleAura, TargetInfo& else if (m_caster->IsFriendlyTo(unit)) { // for delayed spells ignore negative spells (after duel end) for friendly targets - if (m_spellInfo->Speed > 0.0f && unit->GetTypeId() == TYPEID_PLAYER && !IsPositive() && !m_caster->IsValidSpellAttackTarget(unit, m_spellInfo)) + if (m_spellInfo->Speed > 0.0f && unit->GetTypeId() == TYPEID_PLAYER && !IsPositive() && !m_caster->IsValidAssistTarget(unit, m_spellInfo)) return SPELL_MISS_EVADE; // assisting case, healing and resurrection @@ -8528,13 +8543,13 @@ bool WorldObjectSpellTargetCheck::operator()(WorldObject* target) const case TARGET_CHECK_ENEMY: if (unitTarget->IsTotem()) return false; - if (!target->IsCorpse() && !_caster->IsValidAttackTarget(unitTarget, _spellInfo, false)) + if (!target->IsCorpse() && !_caster->IsValidAttackTarget(unitTarget, _spellInfo)) return false; break; case TARGET_CHECK_ALLY: if (unitTarget->IsTotem()) return false; - if (!target->IsCorpse() && !_caster->IsValidAssistTarget(unitTarget, _spellInfo, false)) + if (!target->IsCorpse() && !_caster->IsValidAssistTarget(unitTarget, _spellInfo)) return false; break; case TARGET_CHECK_PARTY: @@ -8542,7 +8557,7 @@ bool WorldObjectSpellTargetCheck::operator()(WorldObject* target) const return false; if (unitTarget->IsTotem()) return false; - if (!target->IsCorpse() && !_caster->IsValidAssistTarget(unitTarget, _spellInfo, false)) + if (!target->IsCorpse() && !_caster->IsValidAssistTarget(unitTarget, _spellInfo)) return false; if (!refUnit->IsInPartyWith(unitTarget)) return false; @@ -8558,7 +8573,7 @@ bool WorldObjectSpellTargetCheck::operator()(WorldObject* target) const return false; if (unitTarget->IsTotem()) return false; - if (!target->IsCorpse() && !_caster->IsValidAssistTarget(unitTarget, _spellInfo, false)) + if (!target->IsCorpse() && !_caster->IsValidAssistTarget(unitTarget, _spellInfo)) return false; if (!refUnit->IsInRaidWith(unitTarget)) return false; @@ -8582,19 +8597,6 @@ bool WorldObjectSpellTargetCheck::operator()(WorldObject* target) const default: break; } - - // then check actual spell positivity to determine if the target is valid - // (negative spells may be targeted on allies) - if (_spellInfo->IsPositive()) - { - if (!_caster->IsValidSpellAssistTarget(unitTarget, _spellInfo)) - return false; - } - else - { - if (!_caster->IsValidSpellAttackTarget(unitTarget, _spellInfo)) - return false; - } } if (!_condSrcInfo) diff --git a/src/server/game/Spells/SpellEffects.cpp b/src/server/game/Spells/SpellEffects.cpp index 155795384c..c7817df1a5 100644 --- a/src/server/game/Spells/SpellEffects.cpp +++ b/src/server/game/Spells/SpellEffects.cpp @@ -953,10 +953,6 @@ void Spell::EffectApplyAura(SpellEffIndex effIndex) aurApp = unitTarget->_CreateAuraApplication(_spellAura, 1 << effIndex); else aurApp->UpdateApplyEffectMask(aurApp->GetEffectsToApply() | 1 << effIndex); - - // apply effect on target (skip for reapply) - if (!aurApp->HasEffect(effIndex)) - unitTarget->_ApplyAuraEffect(_spellAura, effIndex); } void Spell::EffectUnlearnSpecialization(SpellEffIndex effIndex)