/* * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ #include "SpellAuras.h" #include "CellImpl.h" #include "Common.h" #include "Containers.h" #include "CreatureAI.h" #include "DynamicObject.h" #include "GridNotifiersImpl.h" #include "Item.h" #include "ListUtils.h" #include "Log.h" #include "MapUtils.h" #include "ObjectAccessor.h" #include "ObjectMgr.h" #include "PhasingHandler.h" #include "Player.h" #include "ScriptMgr.h" #include "Spell.h" #include "SpellAuraEffects.h" #include "SpellHistory.h" #include "SpellMgr.h" #include "SpellPackets.h" #include "SpellScript.h" #include "Unit.h" #include "Util.h" #include "Vehicle.h" #include "World.h" #include class ChargeDropEvent : public BasicEvent { public: ChargeDropEvent(Aura* base, AuraRemoveMode mode) : _base(base), _mode(mode) { } bool Execute(uint64 /*e_time*/, uint32 /*p_time*/) override { // _base is always valid (look in Aura::_Remove()) _base->ModChargesDelayed(-1, _mode); return true; } private: Aura* _base; AuraRemoveMode _mode; }; AuraCreateInfo::AuraCreateInfo(ObjectGuid castId, SpellInfo const* spellInfo, Difficulty castDifficulty, uint32 auraEffMask, WorldObject* owner) : _castId(castId), _spellInfo(spellInfo), _castDifficulty(castDifficulty), _auraEffectMask(auraEffMask), _owner(owner) { ASSERT(spellInfo); ASSERT(auraEffMask); ASSERT(owner); ASSERT(auraEffMask <= MAX_EFFECT_MASK); } AuraApplication::AuraApplication(Unit* target, Unit* caster, Aura* aura, uint32 effMask) : _target(target), _base(aura), _removeMode(AURA_REMOVE_NONE), _slot(MAX_AURAS), _flags(AFLAG_NONE), _effectsToApply(effMask), _needClientUpdate(false), _effectMask(0) { ASSERT(GetTarget() && GetBase()); // Try find slot for aura uint16 slot = 0; // lookup for free slots in units visibleAuras for (AuraApplication* visibleAura : GetTarget()->GetVisibleAuras()) { if (slot < visibleAura->GetSlot()) break; ++slot; } // Register Visible Aura if (slot < MAX_AURAS) { _slot = slot; GetTarget()->SetVisibleAura(this); _needClientUpdate = true; TC_LOG_DEBUG("spells", "Aura: {} Effect: {} put to unit visible auras slot: {}", GetBase()->GetId(), GetEffectMask(), slot); } else TC_LOG_ERROR("spells", "Aura: {} Effect: {} could not find empty unit visible slot", GetBase()->GetId(), GetEffectMask()); _InitFlags(caster, effMask); } void AuraApplication::_Remove() { // update for out of range group members if (GetSlot() < MAX_AURAS) { GetTarget()->RemoveVisibleAura(this); ClientUpdate(true); } } void AuraApplication::_InitFlags(Unit* caster, uint32 effMask) { // mark as selfcast if needed _flags |= (GetBase()->GetCasterGUID() == GetTarget()->GetGUID()) ? AFLAG_NOCASTER : AFLAG_NONE; // aura is cast by self or an enemy // one negative effect and we know aura is negative if (IsSelfcast() || !caster || !caster->IsFriendlyTo(GetTarget())) { bool negativeFound = false; for (uint8 i = 0; i < GetBase()->GetAuraEffectCount(); ++i) { if (((1 << i) & effMask) && !GetBase()->GetSpellInfo()->IsPositiveEffect(i)) { negativeFound = true; break; } } _flags |= negativeFound ? AFLAG_NEGATIVE : AFLAG_POSITIVE; } // aura is cast by friend // one positive effect and we know aura is positive else { bool positiveFound = false; for (uint8 i = 0; i < GetBase()->GetAuraEffectCount(); ++i) { if (((1 << i) & effMask) && GetBase()->GetSpellInfo()->IsPositiveEffect(i)) { positiveFound = true; break; } } _flags |= positiveFound ? AFLAG_POSITIVE : AFLAG_NEGATIVE; } auto effectNeedsAmount = [this](AuraEffect const* effect) { return GetEffectsToApply() & (1 << effect->GetEffIndex()) && Aura::EffectTypeNeedsSendingAmount(effect->GetAuraType()); }; if (GetBase()->GetSpellInfo()->HasAttribute(SPELL_ATTR8_AURA_POINTS_ON_CLIENT) || std::ranges::any_of(GetBase()->GetAuraEffects(), effectNeedsAmount)) _flags |= AFLAG_SCALABLE; } void AuraApplication::_HandleEffect(uint8 effIndex, bool apply) { AuraEffect* aurEff = GetBase()->GetEffect(effIndex); if (!aurEff) { TC_LOG_ERROR("spells", "Aura {} has no effect at effectIndex {} but _HandleEffect was called", GetBase()->GetSpellInfo()->Id, uint32(effIndex)); return; } ASSERT(aurEff); ASSERT(HasEffect(effIndex) == (!apply)); ASSERT((1<GetAuraType(), apply, aurEff->GetAmount()); if (apply) { ASSERT(!(_effectMask & (1<HandleEffect(this, AURA_EFFECT_HANDLE_REAL, true); } else { ASSERT(_effectMask & (1<HandleEffect(this, AURA_EFFECT_HANDLE_REAL, false); } SetNeedClientUpdate(); } void AuraApplication::UpdateApplyEffectMask(uint32 newEffMask, bool canHandleNewEffects) { if (_effectsToApply == newEffMask) return; uint32 removeEffMask = (_effectsToApply ^ newEffMask) & (~newEffMask); uint32 addEffMask = (_effectsToApply ^ newEffMask) & (~_effectsToApply); // quick check, removes application completely if (removeEffMask == _effectsToApply && !addEffMask) { _target->_UnapplyAura(this, AURA_REMOVE_BY_DEFAULT); return; } // update real effects only if they were applied already for (std::size_t i = 0; i < GetBase()->GetAuraEffectCount(); ++i) if (HasEffect(i) && (removeEffMask & (1 << i))) _HandleEffect(i, false); _effectsToApply = newEffMask; if (canHandleNewEffects) for (std::size_t i = 0; i < GetBase()->GetAuraEffectCount(); ++i) if (addEffMask & (1 << i)) _HandleEffect(i, true); } void AuraApplication::AddEffectToApplyEffectMask(SpellEffIndex spellEffectIndex) { if (_effectsToApply & (1 << spellEffectIndex)) return; _effectsToApply |= 1 << spellEffectIndex; if (Aura::EffectTypeNeedsSendingAmount(GetBase()->GetEffect(spellEffectIndex)->GetAuraType())) _flags |= AFLAG_SCALABLE; } void AuraApplication::SetNeedClientUpdate() { if (_needClientUpdate || GetSlot() >= MAX_AURAS || GetRemoveMode() != AURA_REMOVE_NONE) return; _needClientUpdate = true; _target->SetVisibleAuraUpdate(this); } void AuraApplication::BuildUpdatePacket(WorldPackets::Spells::AuraInfo& auraInfo, bool remove) { ASSERT(_target->HasVisibleAura(this) != remove); auraInfo.Slot = GetSlot(); if (remove) return; Aura const* aura = GetBase(); WorldPackets::Spells::AuraDataInfo& auraData = auraInfo.AuraData.emplace(); auraData.CastID = aura->GetCastId(); auraData.SpellID = aura->GetId(); auraData.Visual = aura->GetSpellVisual(); auraData.Flags = GetFlags(); if (aura->GetType() != DYNOBJ_AURA_TYPE && aura->GetMaxDuration() > 0 && !aura->GetSpellInfo()->HasAttribute(SPELL_ATTR5_DO_NOT_DISPLAY_DURATION)) auraData.Flags |= AFLAG_DURATION; auraData.ActiveFlags = GetEffectMask(); if (!aura->GetSpellInfo()->HasAttribute(SPELL_ATTR11_SCALES_WITH_ITEM_LEVEL)) auraData.CastLevel = aura->GetCasterLevel(); else auraData.CastLevel = uint16(aura->GetCastItemLevel()); // send stack amount for aura which could be stacked (never 0 - causes incorrect display) or charges // stack amount has priority over charges (checked on retail with spell 50262) auraData.Applications = aura->IsUsingStacks() ? aura->GetStackAmount() : aura->GetCharges(); if (!aura->GetCasterGUID().IsUnit()) auraData.CastUnit = ObjectGuid::Empty; // optional data is filled in, but cast unit contains empty guid in packet else if (!(auraData.Flags & AFLAG_NOCASTER)) auraData.CastUnit = aura->GetCasterGUID(); if (!aura->GetCastItemGUID().IsEmpty()) auraData.CastItem = aura->GetCastItemGUID(); if (auraData.Flags & AFLAG_DURATION) { auraData.Duration = aura->GetMaxDuration(); auraData.Remaining = aura->GetDuration(); } if (auraData.Flags & AFLAG_SCALABLE) { auraData.Points.reserve(aura->GetAuraEffectCount()); bool hasEstimatedAmounts = false; for (AuraEffect const* effect : GetBase()->GetAuraEffects()) { if (HasEffect(effect->GetEffIndex())) // Not all of aura's effects have to be applied on every target { Trinity::Containers::EnsureWritableVectorIndex(auraData.Points, effect->GetEffIndex()) = float(effect->GetAmount()); if (effect->GetEstimatedAmount()) hasEstimatedAmounts = true; } } if (hasEstimatedAmounts) { // When sending EstimatedPoints all effects (at least up to the last one that uses GetEstimatedAmount) must have proper value in packet auraData.EstimatedPoints.resize(auraData.Points.size()); for (AuraEffect const* effect : GetBase()->GetAuraEffects()) if (HasEffect(effect->GetEffIndex())) // Not all of aura's effects have to be applied on every target auraData.EstimatedPoints[effect->GetEffIndex()] = effect->GetEstimatedAmount().value_or(effect->GetAmount()); } } } void AuraApplication::ClientUpdate(bool remove) { _needClientUpdate = false; WorldPackets::Spells::AuraUpdate update; update.UpdateAll = false; update.UnitGUID = GetTarget()->GetGUID(); WorldPackets::Spells::AuraInfo auraInfo; BuildUpdatePacket(auraInfo, remove); update.Auras.push_back(auraInfo); _target->SendMessageToSet(update.Write(), true); } std::string AuraApplication::GetDebugInfo() const { std::stringstream sstr; sstr << "Base: " << (GetBase() ? GetBase()->GetDebugInfo() : "NULL") << "\nTarget: " << (GetTarget() ? GetTarget()->GetDebugInfo() : "NULL"); return sstr.str(); } uint32 Aura::BuildEffectMaskForOwner(SpellInfo const* spellProto, uint32 availableEffectMask, WorldObject* owner) { ASSERT_NODEBUGINFO(spellProto); ASSERT_NODEBUGINFO(owner); uint32 effMask = 0; switch (owner->GetTypeId()) { case TYPEID_UNIT: case TYPEID_PLAYER: for (SpellEffectInfo const& spellEffectInfo : spellProto->GetEffects()) { if (spellEffectInfo.IsUnitOwnedAuraEffect()) effMask |= 1 << spellEffectInfo.EffectIndex; } break; case TYPEID_DYNAMICOBJECT: for (SpellEffectInfo const& spellEffectInfo : spellProto->GetEffects()) { if (spellEffectInfo.IsEffect(SPELL_EFFECT_PERSISTENT_AREA_AURA)) effMask |= 1 << spellEffectInfo.EffectIndex; } break; default: ABORT(); break; } return effMask & availableEffectMask; } Aura* Aura::TryRefreshStackOrCreate(AuraCreateInfo& createInfo, bool updateEffectMask) { ASSERT_NODEBUGINFO(createInfo.Caster || !createInfo.CasterGUID.IsEmpty()); if (createInfo.IsRefresh) *createInfo.IsRefresh = false; createInfo._auraEffectMask = Aura::BuildEffectMaskForOwner(createInfo._spellInfo, createInfo._auraEffectMask, createInfo._owner); createInfo._targetEffectMask &= createInfo._auraEffectMask; uint32 effMask = createInfo._auraEffectMask; if (createInfo._targetEffectMask) effMask = createInfo._targetEffectMask; if (!effMask) return nullptr; if (Aura* foundAura = createInfo._owner->ToUnit()->_TryStackingOrRefreshingExistingAura(createInfo)) { // we've here aura, which script triggered removal after modding stack amount // check the state here, so we won't create new Aura object if (foundAura->IsRemoved()) return nullptr; if (createInfo.IsRefresh) *createInfo.IsRefresh = true; // add owner Unit* unit = createInfo._owner->ToUnit(); // check effmask on owner application (if existing) if (updateEffectMask) if (AuraApplication* aurApp = foundAura->GetApplicationOfTarget(unit->GetGUID())) aurApp->UpdateApplyEffectMask(effMask, false); return foundAura; } else return Create(createInfo); } Aura* Aura::TryCreate(AuraCreateInfo& createInfo) { uint32 effMask = createInfo._auraEffectMask; if (createInfo._targetEffectMask) effMask = createInfo._targetEffectMask; effMask = Aura::BuildEffectMaskForOwner(createInfo._spellInfo, effMask, createInfo._owner); if (!effMask) return nullptr; return Create(createInfo); } Aura* Aura::Create(AuraCreateInfo& createInfo) { // try to get caster of aura if (!createInfo.CasterGUID.IsEmpty()) { if (createInfo.CasterGUID.IsUnit()) { if (createInfo._owner->GetGUID() == createInfo.CasterGUID) createInfo.Caster = createInfo._owner->ToUnit(); else createInfo.Caster = ObjectAccessor::GetUnit(*createInfo._owner, createInfo.CasterGUID); } } else if (createInfo.Caster) createInfo.CasterGUID = createInfo.Caster->GetGUID(); // check if aura can be owned by owner if (Unit* ownerUnit = createInfo._owner->ToUnit()) if (!ownerUnit->IsInWorld() || ownerUnit->IsDuringRemoveFromWorld()) // owner not in world so don't allow to own not self cast single target auras if (createInfo.CasterGUID != ownerUnit->GetGUID() && createInfo._spellInfo->IsSingleTarget()) return nullptr; Aura* aura = nullptr; switch (createInfo._owner->GetTypeId()) { case TYPEID_UNIT: case TYPEID_PLAYER: { aura = new UnitAura(createInfo); // aura can be removed in Unit::_AddAura call if (aura->IsRemoved()) return nullptr; // add owner uint32 effMask = createInfo._auraEffectMask; if (createInfo._targetEffectMask) effMask = createInfo._targetEffectMask; effMask = Aura::BuildEffectMaskForOwner(createInfo._spellInfo, effMask, createInfo._owner); ASSERT_NODEBUGINFO(effMask); Unit* unit = createInfo._owner->ToUnit(); aura->ToUnitAura()->AddStaticApplication(unit, effMask); break; } case TYPEID_DYNAMICOBJECT: createInfo._auraEffectMask = Aura::BuildEffectMaskForOwner(createInfo._spellInfo, createInfo._auraEffectMask, createInfo._owner); ASSERT_NODEBUGINFO(createInfo._auraEffectMask); aura = new DynObjAura(createInfo); break; default: ABORT(); return nullptr; } // scripts, etc. if (aura->IsRemoved()) return nullptr; return aura; } SpellCastVisual AuraCreateInfo::CalcSpellVisual() const { return _spellVisual.value_or({ .SpellXSpellVisualID = Caster ? Caster->GetCastSpellXSpellVisualId(_spellInfo) : _spellInfo->GetSpellXSpellVisualId(), .ScriptVisualID = 0 }); } Aura::Aura(AuraCreateInfo const& createInfo) : m_spellInfo(createInfo._spellInfo), m_castDifficulty(createInfo._castDifficulty), m_castId(createInfo._castId), m_casterGuid(createInfo.CasterGUID), m_castItemGuid(createInfo.CastItemGUID), m_castItemId(createInfo.CastItemId), m_castItemLevel(createInfo.CastItemLevel), m_spellVisual(createInfo.CalcSpellVisual()), m_applyTime(GameTime::GetGameTime()), m_owner(createInfo._owner), m_timeCla(0), m_updateTargetMapInterval(0), m_casterLevel(createInfo.Caster ? createInfo.Caster->GetLevel() : m_spellInfo->SpellLevel), m_procCharges(0), m_stackAmount(createInfo.StackAmount), m_isRemoved(false), m_isSingleTarget(false), m_isUsingCharges(false), m_dropEvent(nullptr), m_procCooldown(TimePoint::min()), m_lastProcAttemptTime(GameTime::Now() - Seconds(10)), m_lastProcSuccessTime(GameTime::Now() - Seconds(120)), m_scriptRef(this, NoopAuraDeleter()) { if (!m_spellInfo->HasAttribute(SPELL_ATTR6_DO_NOT_CONSUME_RESOURCES)) { for (SpellPowerEntry const* power : m_spellInfo->PowerCosts) if (power && (power->ManaPerSecond != 0 || power->PowerPctPerSecond > 0.0f)) m_periodicCosts.push_back(power); if (!m_periodicCosts.empty()) m_timeCla = 1 * IN_MILLISECONDS; } m_maxDuration = CalcMaxDuration(createInfo.Caster); m_duration = m_maxDuration; m_procCharges = CalcMaxCharges(createInfo.Caster); m_isUsingCharges = m_procCharges != 0; // m_casterLevel = cast item level/caster level, caster level should be saved to db, confirmed with sniffs } AuraScript* Aura::GetScriptByType(std::type_info const& type) const { for (AuraScript* script : m_loadedScripts) if (typeid(*script) == type) return script; return nullptr; } void Aura::_InitEffects(uint32 effMask, Unit* caster, int32 const* baseAmount) { // shouldn't be in constructor - functions in AuraEffect::AuraEffect use polymorphism _effects.resize(GetSpellInfo()->GetEffects().size()); for (SpellEffectInfo const& spellEffectInfo : GetSpellInfo()->GetEffects()) if (effMask & (1 << spellEffectInfo.EffectIndex)) _effects[spellEffectInfo.EffectIndex] = new AuraEffect(this, spellEffectInfo, baseAmount ? baseAmount + spellEffectInfo.EffectIndex : nullptr, caster); while (!_effects.back()) _effects.pop_back(); } bool Aura::CanPeriodicTickCrit() const { if (GetSpellInfo()->HasAttribute(SPELL_ATTR2_CANT_CRIT)) return false; if (GetSpellInfo()->HasAttribute(SPELL_ATTR8_PERIODIC_CAN_CRIT)) return true; return false; } Aura::~Aura() { // unload scripts for (AuraScript* script : m_loadedScripts) { script->_Unload(); delete script; } for (AuraEffect* effect : _effects) delete effect; ASSERT(m_applications.empty()); _DeleteRemovedApplications(); } void Aura::SetSpellVisual(SpellCastVisual const& spellVisual) { m_spellVisual = spellVisual; SetNeedClientUpdateForTargets(); } Unit* Aura::GetCaster() const { if (GetOwner()->GetGUID() == GetCasterGUID()) return GetUnitOwner(); return ObjectAccessor::GetUnit(*GetOwner(), GetCasterGUID()); } WorldObject* Aura::GetWorldObjectCaster() const { if (GetCasterGUID().IsUnit()) return GetCaster(); return ObjectAccessor::GetWorldObject(*GetOwner(), GetCasterGUID()); } AuraEffect* Aura::GetEffect(uint32 index) const { if (index >= _effects.size()) return nullptr; return _effects[index]; } AuraObjectType Aura::GetType() const { return (m_owner->GetTypeId() == TYPEID_DYNAMICOBJECT) ? DYNOBJ_AURA_TYPE : UNIT_AURA_TYPE; } void Aura::_ApplyForTarget(Unit* target, Unit* caster, AuraApplication* auraApp) { ASSERT(target); ASSERT(auraApp); // aura mustn't be already applied on target ASSERT (!IsAppliedOnTarget(target->GetGUID()) && "Aura::_ApplyForTarget: aura musn't be already applied on target"); m_applications[target->GetGUID()] = auraApp; // set infinity cooldown state for spells if (caster && caster->GetTypeId() == TYPEID_PLAYER) { if (m_spellInfo->IsCooldownStartedOnEvent()) { Item* castItem = !m_castItemGuid.IsEmpty() ? caster->ToPlayer()->GetItemByGuid(m_castItemGuid) : nullptr; caster->GetSpellHistory()->StartCooldown(m_spellInfo, castItem ? castItem->GetEntry() : 0, nullptr, true); } } } void Aura::_UnapplyForTarget(Unit* target, Unit* caster, AuraApplication* auraApp) { ASSERT(target); ASSERT(auraApp->GetRemoveMode()); ASSERT(auraApp); ApplicationMap::iterator itr = m_applications.find(target->GetGUID()); /// @todo Figure out why this happens if (itr == m_applications.end()) { TC_LOG_ERROR("spells", "Aura::_UnapplyForTarget, target: {}, caster: {}, spell:{} was not found in owners application map!", target->GetGUID().ToString(), caster ? caster->GetGUID().ToString().c_str() : "Empty", auraApp->GetBase()->GetSpellInfo()->Id); ABORT(); } // aura has to be already applied ASSERT(itr->second == auraApp); m_applications.erase(itr); _removedApplications.push_back(auraApp); // reset cooldown state for spells if (caster && GetSpellInfo()->IsCooldownStartedOnEvent()) // note: item based cooldowns and cooldown spell mods with charges ignored (unknown existed cases) caster->GetSpellHistory()->SendCooldownEvent(GetSpellInfo()); } // removes aura from all targets // and marks aura as removed void Aura::_Remove(AuraRemoveMode removeMode) { ASSERT (!m_isRemoved); m_isRemoved = true; ApplicationMap::iterator appItr = m_applications.begin(); for (appItr = m_applications.begin(); appItr != m_applications.end();) { AuraApplication * aurApp = appItr->second; Unit* target = aurApp->GetTarget(); target->_UnapplyAura(aurApp, removeMode); appItr = m_applications.begin(); } if (m_dropEvent) { m_dropEvent->ScheduleAbort(); m_dropEvent = nullptr; } m_scriptRef = nullptr; } void Aura::UpdateTargetMap(Unit* caster, bool apply) { if (IsRemoved()) return; m_updateTargetMapInterval = UPDATE_TARGET_MAP_INTERVAL; // fill up to date target list // target, effMask std::unordered_map targets; FillTargetMap(targets, caster); std::vector targetsToRemove; // mark all auras as ready to remove for (auto const& applicationPair : m_applications) { auto itr = targets.find(applicationPair.second->GetTarget()); // not found in current area - remove the aura if (itr == targets.end()) targetsToRemove.push_back(applicationPair.second->GetTarget()); else { // needs readding - remove now, will be applied in next update cycle // (dbcs do not have auras which apply on same type of targets but have different radius, so this is not really needed) if (itr->first->IsImmunedToSpell(GetSpellInfo(), itr->second, caster, true) || !CanBeAppliedOn(itr->first)) { targetsToRemove.push_back(applicationPair.second->GetTarget()); continue; } // check target immunities (for existing targets) for (SpellEffectInfo const& spellEffectInfo : GetSpellInfo()->GetEffects()) if (itr->first->IsImmunedToSpellEffect(GetSpellInfo(), spellEffectInfo, caster, true)) itr->second &= ~(1 << spellEffectInfo.EffectIndex); // needs to add/remove effects from application, don't remove from map so it gets updated if (applicationPair.second->GetEffectMask() != itr->second) continue; // nothing to do - aura already applied // remove from auras to register list targets.erase(itr); } } // register auras for units for (auto itr = targets.begin(); itr != targets.end();) { bool addUnit = true; AuraApplication* aurApp = GetApplicationOfTarget(itr->first->GetGUID()); if (!aurApp) { // check target immunities (for new targets) for (SpellEffectInfo const& spellEffectInfo : GetSpellInfo()->GetEffects()) if (itr->first->IsImmunedToSpellEffect(GetSpellInfo(), spellEffectInfo, caster)) itr->second &= ~(1 << spellEffectInfo.EffectIndex); if (!itr->second || itr->first->IsImmunedToSpell(GetSpellInfo(), itr->second, caster) || !CanBeAppliedOn(itr->first)) addUnit = false; } if (addUnit && !itr->first->IsHighestExclusiveAura(this, true)) addUnit = false; // Dynobj auras don't hit flying targets if (GetType() == DYNOBJ_AURA_TYPE && itr->first->IsInFlight()) addUnit = false; // Do not apply aura if it cannot stack with existing auras if (addUnit) { // Allow to remove by stack when aura is going to be applied on owner if (itr->first != GetOwner()) { // check if not stacking aura already on target // this one prevents unwanted usefull buff loss because of stacking and prevents overriding auras periodicaly by 2 near area aura owners for (Unit::AuraApplicationMap::iterator iter = itr->first->GetAppliedAuras().begin(); iter != itr->first->GetAppliedAuras().end(); ++iter) { Aura const* aura = iter->second->GetBase(); if (!CanStackWith(aura)) { addUnit = false; break; } } } } if (!addUnit) itr = targets.erase(itr); else { // owner has to be in world, or effect has to be applied to self if (!GetOwner()->IsSelfOrInSameMap(itr->first)) { /// @todo There is a crash caused by shadowfiend load addon TC_LOG_FATAL("spells", "Aura {}: Owner {} (map {}) is not in the same map as target {} (map {}).", GetSpellInfo()->Id, GetOwner()->GetName(), GetOwner()->IsInWorld() ? GetOwner()->GetMap()->GetId() : uint32(-1), itr->first->GetName(), itr->first->IsInWorld() ? itr->first->GetMap()->GetId() : uint32(-1)); ABORT(); } if (aurApp) { aurApp->UpdateApplyEffectMask(itr->second, true); // aura is already applied, this means we need to update effects of current application itr = targets.erase(itr); } else { itr->first->_CreateAuraApplication(this, itr->second); ++itr; } } } // remove auras from units no longer needing them for (Unit* unit : targetsToRemove) if (AuraApplication* aurApp = GetApplicationOfTarget(unit->GetGUID())) unit->_UnapplyAura(aurApp, AURA_REMOVE_BY_DEFAULT); if (!apply) return; // apply aura effects for units for (auto itr = targets.begin(); itr!= targets.end(); ++itr) { if (AuraApplication* aurApp = GetApplicationOfTarget(itr->first->GetGUID())) { // owner has to be in world, or effect has to be applied to self ASSERT((!GetOwner()->IsInWorld() && GetOwner() == itr->first) || GetOwner()->IsInMap(itr->first)); itr->first->_ApplyAura(aurApp, itr->second); } } } // targets have to be registered and not have effect applied yet to use this function void Aura::_ApplyEffectForTargets(uint8 effIndex) { // prepare list of aura targets UnitList targetList; for (ApplicationMap::iterator appIter = m_applications.begin(); appIter != m_applications.end(); ++appIter) { if ((appIter->second->GetEffectsToApply() & (1 << effIndex)) && !appIter->second->HasEffect(effIndex)) targetList.push_back(appIter->second->GetTarget()); } // apply effect to targets for (UnitList::iterator itr = targetList.begin(); itr != targetList.end(); ++itr) { if (GetApplicationOfTarget((*itr)->GetGUID())) { // owner has to be in world, or effect has to be applied to self ASSERT((!GetOwner()->IsInWorld() && GetOwner() == *itr) || GetOwner()->IsInMap(*itr)); (*itr)->_ApplyAuraEffect(this, effIndex); } } } void Aura::UpdateOwner(uint32 diff, WorldObject* owner) { ASSERT(owner == m_owner); Unit* caster = GetCaster(); // Apply spellmods for channeled auras // used for example when triggered spell of spell:10 is modded Spell* modSpell = nullptr; Player* modOwner = nullptr; if (caster) { modOwner = caster->GetSpellModOwner(); if (modOwner) { modSpell = modOwner->FindCurrentSpellBySpellId(GetId()); if (modSpell) modOwner->SetSpellModTakingSpell(modSpell, true); } } Update(diff, caster); if (m_updateTargetMapInterval <= int32(diff)) UpdateTargetMap(caster); else m_updateTargetMapInterval -= diff; // update aura effects for (AuraEffect* effect : GetAuraEffects()) effect->Update(diff, caster); // remove spellmods after effects update if (modSpell) modOwner->SetSpellModTakingSpell(modSpell, false); _DeleteRemovedApplications(); } void Aura::Update(uint32 diff, Unit* caster) { if (m_duration > 0) { m_duration -= diff; if (m_duration < 0) m_duration = 0; // handle manaPerSecond/manaPerSecondPerLevel if (m_timeCla) { if (m_timeCla > int32(diff)) m_timeCla -= diff; else if (caster && (caster == GetOwner() || !GetSpellInfo()->HasAttribute(SPELL_ATTR2_NO_TARGET_PER_SECOND_COSTS))) { if (!m_periodicCosts.empty()) { m_timeCla += 1000 - diff; for (SpellPowerEntry const* power : m_periodicCosts) { if (power->RequiredAuraSpellID && !caster->HasAura(power->RequiredAuraSpellID)) continue; int32 manaPerSecond = power->ManaPerSecond; Powers powertype = Powers(power->PowerType); if (powertype != POWER_HEALTH) manaPerSecond += int32(CalculatePct(caster->GetMaxPower(powertype), power->PowerPctPerSecond)); else manaPerSecond += int32(CalculatePct(caster->GetMaxHealth(), power->PowerPctPerSecond)); if (manaPerSecond) { if (powertype == POWER_HEALTH) { if (int32(caster->GetHealth()) > manaPerSecond) caster->ModifyHealth(-manaPerSecond); else Remove(); } else if (int32(caster->GetPower(powertype)) >= manaPerSecond) caster->ModifyPower(powertype, -manaPerSecond); else Remove(); } } } } } } } int32 Aura::CalcMaxDuration(Unit* caster) const { return Aura::CalcMaxDuration(GetSpellInfo(), caster, nullptr); } /*static*/ int32 Aura::CalcMaxDuration(SpellInfo const* spellInfo, WorldObject const* caster, std::vector const* powerCosts) { Player* modOwner = nullptr; int32 maxDuration; if (caster) { modOwner = caster->GetSpellModOwner(); maxDuration = caster->CalcSpellDuration(spellInfo, powerCosts); } else maxDuration = spellInfo->GetDuration(); if (spellInfo->IsPassive() && !spellInfo->DurationEntry) maxDuration = -1; // IsPermanent() checks max duration (which we are supposed to calculate here) if (maxDuration != -1) { if (modOwner) modOwner->ApplySpellMod(spellInfo, SpellModOp::Duration, maxDuration); if (spellInfo->IsEmpowerSpell()) maxDuration += SPELL_EMPOWER_HOLD_TIME_AT_MAX; } return maxDuration; } void Aura::SetDuration(int32 duration, bool withMods) { if (withMods) if (Unit* caster = GetCaster()) if (Player* modOwner = caster->GetSpellModOwner()) modOwner->ApplySpellMod(GetSpellInfo(), SpellModOp::Duration, duration); m_duration = duration; SetNeedClientUpdateForTargets(); } void Aura::RefreshDuration(bool withMods) { Unit* caster = GetCaster(); if (withMods && caster) { int32 duration = m_spellInfo->GetMaxDuration(); // Calculate duration of periodics affected by haste. if (m_spellInfo->HasAttribute(SPELL_ATTR8_HASTE_AFFECTS_DURATION)) duration = int32(duration * caster->m_unitData->ModCastingSpeed); SetMaxDuration(duration); SetDuration(duration); } else SetDuration(GetMaxDuration()); if (!m_periodicCosts.empty()) m_timeCla = 1 * IN_MILLISECONDS; // also reset periodic counters for (AuraEffect* aurEff : GetAuraEffects()) aurEff->ResetTicks(); } void Aura::RefreshTimers(bool resetPeriodicTimer) { m_maxDuration = CalcMaxDuration(); // Pandemic Mechanic if (m_spellInfo->HasAttribute(SPELL_ATTR13_PERIODIC_REFRESH_EXTENDS_DURATION)) { // Pandemic doesn't reset periodic timer resetPeriodicTimer = false; } RefreshDuration(); Unit* caster = GetCaster(); for (AuraEffect* aurEff : GetAuraEffects()) aurEff->CalculatePeriodic(caster, resetPeriodicTimer, false); } void Aura::SetCharges(uint8 charges) { if (m_procCharges == charges) return; m_procCharges = charges; m_isUsingCharges = m_procCharges != 0; SetNeedClientUpdateForTargets(); } uint8 Aura::CalcMaxCharges(Unit* caster) const { uint32 maxProcCharges = m_spellInfo->ProcCharges; if (SpellProcEntry const* procEntry = sSpellMgr->GetSpellProcEntry(GetSpellInfo())) maxProcCharges = procEntry->Charges; if (caster) if (Player* modOwner = caster->GetSpellModOwner()) modOwner->ApplySpellMod(GetSpellInfo(), SpellModOp::ProcCharges, maxProcCharges); return uint8(maxProcCharges); } bool Aura::ModCharges(int32 num, AuraRemoveMode removeMode) { if (IsUsingCharges()) { int32 charges = m_procCharges + num; int32 maxCharges = CalcMaxCharges(); // limit charges (only on charges increase, charges may be changed manually) if ((num > 0) && (charges > int32(maxCharges))) charges = maxCharges; // we're out of charges, remove else if (charges <= 0) { Remove(removeMode); return true; } SetCharges(charges); } return false; } void Aura::ModChargesDelayed(int32 num, AuraRemoveMode removeMode) { m_dropEvent = nullptr; ModCharges(num, removeMode); } void Aura::DropChargeDelayed(uint32 delay, AuraRemoveMode removeMode) { // aura is already during delayed charge drop if (m_dropEvent) return; m_dropEvent = new ChargeDropEvent(this, removeMode); m_owner->m_Events.AddEventAtOffset(m_dropEvent, Milliseconds(delay)); } void Aura::SetStackAmount(uint8 stackAmount) { m_stackAmount = stackAmount; Unit* caster = GetCaster(); std::vector applications; GetApplicationVector(applications); for (AuraApplication* aurApp : applications) if (!aurApp->GetRemoveMode()) HandleAuraSpecificMods(aurApp, caster, false, true); for (AuraEffect* aurEff : GetAuraEffects()) aurEff->ChangeAmount(aurEff->CalculateAmount(caster), false, true); for (AuraApplication* aurApp : applications) if (!aurApp->GetRemoveMode()) HandleAuraSpecificMods(aurApp, caster, true, true); SetNeedClientUpdateForTargets(); } bool Aura::IsUsingStacks() const { return m_spellInfo->StackAmount > 0 || m_stackAmount > 1; } uint32 Aura::CalcMaxStackAmount() const { int32 maxStackAmount = m_spellInfo->StackAmount; if (Unit* caster = GetCaster()) if (Player* modOwner = caster->GetSpellModOwner()) modOwner->ApplySpellMod(m_spellInfo, SpellModOp::MaxAuraStacks, maxStackAmount); return maxStackAmount; } bool Aura::ModStackAmount(int32 num, AuraRemoveMode removeMode /*= AURA_REMOVE_BY_DEFAULT*/, bool resetPeriodicTimer /*= true*/) { int32 stackAmount = m_stackAmount + num; int32 maxStackAmount = int32(CalcMaxStackAmount()); // limit the stack amount (only on stack increase, stack amount may be changed manually) if ((num > 0) && (stackAmount > maxStackAmount)) { // not stackable aura - set stack amount to 1 if (!m_spellInfo->StackAmount) stackAmount = 1; else stackAmount = maxStackAmount; } // we're out of stacks, remove else if (stackAmount <= 0) { Remove(removeMode); return true; } bool refresh = stackAmount >= GetStackAmount() && (m_spellInfo->StackAmount || (!m_spellInfo->HasAttribute(SPELL_ATTR1_AURA_UNIQUE) && !m_spellInfo->HasAttribute(SPELL_ATTR5_AURA_UNIQUE_PER_CASTER))); // Update stack amount SetStackAmount(stackAmount); if (refresh) { RefreshTimers(resetPeriodicTimer); // reset charges SetCharges(CalcMaxCharges()); } SetNeedClientUpdateForTargets(); return false; } bool Aura::HasMoreThanOneEffectForType(AuraType auraType) const { uint32 count = 0; for (SpellEffectInfo const& spellEffectInfo : GetSpellInfo()->GetEffects()) if (HasEffect(spellEffectInfo.EffectIndex) && spellEffectInfo.ApplyAuraName == auraType) ++count; return count > 1; } bool Aura::IsArea() const { for (SpellEffectInfo const& spellEffectInfo : GetSpellInfo()->GetEffects()) if (HasEffect(spellEffectInfo.EffectIndex) && spellEffectInfo.IsAreaAuraEffect()) return true; return false; } bool Aura::IsPassive() const { return GetSpellInfo()->IsPassive(); } bool Aura::IsDeathPersistent() const { return GetSpellInfo()->IsDeathPersistent(); } bool Aura::IsRemovedOnShapeLost(Unit* target) const { return GetCasterGUID() == target->GetGUID() && m_spellInfo->Stances && !m_spellInfo->HasAttribute(SPELL_ATTR2_ALLOW_WHILE_NOT_SHAPESHIFTED_CASTER_FORM) && !m_spellInfo->HasAttribute(SPELL_ATTR0_NOT_SHAPESHIFTED); } bool Aura::CanBeSaved() const { if (IsPassive()) return false; if (GetSpellInfo()->IsChanneled()) return false; // Check if aura is single target, not only spell info if (GetCasterGUID() != GetOwner()->GetGUID()) { // owner == caster for area auras, check for possible bad data in DB for (SpellEffectInfo const& spellEffectInfo : GetSpellInfo()->GetEffects()) { if (!spellEffectInfo.IsEffect()) continue; if (spellEffectInfo.IsTargetingArea() || spellEffectInfo.IsAreaAuraEffect()) return false; } if (IsSingleTarget() || GetSpellInfo()->IsSingleTarget()) return false; } if (GetSpellInfo()->HasAttribute(SPELL_ATTR0_CU_AURA_CANNOT_BE_SAVED)) return false; // don't save auras removed by proc system if (IsUsingCharges() && !GetCharges()) return false; // don't save permanent auras triggered by items, they'll be recasted on login if necessary if (!GetCastItemGUID().IsEmpty() && IsPermanent()) return false; return true; } bool Aura::IsSingleTargetWith(Aura const* aura) const { // Same spell? if (GetSpellInfo()->IsRankOf(aura->GetSpellInfo())) return true; SpellSpecificType spec = GetSpellInfo()->GetSpellSpecific(); // spell with single target specific types switch (spec) { case SPELL_SPECIFIC_MAGE_POLYMORPH: if (aura->GetSpellInfo()->GetSpellSpecific() == spec) return true; break; default: break; } return false; } void Aura::UnregisterSingleTarget() { ASSERT(m_isSingleTarget); Unit* caster = GetCaster(); ASSERT(caster); Trinity::Containers::Lists::RemoveUnique(caster->GetSingleCastAuras(), this); SetIsSingleTarget(false); } int32 Aura::CalcDispelChance(Unit const* /*auraTarget*/, bool /*offensive*/) const { // we assume that aura dispel chance is 100% on start // need formula for level difference based chance int32 resistChance = 0; // Apply dispel mod from aura caster if (Unit* caster = GetCaster()) if (Player* modOwner = caster->GetSpellModOwner()) modOwner->ApplySpellMod(GetSpellInfo(), SpellModOp::DispelResistance, resistChance); RoundToInterval(resistChance, 0, 100); return 100 - resistChance; } AuraKey Aura::GenerateKey(uint32& recalculateMask) const { AuraKey key; key.Caster = GetCasterGUID(); key.Item = GetCastItemGUID(); key.SpellId = GetId(); key.EffectMask = 0; recalculateMask = 0; for (uint32 i = 0; i < _effects.size(); ++i) { if (AuraEffect const* effect = _effects[i]) { key.EffectMask |= 1 << i; if (effect->CanBeRecalculated()) recalculateMask |= 1 << i; } } return key; } void Aura::SetLoadedState(int32 maxDuration, int32 duration, int32 charges, uint32 recalculateMask, int32* amount) { m_maxDuration = maxDuration; m_duration = duration; m_procCharges = charges; m_isUsingCharges = m_procCharges != 0; Unit* caster = GetCaster(); for (AuraEffect* effect : GetAuraEffects()) { effect->SetAmount(amount[effect->GetEffIndex()]); effect->SetCanBeRecalculated((recalculateMask & (1 << effect->GetEffIndex())) != 0); effect->CalculatePeriodic(caster, false, true); effect->CalculateSpellMod(); effect->RecalculateAmount(caster); } } bool Aura::HasEffectType(AuraType type) const { for (AuraEffect const* effect : GetAuraEffects()) if (effect->GetAuraType() == type) return true; return false; } bool Aura::EffectTypeNeedsSendingAmount(AuraType type) { switch (type) { case SPELL_AURA_OVERRIDE_ACTIONBAR_SPELLS: case SPELL_AURA_OVERRIDE_ACTIONBAR_SPELLS_TRIGGERED: case SPELL_AURA_MOD_SPELL_CATEGORY_COOLDOWN: case SPELL_AURA_MOD_MAX_CHARGES: case SPELL_AURA_CHARGE_RECOVERY_MOD: case SPELL_AURA_CHARGE_RECOVERY_MULTIPLIER: return true; default: break; } return false; } void Aura::RecalculateAmountOfEffects() { ASSERT (!IsRemoved()); Unit* caster = GetCaster(); for (AuraEffect* effect : GetAuraEffects()) if (!IsRemoved()) effect->RecalculateAmount(caster); } void Aura::HandleAllEffects(AuraApplication * aurApp, uint8 mode, bool apply) { ASSERT (!IsRemoved()); for (AuraEffect* effect : GetAuraEffects()) if (!IsRemoved()) effect->HandleEffect(aurApp, mode, apply); } uint32 Aura::GetEffectMask() const { uint32 effMask = 0; for (AuraEffect const* aurEff : GetAuraEffects()) effMask |= 1 << aurEff->GetEffIndex(); return effMask; } void Aura::GetApplicationVector(std::vector& applications) const { for (auto const& applicationPair : m_applications) { if (!applicationPair.second->GetEffectMask()) continue; applications.push_back(applicationPair.second); } } AuraApplication const* Aura::GetApplicationOfTarget(ObjectGuid guid) const { return Trinity::Containers::MapGetValuePtr(m_applications, guid); } AuraApplication* Aura::GetApplicationOfTarget(ObjectGuid guid) { return Trinity::Containers::MapGetValuePtr(m_applications, guid); } bool Aura::IsAppliedOnTarget(ObjectGuid guid) const { return m_applications.contains(guid); } void Aura::SetNeedClientUpdateForTargets() const { for (ApplicationMap::const_iterator appIter = m_applications.begin(); appIter != m_applications.end(); ++appIter) appIter->second->SetNeedClientUpdate(); } // trigger effects on real aura apply/remove void Aura::HandleAuraSpecificMods(AuraApplication const* aurApp, Unit* caster, bool apply, bool onReapply) { Unit* target = aurApp->GetTarget(); AuraRemoveMode removeMode = aurApp->GetRemoveMode(); // handle spell_area table SpellAreaForAreaMapBounds saBounds = sSpellMgr->GetSpellAreaForAuraMapBounds(GetId()); if (saBounds.first != saBounds.second) { uint32 zone, area; target->GetZoneAndAreaId(zone, area); for (SpellAreaForAreaMap::const_iterator itr = saBounds.first; itr != saBounds.second; ++itr) { // some auras remove at aura remove if (itr->second->flags & SPELL_AREA_FLAG_AUTOREMOVE && !itr->second->IsFitToRequirements(target->ToPlayer(), zone, area)) target->RemoveAurasDueToSpell(itr->second->spellId); // some auras applied at aura apply else if (itr->second->flags & SPELL_AREA_FLAG_AUTOCAST) { if (!target->HasAura(itr->second->spellId)) target->CastSpell(target, itr->second->spellId, CastSpellExtraArgs(TRIGGERED_FULL_MASK) .SetOriginalCastId(GetCastId())); } } } // handle spell_linked_spell table if (!onReapply) { // apply linked auras if (apply) { if (std::vector const* spellTriggered = sSpellMgr->GetSpellLinked(SPELL_LINK_AURA, GetId())) { for (std::vector::const_iterator itr = spellTriggered->begin(); itr != spellTriggered->end(); ++itr) { if (*itr < 0) target->ApplySpellImmune(GetId(), IMMUNITY_ID, -(*itr), true); else if (caster) caster->AddAura(*itr, target); } } } else { // remove linked auras if (std::vector const* spellTriggered = sSpellMgr->GetSpellLinked(SPELL_LINK_REMOVE, GetId())) { for (std::vector::const_iterator itr = spellTriggered->begin(); itr != spellTriggered->end(); ++itr) { if (*itr < 0) target->RemoveAurasDueToSpell(-(*itr)); else if (removeMode != AURA_REMOVE_BY_DEATH) target->CastSpell(target, *itr, CastSpellExtraArgs(TRIGGERED_FULL_MASK) .SetOriginalCaster(GetCasterGUID()) .SetOriginalCastId(GetCastId())); } } if (std::vector const* spellTriggered = sSpellMgr->GetSpellLinked(SPELL_LINK_AURA, GetId())) { for (std::vector::const_iterator itr = spellTriggered->begin(); itr != spellTriggered->end(); ++itr) { if (*itr < 0) target->ApplySpellImmune(GetId(), IMMUNITY_ID, -(*itr), false); else target->RemoveAura(*itr, GetCasterGUID(), 0, removeMode); } } } } else if (apply) { // modify stack amount of linked auras if (std::vector const* spellTriggered = sSpellMgr->GetSpellLinked(SPELL_LINK_AURA, GetId())) { for (std::vector::const_iterator itr = spellTriggered->begin(); itr != spellTriggered->end(); ++itr) if (*itr > 0) if (Aura* triggeredAura = target->GetAura(*itr, GetCasterGUID())) triggeredAura->ModStackAmount(GetStackAmount() - triggeredAura->GetStackAmount()); } } // mods at aura apply if (apply) { switch (GetSpellInfo()->SpellFamilyName) { case SPELLFAMILY_GENERIC: switch (GetId()) { case 33572: // Gronn Lord's Grasp, becomes stoned if (GetStackAmount() >= 5 && !target->HasAura(33652)) target->CastSpell(target, 33652, CastSpellExtraArgs(TRIGGERED_FULL_MASK) .SetOriginalCastId(GetCastId())); break; case 50836: //Petrifying Grip, becomes stoned if (GetStackAmount() >= 5 && !target->HasAura(50812)) target->CastSpell(target, 50812, CastSpellExtraArgs(TRIGGERED_FULL_MASK) .SetOriginalCastId(GetCastId())); break; case 60970: // Heroic Fury (remove Intercept cooldown) if (target->GetTypeId() == TYPEID_PLAYER) target->GetSpellHistory()->ResetCooldown(20252, true); break; } break; case SPELLFAMILY_DRUID: if (!caster) break; // Rejuvenation if (GetSpellInfo()->SpellFamilyFlags[0] & 0x10 && GetEffect(EFFECT_0)) { // Druid T8 Restoration 4P Bonus if (caster->HasAura(64760)) { CastSpellExtraArgs args(GetEffect(EFFECT_0)); args.AddSpellMod(SPELLVALUE_BASE_POINT0, GetEffect(EFFECT_0)->GetAmount()); caster->CastSpell(target, 64801, args); } } break; } } // mods at aura remove else { switch (GetSpellInfo()->SpellFamilyName) { case SPELLFAMILY_MAGE: switch (GetId()) { case 66: // Invisibility if (removeMode != AURA_REMOVE_BY_EXPIRE) break; target->CastSpell(target, 32612, GetEffect(1)); break; default: break; } break; case SPELLFAMILY_PRIEST: if (!caster) break; // Power word: shield if (removeMode == AURA_REMOVE_BY_ENEMY_SPELL && GetSpellInfo()->SpellFamilyFlags[0] & 0x00000001) { // Rapture if (Aura const* aura = caster->GetAuraOfRankedSpell(47535)) { // check cooldown if (caster->GetTypeId() == TYPEID_PLAYER) { if (caster->GetSpellHistory()->HasCooldown(aura->GetSpellInfo())) { // This additional check is needed to add a minimal delay before cooldown in in effect // to allow all bubbles broken by a single damage source proc mana return if (caster->GetSpellHistory()->GetRemainingCooldown(aura->GetSpellInfo()) <= 11s) break; } else // and add if needed caster->GetSpellHistory()->AddCooldown(aura->GetId(), 0, 12s); } // effect on caster if (AuraEffect const* aurEff = aura->GetEffect(0)) { float multiplier = float(aurEff->GetAmount()); CastSpellExtraArgs args(TRIGGERED_FULL_MASK); args.SetOriginalCastId(GetCastId()); args.AddSpellMod(SPELLVALUE_BASE_POINT0, CalculatePct(caster->GetMaxPower(POWER_MANA), multiplier)); caster->CastSpell(caster, 47755, args); } } } break; case SPELLFAMILY_ROGUE: // Remove Vanish on stealth remove if (GetId() == 1784) target->RemoveAurasWithFamily(SPELLFAMILY_ROGUE, flag128(0x0000800, 0, 0, 0), target->GetGUID()); break; } } // mods at aura apply or remove switch (GetSpellInfo()->SpellFamilyName) { case SPELLFAMILY_HUNTER: switch (GetId()) { case 19574: // Bestial Wrath // The Beast Within cast on owner if talent present if (Unit* owner = target->GetOwner()) { // Search talent if (owner->HasAura(34692)) { if (apply) owner->CastSpell(owner, 34471, GetEffect(0)); else owner->RemoveAurasDueToSpell(34471); } } break; } break; case SPELLFAMILY_PALADIN: switch (GetId()) { case 31842: // Divine Favor // Item - Paladin T10 Holy 2P Bonus if (target->HasAura(70755)) { if (apply) target->CastSpell(target, 71166, CastSpellExtraArgs(TRIGGERED_FULL_MASK) .SetOriginalCastId(GetCastId())); else target->RemoveAurasDueToSpell(71166); } break; } break; } if (apply) { if (Creature* creature = target->ToCreature()) if (CreatureAI* ai = creature->AI()) ai->OnAuraApplied(aurApp); } else { if (Creature* creature = target->ToCreature()) if (CreatureAI* ai = creature->AI()) ai->OnAuraRemoved(aurApp); } } bool Aura::CanBeAppliedOn(Unit* target) { for (uint32 label : GetSpellInfo()->Labels) if (target->HasAuraTypeWithMiscvalue(SPELL_AURA_SUPPRESS_ITEM_PASSIVE_EFFECT_BY_SPELL_LABEL, label)) return false; // unit not in world or during remove from world if (!target->IsInWorld() || target->IsDuringRemoveFromWorld()) { // area auras mustn't be applied if (GetOwner() != target) return false; // do not apply non-selfcast single target auras if (GetCasterGUID() != GetOwner()->GetGUID() && GetSpellInfo()->IsSingleTarget()) return false; return true; } else return CheckAreaTarget(target); } bool Aura::CheckAreaTarget(Unit* target) { return CallScriptCheckAreaTargetHandlers(target); } bool Aura::CanStackWith(Aura const* existingAura) const { // Can stack with self if (this == existingAura) return true; bool sameCaster = GetCasterGUID() == existingAura->GetCasterGUID(); SpellInfo const* existingSpellInfo = existingAura->GetSpellInfo(); // Dynobj auras do not stack when they come from the same spell cast by the same caster if (GetType() == DYNOBJ_AURA_TYPE || existingAura->GetType() == DYNOBJ_AURA_TYPE) { if (sameCaster && m_spellInfo->Id == existingSpellInfo->Id) return false; return true; } // passive auras don't stack with another rank of the spell cast by same caster if (IsPassive() && sameCaster && (m_spellInfo->IsDifferentRankOf(existingSpellInfo) || (m_spellInfo->Id == existingSpellInfo->Id && m_castItemGuid.IsEmpty()))) return false; for (SpellEffectInfo const& spellEffectInfo : existingSpellInfo->GetEffects()) { // prevent remove triggering aura by triggered aura if (spellEffectInfo.TriggerSpell == GetId()) return true; } for (SpellEffectInfo const& spellEffectInfo : GetSpellInfo()->GetEffects()) { // prevent remove triggered aura by triggering aura refresh if (spellEffectInfo.TriggerSpell == existingAura->GetId()) return true; } // check spell specific stack rules if (m_spellInfo->IsAuraExclusiveBySpecificWith(existingSpellInfo) || (sameCaster && m_spellInfo->IsAuraExclusiveBySpecificPerCasterWith(existingSpellInfo))) return false; // check spell group stack rules switch (sSpellMgr->CheckSpellGroupStackRules(m_spellInfo, existingSpellInfo)) { case SPELL_GROUP_STACK_RULE_EXCLUSIVE: case SPELL_GROUP_STACK_RULE_EXCLUSIVE_HIGHEST: // if it reaches this point, existing aura is lower/equal return false; case SPELL_GROUP_STACK_RULE_EXCLUSIVE_FROM_SAME_CASTER: if (sameCaster) return false; break; case SPELL_GROUP_STACK_RULE_DEFAULT: case SPELL_GROUP_STACK_RULE_EXCLUSIVE_SAME_EFFECT: default: break; } if (m_spellInfo->SpellFamilyName != existingSpellInfo->SpellFamilyName) return true; if (!sameCaster) { // Channeled auras can stack if not forbidden by db or aura type if (existingAura->GetSpellInfo()->IsChanneled()) return true; if (m_spellInfo->HasAttribute(SPELL_ATTR3_DOT_STACKING_RULE)) return true; // check same periodic auras auto hasPeriodicNonAreaEffect = [](SpellInfo const* spellInfo) { for (SpellEffectInfo const& spellEffectInfo : spellInfo->GetEffects()) { switch (spellEffectInfo.ApplyAuraName) { // DOT or HOT from different casters will stack case SPELL_AURA_PERIODIC_DAMAGE: case SPELL_AURA_PERIODIC_WEAPON_PERCENT_DAMAGE: case SPELL_AURA_PERIODIC_DUMMY: case SPELL_AURA_PERIODIC_HEAL: case SPELL_AURA_PERIODIC_TRIGGER_SPELL: case SPELL_AURA_PERIODIC_ENERGIZE: case SPELL_AURA_PERIODIC_MANA_LEECH: case SPELL_AURA_PERIODIC_LEECH: case SPELL_AURA_POWER_BURN: case SPELL_AURA_OBS_MOD_POWER: case SPELL_AURA_OBS_MOD_HEALTH: case SPELL_AURA_PERIODIC_TRIGGER_SPELL_WITH_VALUE: case SPELL_AURA_PERIODIC_DAMAGE_PERCENT: // periodic auras which target areas are not allowed to stack this way (replenishment for example) if (spellEffectInfo.IsTargetingArea()) return false; return true; default: break; } } return false; }; if (hasPeriodicNonAreaEffect(m_spellInfo) && hasPeriodicNonAreaEffect(existingSpellInfo)) return true; } if (HasEffectType(SPELL_AURA_CONTROL_VEHICLE) && existingAura->HasEffectType(SPELL_AURA_CONTROL_VEHICLE)) { Vehicle* veh = nullptr; if (GetOwner()->ToUnit()) veh = GetOwner()->ToUnit()->GetVehicleKit(); if (!veh) // We should probably just let it stack. Vehicle system will prevent undefined behaviour later return true; if (!veh->GetAvailableSeatCount()) return false; // No empty seat available return true; // Empty seat available (skip rest) } if (HasEffectType(SPELL_AURA_SHOW_CONFIRMATION_PROMPT) || HasEffectType(SPELL_AURA_SHOW_CONFIRMATION_PROMPT_WITH_DIFFICULTY)) if (existingAura->HasEffectType(SPELL_AURA_SHOW_CONFIRMATION_PROMPT) || existingAura->HasEffectType(SPELL_AURA_SHOW_CONFIRMATION_PROMPT_WITH_DIFFICULTY)) return false; // spell of same spell rank chain if (m_spellInfo->IsRankOf(existingSpellInfo)) { // don't allow passive area auras to stack if (m_spellInfo->IsMultiSlotAura() && !IsArea()) return true; if (!GetCastItemGUID().IsEmpty() && !existingAura->GetCastItemGUID().IsEmpty()) if (GetCastItemGUID() != existingAura->GetCastItemGUID() && (m_spellInfo->HasAttribute(SPELL_ATTR0_CU_ENCHANT_PROC))) return true; // same spell with same caster should not stack return false; } return true; } bool Aura::IsProcOnCooldown(TimePoint now) const { return m_procCooldown > now; } void Aura::AddProcCooldown(SpellProcEntry const* procEntry, TimePoint now) { // cooldowns should be added to the whole aura (see 51698 area aura) int32 procCooldown = procEntry->Cooldown.count(); if (Unit* caster = GetCaster()) if (Player* modOwner = caster->GetSpellModOwner()) modOwner->ApplySpellMod(GetSpellInfo(), SpellModOp::ProcCooldown, procCooldown); m_procCooldown = now + Milliseconds(procCooldown); } void Aura::ResetProcCooldown() { m_procCooldown = GameTime::Now(); } void Aura::PrepareProcToTrigger(AuraApplication* aurApp, ProcEventInfo& eventInfo, TimePoint now) { bool prepare = CallScriptPrepareProcHandlers(aurApp, eventInfo); if (!prepare) return; SpellProcEntry const* procEntry = sSpellMgr->GetSpellProcEntry(GetSpellInfo()); ASSERT(procEntry); PrepareProcChargeDrop(procEntry, eventInfo); // cooldowns should be added to the whole aura (see 51698 area aura) AddProcCooldown(procEntry, now); SetLastProcSuccessTime(now); } void Aura::PrepareProcChargeDrop(SpellProcEntry const* procEntry, ProcEventInfo const& eventInfo) { // take one charge, aura expiration will be handled in Aura::TriggerProcOnEvent (if needed) if (!(procEntry->AttributesMask & PROC_ATTR_USE_STACKS_FOR_CHARGES) && IsUsingCharges() && (!eventInfo.GetSpellInfo() || !eventInfo.GetSpellInfo()->HasAttribute(SPELL_ATTR6_DO_NOT_CONSUME_RESOURCES))) { --m_procCharges; SetNeedClientUpdateForTargets(); } } void Aura::ConsumeProcCharges(SpellProcEntry const* procEntry) { // Remove aura if we've used last charge to proc if (procEntry->AttributesMask & PROC_ATTR_USE_STACKS_FOR_CHARGES) { ModStackAmount(-1); } else if (IsUsingCharges()) { if (!GetCharges()) Remove(); } } uint32 Aura::GetProcEffectMask(AuraApplication* aurApp, ProcEventInfo& eventInfo, TimePoint now) const { SpellProcEntry const* procEntry = sSpellMgr->GetSpellProcEntry(GetSpellInfo()); // only auras with spell proc entry can trigger proc if (!procEntry) return 0; // check spell triggering us if (Spell const* spell = eventInfo.GetProcSpell()) { // Do not allow auras to proc from effect triggered from itself if (spell->IsTriggeredByAura(m_spellInfo)) return 0; // check if aura can proc when spell is triggered (exception for hunter auto shot & wands) if (!GetSpellInfo()->HasAttribute(SPELL_ATTR3_CAN_PROC_FROM_PROCS) && !(procEntry->AttributesMask & PROC_ATTR_TRIGGERED_CAN_PROC) && !(eventInfo.GetTypeMask() & AUTO_ATTACK_PROC_FLAG_MASK)) if (spell->IsTriggered() && !spell->GetSpellInfo()->HasAttribute(SPELL_ATTR3_NOT_A_PROC)) return 0; if (spell->m_CastItem && (procEntry->AttributesMask & PROC_ATTR_CANT_PROC_FROM_ITEM_CAST)) return 0; if (spell->GetSpellInfo()->HasAttribute(SPELL_ATTR4_SUPPRESS_WEAPON_PROCS) && GetSpellInfo()->HasAttribute(SPELL_ATTR6_AURA_IS_WEAPON_PROC)) return 0; if (GetSpellInfo()->HasAttribute(SPELL_ATTR12_ONLY_PROC_FROM_CLASS_ABILITIES) && !spell->GetSpellInfo()->HasAttribute(SPELL_ATTR13_ALLOW_CLASS_ABILITY_PROCS)) return 0; if (eventInfo.GetTypeMask() & TAKEN_HIT_PROC_FLAG_MASK) { if (spell->GetSpellInfo()->HasAttribute(SPELL_ATTR3_SUPPRESS_TARGET_PROCS) && !GetSpellInfo()->HasAttribute(SPELL_ATTR7_CAN_PROC_FROM_SUPPRESSED_TARGET_PROCS)) return 0; } else { if (spell->GetSpellInfo()->HasAttribute(SPELL_ATTR3_SUPPRESS_CASTER_PROCS) && !spell->GetSpellInfo()->HasAttribute(SPELL_ATTR12_ENABLE_PROCS_FROM_SUPPRESSED_CASTER_PROCS) && !GetSpellInfo()->HasAttribute(SPELL_ATTR12_CAN_PROC_FROM_SUPPRESSED_CASTER_PROCS)) return 0; } } // check don't break stealth attr present if (m_spellInfo->HasAura(SPELL_AURA_MOD_STEALTH)) { if (SpellInfo const* spellInfo = eventInfo.GetSpellInfo()) if (spellInfo->HasAttribute(SPELL_ATTR0_CU_DONT_BREAK_STEALTH)) return 0; } // check if we have charges to proc with if (IsUsingCharges() && !GetCharges()) return 0; if (procEntry->AttributesMask & PROC_ATTR_REQ_SPELLMOD && (IsUsingCharges() || procEntry->AttributesMask & PROC_ATTR_USE_STACKS_FOR_CHARGES)) if (Spell const* spell = eventInfo.GetProcSpell()) if (!spell->m_appliedMods.contains(const_cast(this))) return 0; // check proc cooldown if (IsProcOnCooldown(now)) return 0; // do checks against db data if (!SpellMgr::CanSpellTriggerProcOnEvent(*procEntry, eventInfo)) return 0; // do checks using conditions table if (!sConditionMgr->IsObjectMeetingNotGroupedConditions(CONDITION_SOURCE_TYPE_SPELL_PROC, GetId(), eventInfo.GetActor(), eventInfo.GetActionTarget())) return 0; // AuraScript Hook bool check = const_cast(this)->CallScriptCheckProcHandlers(aurApp, eventInfo); if (!check) return 0; // At least one effect has to pass checks to proc aura uint32 procEffectMask = aurApp->GetEffectMask(); for (AuraEffect const* aurEff : GetAuraEffects()) if (procEffectMask & (1u << aurEff->GetEffIndex())) if ((procEntry->DisableEffectsMask & (1u << aurEff->GetEffIndex())) || !aurEff->CheckEffectProc(aurApp, eventInfo)) procEffectMask &= ~(1u << aurEff->GetEffIndex()); if (!procEffectMask) return 0; /// @todo // do allow additional requirements for procs // this is needed because this is the last moment in which you can prevent aura charge drop on proc // and possibly a way to prevent default checks (if there're going to be any) // Check if current equipment meets aura requirements // do that only for passive spells /// @todo this needs to be unified for all kinds of auras Unit* target = aurApp->GetTarget(); if (IsPassive() && target->GetTypeId() == TYPEID_PLAYER && GetSpellInfo()->EquippedItemClass != -1) { if (!GetSpellInfo()->HasAttribute(SPELL_ATTR3_NO_PROC_EQUIP_REQUIREMENT)) { Item* item = nullptr; if (GetSpellInfo()->EquippedItemClass == ITEM_CLASS_WEAPON) { if (target->ToPlayer()->IsInFeralForm()) return 0; if (DamageInfo const* damageInfo = eventInfo.GetDamageInfo()) { if (damageInfo->GetAttackType() != OFF_ATTACK) item = target->ToPlayer()->GetUseableItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND); else item = target->ToPlayer()->GetUseableItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND); } } else if (GetSpellInfo()->EquippedItemClass == ITEM_CLASS_ARMOR) { // Check if player is wearing shield item = target->ToPlayer()->GetUseableItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND); } if (!item || item->IsBroken() || !item->IsFitToSpellRequirements(GetSpellInfo())) return 0; } } if (m_spellInfo->HasAttribute(SPELL_ATTR3_ONLY_PROC_OUTDOORS)) if (!target->IsOutdoors()) return 0; if (m_spellInfo->HasAttribute(SPELL_ATTR3_ONLY_PROC_ON_CASTER)) if (target->GetGUID() != GetCasterGUID()) return 0; if (!m_spellInfo->HasAttribute(SPELL_ATTR4_ALLOW_PROC_WHILE_SITTING)) if (!target->IsStandState()) return 0; bool success = roll_chance_f(CalcProcChance(*procEntry, eventInfo)); const_cast(this)->SetLastProcAttemptTime(now); if (success) return procEffectMask; return 0; } float Aura::CalcProcChance(SpellProcEntry const& procEntry, ProcEventInfo& eventInfo) const { float chance = procEntry.Chance; // calculate chances depending on unit with caster's data // so talents modifying chances and judgements will have properly calculated proc chance if (Unit* caster = GetCaster()) { // calculate ppm chance if present and we're using weapon if (eventInfo.GetDamageInfo() && procEntry.ProcsPerMinute != 0) { uint32 WeaponSpeed = caster->GetBaseAttackTime(eventInfo.GetDamageInfo()->GetAttackType()); chance = caster->GetPPMProcChance(WeaponSpeed, procEntry.ProcsPerMinute, GetSpellInfo()); } if (GetSpellInfo()->ProcBasePPM > 0.0f) chance = CalcPPMProcChance(caster); // apply chance modifer aura, applies also to ppm chance (see improved judgement of light spell) if (Player* modOwner = caster->GetSpellModOwner()) modOwner->ApplySpellMod(GetSpellInfo(), SpellModOp::ProcChance, chance); } // proc chance is reduced by an additional 3.333% per level past 60 if ((procEntry.AttributesMask & PROC_ATTR_REDUCE_PROC_60) && eventInfo.GetActor()->GetLevel() > 60) chance = std::max(0.f, (1.f - ((eventInfo.GetActor()->GetLevel() - 60) * 1.f / 30.f)) * chance); return chance; } void Aura::TriggerProcOnEvent(uint32 procEffectMask, AuraApplication* aurApp, ProcEventInfo& eventInfo) { if (procEffectMask) { bool prevented = CallScriptProcHandlers(aurApp, eventInfo); if (!prevented) { for (std::size_t i = 0; i < GetAuraEffectCount(); ++i) { if (!(procEffectMask & (1 << i))) continue; // OnEffectProc / AfterEffectProc hooks handled in AuraEffect::HandleProc() if (aurApp->HasEffect(i)) GetEffect(i)->HandleProc(aurApp, eventInfo); } CallScriptAfterProcHandlers(aurApp, eventInfo); } } ConsumeProcCharges(ASSERT_NOTNULL(sSpellMgr->GetSpellProcEntry(GetSpellInfo()))); } float Aura::CalcPPMProcChance(Unit* actor) const { // Formula see http://us.battle.net/wow/en/forum/topic/8197741003#1 float ppm = m_spellInfo->CalcProcPPM(actor, GetCastItemLevel()); float averageProcInterval = 60.0f / ppm; TimePoint currentTime = GameTime::Now(); float secondsSinceLastAttempt = std::min(duration_cast(currentTime - m_lastProcAttemptTime).count(), 10.0f); float secondsSinceLastProc = std::min(duration_cast(currentTime - m_lastProcSuccessTime).count(), 1000.0f); float chance = std::max(1.0f, 1.0f + ((secondsSinceLastProc / averageProcInterval - 1.5f) * 3.0f)) * ppm * secondsSinceLastAttempt / 60.0f; RoundToInterval(chance, 0.0f, 1.0f); return chance * 100.0f; } void Aura::_DeleteRemovedApplications() { for (AuraApplication* aurApp : _removedApplications) delete aurApp; _removedApplications.clear(); } void Aura::LoadScripts() { sScriptMgr->CreateAuraScripts(m_spellInfo->Id, m_loadedScripts, this); for (AuraScript* script : m_loadedScripts) { TC_LOG_DEBUG("spells", "Aura::LoadScripts: Script `{}` for aura `{}` is loaded now", script->GetScriptName(), m_spellInfo->Id); script->Register(); } } bool Aura::CallScriptCheckAreaTargetHandlers(Unit* target) { bool result = true; for (AuraScript* script : m_loadedScripts) { script->_PrepareScriptCall(AURA_SCRIPT_HOOK_CHECK_AREA_TARGET); for (AuraScript::CheckAreaTargetHandler const& checkAreaTarget : script->DoCheckAreaTarget) result &= checkAreaTarget.Call(script, target); script->_FinishScriptCall(); } return result; } void Aura::CallScriptDispel(DispelInfo* dispelInfo) { for (AuraScript* script : m_loadedScripts) { script->_PrepareScriptCall(AURA_SCRIPT_HOOK_DISPEL); for (AuraScript::AuraDispelHandler const& onDispel : script->OnDispel) onDispel.Call(script, dispelInfo); script->_FinishScriptCall(); } } void Aura::CallScriptAfterDispel(DispelInfo* dispelInfo) { for (AuraScript* script : m_loadedScripts) { script->_PrepareScriptCall(AURA_SCRIPT_HOOK_AFTER_DISPEL); for (AuraScript::AuraDispelHandler const& afterDispel : script->AfterDispel) afterDispel.Call(script, dispelInfo); script->_FinishScriptCall(); } } void Aura::CallScriptOnHeartbeat() { for (AuraScript* script : m_loadedScripts) { script->_PrepareScriptCall(AURA_SCRIPT_HOOK_ON_HEARTBEAT); for (AuraScript::AuraHeartbeatHandler const& onHeartbeat : script->OnHeartbeat) onHeartbeat.Call(script); script->_FinishScriptCall(); } } bool Aura::CallScriptEffectApplyHandlers(AuraEffect const* aurEff, AuraApplication const* aurApp, AuraEffectHandleModes mode) { bool preventDefault = false; for (AuraScript* script : m_loadedScripts) { script->_PrepareScriptCall(AURA_SCRIPT_HOOK_EFFECT_APPLY, aurApp); for (AuraScript::EffectApplyHandler const& onEffectApply : script->OnEffectApply) if (onEffectApply.IsEffectAffected(m_spellInfo, aurEff->GetEffIndex())) onEffectApply.Call(script, aurEff, mode); if (!preventDefault) preventDefault = script->_IsDefaultActionPrevented(); script->_FinishScriptCall(); } return preventDefault; } bool Aura::CallScriptEffectRemoveHandlers(AuraEffect const* aurEff, AuraApplication const* aurApp, AuraEffectHandleModes mode) { bool preventDefault = false; for (AuraScript* script : m_loadedScripts) { script->_PrepareScriptCall(AURA_SCRIPT_HOOK_EFFECT_REMOVE, aurApp); for (AuraScript::EffectApplyHandler const& onEffectRemove : script->OnEffectRemove) if (onEffectRemove.IsEffectAffected(m_spellInfo, aurEff->GetEffIndex())) onEffectRemove.Call(script, aurEff, mode); if (!preventDefault) preventDefault = script->_IsDefaultActionPrevented(); script->_FinishScriptCall(); } return preventDefault; } void Aura::CallScriptAfterEffectApplyHandlers(AuraEffect const* aurEff, AuraApplication const* aurApp, AuraEffectHandleModes mode) { for (AuraScript* script : m_loadedScripts) { script->_PrepareScriptCall(AURA_SCRIPT_HOOK_EFFECT_AFTER_APPLY, aurApp); for (AuraScript::EffectApplyHandler const& afterEffectApply : script->AfterEffectApply) if (afterEffectApply.IsEffectAffected(m_spellInfo, aurEff->GetEffIndex())) afterEffectApply.Call(script, aurEff, mode); script->_FinishScriptCall(); } } void Aura::CallScriptAfterEffectRemoveHandlers(AuraEffect const* aurEff, AuraApplication const* aurApp, AuraEffectHandleModes mode) { for (AuraScript* script : m_loadedScripts) { script->_PrepareScriptCall(AURA_SCRIPT_HOOK_EFFECT_AFTER_REMOVE, aurApp); for (AuraScript::EffectApplyHandler const& afterEffectRemove : script->AfterEffectRemove) if (afterEffectRemove.IsEffectAffected(m_spellInfo, aurEff->GetEffIndex())) afterEffectRemove.Call(script, aurEff, mode); script->_FinishScriptCall(); } } bool Aura::CallScriptEffectPeriodicHandlers(AuraEffect const* aurEff, AuraApplication const* aurApp) { bool preventDefault = false; for (AuraScript* script : m_loadedScripts) { script->_PrepareScriptCall(AURA_SCRIPT_HOOK_EFFECT_PERIODIC, aurApp); for (AuraScript::EffectPeriodicHandler const& onEffectPeriodic : script->OnEffectPeriodic) if (onEffectPeriodic.IsEffectAffected(m_spellInfo, aurEff->GetEffIndex())) onEffectPeriodic.Call(script, aurEff); if (!preventDefault) preventDefault = script->_IsDefaultActionPrevented(); script->_FinishScriptCall(); } return preventDefault; } void Aura::CallScriptEffectUpdatePeriodicHandlers(AuraEffect* aurEff) { for (AuraScript* script : m_loadedScripts) { script->_PrepareScriptCall(AURA_SCRIPT_HOOK_EFFECT_UPDATE_PERIODIC); for (AuraScript::EffectUpdatePeriodicHandler const& onEffectUpdatePeriodic : script->OnEffectUpdatePeriodic) if (onEffectUpdatePeriodic.IsEffectAffected(m_spellInfo, aurEff->GetEffIndex())) onEffectUpdatePeriodic.Call(script, aurEff); script->_FinishScriptCall(); } } void Aura::CallScriptEffectCalcAmountHandlers(AuraEffect const* aurEff, int32& amount, bool& canBeRecalculated) { for (AuraScript* script : m_loadedScripts) { script->_PrepareScriptCall(AURA_SCRIPT_HOOK_EFFECT_CALC_AMOUNT); for (AuraScript::EffectCalcAmountHandler const& effectCalcAmount : script->DoEffectCalcAmount) if (effectCalcAmount.IsEffectAffected(m_spellInfo, aurEff->GetEffIndex())) effectCalcAmount.Call(script, aurEff, amount, canBeRecalculated); script->_FinishScriptCall(); } } void Aura::CallScriptEffectCalcPeriodicHandlers(AuraEffect const* aurEff, bool& isPeriodic, int32& amplitude) { for (AuraScript* script : m_loadedScripts) { script->_PrepareScriptCall(AURA_SCRIPT_HOOK_EFFECT_CALC_PERIODIC); for (AuraScript::EffectCalcPeriodicHandler const& effectCalcPeriodic : script->DoEffectCalcPeriodic) if (effectCalcPeriodic.IsEffectAffected(m_spellInfo, aurEff->GetEffIndex())) effectCalcPeriodic.Call(script, aurEff, isPeriodic, amplitude); script->_FinishScriptCall(); } } void Aura::CallScriptEffectCalcSpellModHandlers(AuraEffect const* aurEff, SpellModifier*& spellMod) { for (AuraScript* script : m_loadedScripts) { script->_PrepareScriptCall(AURA_SCRIPT_HOOK_EFFECT_CALC_SPELLMOD); for (AuraScript::EffectCalcSpellModHandler const& effectCalcSpellMod : script->DoEffectCalcSpellMod) if (effectCalcSpellMod.IsEffectAffected(m_spellInfo, aurEff->GetEffIndex())) effectCalcSpellMod.Call(script, aurEff, spellMod); script->_FinishScriptCall(); } } void Aura::CallScriptEffectCalcCritChanceHandlers(AuraEffect const* aurEff, AuraApplication const* aurApp, Unit const* victim, float& critChance) { for (AuraScript* script : m_loadedScripts) { script->_PrepareScriptCall(AURA_SCRIPT_HOOK_EFFECT_CALC_CRIT_CHANCE, aurApp); for (AuraScript::EffectCalcCritChanceHandler const& effectCalcCritChance : script->DoEffectCalcCritChance) if (effectCalcCritChance.IsEffectAffected(m_spellInfo, aurEff->GetEffIndex())) effectCalcCritChance.Call(script, aurEff, victim, critChance); script->_FinishScriptCall(); } } void Aura::CallScriptCalcDamageAndHealingHandlers(AuraEffect const* aurEff, AuraApplication const* aurApp, Unit* victim, int32& damageOrHealing, int32& flatMod, float& pctMod) { for (AuraScript* script : m_loadedScripts) { script->_PrepareScriptCall(AURA_SCRIPT_HOOK_EFFECT_CALC_DAMAGE_AND_HEALING, aurApp); for (AuraScript::EffectCalcDamageAndHealingHandler const& effectCalcDamageAndHealing : script->DoEffectCalcDamageAndHealing) if (effectCalcDamageAndHealing.IsEffectAffected(m_spellInfo, aurEff->GetEffIndex())) effectCalcDamageAndHealing.Call(script, aurEff, victim, damageOrHealing, flatMod, pctMod); script->_FinishScriptCall(); } } void Aura::CallScriptEffectAbsorbHandlers(AuraEffect* aurEff, AuraApplication const* aurApp, DamageInfo& dmgInfo, uint32& absorbAmount, bool& defaultPrevented) { for (AuraScript* script : m_loadedScripts) { script->_PrepareScriptCall(AURA_SCRIPT_HOOK_EFFECT_ABSORB, aurApp); for (AuraScript::EffectAbsorbHandler const& onEffectAbsorb : script->OnEffectAbsorb) if (onEffectAbsorb.IsEffectAffected(m_spellInfo, aurEff->GetEffIndex())) onEffectAbsorb.Call(script, aurEff, dmgInfo, absorbAmount); if (!defaultPrevented) defaultPrevented = script->_IsDefaultActionPrevented(); script->_FinishScriptCall(); } } void Aura::CallScriptEffectAfterAbsorbHandlers(AuraEffect* aurEff, AuraApplication const* aurApp, DamageInfo& dmgInfo, uint32& absorbAmount) { for (AuraScript* script : m_loadedScripts) { script->_PrepareScriptCall(AURA_SCRIPT_HOOK_EFFECT_AFTER_ABSORB, aurApp); for (AuraScript::EffectAbsorbHandler const& afterEffectAbsorb : script->AfterEffectAbsorb) if (afterEffectAbsorb.IsEffectAffected(m_spellInfo, aurEff->GetEffIndex())) afterEffectAbsorb.Call(script, aurEff, dmgInfo, absorbAmount); script->_FinishScriptCall(); } } void Aura::CallScriptEffectAbsorbHandlers(AuraEffect* aurEff, AuraApplication const* aurApp, HealInfo& healInfo, uint32& absorbAmount, bool& defaultPrevented) { for (AuraScript* script : m_loadedScripts) { script->_PrepareScriptCall(AURA_SCRIPT_HOOK_EFFECT_ABSORB, aurApp); for (AuraScript::EffectAbsorbHealHandler const& onEffectAbsorbHeal : script->OnEffectAbsorbHeal) if (onEffectAbsorbHeal.IsEffectAffected(m_spellInfo, aurEff->GetEffIndex())) onEffectAbsorbHeal.Call(script, aurEff, healInfo, absorbAmount); if (!defaultPrevented) defaultPrevented = script->_IsDefaultActionPrevented(); script->_FinishScriptCall(); } } void Aura::CallScriptEffectAfterAbsorbHandlers(AuraEffect* aurEff, AuraApplication const* aurApp, HealInfo& healInfo, uint32& absorbAmount) { for (AuraScript* script : m_loadedScripts) { script->_PrepareScriptCall(AURA_SCRIPT_HOOK_EFFECT_AFTER_ABSORB, aurApp); for (AuraScript::EffectAbsorbHealHandler const& afterEffectAbsorbHeal : script->AfterEffectAbsorbHeal) if (afterEffectAbsorbHeal.IsEffectAffected(m_spellInfo, aurEff->GetEffIndex())) afterEffectAbsorbHeal.Call(script, aurEff, healInfo, absorbAmount); script->_FinishScriptCall(); } } void Aura::CallScriptEffectManaShieldHandlers(AuraEffect* aurEff, AuraApplication const* aurApp, DamageInfo& dmgInfo, uint32& absorbAmount, bool& defaultPrevented) { for (AuraScript* script : m_loadedScripts) { script->_PrepareScriptCall(AURA_SCRIPT_HOOK_EFFECT_MANASHIELD, aurApp); for (AuraScript::EffectAbsorbHandler const& onEffectManaShield : script->OnEffectManaShield) if (onEffectManaShield.IsEffectAffected(m_spellInfo, aurEff->GetEffIndex())) onEffectManaShield.Call(script, aurEff, dmgInfo, absorbAmount); if (!defaultPrevented) defaultPrevented = script->_IsDefaultActionPrevented(); script->_FinishScriptCall(); } } void Aura::CallScriptEffectAfterManaShieldHandlers(AuraEffect* aurEff, AuraApplication const* aurApp, DamageInfo& dmgInfo, uint32& absorbAmount) { for (AuraScript* script : m_loadedScripts) { script->_PrepareScriptCall(AURA_SCRIPT_HOOK_EFFECT_AFTER_MANASHIELD, aurApp); for (AuraScript::EffectAbsorbHandler const& afterEffectManaShield : script->AfterEffectManaShield) if (afterEffectManaShield.IsEffectAffected(m_spellInfo, aurEff->GetEffIndex())) afterEffectManaShield.Call(script, aurEff, dmgInfo, absorbAmount); script->_FinishScriptCall(); } } void Aura::CallScriptEffectSplitHandlers(AuraEffect* aurEff, AuraApplication const* aurApp, DamageInfo& dmgInfo, uint32& splitAmount) { for (AuraScript* script : m_loadedScripts) { script->_PrepareScriptCall(AURA_SCRIPT_HOOK_EFFECT_SPLIT, aurApp); for (AuraScript::EffectAbsorbHandler const& effectSplit : script->OnEffectSplit) if (effectSplit.IsEffectAffected(m_spellInfo, aurEff->GetEffIndex())) effectSplit.Call(script, aurEff, dmgInfo, splitAmount); script->_FinishScriptCall(); } } void Aura::CallScriptEnterLeaveCombatHandlers(AuraApplication const* aurApp, bool isNowInCombat) { for (AuraScript* script : m_loadedScripts) { script->_PrepareScriptCall(AURA_SCRIPT_HOOK_ENTER_LEAVE_COMBAT, aurApp); for (AuraScript::EnterLeaveCombatHandler const& onEnterLeaveCombat : script->OnEnterLeaveCombat) onEnterLeaveCombat.Call(script, isNowInCombat); script->_FinishScriptCall(); } } bool Aura::CallScriptCheckProcHandlers(AuraApplication const* aurApp, ProcEventInfo& eventInfo) { bool result = true; for (AuraScript* script : m_loadedScripts) { script->_PrepareScriptCall(AURA_SCRIPT_HOOK_CHECK_PROC, aurApp); for (AuraScript::CheckProcHandler const& checkProc : script->DoCheckProc) result &= checkProc.Call(script, eventInfo); script->_FinishScriptCall(); } return result; } bool Aura::CallScriptPrepareProcHandlers(AuraApplication const* aurApp, ProcEventInfo& eventInfo) { bool prepare = true; for (AuraScript* script : m_loadedScripts) { script->_PrepareScriptCall(AURA_SCRIPT_HOOK_PREPARE_PROC, aurApp); for (AuraScript::AuraProcHandler const& prepareProc : script->DoPrepareProc) prepareProc.Call(script, eventInfo); if (prepare) prepare = !script->_IsDefaultActionPrevented(); script->_FinishScriptCall(); } return prepare; } bool Aura::CallScriptProcHandlers(AuraApplication const* aurApp, ProcEventInfo& eventInfo) { bool handled = false; for (AuraScript* script : m_loadedScripts) { script->_PrepareScriptCall(AURA_SCRIPT_HOOK_PROC, aurApp); for (AuraScript::AuraProcHandler const& onProc : script->OnProc) onProc.Call(script, eventInfo); handled |= script->_IsDefaultActionPrevented(); script->_FinishScriptCall(); } return handled; } void Aura::CallScriptAfterProcHandlers(AuraApplication const* aurApp, ProcEventInfo& eventInfo) { for (AuraScript* script : m_loadedScripts) { script->_PrepareScriptCall(AURA_SCRIPT_HOOK_AFTER_PROC, aurApp); for (AuraScript::AuraProcHandler const& afterProc : script->AfterProc) afterProc.Call(script, eventInfo); script->_FinishScriptCall(); } } bool Aura::CallScriptCheckEffectProcHandlers(AuraEffect const* aurEff, AuraApplication const* aurApp, ProcEventInfo& eventInfo) { bool result = true; for (AuraScript* script : m_loadedScripts) { script->_PrepareScriptCall(AURA_SCRIPT_HOOK_CHECK_EFFECT_PROC, aurApp); for (AuraScript::CheckEffectProcHandler const& checkEffectProc : script->DoCheckEffectProc) if (checkEffectProc.IsEffectAffected(m_spellInfo, aurEff->GetEffIndex())) result &= checkEffectProc.Call(script, aurEff, eventInfo); script->_FinishScriptCall(); } return result; } bool Aura::CallScriptEffectProcHandlers(AuraEffect* aurEff, AuraApplication const* aurApp, ProcEventInfo& eventInfo) { bool preventDefault = false; for (AuraScript* script : m_loadedScripts) { script->_PrepareScriptCall(AURA_SCRIPT_HOOK_EFFECT_PROC, aurApp); for (AuraScript::EffectProcHandler const& onEffectProc : script->OnEffectProc) if (onEffectProc.IsEffectAffected(m_spellInfo, aurEff->GetEffIndex())) onEffectProc.Call(script, aurEff, eventInfo); if (!preventDefault) preventDefault = script->_IsDefaultActionPrevented(); script->_FinishScriptCall(); } return preventDefault; } void Aura::CallScriptAfterEffectProcHandlers(AuraEffect* aurEff, AuraApplication const* aurApp, ProcEventInfo& eventInfo) { for (AuraScript* script : m_loadedScripts) { script->_PrepareScriptCall(AURA_SCRIPT_HOOK_EFFECT_AFTER_PROC, aurApp); for (AuraScript::EffectProcHandler const& afterEffectProc : script->AfterEffectProc) if (afterEffectProc.IsEffectAffected(m_spellInfo, aurEff->GetEffIndex())) afterEffectProc.Call(script, aurEff, eventInfo); script->_FinishScriptCall(); } } std::string Aura::GetDebugInfo() const { std::stringstream sstr; sstr << std::boolalpha << "Id: " << GetId() << " Name: '" << (*GetSpellInfo()->SpellName)[sWorld->GetDefaultDbcLocale()] << "' Caster: " << GetCasterGUID().ToString() << "\nOwner: " << (GetOwner() ? GetOwner()->GetDebugInfo() : "NULL"); return sstr.str(); } UnitAura::UnitAura(AuraCreateInfo const& createInfo) : Aura(createInfo) { m_AuraDRGroup = DIMINISHING_NONE; LoadScripts(); _InitEffects(createInfo._auraEffectMask, createInfo.Caster, createInfo.BaseAmount); GetUnitOwner()->_AddAura(this, createInfo.Caster); } void UnitAura::_ApplyForTarget(Unit* target, Unit* caster, AuraApplication* aurApp) { Aura::_ApplyForTarget(target, caster, aurApp); // register aura diminishing on apply if (DiminishingGroup group = GetDiminishGroup()) target->ApplyDiminishingAura(group, true); } void UnitAura::_UnapplyForTarget(Unit* target, Unit* caster, AuraApplication* aurApp) { Aura::_UnapplyForTarget(target, caster, aurApp); // unregister aura diminishing (and store last time) if (DiminishingGroup group = GetDiminishGroup()) target->ApplyDiminishingAura(group, false); } void UnitAura::Remove(AuraRemoveMode removeMode) { if (IsRemoved()) return; GetUnitOwner()->RemoveOwnedAura(this, removeMode); } void UnitAura::FillTargetMap(std::unordered_map& targets, Unit* caster) { Unit* unitOwner = GetUnitOwner(); if (GetSpellInfo()->HasAttribute(SPELL_ATTR7_DISABLE_AURA_WHILE_DEAD) && !unitOwner->IsAlive()) return; Unit* ref = caster; if (!ref) ref = unitOwner; // add non area aura targets // static applications go through spell system first, so we assume they meet conditions for (auto const& [targetGuid, effectMask] : _staticApplications) { Unit* target = ObjectAccessor::GetUnit(*unitOwner, targetGuid); if (!target && targetGuid == unitOwner->GetGUID()) target = unitOwner; if (target) targets.emplace(target, effectMask); } // skip area update if owner is not in world! if (!unitOwner->IsInWorld()) return; if (unitOwner->HasAuraState(AURA_STATE_BANISHED, GetSpellInfo(), caster)) return; for (SpellEffectInfo const& spellEffectInfo : GetSpellInfo()->GetEffects()) { if (!HasEffect(spellEffectInfo.EffectIndex)) continue; // area auras only if (spellEffectInfo.IsEffect(SPELL_EFFECT_APPLY_AURA)) continue; std::vector units; ConditionContainer* condList = spellEffectInfo.ImplicitTargetConditions.get(); float radius = spellEffectInfo.CalcRadius(ref); float extraSearchRadius = 0.0f; SpellTargetCheckTypes selectionType = TARGET_CHECK_DEFAULT; switch (spellEffectInfo.Effect) { case SPELL_EFFECT_APPLY_AREA_AURA_PARTY: case SPELL_EFFECT_APPLY_AREA_AURA_PARTY_NONRANDOM: selectionType = TARGET_CHECK_PARTY; break; case SPELL_EFFECT_APPLY_AREA_AURA_RAID: selectionType = TARGET_CHECK_RAID; break; case SPELL_EFFECT_APPLY_AREA_AURA_FRIEND: selectionType = TARGET_CHECK_ALLY; break; case SPELL_EFFECT_APPLY_AREA_AURA_ENEMY: selectionType = TARGET_CHECK_ENEMY; extraSearchRadius = radius > 0.0f ? EXTRA_CELL_SEARCH_RADIUS : 0.0f; break; case SPELL_EFFECT_APPLY_AREA_AURA_PET: if (!condList || sConditionMgr->IsObjectMeetToConditions(unitOwner, ref, *condList)) units.push_back(unitOwner); [[fallthrough]]; case SPELL_EFFECT_APPLY_AREA_AURA_OWNER: { if (Unit* owner = unitOwner->GetCharmerOrOwner()) if (unitOwner->IsWithinDistInMap(owner, radius)) if (!condList || sConditionMgr->IsObjectMeetToConditions(owner, ref, *condList)) units.push_back(owner); break; } case SPELL_EFFECT_APPLY_AURA_ON_PET: { if (Unit* pet = ObjectAccessor::GetUnit(*unitOwner, unitOwner->GetPetGUID())) if (!condList || sConditionMgr->IsObjectMeetToConditions(pet, ref, *condList)) units.push_back(pet); break; } case SPELL_EFFECT_APPLY_AREA_AURA_SUMMONS: { if (!condList || sConditionMgr->IsObjectMeetToConditions(unitOwner, ref, *condList)) units.push_back(unitOwner); selectionType = TARGET_CHECK_SUMMONED; break; } default: break; } if (selectionType != TARGET_CHECK_DEFAULT) { if (uint32 containerTypeMask = Spell::GetSearcherTypeMask(m_spellInfo, spellEffectInfo, TARGET_OBJECT_TYPE_UNIT, condList)) { Trinity::WorldObjectSpellAreaTargetCheck check(radius, unitOwner, ref, unitOwner, m_spellInfo, selectionType, condList, TARGET_OBJECT_TYPE_UNIT); Trinity::UnitListSearcher searcher(PhasingHandler::GetAlwaysVisiblePhaseShift(), units, check); Spell::SearchTargets(searcher, containerTypeMask, unitOwner, unitOwner, radius + extraSearchRadius); // by design WorldObjectSpellAreaTargetCheck allows not-in-world units (for spells) but for auras it is not acceptable Trinity::Containers::EraseIf(units, [unitOwner](Unit const* unit) { return !unit->IsSelfOrInSameMap(unitOwner); }); } } for (Unit* unit : units) targets[unit] |= 1 << spellEffectInfo.EffectIndex; } } void UnitAura::AddStaticApplication(Unit* target, uint32 effMask) { // only valid for non-area auras for (SpellEffectInfo const& spellEffectInfo : GetSpellInfo()->GetEffects()) { if ((effMask & (1 << spellEffectInfo.EffectIndex)) && !spellEffectInfo.IsEffect(SPELL_EFFECT_APPLY_AURA)) effMask &= ~(1 << spellEffectInfo.EffectIndex); } if (!effMask) return; _staticApplications[target->GetGUID()] |= effMask; } void UnitAura::Heartbeat() { Aura::Heartbeat(); // Periodic food and drink emote animation HandlePeriodicFoodSpellVisualKit(); // Invoke the OnHeartbeat AuraScript hook CallScriptOnHeartbeat(); } void UnitAura::HandlePeriodicFoodSpellVisualKit() { SpellSpecificType specificType = GetSpellInfo()->GetSpellSpecific(); bool food = specificType == SPELL_SPECIFIC_FOOD || specificType == SPELL_SPECIFIC_FOOD_AND_DRINK; bool drink = specificType == SPELL_SPECIFIC_DRINK || specificType == SPELL_SPECIFIC_FOOD_AND_DRINK; if (food) GetUnitOwner()->SendPlaySpellVisualKit(SPELL_VISUAL_KIT_FOOD, 0, 0); if (drink) GetUnitOwner()->SendPlaySpellVisualKit(SPELL_VISUAL_KIT_DRINK, 0, 0); } DynObjAura::DynObjAura(AuraCreateInfo const& createInfo) : Aura(createInfo) { LoadScripts(); DynamicObject* dynObjOwner = GetDynobjOwner(); ASSERT(dynObjOwner); ASSERT(createInfo.Caster); ASSERT(dynObjOwner->IsInMap(createInfo.Caster)); _InitEffects(createInfo._auraEffectMask, createInfo.Caster, createInfo.BaseAmount); dynObjOwner->SetAura(this); } void DynObjAura::Remove(AuraRemoveMode removeMode) { if (IsRemoved()) return; _Remove(removeMode); } void DynObjAura::FillTargetMap(std::unordered_map& targets, Unit* /*caster*/) { DynamicObject* dynObjOwner = GetDynobjOwner(); Unit* dynObjOwnerCaster = dynObjOwner->GetCaster(); float radius = dynObjOwner->GetRadius(); for (SpellEffectInfo const& spellEffectInfo : GetSpellInfo()->GetEffects()) { if (!HasEffect(spellEffectInfo.EffectIndex)) continue; // we can't use effect type like area auras to determine check type, check targets SpellTargetCheckTypes selectionType = spellEffectInfo.TargetA.GetCheckType(); if (spellEffectInfo.TargetB.GetReferenceType() == TARGET_REFERENCE_TYPE_DEST) selectionType = spellEffectInfo.TargetB.GetCheckType(); std::vector units; ConditionContainer* condList = spellEffectInfo.ImplicitTargetConditions.get(); Trinity::WorldObjectSpellAreaTargetCheck check(radius, dynObjOwner, dynObjOwnerCaster, dynObjOwnerCaster, m_spellInfo, selectionType, condList, TARGET_OBJECT_TYPE_UNIT); Trinity::UnitListSearcher searcher(dynObjOwner, units, check); Cell::VisitAllObjects(dynObjOwner, searcher, radius); // by design WorldObjectSpellAreaTargetCheck allows not-in-world units (for spells) but for auras it is not acceptable Trinity::Containers::EraseIf(units, [dynObjOwner](Unit const* unit) { return !unit->IsSelfOrInSameMap(dynObjOwner); }); for (Unit* unit : units) targets[unit] |= 1 << spellEffectInfo.EffectIndex; } }