/* * Copyright (C) 2005-2011 MaNGOS * Copyright (C) 2009-2011 MaNGOSZero * Copyright (C) 2011-2016 Nostalrius * Copyright (C) 2016-2017 Elysium Project * * 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, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "Spell.h" #include "Database/DatabaseEnv.h" #include "WorldPacket.h" #include "WorldSession.h" #include "GridNotifiers.h" #include "GridNotifiersImpl.h" #include "Opcodes.h" #include "Log.h" #include "UpdateMask.h" #include "World.h" #include "ObjectMgr.h" #include "SpellMgr.h" #include "Player.h" #include "Pet.h" #include "Unit.h" #include "DynamicObject.h" #include "Group.h" #include "UpdateData.h" #include "MapManager.h" #include "ObjectAccessor.h" #include "CellImpl.h" #include "Policies/SingletonImp.h" #include "SharedDefines.h" #include "LootMgr.h" #include "VMapFactory.h" #include "BattleGround.h" #include "Util.h" #include "Chat.h" #include "PathFinder.h" #include "CharacterDatabaseCache.h" #include "GameObjectAI.h" #include "ZoneScript.h" #define SPELL_CHANNEL_UPDATE_INTERVAL (1 * IN_MILLISECONDS) extern pEffect SpellEffects[TOTAL_SPELL_EFFECTS]; bool IsQuestTameSpell(uint32 spellId) { SpellEntry const *spellproto = sSpellMgr.GetSpellEntry(spellId); if (!spellproto) return false; return spellproto->Effect[EFFECT_INDEX_0] == SPELL_EFFECT_THREAT && spellproto->Effect[EFFECT_INDEX_1] == SPELL_EFFECT_APPLY_AURA && spellproto->EffectApplyAuraName[EFFECT_INDEX_1] == SPELL_AURA_DUMMY; } SpellCastTargets::SpellCastTargets() { m_unitTarget = nullptr; m_itemTarget = nullptr; m_GOTarget = nullptr; m_itemTargetEntry = 0; m_srcX = m_srcY = m_srcZ = m_destX = m_destY = m_destZ = 0.0f; m_targetMask = 0; } SpellCastTargets::~SpellCastTargets() { } void SpellCastTargets::setUnitTarget(Unit *target) { if (!target) return; m_destX = target->GetPositionX(); m_destY = target->GetPositionY(); m_destZ = target->GetPositionZ(); m_unitTarget = target; m_unitTargetGUID = target->GetObjectGuid(); m_targetMask |= TARGET_FLAG_UNIT; } void SpellCastTargets::setDestination(float x, float y, float z) { m_destX = x; m_destY = y; m_destZ = z; m_targetMask |= TARGET_FLAG_DEST_LOCATION; } void SpellCastTargets::setSource(float x, float y, float z) { m_srcX = x; m_srcY = y; m_srcZ = z; m_targetMask |= TARGET_FLAG_SOURCE_LOCATION; } void SpellCastTargets::setGOTarget(GameObject *target) { m_GOTarget = target; m_GOTargetGUID = target->GetObjectGuid(); // m_targetMask |= TARGET_FLAG_OBJECT; } void SpellCastTargets::setItemTarget(Item* item) { if (!item) return; m_itemTarget = item; m_itemTargetGUID = item->GetObjectGuid(); m_itemTargetEntry = item->GetEntry(); m_targetMask |= TARGET_FLAG_ITEM; } void SpellCastTargets::setTradeItemTarget(Player* caster) { m_itemTargetGUID = ObjectGuid(uint64(TRADE_SLOT_NONTRADED)); m_itemTargetEntry = 0; m_targetMask |= TARGET_FLAG_TRADE_ITEM; Update(caster); } void SpellCastTargets::setCorpseTarget(Corpse* corpse) { m_CorpseTargetGUID = corpse->GetObjectGuid(); } void SpellCastTargets::Update(Unit* caster) { m_GOTarget = m_GOTargetGUID ? caster->GetMap()->GetGameObject(m_GOTargetGUID) : NULL; m_unitTarget = m_unitTargetGUID ? (m_unitTargetGUID == caster->GetObjectGuid() ? caster : ObjectAccessor::GetUnit(*caster, m_unitTargetGUID)) : NULL; m_itemTarget = nullptr; if (caster->GetTypeId() == TYPEID_PLAYER) { Player *player = ((Player*)caster); if (m_targetMask & TARGET_FLAG_ITEM) m_itemTarget = player->GetItemByGuid(m_itemTargetGUID); else if (m_targetMask & TARGET_FLAG_TRADE_ITEM) { if (TradeData* pTrade = player->GetTradeData()) if (m_itemTargetGUID.GetRawValue() < TRADE_SLOT_COUNT) m_itemTarget = pTrade->GetTraderData()->GetItem(TradeSlots(m_itemTargetGUID.GetRawValue())); } if (m_itemTarget) m_itemTargetEntry = m_itemTarget->GetEntry(); } } void SpellCastTargets::read(ByteBuffer& data, Unit *caster) { data >> m_targetMask; if (m_targetMask == TARGET_FLAG_SELF) { m_destX = caster->GetPositionX(); m_destY = caster->GetPositionY(); m_destZ = caster->GetPositionZ(); m_unitTarget = caster; m_unitTargetGUID = caster->GetObjectGuid(); return; } // TARGET_FLAG_UNK2 is used for non-combat pets, maybe other? if (m_targetMask & (TARGET_FLAG_UNIT | TARGET_FLAG_UNK2)) #if SUPPORTED_CLIENT_BUILD > CLIENT_BUILD_1_8_4 data >> m_unitTargetGUID.ReadAsPacked(); #else data >> m_unitTargetGUID; #endif if (m_targetMask & (TARGET_FLAG_OBJECT | TARGET_FLAG_OBJECT_UNK)) #if SUPPORTED_CLIENT_BUILD > CLIENT_BUILD_1_8_4 data >> m_GOTargetGUID.ReadAsPacked(); #else data >> m_GOTargetGUID; #endif if ((m_targetMask & (TARGET_FLAG_ITEM | TARGET_FLAG_TRADE_ITEM)) && caster->GetTypeId() == TYPEID_PLAYER) #if SUPPORTED_CLIENT_BUILD > CLIENT_BUILD_1_8_4 data >> m_itemTargetGUID.ReadAsPacked(); #else data >> m_itemTargetGUID; #endif if (m_targetMask & TARGET_FLAG_SOURCE_LOCATION) { data >> m_srcX >> m_srcY >> m_srcZ; if (!MaNGOS::IsValidMapCoord(m_srcX, m_srcY, m_srcZ)) throw ByteBufferException(false, data.rpos(), 0, data.size()); } if (m_targetMask & TARGET_FLAG_DEST_LOCATION) { data >> m_destX >> m_destY >> m_destZ; if (!MaNGOS::IsValidMapCoord(m_destX, m_destY, m_destZ)) throw ByteBufferException(false, data.rpos(), 0, data.size()); } if (m_targetMask & TARGET_FLAG_STRING) data >> m_strTarget; if (m_targetMask & (TARGET_FLAG_CORPSE | TARGET_FLAG_PVP_CORPSE)) #if SUPPORTED_CLIENT_BUILD > CLIENT_BUILD_1_8_4 data >> m_CorpseTargetGUID.ReadAsPacked(); #else data >> m_CorpseTargetGUID; #endif // find real units/GOs Update(caster); } void SpellCastTargets::write(ByteBuffer& data) const { data << uint16(m_targetMask); if (m_targetMask & (TARGET_FLAG_UNIT | TARGET_FLAG_PVP_CORPSE | TARGET_FLAG_OBJECT | TARGET_FLAG_CORPSE | TARGET_FLAG_UNK2)) { if (m_targetMask & TARGET_FLAG_UNIT) { if (m_unitTarget) #if SUPPORTED_CLIENT_BUILD > CLIENT_BUILD_1_8_4 data << m_unitTarget->GetPackGUID(); #else data << m_unitTarget->GetGUID(); #endif else data << uint8(0); } else if (m_targetMask & (TARGET_FLAG_OBJECT | TARGET_FLAG_OBJECT_UNK)) { if (m_GOTarget) #if SUPPORTED_CLIENT_BUILD > CLIENT_BUILD_1_8_4 data << m_GOTarget->GetPackGUID(); #else data << m_GOTarget->GetGUID(); #endif else data << uint8(0); } else if (m_targetMask & (TARGET_FLAG_CORPSE | TARGET_FLAG_PVP_CORPSE)) data << m_CorpseTargetGUID.WriteAsPacked(); else data << uint8(0); } if (m_targetMask & (TARGET_FLAG_ITEM | TARGET_FLAG_TRADE_ITEM)) { if (m_itemTarget) #if SUPPORTED_CLIENT_BUILD > CLIENT_BUILD_1_8_4 data << m_itemTarget->GetPackGUID(); #else data << m_itemTarget->GetGUID(); #endif else data << uint8(0); } if (m_targetMask & TARGET_FLAG_SOURCE_LOCATION) data << m_srcX << m_srcY << m_srcZ; if (m_targetMask & TARGET_FLAG_DEST_LOCATION) data << m_destX << m_destY << m_destZ; if (m_targetMask & TARGET_FLAG_STRING) data << m_strTarget; } Spell::Spell(Unit* caster, SpellEntry const *info, bool triggered, ObjectGuid originalCasterGUID, SpellEntry const* triggeredBy, Unit* victim, SpellEntry const* triggeredByParent): m_immediateHandled(false), m_needSpellLog(false), m_canTrigger(false), m_setCreatureTarget(false) { MANGOS_ASSERT(caster != NULL && info != NULL); MANGOS_ASSERT(info == sSpellMgr.GetSpellEntry(info->Id) && "`info` must be pointer to a sSpellMgr element"); m_successCast = false; m_destroyed = false; m_spellInfo = info; m_triggeredBySpellInfo = triggeredBy; m_triggeredByParentSpellInfo = triggeredByParent; m_caster = caster; m_selfContainer = nullptr; m_referencedFromCurrentSpell = false; m_executeStack = 0; m_delayStart = 0; m_delayAtDamageCount = 0; m_isChannelingVisual = false; m_applyMultiplierMask = 0; m_absorbed = 0; // Get data for type of attack m_attackType = GetWeaponAttackType(m_spellInfo); m_spellSchoolMask = GetSpellSchoolMask(info); // Can be override for some spell (wand shoot for example) // wand case if (m_attackType == RANGED_ATTACK) if (!!(m_caster->getClassMask() & CLASSMASK_WAND_USERS) && m_caster->GetTypeId() == TYPEID_PLAYER) m_spellSchoolMask = GetSchoolMask(m_caster->GetWeaponDamageSchool(RANGED_ATTACK)); // Set health leech amount to zero m_healthLeech = 0; m_originalCasterGUID = originalCasterGUID ? originalCasterGUID : m_caster->GetObjectGuid(); UpdateOriginalCasterPointer(); for (int i = 0; i < MAX_EFFECT_INDEX; ++i) m_currentBasePoints[i] = m_spellInfo->CalculateSimpleValue(SpellEffectIndex(i)); m_spellState = SPELL_STATE_NULL; m_castPositionX = m_castPositionY = m_castPositionZ = m_castOrientation = 0; m_TriggerSpells.clear(); m_preCastSpells.clear(); m_IsTriggeredSpell = triggered; //m_AreaAura = false; m_CastItem = nullptr; m_IsCastByItem = false; unitTarget = nullptr; itemTarget = nullptr; corpseTarget = nullptr; gameObjTarget = nullptr; focusObject = nullptr; m_triggeredByAuraSpell = nullptr; m_triggeredByAuraBasePoints = 0; //Auto Shot & Shoot m_autoRepeat = IsAutoRepeatRangedSpell(m_spellInfo); m_powerCost = 0; // setup to correct value in Spell::prepare, don't must be used before. m_casttime = 0; // setup to correct value in Spell::prepare, don't must be used before. m_timer = 0; // will set to cast time in prepare m_duration = 0; m_channeled = IsChanneledSpell(info); m_needAliveTargetMask = 0; // determine reflection m_canReflect = victim ? IsReflectableSpell(m_spellInfo, caster, victim) : IsReflectableSpell(m_spellInfo); m_isClientStarted = false; m_spellAuraHolder = nullptr; m_delayed = false; // Must initialize to an element in the list or bad things happen, // begin changes so use end m_channeledUpdateIterator = m_channeledHolders.end(); CleanupTargetList(); } Spell::~Spell() { m_destroyed = true; } template WorldObject* Spell::FindCorpseUsing() { // non-standard target selection SpellRangeEntry const* srange = sSpellRangeStore.LookupEntry(m_spellInfo->rangeIndex); float max_range = GetSpellMaxRange(srange); WorldObject* result = nullptr; T u_check(m_caster, max_range); MaNGOS::WorldObjectSearcher searcher(result, u_check); Cell::VisitGridObjects(m_caster, searcher, max_range); if (!result) Cell::VisitWorldObjects(m_caster, searcher, max_range); return result; } void Spell::FillTargetMap() { // TODO: ADD the correct target FILLS!!!!!! for (int i = 0; i < MAX_EFFECT_INDEX; ++i) { // not call for empty effect. // Also some spells use not used effect targets for store targets for dummy effect in triggered spells if (m_spellInfo->Effect[i] == SPELL_EFFECT_NONE) continue; // targets for TARGET_SCRIPT_COORDINATES (A) and TARGET_SCRIPT // for TARGET_FOCUS_OR_SCRIPTED_GAMEOBJECT (A) all is checked in Spell::CheckCast and in Spell::CheckItem // filled in Spell::CheckCast call if (m_spellInfo->EffectImplicitTargetA[i] == TARGET_SCRIPT_COORDINATES || m_spellInfo->EffectImplicitTargetA[i] == TARGET_SCRIPT || m_spellInfo->EffectImplicitTargetA[i] == TARGET_FOCUS_OR_SCRIPTED_GAMEOBJECT || (m_spellInfo->EffectImplicitTargetB[i] == TARGET_SCRIPT && m_spellInfo->EffectImplicitTargetA[i] != TARGET_SELF)) continue; // TODO: find a way so this is not needed? // for area auras always add caster as target (needed for totems for example) if (IsAreaAuraEffect(m_spellInfo->Effect[i])) AddUnitTarget(m_caster, SpellEffectIndex(i)); // If same target already filled, use it // Example: AoE fear has effects speedup and modfear, with maxtargets = 1 // We dont want to apply speedup to the first, fear to the second. if (m_spellInfo->MaxAffectedTargets > 1) { bool targetsCopied = false; for (int j = 0; j < i; ++j) { // Check if same target, but handle i.e. AreaAuras different if (m_spellInfo->EffectImplicitTargetA[i] == m_spellInfo->EffectImplicitTargetA[j] && m_spellInfo->EffectImplicitTargetB[i] == m_spellInfo->EffectImplicitTargetB[j] && m_spellInfo->Effect[j] != SPELL_EFFECT_NONE && !IsAreaAuraEffect(m_spellInfo->Effect[i]) && !IsAreaAuraEffect(m_spellInfo->Effect[j])) { targetsCopied = true; // Copy targets from effect j to effect i for (TargetList::iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) if (ihit->effectMask & (1 << j)) { if (Unit* unit = m_caster->GetMap()->GetUnit(ihit->targetGUID)) AddUnitTarget(unit, SpellEffectIndex(i)); // Will not invalidate the iterator, since it updates the current Target. else // Failed. Better do the computation once again. { for (ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) ihit->effectMask &= ~(1 << i); targetsCopied = false; break; } } break; } } if (targetsCopied) continue; } UnitList tmpUnitMap; // TargetA/TargetB dependent from each other, we not switch to full support this dependences // but need it support in some know cases switch (m_spellInfo->EffectImplicitTargetA[i]) { case TARGET_NONE: switch (m_spellInfo->EffectImplicitTargetB[i]) { case TARGET_NONE: SetTargetMap(SpellEffectIndex(i), TARGET_EFFECT_SELECT, tmpUnitMap); break; default: SetTargetMap(SpellEffectIndex(i), m_spellInfo->EffectImplicitTargetB[i], tmpUnitMap); break; } break; case TARGET_SELF: switch (m_spellInfo->EffectImplicitTargetB[i]) { case TARGET_NONE: // Arcane Missiles have strange targeting for auras if (m_spellInfo->IsFitToFamily()) { if (Unit* pUnitTarget = m_caster->SelectMagnetTarget(m_targets.getUnitTarget(), this, SpellEffectIndex(i))) { if (m_caster->IsValidAttackTarget(pUnitTarget)) tmpUnitMap.push_back(pUnitTarget); } } else SetTargetMap(SpellEffectIndex(i), m_spellInfo->EffectImplicitTargetA[i], tmpUnitMap); break; case TARGET_EFFECT_SELECT: SetTargetMap(SpellEffectIndex(i), m_spellInfo->EffectImplicitTargetA[i], tmpUnitMap); break; case TARGET_AREAEFFECT_INSTANT: // use B case that not dependent from from A in fact if ((m_targets.m_targetMask & TARGET_FLAG_DEST_LOCATION) == 0) m_targets.setDestination(m_caster->GetPositionX(), m_caster->GetPositionY(), m_caster->GetPositionZ()); SetTargetMap(SpellEffectIndex(i), m_spellInfo->EffectImplicitTargetB[i], tmpUnitMap); break; default: SetTargetMap(SpellEffectIndex(i), m_spellInfo->EffectImplicitTargetA[i], tmpUnitMap); SetTargetMap(SpellEffectIndex(i), m_spellInfo->EffectImplicitTargetB[i], tmpUnitMap); break; } break; case TARGET_EFFECT_SELECT: switch (m_spellInfo->EffectImplicitTargetB[i]) { case 0: case TARGET_EFFECT_SELECT: SetTargetMap(SpellEffectIndex(i), m_spellInfo->EffectImplicitTargetA[i], tmpUnitMap); break; // dest point setup required case TARGET_AREAEFFECT_INSTANT: case TARGET_AREAEFFECT_CUSTOM: case TARGET_ALL_ENEMY_IN_AREA: case TARGET_ALL_ENEMY_IN_AREA_INSTANT: case TARGET_ALL_ENEMY_IN_AREA_CHANNELED: case TARGET_ALL_FRIENDLY_UNITS_IN_AREA: // triggered spells get dest point from default target set, ignore it if (!(m_targets.m_targetMask & TARGET_FLAG_DEST_LOCATION) || m_IsTriggeredSpell) if (WorldObject* castObject = GetCastingObject()) m_targets.setDestination(castObject->GetPositionX(), castObject->GetPositionY(), castObject->GetPositionZ()); SetTargetMap(SpellEffectIndex(i), m_spellInfo->EffectImplicitTargetB[i], tmpUnitMap); break; // target pre-selection required case TARGET_INNKEEPER_COORDINATES: case TARGET_TABLE_X_Y_Z_COORDINATES: case TARGET_CASTER_COORDINATES: case TARGET_SCRIPT_COORDINATES: case TARGET_CURRENT_ENEMY_COORDINATES: case TARGET_DUELVSPLAYER_COORDINATES: // need some target for processing SetTargetMap(SpellEffectIndex(i), m_spellInfo->EffectImplicitTargetA[i], tmpUnitMap); SetTargetMap(SpellEffectIndex(i), m_spellInfo->EffectImplicitTargetB[i], tmpUnitMap); break; default: SetTargetMap(SpellEffectIndex(i), m_spellInfo->EffectImplicitTargetB[i], tmpUnitMap); break; } break; case TARGET_CASTER_COORDINATES: switch (m_spellInfo->EffectImplicitTargetB[i]) { case TARGET_ALL_ENEMY_IN_AREA: // Note: this hack with search required until GO casting not implemented // environment damage spells already have around enemies targeting but this not help in case nonexistent GO casting support // currently each enemy selected explicitly and self cast damage if (m_spellInfo->Effect[i] == SPELL_EFFECT_ENVIRONMENTAL_DAMAGE) { if (m_targets.getUnitTarget()) tmpUnitMap.push_back(m_targets.getUnitTarget()); } else { SetTargetMap(SpellEffectIndex(i), m_spellInfo->EffectImplicitTargetA[i], tmpUnitMap); SetTargetMap(SpellEffectIndex(i), m_spellInfo->EffectImplicitTargetB[i], tmpUnitMap); switch (m_spellInfo->Id) { case 5246: // Exception: Intimidating Shout // The AoE fear does not apply to spell main target (that is stunned by another aura) for (UnitList::iterator itr = tmpUnitMap.begin(); itr != tmpUnitMap.end();) { if (*itr == m_targets.getUnitTarget()) itr = tmpUnitMap.erase(itr); else ++itr; } break; case 27831: // Shadow Bolt volley which should only target players with the Shadow Mark debuff for (UnitList::iterator itr = tmpUnitMap.begin(); itr != tmpUnitMap.end();) { if (!(*itr)->HasAura(27825)) // Shadow Mark itr = tmpUnitMap.erase(itr); else ++itr; } break; case 29484: // Maexxna Web Spray should not hit players under Web Wrap or Petrification for (UnitList::iterator itr = tmpUnitMap.begin(); itr != tmpUnitMap.end();) { if ((*itr)->HasAura(17624) || (*itr)->HasAura(28622)) itr = tmpUnitMap.erase(itr); else ++itr; } break; } } break; case 0: SetTargetMap(SpellEffectIndex(i), m_spellInfo->EffectImplicitTargetA[i], tmpUnitMap); tmpUnitMap.push_back(m_caster); break; default: SetTargetMap(SpellEffectIndex(i), m_spellInfo->EffectImplicitTargetA[i], tmpUnitMap); SetTargetMap(SpellEffectIndex(i), m_spellInfo->EffectImplicitTargetB[i], tmpUnitMap); break; } break; case TARGET_TABLE_X_Y_Z_COORDINATES: switch (m_spellInfo->EffectImplicitTargetB[i]) { case 0: SetTargetMap(SpellEffectIndex(i), m_spellInfo->EffectImplicitTargetA[i], tmpUnitMap); // need some target for processing SetTargetMap(SpellEffectIndex(i), TARGET_EFFECT_SELECT, tmpUnitMap); break; case TARGET_AREAEFFECT_INSTANT: // All 17/7 pairs used for dest teleportation, A processed in effect code SetTargetMap(SpellEffectIndex(i), m_spellInfo->EffectImplicitTargetB[i], tmpUnitMap); break; default: SetTargetMap(SpellEffectIndex(i), m_spellInfo->EffectImplicitTargetA[i], tmpUnitMap); SetTargetMap(SpellEffectIndex(i), m_spellInfo->EffectImplicitTargetB[i], tmpUnitMap); break; } break; case TARGET_DUELVSPLAYER_COORDINATES: switch (m_spellInfo->EffectImplicitTargetB[i]) { case 0: case TARGET_EFFECT_SELECT: SetTargetMap(SpellEffectIndex(i), m_spellInfo->EffectImplicitTargetA[i], tmpUnitMap); if (Unit* currentTarget = m_targets.getUnitTarget()) tmpUnitMap.push_back(currentTarget); break; default: SetTargetMap(SpellEffectIndex(i), m_spellInfo->EffectImplicitTargetA[i], tmpUnitMap); SetTargetMap(SpellEffectIndex(i), m_spellInfo->EffectImplicitTargetB[i], tmpUnitMap); break; } break; case TARGET_ALL_ENEMY_IN_AREA: if ((m_targets.m_targetMask & TARGET_FLAG_SOURCE_LOCATION) && !(m_targets.m_targetMask & TARGET_FLAG_DEST_LOCATION)) m_targets.setDestination(m_targets.m_srcX, m_targets.m_srcY, m_targets.m_srcZ); SetTargetMap(SpellEffectIndex(i), m_spellInfo->EffectImplicitTargetA[i], tmpUnitMap); break; default: switch (m_spellInfo->EffectImplicitTargetB[i]) { case TARGET_NONE: case TARGET_EFFECT_SELECT: SetTargetMap(SpellEffectIndex(i), m_spellInfo->EffectImplicitTargetA[i], tmpUnitMap); break; case TARGET_SCRIPT_COORDINATES: // B case filled in CheckCast but we need fill unit list base at A case SetTargetMap(SpellEffectIndex(i), m_spellInfo->EffectImplicitTargetA[i], tmpUnitMap); break; default: SetTargetMap(SpellEffectIndex(i), m_spellInfo->EffectImplicitTargetA[i], tmpUnitMap); SetTargetMap(SpellEffectIndex(i), m_spellInfo->EffectImplicitTargetB[i], tmpUnitMap); break; } break; } for (UnitList::iterator itr = tmpUnitMap.begin(); itr != tmpUnitMap.end();) { if (!CheckTarget(*itr, SpellEffectIndex(i))) { itr = tmpUnitMap.erase(itr); continue; } else ++itr; } for (UnitList::const_iterator iunit = tmpUnitMap.begin(); iunit != tmpUnitMap.end(); ++iunit) AddUnitTarget((*iunit), SpellEffectIndex(i)); } } void Spell::prepareDataForTriggerSystem() { //========================================================================================== // Now fill data for trigger system, need know: // an spell trigger another or not ( m_canTrigger ) // Create base triggers flags for Attacker and Victim ( m_procAttacker and m_procVictim) //========================================================================================== // Fill flag can spell trigger or not // TODO: possible exist spell attribute for this m_canTrigger = false; m_procAttacker = PROC_FLAG_NONE; m_procVictim = PROC_FLAG_NONE; if (m_spellInfo->AttributesEx3 & SPELL_ATTR_EX3_CANT_TRIGGER_PROC) m_canTrigger = false; // Explicitly not allowed to trigger else if (m_CastItem) m_canTrigger = false; // Do not trigger from item cast spell else if (m_originalCasterGUID.IsGameObject()) // Do not trigger anything if the spell is casted by using a game object (eg. Lightwell) m_canTrigger = false; else if (!m_IsTriggeredSpell) m_canTrigger = true; // Normal cast - can trigger else if (!m_triggeredByAuraSpell) m_canTrigger = true; // Triggered from SPELL_EFFECT_TRIGGER_SPELL - can trigger if (!m_canTrigger) // Exceptions (some periodic triggers) { switch (m_spellInfo->SpellFamilyName) { case SPELLFAMILY_MAGE: // Arcane Missiles / Blizzard triggers need do it if (m_spellInfo->IsFitToFamilyMask(uint64(0x0000000000200080))) m_canTrigger = true; break; case SPELLFAMILY_WARLOCK: // For Hellfire Effect / Rain of Fire triggers need do it if (m_spellInfo->IsFitToFamilyMask()) m_canTrigger = true; break; case SPELLFAMILY_HUNTER: // Hunter Explosive Trap Effect/Immolation Trap Effect/Frost Trap Aura Effect if (m_spellInfo->IsFitToFamilyMask()) m_canTrigger = true; break; case SPELLFAMILY_PALADIN: // Seal of Command (example Vengeance proc) | SPELLFAMILY_PALADIN override in spell_mod if (m_spellInfo->Id == 20424) m_canTrigger = true; #if SUPPORTED_CLIENT_BUILD > CLIENT_BUILD_1_9_4 // Seal of Righteousness else if (!m_spellInfo->SpellFamilyFlags && m_spellInfo->SpellIconID == 25) m_canTrigger = true; #endif // Holy Shock else if (m_spellInfo->IsFitToFamilyMask()) m_canTrigger = true; #if SUPPORTED_CLIENT_BUILD > CLIENT_BUILD_1_8_4 // Eye for an Eye triggered spell else if (m_spellInfo->Id == 25997) m_canTrigger = true; #endif break; case SPELLFAMILY_PRIEST: // Touch of Weakness / Devouring Plague if (m_spellInfo->IsFitToFamilyMask()) m_canTrigger = true; break; default: break; } } // Get data for type of attack and fill base info for trigger switch (m_spellInfo->DmgClass) { case SPELL_DAMAGE_CLASS_MELEE: m_procAttacker = PROC_FLAG_SUCCESSFUL_MELEE_SPELL_HIT; if (m_attackType == OFF_ATTACK) m_procAttacker |= PROC_FLAG_SUCCESSFUL_OFFHAND_HIT; m_procVictim = PROC_FLAG_TAKEN_MELEE_SPELL_HIT; if (IsNextMeleeSwingSpell()) { m_procAttacker |= PROC_FLAG_SUCCESSFUL_MELEE_HIT; m_procVictim |= PROC_FLAG_TAKEN_MELEE_HIT; } break; case SPELL_DAMAGE_CLASS_RANGED: // Auto attack if (m_spellInfo->AttributesEx2 & SPELL_ATTR_EX2_AUTOREPEAT_FLAG) { m_procAttacker = PROC_FLAG_SUCCESSFUL_RANGED_HIT; m_procVictim = PROC_FLAG_TAKEN_RANGED_HIT; } else // Ranged spell attack { // If blind or Expose Weakness, don't add proc flags for typical ranged abilities // proc none if (m_spellInfo->Id == 2094 || m_spellInfo->Id == 23577) { m_procAttacker = PROC_FLAG_NONE; m_procVictim = PROC_FLAG_NONE; } else { m_procAttacker = PROC_FLAG_SUCCESSFUL_RANGED_SPELL_HIT; m_procVictim = PROC_FLAG_TAKEN_RANGED_SPELL_HIT; } if (IsAreaOfEffectSpell(m_spellInfo)) m_procAttacker |= PROC_FLAG_SUCCESSFUL_AOE; } break; default: { bool aoe = IsAreaOfEffectSpell(m_spellInfo); bool positive = IsPositiveSpell(m_spellInfo); // Some spells should be categorized as AoE // Hellfire regularly triggers an AoE spell. if (m_spellInfo->IsFitToFamily() && m_spellInfo->SpellIconID == 937) aoe = true; // Check for positive spell. 'Positive' spells include various things such as buffs, but // they shouldn't proc with PROC_FLAG_SUCCESSFUL_POSITIVE_SPELL. Note that some spells // with SPELL_EFFECT_DISPEL are negative, such as Purge. if (positive) { if (IsHealSpell(m_spellInfo)) { m_procAttacker = PROC_FLAG_SUCCESSFUL_POSITIVE_SPELL; m_procVictim = PROC_FLAG_TAKEN_POSITIVE_SPELL; } else { m_procAttacker = PROC_FLAG_SUCCESSFUL_NONE_POSITIVE_SPELL; m_procVictim = PROC_FLAG_TAKEN_NONE_POSITIVE_SPELL; } if (m_spellInfo->IsDispel()) m_procAttacker |= PROC_FLAG_SUCCESSFUL_CURE_SPELL_CAST; if (aoe) m_procAttacker |= PROC_FLAG_SUCCESSFUL_AOE; if (IsSpellAppliesPeriodicAura(m_spellInfo)) { m_procAttacker |= PROC_FLAG_SUCCESSFUL_PERIODIC_SPELL_HIT; m_procVictim |= PROC_FLAG_TAKEN_PERIODIC_SPELL_HIT; } // Always proc with PROC_FLAG_SUCCESSFUL_SPELL_CAST if not AoE or triggered spell if (!aoe && !IsTriggered()) { m_procAttacker |= PROC_FLAG_SUCCESSFUL_SPELL_CAST; if (m_powerCost > 0 && m_spellInfo->powerType == POWER_MANA) m_procAttacker |= PROC_FLAG_SUCCESSFUL_MANA_SPELL_CAST; } } // Wands auto attack else if (m_spellInfo->AttributesEx2 & SPELL_ATTR_EX2_AUTOREPEAT_FLAG) { m_procAttacker = PROC_FLAG_SUCCESSFUL_RANGED_HIT; m_procVictim = PROC_FLAG_TAKEN_RANGED_HIT; } else // Negative spell { if (m_spellInfo->DmgClass == SPELL_DAMAGE_CLASS_MAGIC) { m_procAttacker = PROC_FLAG_SUCCESSFUL_NEGATIVE_SPELL_HIT; m_procVictim = PROC_FLAG_TAKEN_NEGATIVE_SPELL_HIT; } else { m_procAttacker = PROC_FLAG_SUCCESSFUL_NONE_SPELL_HIT; m_procVictim = PROC_FLAG_TAKEN_NONE_SPELL_HIT; } if (aoe) m_procAttacker |= PROC_FLAG_SUCCESSFUL_AOE; if (IsSpellAppliesPeriodicAura(m_spellInfo)) { m_procAttacker |= PROC_FLAG_SUCCESSFUL_PERIODIC_SPELL_HIT; m_procVictim |= PROC_FLAG_TAKEN_PERIODIC_SPELL_HIT; } if (!aoe && !IsTriggered()) { m_procAttacker |= PROC_FLAG_SUCCESSFUL_SPELL_CAST; if (m_powerCost > 0 && m_spellInfo->powerType == POWER_MANA) m_procAttacker |= PROC_FLAG_SUCCESSFUL_MANA_SPELL_CAST; } } break; } } // some negative spells have positive effects to another or same targets // avoid triggering negative hit for only positive targets m_negativeEffectMask = 0x0; for (int i = 0; i < MAX_EFFECT_INDEX; ++i) { if (!IsPositiveEffect(m_spellInfo, SpellEffectIndex(i))) m_negativeEffectMask |= (1 << i); // Self damage is a positive effect but should still trigger negative proc flags if (m_spellInfo->Effect[i] == SPELL_EFFECT_SCHOOL_DAMAGE && m_spellInfo->EffectImplicitTargetA[i] == TARGET_SELF) m_negativeEffectMask |= (1 << i); } } void Spell::CleanupTargetList() { /* m_UniqueTargetInfo.clear(); m_UniqueGOTargetInfo.clear(); */ for (TargetList::iterator itr = m_UniqueTargetInfo.begin(); itr != m_UniqueTargetInfo.end(); ++itr) itr->deleted = true; for (GOTargetList::iterator itr = m_UniqueGOTargetInfo.begin(); itr != m_UniqueGOTargetInfo.end(); ++itr) itr->deleted = true; m_UniqueItemInfo.clear(); m_delayMoment = 0; } void Spell::AddUnitTarget(Unit* pVictim, SpellEffectIndex effIndex) { if (m_spellInfo->Effect[effIndex] == 0) return; if ((m_spellInfo->AttributesEx & SPELL_ATTR_EX_CANT_TARGET_SELF) && (m_spellInfo->EffectImplicitTargetA[effIndex] != TARGET_SELF) && (m_spellInfo->EffectImplicitTargetB[effIndex] != TARGET_SELF) && (pVictim->GetObjectGuid() == m_originalCasterGUID)) return; // Check for effect immune skip if immuned bool immuned = pVictim->IsImmuneToSpellEffect(m_spellInfo, effIndex, pVictim == m_caster); ObjectGuid targetGUID = pVictim->GetObjectGuid(); // Lookup target in already in list for (TargetList::iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) { if (ihit->deleted == true) continue; if (targetGUID == ihit->targetGUID) // Found in list { if (!immuned) ihit->effectMask |= 1 << effIndex; // Add only effect mask if not immuned return; } } // This is new target calculate data for him // Get spell hit result on target TargetInfo target; target.targetGUID = targetGUID; // Store target GUID target.effectMask = immuned ? 0 : 1 << effIndex; // Store index of effect if not immuned target.processed = false; // Effects not apply on target target.deleted = false; target.HitInfo = 0x0; target.damage = 0; // Calculate hit result target.missCondition = m_caster->SpellHitResult(pVictim, m_spellInfo, effIndex, m_canReflect, this); // spell fly from visual cast object WorldObject* affectiveObject = GetAffectiveCasterObject(); // Spell have speed - need calculate incoming time if (m_spellInfo->speed > 0.0f && affectiveObject && pVictim != affectiveObject) { // calculate spell incoming interval float dist = affectiveObject->GetDistance(pVictim->GetPositionX(), pVictim->GetPositionY(), pVictim->GetPositionZ()); if (dist < 5.0f) dist = 5.0f; target.timeDelay = (uint64) floor(dist / m_spellInfo->speed * 1000.0f); // Calculate minimum incoming time if (m_delayMoment == 0 || m_delayMoment > target.timeDelay) m_delayMoment = target.timeDelay; } else if (m_delayed) m_delayMoment = target.timeDelay = sWorld.getConfig(CONFIG_UINT32_SPELLS_CCDELAY); else target.timeDelay = UI64LIT(0); // If target reflect spell back to caster if (target.missCondition == SPELL_MISS_REFLECT) { // Calculate reflected spell result on caster target.reflectResult = m_caster->SpellHitResult(m_caster, m_spellInfo, effIndex, m_canReflect, this); if (target.reflectResult == SPELL_MISS_REFLECT) // Impossible reflect again, so simply deflect spell target.reflectResult = SPELL_MISS_PARRY; // Increase time interval for reflected spells by 1.5 target.timeDelay += target.timeDelay >> 1; } else target.reflectResult = SPELL_MISS_NONE; // Add target to list m_UniqueTargetInfo.push_back(target); } void Spell::AddUnitTarget(ObjectGuid unitGuid, SpellEffectIndex effIndex) { if (Unit* unit = m_caster->GetObjectGuid() == unitGuid ? m_caster : ObjectAccessor::GetUnit(*m_caster, unitGuid)) AddUnitTarget(unit, effIndex); } void Spell::CheckAtDelay(TargetInfo* pInf) { Unit* pTarget = m_caster->GetObjectGuid() == pInf->targetGUID ? m_caster : ObjectAccessor::GetUnit(*m_caster, pInf->targetGUID); if (!pTarget) return; for (int i = 0; i < 3; ++i) { uint32 effMask = 1 << i; if (pInf->effectMask & effMask) { if (pTarget->IsImmuneToSpellEffect(m_spellInfo, SpellEffectIndex(i), pTarget == m_caster)) pInf->effectMask &= ~effMask; } } if (pTarget != m_caster && (pTarget->IsImmuneToDamage(GetSpellSchoolMask(m_spellInfo), m_spellInfo) || pTarget->IsImmuneToSpell(m_spellInfo, pTarget == m_caster))) pInf->missCondition = SPELL_MISS_IMMUNE; if (pTarget->GetTypeId() == TYPEID_UNIT && ((Creature*)pTarget)->IsInEvadeMode()) pInf->missCondition = SPELL_MISS_EVADE; } void Spell::AddGOTarget(GameObject* pVictim, SpellEffectIndex effIndex) { if (m_spellInfo->Effect[effIndex] == 0) return; ObjectGuid targetGUID = pVictim->GetObjectGuid(); // Lookup target in already in list for (GOTargetList::iterator ihit = m_UniqueGOTargetInfo.begin(); ihit != m_UniqueGOTargetInfo.end(); ++ihit) { if (ihit->deleted) continue; if (targetGUID == ihit->targetGUID) // Found in list { ihit->effectMask |= (1 << effIndex); // Add only effect mask return; } } // This is new target calculate data for him GOTargetInfo target; target.targetGUID = targetGUID; target.effectMask = (1 << effIndex); target.processed = false; // Effects not apply on target target.deleted = false; // spell fly from visual cast object WorldObject* affectiveObject = GetAffectiveCasterObject(); // Spell have speed - need calculate incoming time if (m_spellInfo->speed > 0.0f && affectiveObject && pVictim != affectiveObject) { // calculate spell incoming interval float dist = affectiveObject->GetDistance(pVictim->GetPositionX(), pVictim->GetPositionY(), pVictim->GetPositionZ()); if (dist < 5.0f) dist = 5.0f; target.timeDelay = (uint64) floor(dist / m_spellInfo->speed * 1000.0f); if (m_delayMoment == 0 || m_delayMoment > target.timeDelay) m_delayMoment = target.timeDelay; } else if (m_delayed) m_delayMoment = target.timeDelay = sWorld.getConfig(CONFIG_UINT32_SPELLS_CCDELAY); else target.timeDelay = UI64LIT(0); // Add target to list m_UniqueGOTargetInfo.push_back(target); } void Spell::AddGOTarget(ObjectGuid goGuid, SpellEffectIndex effIndex) { if (GameObject* go = m_caster->GetMap()->GetGameObject(goGuid)) AddGOTarget(go, effIndex); } void Spell::AddItemTarget(Item* pitem, SpellEffectIndex effIndex) { if (m_spellInfo->Effect[effIndex] == 0) return; // Lookup target in already in list for (ItemTargetList::iterator ihit = m_UniqueItemInfo.begin(); ihit != m_UniqueItemInfo.end(); ++ihit) { if (pitem == ihit->item) // Found in list { ihit->effectMask |= (1 << effIndex); // Add only effect mask return; } } // This is new target add data ItemTargetInfo target; target.item = pitem; target.effectMask = (1 << effIndex); target.deleted = false; m_UniqueItemInfo.push_back(target); } void Spell::DoAllEffectOnTarget(TargetInfo *target) { ASSERT(target); // Arrive pour des sorts comme 11567, 20569, 10626 if (target->processed) return; ASSERT(m_caster); target->processed = true; // Target checked in apply effects procedure // Get mask of effects for target uint32 mask = target->effectMask; Unit* unit = m_caster->GetObjectGuid() == target->targetGUID ? m_caster : ObjectAccessor::GetUnit(*m_caster, target->targetGUID); if (!unit) return; if (getState() == SPELL_STATE_DELAYED && (WorldTimer::getMSTime() - target->timeDelay) <= unit->m_lastSanctuaryTime && !IsPositiveSpell(m_spellInfo)) return; // Get original caster (if exist) and calculate damage/healing from him data Unit *real_caster = GetAffectiveCaster(); // FIXME: in case wild GO heal/damage spells will be used target bonuses Unit *caster = real_caster ? real_caster : m_caster; SpellMissInfo missInfo = target->missCondition; // Need init unitTarget by default unit (can changed in code on reflect) // Or on missInfo!=SPELL_MISS_NONE unitTarget undefined (but need in trigger subsystem) unitTarget = unit; m_spellAuraHolder = nullptr; // Reset damage/healing counter ResetEffectDamageAndHeal(); // Fill base trigger info. If this is hitting multiple targets, attacker procs should // only apply on the first target aside from some special cases. uint32 procAttacker = m_procAttacker; uint32 procVictim = m_procVictim; uint32 procEx = PROC_EX_NONE; // Drop some attacker proc flags if this is a secondary target. Do not need to change // the victim proc flags. if (m_targetNum > 1) { // If this is a melee spell hit, strip the flag and apply a spell hit flag instead. // This is required to proc things like Deep Wounds on the victim when hitting // multiple targets, but not proc additional melee-only beneficial auras on the // attacker like Sweeping Strikes. Leave the victim proc flags responding to a melee // spell. if (procAttacker & PROC_FLAG_SUCCESSFUL_MELEE_SPELL_HIT) { procAttacker &= ~(PROC_FLAG_SUCCESSFUL_MELEE_SPELL_HIT); procAttacker |= PROC_FLAG_SUCCESSFUL_NEGATIVE_SPELL_HIT; } else if (procAttacker & (PROC_FLAG_SUCCESSFUL_SPELL_CAST | PROC_FLAG_SUCCESSFUL_MANA_SPELL_CAST)) { // Secondary target on a successful spell cast. Remove these flags so we're not // proccing beneficial auras multiple times. Also remove negative spell hit for // chain lightning + clearcasting. Leave positive effects // eg. Chain heal/lightning & Zandalarian Hero Charm procAttacker &= ~(PROC_FLAG_SUCCESSFUL_SPELL_CAST | PROC_FLAG_SUCCESSFUL_MANA_SPELL_CAST | PROC_FLAG_SUCCESSFUL_NEGATIVE_SPELL_HIT); } else if (procAttacker & (PROC_FLAG_SUCCESSFUL_AOE | PROC_FLAG_SUCCESSFUL_NEGATIVE_SPELL_HIT)) { // Do not allow secondary hits for negative aoe spells (such as Arcane Explosion) // to proc beneficial abilities such as Clearcasting. Positive aoe spells can // still trigger, as in the case of prayer of healing and inspiration... procAttacker &= ~(PROC_FLAG_SUCCESSFUL_AOE | PROC_FLAG_SUCCESSFUL_NEGATIVE_SPELL_HIT); } } // drop proc flags in case target not affected negative effects in negative spell // for example caster bonus or animation, // except miss case where will assigned PROC_EX_* flags later if (((procAttacker | procVictim) & NEGATIVE_TRIGGER_MASK) && !(target->effectMask & m_negativeEffectMask) && (missInfo == SPELL_MISS_NONE || missInfo == SPELL_MISS_REFLECT)) { procAttacker = procAttacker & PROC_FLAG_SUCCESSFUL_SPELL_CAST; procVictim = PROC_FLAG_NONE; } if (m_delayed) { // mark effects that were already handled in Spell::HandleDelayedSpellLaunch on spell launch as processed for (int32 i = 0; i < MAX_EFFECT_INDEX; ++i) if (IsEffectHandledOnDelayedSpellLaunch(m_spellInfo, SpellEffectIndex(i))) mask &= ~(1 << i); // maybe used in effects that are handled on hit m_damage += target->damage; } if (missInfo == SPELL_MISS_NONE) // In case spell hit target, do all effect on that target DoSpellHitOnUnit(unit, mask); if (missInfo == SPELL_MISS_REFLECT && target->reflectResult == SPELL_MISS_NONE) { isReflected = true; DoSpellHitOnUnit(m_caster, mask); unitTarget = m_caster; } else // in 1.12.1 we need explicit miss info { if (real_caster && !IsNeedSendToClient()) real_caster->SendSpellMiss(unit, m_spellInfo->Id, missInfo); if (missInfo == SPELL_MISS_MISS || missInfo == SPELL_MISS_RESIST) { if (real_caster && real_caster != unit) { if (!IsPositiveSpell(m_spellInfo) && (m_caster->isVisibleForOrDetect(unit, unit, false) || m_spellInfo->HasAttribute(SPELL_ATTR_EX_IS_PICKPOCKET))) { bool combat = (m_spellInfo->Id != 3600) && !m_spellInfo->HasAttribute(SPELL_ATTR_EX3_NO_INITIAL_AGGRO); // Pickpocket can cause back attack if failed if (m_spellInfo->HasAttribute(SPELL_ATTR_EX_IS_PICKPOCKET)) { real_caster->RemoveSpellsCausingAura(SPELL_AURA_MOD_STEALTH); combat = true; } if (combat) { if (!IsSpellHaveAura(m_spellInfo, SPELL_AURA_MOD_POSSESS)) { if (!unit->isInCombat()) unit->AttackedBy(real_caster); unit->AddThreat(real_caster); unit->SetInCombatWithAggressor(real_caster); } real_caster->SetInCombatWithVictim(unit); } else if (m_spellInfo->HasAttribute(SPELL_ATTR_EX3_OUT_OF_COMBAT_ATTACK)) { unit->SetOutOfCombatWithAggressor(real_caster); real_caster->SetOutOfCombatWithVictim(unit); } } } } } if (real_caster) { if (m_spellInfo->AttributesEx & SPELL_ATTR_EX_REFUND_POWER) { // Restauration d'energie sur miss/dodge des sorts rapportant un CP. // Source : http://roguecrap.blogspot.co.uk/2006/03/energy-regeneration-oddities.html, + verifiable en video // Source for Parry: https://youtu.be/aDXXr3ad3is?t=3m07s if ((missInfo == SPELL_MISS_MISS || missInfo == SPELL_MISS_DODGE || missInfo == SPELL_MISS_PARRY) && m_spellInfo->powerType == POWER_ENERGY) { int32 regen = lroundf(m_powerCost * 0.82f); real_caster->ModifyPower(POWER_ENERGY, regen); } // Warriors and Druids are refunded 80% of the rage cost on dodge/parry // http://blue.mmo-champion.com/topic/69365-18-02-05-kalgans-response-to-warriors/ // https://youtu.be/YzPlictRoK8?t=1m45s else if ((missInfo == SPELL_MISS_PARRY || missInfo == SPELL_MISS_DODGE) && m_spellInfo->powerType == POWER_RAGE) { int32 regen = lroundf(m_powerCost * 0.82f); real_caster->ModifyPower(POWER_RAGE, regen); } } } // All calculated do it! // Do healing and triggers if (m_healing) { bool crit = real_caster && real_caster->IsSpellCrit(unitTarget, m_spellInfo, m_spellSchoolMask, BASE_ATTACK, this); uint32 addhealth = m_healing; if (crit) { procEx |= PROC_EX_CRITICAL_HIT; addhealth = caster->SpellCriticalHealingBonus(m_spellInfo, addhealth, nullptr); #if SUPPORTED_CLIENT_BUILD <= CLIENT_BUILD_1_9_4 // If healing crits, we need to update the execute log data. for (uint32 i = 0; i < MAX_EFFECT_INDEX; ++i) { if (m_executeLogInfo[i].empty()) continue; for (uint32 j = 0; j < m_executeLogInfo[i].size(); ++j) { switch (m_spellInfo->Effect[i]) { case SPELL_EFFECT_HEAL: case SPELL_EFFECT_HEAL_MAX_HEALTH: { m_executeLogInfo[i][j].heal.amount = addhealth; m_executeLogInfo[i][j].heal.critical = 1; break; } } } } #endif } else procEx |= PROC_EX_NORMAL_HIT; // Do triggers for unit (reflect triggers passed on hit phase for correct drop charge) if (m_canTrigger) { // Some spell expected send main spell info to triggered system SpellEntry const* spellInfo = m_spellInfo; switch (m_spellInfo->Id) { //case 19968: // Holy Light triggered heal case 19993: // Flash of Light triggered heal { // stored in unused spell effect basepoints in main spell code uint32 spellid = m_currentBasePoints[EFFECT_INDEX_1]; spellInfo = sSpellMgr.GetSpellEntry(spellid); procAttacker |= (PROC_FLAG_SUCCESSFUL_SPELL_CAST | PROC_FLAG_SUCCESSFUL_MANA_SPELL_CAST); } } caster->ProcDamageAndSpell(unitTarget, real_caster ? procAttacker : PROC_FLAG_NONE, procVictim, procEx, addhealth, m_attackType, spellInfo, this); } int32 gain = caster->DealHeal(unitTarget, addhealth, m_spellInfo, crit); float classThreatModifier = caster->getClass() == CLASS_PALADIN ? 0.25f : 0.5f; if (real_caster) unitTarget->getHostileRefManager().threatAssist(real_caster, float(gain) * classThreatModifier * sSpellMgr.GetSpellThreatMultiplier(m_spellInfo), m_spellInfo); } // Do damage and triggers else if (m_damage) { // Fill base damage struct (unitTarget - is real spell target) SpellNonMeleeDamage damageInfo(caster, unitTarget, m_spellInfo->Id, GetFirstSchoolInMask(m_spellSchoolMask)); damageInfo.spell = this; // World of Warcraft Client Patch 1.11.0 (2006-06-20) // - Fear: The calculations to determine if Fear effects should break due // to receiving damage have been changed.The old calculation used the // base damage of the ability.The new calculation uses the final amount // of damage dealt, after all modifiers. #if SUPPORTED_CLIENT_BUILD <= CLIENT_BUILD_1_10_2 // Has to be called from here instead of Unit::DealDamage to calculate with base damage. if ((damage > 0) && unitTarget && !(m_caster->IsCreature() && static_cast(m_caster)->IsWorldBoss())) unitTarget->RemoveFearEffectsByDamageTaken(damage, m_spellInfo->Id, SPELL_DIRECT_DAMAGE); #endif if (m_delayed) { damageInfo.damage = m_damage; damageInfo.HitInfo = target->HitInfo; } // Add bonuses and fill damageInfo struct else { caster->CalculateSpellDamage(&damageInfo, m_damage, m_spellInfo, m_attackType, this); } unitTarget->CalculateAbsorbResistBlock(caster, &damageInfo, m_spellInfo, BASE_ATTACK, this); m_absorbed = damageInfo.absorb; caster->DealDamageMods(damageInfo.target, damageInfo.damage, &damageInfo.absorb); // terribly ugly hack for Gluth Decimate to not be affected by any damage modifiers. // SPELL_ATTR_EX3_UNK29 is probably meant to make the spell ignore any damage modifiers, // but until implemented, this is the best we can do. if (m_spellInfo->Id == 28375) { damageInfo.damage = m_damage; } // Send log damage message to client caster->SendSpellNonMeleeDamageLog(&damageInfo); procEx = createProcExtendMask(&damageInfo, missInfo); procVictim |= PROC_FLAG_TAKEN_ANY_DAMAGE; // (HACK) trigger Vengeance on weapon crits for Paladins // item procs should probably trigger for all classes, e.g. Judgement of Wisdom, but many issues to test for first. Paladin exclusive for now. if (m_CastItem && m_caster->getClass() == CLASS_PALADIN && (procEx & PROC_EX_CRITICAL_HIT) && !(m_spellInfo->AttributesEx3 & SPELL_ATTR_EX3_CANT_TRIGGER_PROC)) m_canTrigger = true; // JoR and JoC: Paladin melee spells trigger melee procs instead of magic if ((m_spellInfo->IsFitToFamilyMask() && m_spellInfo->SpellIconID == 25) || (m_spellInfo->SpellFamilyName == SPELLFAMILY_PALADIN && m_spellInfo->SpellIconID == 561 && m_spellInfo->SpellVisual == 0)) { procAttacker = PROC_FLAG_SUCCESSFUL_MELEE_SPELL_HIT; procVictim = PROC_FLAG_TAKEN_MELEE_SPELL_HIT | PROC_FLAG_TAKEN_ANY_DAMAGE; } // Do triggers for unit (reflect triggers passed on hit phase for correct drop charge) if (m_canTrigger) caster->ProcDamageAndSpell(unitTarget, real_caster ? procAttacker : PROC_FLAG_NONE, procVictim, procEx, damageInfo.damage, m_attackType, m_spellInfo, this); if (m_caster->GetTypeId() == TYPEID_PLAYER) { // trigger weapon enchants for weapon based spells; exclude spells that stop attack, because may break CC if (m_spellInfo->EquippedItemClass == ITEM_CLASS_WEAPON && !(m_spellInfo->Attributes & SPELL_ATTR_STOP_ATTACK_TARGET)) ((Player*)m_caster)->CastItemCombatSpell(unitTarget, m_attackType); // trigger mainhand weapon procs for shield attacks (Shield Bash, Shield Slam) NOTE: vanilla only mechanic, patched out in 2.0.1 else if (m_spellInfo->EquippedItemClass == ITEM_CLASS_ARMOR && m_spellInfo->EquippedItemSubClassMask & (1 << ITEM_SUBCLASS_ARMOR_SHIELD) && (m_spellInfo->SpellIconID == 280 || m_spellInfo->SpellIconID == 413)) ((Player*)m_caster)->CastItemCombatSpell(unitTarget, BASE_ATTACK); // Bloodthirt triggers main hand despite not requiring weapon // Execute damage component triggers main hand else if ((m_spellInfo->SpellIconID == 38 && m_spellInfo->SpellVisual == 372) || //bloodthirst m_spellInfo->Id == 20647) //execute (damage dealing component does not require weapon) { ((Player*)m_caster)->CastItemCombatSpell(unitTarget, BASE_ATTACK); } // special Paladin cases - trigger weapon procs despite not having EquippedItemClass else if (m_spellInfo->SpellFamilyName == SPELLFAMILY_PALADIN) { // Seal of Command if (m_spellInfo->Id == 20424) ((Player*)m_caster)->CastItemCombatSpell(unitTarget, BASE_ATTACK); // Judgement of Command else if (m_spellInfo->SpellIconID == 561) ((Player*)m_caster)->CastItemCombatSpell(unitTarget, BASE_ATTACK); // Judgement of Righteousness else if (m_spellInfo->IsFitToFamilyMask() && m_spellInfo->SpellIconID == 25) ((Player*)m_caster)->CastItemCombatSpell(unitTarget, BASE_ATTACK); } } caster->DealSpellDamage(&damageInfo, true); // Courroux Naturel a 20% de chance de faire proc WF. if (m_spellInfo->Id == 17364 && caster->GetTypeId() == TYPEID_PLAYER) { Player* pCaster = caster->ToPlayer(); Item *item = pCaster->GetWeaponForAttack(BASE_ATTACK, true, true); if (item) { uint32 enchant_id = item->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT); SpellItemEnchantmentEntry const *pEnchant = sSpellItemEnchantmentStore.LookupEntry(enchant_id); if (pEnchant && roll_chance_f(20.0f)) if (SpellEntry const *spellInfo = sSpellMgr.GetSpellEntry(pEnchant->spellid[0])) if (spellInfo->IsFitToFamily()) pCaster->CastSpell(unitTarget, pEnchant->spellid[0], true, item); } } } else if (m_canTrigger && (procAttacker || procVictim)) { // TODO: Allow all procs, and explicitly deny some rather than denying all and // explicitly allowing some bool foundDamageOrHealAura = false; // m_spellAuraHolder is null for non-stacking periodic effects that are already // on the target - they simply refresh. However, they should still proc on // cast! if (IsSpellAppliesAura(m_spellInfo, target->effectMask)) { for (int i = 0; i < MAX_EFFECT_INDEX && !foundDamageOrHealAura; ++i) { switch (m_spellInfo->EffectApplyAuraName[i]) { case SPELL_AURA_PERIODIC_DAMAGE: case SPELL_AURA_PERIODIC_DAMAGE_PERCENT: case SPELL_AURA_PERIODIC_LEECH: case SPELL_AURA_PERIODIC_HEALTH_FUNNEL: case SPELL_AURA_PERIODIC_HEAL: case SPELL_AURA_OBS_MOD_HEALTH: case SPELL_AURA_POWER_BURN_MANA: case SPELL_AURA_SCHOOL_ABSORB: foundDamageOrHealAura = true; break; } } } // Sunder Armor triggers weapon proc as well as normal procs despite dealing no damage if (m_caster->GetTypeId() == TYPEID_PLAYER && m_spellInfo->IsFitToFamily() && missInfo == SPELL_MISS_NONE) ((Player*)m_caster)->CastItemCombatSpell(unitTarget, BASE_ATTACK); // Fill base damage struct (unitTarget - is real spell target) SpellNonMeleeDamage damageInfo(caster, unitTarget, m_spellInfo->Id, GetFirstSchoolInMask(m_spellSchoolMask)); procEx = createProcExtendMask(&damageInfo, missInfo); // Do triggers for unit (reflect triggers passed on hit phase for correct drop charge) uint32 dmg = 0; // Sometime we need to manually set dmg != 0 (arcane projectile triggers a spell that deals damage) // Cant check SpellFamilyFlags & 0x40800 because some spells have strange SpellFamilyFlags=0x7FFFFFFF (sheep, ...) if (m_spellInfo->SpellFamilyName == SPELLFAMILY_MAGE && m_spellInfo->SpellFamilyFlags == 0x40800) dmg = 1; // Proc periodic casts now, so initial cast procs are triggered if (foundDamageOrHealAura) dmg = 1; // Proc for dispels (or "cures") if (m_spellInfo->IsDispel()) { dmg = 1; // Override proc flags for offensive dispel if (caster->IsHostileTo(unitTarget)) { procAttacker &= ~(PROC_FLAG_SUCCESSFUL_NONE_POSITIVE_SPELL | PROC_FLAG_SUCCESSFUL_CURE_SPELL_CAST); procAttacker |= PROC_FLAG_SUCCESSFUL_NEGATIVE_SPELL_HIT; procVictim &= ~PROC_FLAG_TAKEN_NONE_POSITIVE_SPELL; procVictim |= PROC_FLAG_TAKEN_NEGATIVE_SPELL_HIT; } } caster->ProcDamageAndSpell(unitTarget, real_caster ? procAttacker : PROC_FLAG_NONE, procVictim, procEx, dmg, m_attackType, m_spellInfo, this); } if (missInfo != SPELL_MISS_NONE) return; // Call scripted function for AI if this spell is casted upon a creature if (unit->GetTypeId() == TYPEID_UNIT) { // cast at creature (or GO) quest objectives update at successful cast finished (+channel finished) // ignore pets or autorepeat/melee casts for speed (not exist quest for spells (hm... ) if (real_caster && !((Creature*)unit)->IsPet() && !IsAutoRepeat() && !IsNextMeleeSwingSpell() && !IsChannelActive()) if (Player* p = real_caster->GetCharmerOrOwnerPlayerOrPlayerItself()) p->RewardPlayerAndGroupAtCast(unit, m_spellInfo->Id); if (((Creature*)unit)->AI()) ((Creature*)unit)->AI()->SpellHit(m_caster, m_spellInfo); if (ZoneScript* pZoneScript = unit->GetZoneScript()) { pZoneScript->OnCreatureSpellHit(m_caster, unit->ToCreature(), m_spellInfo); } } // Tell any pets to stop attacking the target on application of breakable crowd control spells if (m_spellInfo->AuraInterruptFlags & AURA_INTERRUPT_FLAG_DAMAGE && unit->HasBreakableByDamageCrowdControlAura(m_caster)) { Unit::AttackerSet attackers = unit->getAttackers(); for (Unit::AttackerSet::iterator itr = attackers.begin(); itr != attackers.end(); ++itr) { if ((*itr)->IsPet()) (*itr)->AttackStop(); } } // Call scripted function for AI if this spell is casted by a creature if (m_caster->GetTypeId() == TYPEID_UNIT && ((Creature*)m_caster)->AI()) ((Creature*)m_caster)->AI()->SpellHitTarget(unit, m_spellInfo); } void Spell::DoSpellHitOnUnit(Unit *unit, uint32 effectMask) { if (!unit) return; Unit* realCaster = GetAffectiveCaster(); if (!effectMask) { if (realCaster && !unit->isInCombat()) unit->AttackedBy(realCaster); return; } // Check mechanic resistance for each effect for (int eff = 0; eff < MAX_EFFECT_INDEX; ++eff) if (unit->IsEffectResist(m_spellInfo, eff)) { if ((realCaster && realCaster->IsPlayer() && realCaster->ToPlayer()->HasOption(PLAYER_CHEAT_UNRANDOMIZE)) || (unit->IsPlayer() && unit->ToPlayer()->HasOption(PLAYER_CHEAT_UNRANDOMIZE))) break; effectMask &= ~(1 << eff); if (!effectMask) return; } // Recheck immune (only for delayed spells) if (m_caster != unit && m_spellInfo->speed && ( unit->IsImmuneToDamage(GetSpellSchoolMask(m_spellInfo), m_spellInfo) || unit->IsImmuneToSpell(m_spellInfo, unit == realCaster))) { if (realCaster) realCaster->SendSpellMiss(unit, m_spellInfo->Id, SPELL_MISS_IMMUNE); ResetEffectDamageAndHeal(); return; } if (realCaster && realCaster != unit) { // Recheck UNIT_FLAG_NON_ATTACKABLE for delayed spells if (unit->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE) && unit->GetCharmerOrOwnerGuid() != m_caster->GetObjectGuid()) { realCaster->SendSpellMiss(unit, m_spellInfo->Id, SPELL_MISS_EVADE); ResetEffectDamageAndHeal(); return; } if (!realCaster->IsFriendlyTo(unit)) { if (m_damage) // Example: stealth. unit->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_HITBYSPELL); // not break stealth by cast targeting if (!(m_spellInfo->AttributesEx & SPELL_ATTR_EX_NOT_BREAK_STEALTH)) { unit->RemoveSpellsCausingAura(SPELL_AURA_MOD_STEALTH); unit->RemoveNonPassiveSpellsCausingAura(SPELL_AURA_MOD_INVISIBILITY); } // for delayed spells ignore not visible explicit target if (m_delayed && unit == m_targets.getUnitTarget() && !unit->isVisibleForOrDetect(m_caster, m_caster, false)) { realCaster->SendSpellMiss(unit, m_spellInfo->Id, SPELL_MISS_EVADE); ResetEffectDamageAndHeal(); return; } // can cause back attack (if detected), stealth removed at Spell::cast if spell break it if ((!IsPositiveSpell(m_spellInfo) || IsSpellHaveEffect(m_spellInfo, SPELL_EFFECT_DISPEL)) && !m_spellInfo->IsFitToFamily() && // Sap handled somewhere else. Without this, sap will remove stealth if the rogue is visible. (m_spellInfo->Id == 6358 || // Exception to fix succubus seduction. m_caster->isVisibleForOrDetect(unit, unit, false))) { if (!m_spellInfo->HasAttribute(SPELL_ATTR_EX3_NO_INITIAL_AGGRO)) { // use speedup check to avoid re-remove after above lines if (m_spellInfo->AttributesEx & SPELL_ATTR_EX_NOT_BREAK_STEALTH) { unit->RemoveSpellsCausingAura(SPELL_AURA_MOD_STEALTH); unit->RemoveNonPassiveSpellsCausingAura(SPELL_AURA_MOD_INVISIBILITY); } // caster can be detected but have stealth aura m_caster->RemoveSpellsCausingAura(SPELL_AURA_MOD_STEALTH); m_caster->RemoveNonPassiveSpellsCausingAura(SPELL_AURA_MOD_INVISIBILITY); if (!IsSpellHaveAura(m_spellInfo, SPELL_AURA_MOD_POSSESS)) { if (!unit->isInCombat()) unit->AttackedBy(realCaster); unit->AddThreat(realCaster); unit->SetInCombatWithAggressor(realCaster); } realCaster->SetInCombatWithVictim(unit); } else if (m_spellInfo->HasAttribute(SPELL_ATTR_EX3_OUT_OF_COMBAT_ATTACK)) { unit->SetOutOfCombatWithAggressor(realCaster); realCaster->SetOutOfCombatWithVictim(unit); } } } else { // for delayed spells ignore negative spells (after duel end) for friendly targets if (m_delayed && !IsPositiveSpell(m_spellInfo)) { realCaster->SendSpellMiss(unit, m_spellInfo->Id, SPELL_MISS_EVADE); ResetEffectDamageAndHeal(); return; } if (unit->isInCombat()) { if (!(m_spellInfo->AttributesEx3 & SPELL_ATTR_EX3_NO_INITIAL_AGGRO)) { realCaster->SetInCombatWithAssisted(unit); unit->getHostileRefManager().threatAssist(realCaster, 0.0f, m_spellInfo); } } else if (unit->IsPvP()) { if (Player* pPlayer = realCaster->GetCharmerOrOwnerPlayerOrPlayerItself()) pPlayer->UpdatePvP(true); } } } // Get Data Needed for Diminishing Returns, some effects may have multiple auras, so this must be done on spell hit, not aura add m_diminishGroup = m_spellInfo->GetDiminishingReturnsGroup(m_triggeredByAuraSpell); m_diminishLevel = unit->GetDiminishing(m_diminishGroup); if (realCaster) DEBUG_UNIT(realCaster, DEBUG_DR, "[%u] Groupe DR : %u. Niveau : %u.", m_spellInfo->Id, m_diminishGroup, m_diminishLevel); // Increase Diminishing on unit, current informations for actually casts will use values above if ((GetDiminishingReturnsGroupType(m_diminishGroup) == DRTYPE_PLAYER && unit->IsPlayer()) || GetDiminishingReturnsGroupType(m_diminishGroup) == DRTYPE_ALL) unit->IncrDiminishing(m_diminishGroup); // Apply additional spell effects to target CastPreCastSpells(unit); if (IsSpellAppliesAura(m_spellInfo, effectMask)) { m_spellAuraHolder = CreateSpellAuraHolder(m_spellInfo, unit, realCaster, m_CastItem); m_spellAuraHolder->SetTriggered(IsTriggered()); m_spellAuraHolder->setDiminishGroup(m_diminishGroup); m_spellAuraHolder->setDiminishLevel(m_diminishLevel); } else m_spellAuraHolder = nullptr; for (int effectNumber = 0; effectNumber < MAX_EFFECT_INDEX; ++effectNumber) { if (effectMask & (1 << effectNumber)) { HandleEffects(unit, nullptr, nullptr, SpellEffectIndex(effectNumber), m_damageMultipliers[effectNumber]); if (m_applyMultiplierMask & (1 << effectNumber)) { // Get multiplier float multiplier = m_spellInfo->DmgMultiplier[effectNumber]; // Apply multiplier mods if (realCaster) if (Player* modOwner = realCaster->GetSpellModOwner()) modOwner->ApplySpellMod(m_spellInfo->Id, SPELLMOD_EFFECT_PAST_FIRST, multiplier, this); m_damageMultipliers[effectNumber] *= multiplier; } } } // now apply all created auras if (m_spellAuraHolder) { // normally shouldn't happen if (!m_spellAuraHolder->IsEmptyHolder()) { int32 duration = m_spellAuraHolder->GetAuraMaxDuration(); int32 originalDuration = duration; if (duration > 0) { unit->ApplyDiminishingToDuration(m_diminishGroup, duration, m_caster, m_diminishLevel, isReflected); // Fully diminished if (duration == 0) { delete m_spellAuraHolder; m_spellAuraHolder = nullptr; // Need to interrupt pet channeling spells or else they get stuck if (m_caster && m_caster->IsControlledByPlayer() && m_channeled) { Spell *channeled = m_caster->GetCurrentSpell(CURRENT_CHANNELED_SPELL); if (channeled && channeled->m_spellInfo->Id == m_spellInfo->Id && channeled->m_targets.getUnitTarget() == unit) m_caster->InterruptSpell(CURRENT_CHANNELED_SPELL); } return; } } if (duration != originalDuration) { m_spellAuraHolder->SetAuraMaxDuration(duration); m_spellAuraHolder->SetAuraDuration(duration); } if (!unit->AddSpellAuraHolder(m_spellAuraHolder)) m_spellAuraHolder = nullptr; } else { delete m_spellAuraHolder; m_spellAuraHolder = nullptr; } // Aura applied successfully. If it's a channeled spell keep track of the holder so // we can update it on spell update. Units do not update holders on themselves if // they are from a channeled spell if (m_channeled && m_spellAuraHolder) { AddChanneledAuraHolder(m_spellAuraHolder); } } } void Spell::DoAllEffectOnTarget(GOTargetInfo *target) { if (target->processed) // Check target return; target->processed = true; // Target checked in apply effects procedure uint32 effectMask = target->effectMask; if (!effectMask) return; GameObject* go = m_caster->GetMap()->GetGameObject(target->targetGUID); if (!go) return; for (int effectNumber = 0; effectNumber < MAX_EFFECT_INDEX; ++effectNumber) if (effectMask & (1 << effectNumber)) HandleEffects(nullptr, nullptr, go, SpellEffectIndex(effectNumber)); // cast at creature (or GO) quest objectives update at successful cast finished (+channel finished) // ignore autorepeat/melee casts for speed (not exist quest for spells (hm... ) if (!IsAutoRepeat() && !IsNextMeleeSwingSpell() && !IsChannelActive()) { if (Player* p = m_caster->GetCharmerOrOwnerPlayerOrPlayerItself()) p->RewardPlayerAndGroupAtCast(go, m_spellInfo->Id); } } void Spell::DoAllEffectOnTarget(ItemTargetInfo *target) { uint32 effectMask = target->effectMask; if (!target->item || !effectMask) return; for (int effectNumber = 0; effectNumber < MAX_EFFECT_INDEX; ++effectNumber) if (effectMask & (1 << effectNumber)) HandleEffects(nullptr, target->item, nullptr, SpellEffectIndex(effectNumber)); } void Spell::HandleDelayedSpellLaunch(TargetInfo *target) { // Get mask of effects for target uint32 mask = target->effectMask; Unit* unit = m_caster->GetObjectGuid() == target->targetGUID ? m_caster : ObjectAccessor::GetUnit(*m_caster, target->targetGUID); if (!unit) return; // Get original caster (if exist) and calculate damage/healing from him data Unit *real_caster = GetAffectiveCaster(); // FIXME: in case wild GO heal/damage spells will be used target bonuses Unit *caster = real_caster ? real_caster : m_caster; SpellMissInfo missInfo = target->missCondition; // Need init unitTarget by default unit (can changed in code on reflect) // Or on missInfo!=SPELL_MISS_NONE unitTarget undefined (but need in trigger subsystem) unitTarget = unit; // Reset damage/healing counter ResetEffectDamageAndHeal(); // Fill base damage struct (unitTarget - is real spell target) SpellNonMeleeDamage damageInfo(caster, unitTarget, m_spellInfo->Id, GetFirstSchoolInMask(m_spellSchoolMask)); if (unit && !caster->IsFriendlyTo(unit)) unit->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_HITBYSPELL); if (missInfo == SPELL_MISS_NONE || (missInfo == SPELL_MISS_REFLECT && target->reflectResult == SPELL_MISS_NONE)) { for (int32 effectNumber = 0; effectNumber < MAX_EFFECT_INDEX; ++effectNumber) { if (mask & (1 << effectNumber) && IsEffectHandledOnDelayedSpellLaunch(m_spellInfo, SpellEffectIndex(effectNumber))) { HandleEffects(unit, nullptr, nullptr, SpellEffectIndex(effectNumber), m_damageMultipliers[effectNumber]); if (m_applyMultiplierMask & (1 << effectNumber)) { // Get multiplier float multiplier = m_spellInfo->DmgMultiplier[effectNumber]; // Apply multiplier mods if (real_caster) if (Player* modOwner = real_caster->GetSpellModOwner()) modOwner->ApplySpellMod(m_spellInfo->Id, SPELLMOD_EFFECT_PAST_FIRST, multiplier, this); m_damageMultipliers[effectNumber] *= multiplier; } } } if (m_damage > 0) caster->CalculateSpellDamage(&damageInfo, m_damage, m_spellInfo, m_attackType, this); } target->damage = damageInfo.damage; target->HitInfo = damageInfo.HitInfo; } void Spell::InitializeDamageMultipliers() { for (int32 i = 0; i < MAX_EFFECT_INDEX; ++i) { if (m_spellInfo->Effect[i] == 0) continue; uint32 EffectChainTarget = m_spellInfo->EffectChainTarget[i]; if (Unit* realCaster = GetAffectiveCaster()) if (Player* modOwner = realCaster->GetSpellModOwner()) modOwner->ApplySpellMod(m_spellInfo->Id, SPELLMOD_JUMP_TARGETS, EffectChainTarget, this); m_damageMultipliers[i] = 1.0f; if ((m_spellInfo->EffectImplicitTargetA[i] == TARGET_CHAIN_DAMAGE || m_spellInfo->EffectImplicitTargetA[i] == TARGET_CHAIN_HEAL) && (EffectChainTarget > 1)) m_applyMultiplierMask |= (1 << i); } } // : Modification du fonctionnement de cette fonction // Indique si une canalisation doit s'arreter ou non // Pour cela, on regarde s'il reste des cibles. bool Spell::HasValidUnitPresentInTargetList() { uint8 foundMask = 0; uint8 needTargetsMask = 0; for (int32 i = 0; i < MAX_EFFECT_INDEX; ++i) if (m_spellInfo->Effect[i] == SPELL_EFFECT_APPLY_AURA && m_spellInfo->EffectApplyAuraName[i]) if (m_spellInfo->EffectImplicitTargetA[i] != TARGET_SELF) needTargetsMask |= 1 << i; // Cette canalisation n'a pas besoin de cible: elle se lance sur le lanceur de sort // Elle ne s'arrete pas, sauf s'il meurt biensur. if (!needTargetsMask) { if (m_caster->isAlive() == IsDeathOnlySpell(m_spellInfo)) return false; return true; } for (TargetList::const_iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) { if (ihit->deleted) continue; if (ihit->missCondition != SPELL_MISS_NONE) continue; Unit *unit = m_caster->GetObjectGuid() == ihit->targetGUID ? m_caster : ObjectAccessor::GetUnit(*m_caster, ihit->targetGUID); // either unit is alive and normal spell, or unit dead and deathonly-spell if (unit && (unit->isAlive() != IsDeathOnlySpell(m_spellInfo))) foundMask |= ihit->effectMask; } return (foundMask & needTargetsMask); } // Helper for Chain Healing // Spell target first // Raidmates then descending by injury suffered (MaxHealth - Health) // Other players/mobs then descending by injury suffered (MaxHealth - Health) struct ChainHealingOrder { const Unit* MainTarget; ChainHealingOrder(Unit const* Target) : MainTarget(Target) {}; // functor for operator ">" bool operator()(Unit const* _Left, Unit const* _Right) const { return (ChainHealingHash(_Left) < ChainHealingHash(_Right)); } int32 ChainHealingHash(Unit const* Target) const { if (Target == MainTarget) return 0; else if (Target->GetTypeId() == TYPEID_PLAYER && MainTarget->GetTypeId() == TYPEID_PLAYER && ((Player const*)Target)->IsInSameRaidWith((Player const*)MainTarget)) { if (Target->GetHealth() == Target->GetMaxHealth()) return 40000; else return 20000 - Target->GetMaxHealth() + Target->GetHealth(); } else return 40000 - Target->GetMaxHealth() + Target->GetHealth(); } }; class ChainHealingFullHealth { public: const Unit* MainTarget; ChainHealingFullHealth(const Unit* Target) : MainTarget(Target) {}; bool operator()(const Unit* Target) const { return (Target != MainTarget && Target->GetHealth() == Target->GetMaxHealth()); } }; // Helper for targets nearest to the spell target // The spell target is always first unless there is a target at _completely_ the same position (unbelievable case) struct TargetDistanceOrderNear { const Unit* MainTarget; TargetDistanceOrderNear(const Unit* Target) : MainTarget(Target) {}; // functor for operator ">" bool operator()(const Unit* _Left, const Unit* _Right) const { return MainTarget->GetDistanceOrder(_Left, _Right); } }; void Spell::SetTargetMap(SpellEffectIndex effIndex, uint32 targetMode, UnitList& targetUnitMap) { float radius; if (m_spellInfo->EffectRadiusIndex[effIndex]) radius = GetSpellRadius(sSpellRadiusStore.LookupEntry(m_spellInfo->EffectRadiusIndex[effIndex])); else radius = GetSpellMaxRange(sSpellRangeStore.LookupEntry(m_spellInfo->rangeIndex)); uint32 EffectChainTarget = m_spellInfo->EffectChainTarget[effIndex]; if (Unit* realCaster = GetAffectiveCaster()) { if (Player* modOwner = realCaster->GetSpellModOwner()) { modOwner->ApplySpellMod(m_spellInfo->Id, SPELLMOD_RADIUS, radius, this); modOwner->ApplySpellMod(m_spellInfo->Id, SPELLMOD_JUMP_TARGETS, EffectChainTarget, this); } } // Add spell leeway to caster centered AoE when moving or jumping switch (targetMode) { case TARGET_ALL_ENEMY_IN_AREA: case TARGET_ALL_PARTY_AROUND_CASTER: case TARGET_IN_FRONT_OF_CASTER: radius += m_caster->GetLeewayBonusRadius(); break; default: break; } uint32 unMaxTargets = m_spellInfo->MaxAffectedTargets; // custom target amount cases switch (m_spellInfo->SpellFamilyName) { case SPELLFAMILY_GENERIC: { switch (m_spellInfo->Id) { case 802: // Mutate Bug (AQ40, Emperor Vek'nilash) case 804: // Explode Bug (AQ40, Emperor Vek'lor) case 23138: // Gate of Shazzrah (MC, Shazzrah) case 24781: // Dream Fog (Emerald Dragons) //case 28560: // Summon Blizzard (Naxx, Sapphiron) unMaxTargets = 1; break; case 10258: // Awaken Vault Warder (Uldaman) unMaxTargets = 2; break; case 28542: // Life Drain (Naxx, Sapphiron) unMaxTargets = urand(7, 10); break; case 28796: // Poison Bolt Volley (Naxx, Faerlina) unMaxTargets = 10; break; } break; } default: break; } // custom radius cases switch (m_spellInfo->SpellFamilyName) { case SPELLFAMILY_GENERIC: { switch (m_spellInfo->Id) { case 28241: // Poison (Naxxramas, Grobbulus Cloud) { // Spell states 30yd radius, which you would think is the max radius once its all grown, // however, the visual of the spell goes no further than ~20yd, so lets stop it there. // It will instantly get a 2(?) yd radius, and grow to 20 from there if (SpellAuraHolder* auraHolder = m_caster->GetSpellAuraHolder(28158)) { const int maxDur = auraHolder->GetAuraMaxDuration(); const int currTick = maxDur - auraHolder->GetAuraDuration(); radius = 18.0f / maxDur*currTick + 2; //radius = 0.5f * (60000 - auraHolder->GetAuraDuration()) * 0.001f; } break; } case 29310: // Mana Burn (Heigan, naxxramas) // Without a bigger raidus its possible to tank heigan in one corner of the platform, and have ranged stay in the other corner radius = 28.0f; break; default: break; } break; } default: break; } bool SelectClosestTargets = false; // custom selection cases switch (m_spellInfo->SpellFamilyName) { case SPELLFAMILY_GENERIC: { switch (m_spellInfo->Id) { case 26052: // Poison Bolt Volley (AQ40, Princess Huhuran) case 29213: // Curse of the Plaguebringer (Naxxramas, Noth the Plaguebringer) SelectClosestTargets = true; break; } break; } case SPELLFAMILY_HUNTER: { switch (m_spellInfo->Id) { case 26180: // Wyvern Sting (AQ40, Princess Huhuran) SelectClosestTargets = true; break; } break; } default: break; } switch (targetMode) { case TARGET_TOTEM_EARTH: case TARGET_TOTEM_WATER: case TARGET_TOTEM_AIR: case TARGET_TOTEM_FIRE: case TARGET_SELF: targetUnitMap.push_back(m_caster); break; case TARGET_RANDOM_ENEMY_CHAIN_IN_AREA: { m_targets.m_targetMask = 0; unMaxTargets = EffectChainTarget; float max_range = radius + unMaxTargets * CHAIN_SPELL_JUMP_RADIUS; UnitList tempTargetUnitMap; { MaNGOS::AnyAoETargetUnitInObjectRangeCheck u_check(m_caster, m_originalCaster, max_range); MaNGOS::UnitListSearcher searcher(tempTargetUnitMap, u_check); Cell::VisitAllObjects(m_caster, searcher, max_range); } if (tempTargetUnitMap.empty()) break; tempTargetUnitMap.sort(TargetDistanceOrderNear(m_caster)); //Now to get us a random target that's in the initial range of the spell uint32 t = 0; auto itr = tempTargetUnitMap.begin(); while (itr != tempTargetUnitMap.end() && (*itr)->IsWithinDist(m_caster, radius)) ++t, ++itr; if (!t) break; itr = tempTargetUnitMap.begin(); std::advance(itr, rand() % t); Unit *pUnitTarget = *itr; targetUnitMap.push_back(pUnitTarget); tempTargetUnitMap.erase(itr); tempTargetUnitMap.sort(TargetDistanceOrderNear(pUnitTarget)); t = unMaxTargets - 1; Unit *prev = pUnitTarget; auto next = tempTargetUnitMap.begin(); while (t && next != tempTargetUnitMap.end()) { if (!prev->IsWithinDist(*next, CHAIN_SPELL_JUMP_RADIUS)) break; if (!(m_spellInfo->AttributesEx2 & SPELL_ATTR_EX2_IGNORE_LOS) && !prev->IsWithinLOSInMap(*next)) { ++next; continue; } prev = *next; targetUnitMap.push_back(prev); tempTargetUnitMap.erase(next); tempTargetUnitMap.sort(TargetDistanceOrderNear(prev)); next = tempTargetUnitMap.begin(); --t; } break; } case TARGET_RANDOM_FRIEND_CHAIN_IN_AREA: { m_targets.m_targetMask = 0; unMaxTargets = EffectChainTarget; float max_range = radius + unMaxTargets * CHAIN_SPELL_JUMP_RADIUS; UnitList tempTargetUnitMap; { MaNGOS::AnyFriendlyUnitInObjectRangeCheck u_check(m_caster, max_range); MaNGOS::UnitListSearcher searcher(tempTargetUnitMap, u_check); Cell::VisitAllObjects(m_caster, searcher, max_range); } if (tempTargetUnitMap.empty()) break; tempTargetUnitMap.sort(TargetDistanceOrderNear(m_caster)); //Now to get us a random target that's in the initial range of the spell uint32 t = 0; auto itr = tempTargetUnitMap.begin(); while (itr != tempTargetUnitMap.end() && (*itr)->IsWithinDist(m_caster, radius)) ++t, ++itr; if (!t) break; itr = tempTargetUnitMap.begin(); std::advance(itr, rand() % t); Unit *pUnitTarget = *itr; targetUnitMap.push_back(pUnitTarget); tempTargetUnitMap.erase(itr); tempTargetUnitMap.sort(TargetDistanceOrderNear(pUnitTarget)); t = unMaxTargets - 1; Unit *prev = pUnitTarget; auto next = tempTargetUnitMap.begin(); while (t && next != tempTargetUnitMap.end()) { if (!prev->IsWithinDist(*next, CHAIN_SPELL_JUMP_RADIUS)) break; if (!(m_spellInfo->AttributesEx2 & SPELL_ATTR_EX2_IGNORE_LOS) && !prev->IsWithinLOSInMap(*next)) { ++next; continue; } prev = *next; targetUnitMap.push_back(prev); tempTargetUnitMap.erase(next); tempTargetUnitMap.sort(TargetDistanceOrderNear(prev)); next = tempTargetUnitMap.begin(); --t; } break; } case TARGET_PET: { Pet* tmpUnit = m_caster->GetPet(); if (!tmpUnit) break; targetUnitMap.push_back(tmpUnit); break; } case TARGET_CHAIN_DAMAGE: { if (EffectChainTarget <= 1) { if (Unit* pUnitTarget = m_caster->SelectMagnetTarget(m_targets.getUnitTarget(), this, effIndex)) { m_targets.setUnitTarget(pUnitTarget); targetUnitMap.push_back(pUnitTarget); } } else { Unit* pUnitTarget = m_targets.getUnitTarget(); if (Unit* redirectTarget = m_caster->SelectMagnetTarget(m_targets.getUnitTarget(), this, effIndex)) { if (redirectTarget != pUnitTarget) { m_targets.setUnitTarget(redirectTarget); targetUnitMap.push_back(redirectTarget); break; } } WorldObject* originalCaster = GetAffectiveCasterObject(); if (!pUnitTarget || !originalCaster) break; unMaxTargets = EffectChainTarget; float max_range; if (m_spellInfo->DmgClass == SPELL_DAMAGE_CLASS_MELEE) max_range = radius; else //FIXME: This very like horrible hack and wrong for most spells max_range = radius + unMaxTargets * CHAIN_SPELL_JUMP_RADIUS; UnitList tempTargetUnitMap; // World of Warcraft Client Patch 1.11.0 (2006-06-20) // - Chain targeted spells and abilities (e.g. Multi-shot, Cleave, Chain // Lightning) will no longer land if target cannot be seen by the caster // due to stealth or invisibility. #if SUPPORTED_CLIENT_BUILD > CLIENT_BUILD_1_10_2 MaNGOS::AnyAoEVisibleTargetUnitInObjectRangeCheck u_check(pUnitTarget, originalCaster, max_range); MaNGOS::UnitListSearcher searcher(tempTargetUnitMap, u_check); #else MaNGOS::AnyAoETargetUnitInObjectRangeCheck u_check(pUnitTarget, originalCaster, max_range); MaNGOS::UnitListSearcher searcher(tempTargetUnitMap, u_check); #endif Cell::VisitAllObjects(m_caster, searcher, max_range); tempTargetUnitMap.sort(TargetDistanceOrderNear(pUnitTarget)); if (tempTargetUnitMap.empty()) break; if (*tempTargetUnitMap.begin() == pUnitTarget) tempTargetUnitMap.erase(tempTargetUnitMap.begin()); targetUnitMap.push_back(pUnitTarget); uint32 t = unMaxTargets - 1; Unit *prev = pUnitTarget; auto next = tempTargetUnitMap.begin(); while (t && next != tempTargetUnitMap.end()) { if (!prev->IsWithinDist(*next, CHAIN_SPELL_JUMP_RADIUS)) break; if (!(m_spellInfo->AttributesEx2 & SPELL_ATTR_EX2_IGNORE_LOS) && !prev->IsWithinLOSInMap(*next)) { ++next; continue; } if ((m_spellInfo->AttributesEx3 & SPELL_ATTR_EX3_TARGET_ONLY_PLAYER) && ((*next)->GetTypeId() != TYPEID_PLAYER)) { ++next; continue; } prev = *next; targetUnitMap.push_back(prev); tempTargetUnitMap.erase(next); tempTargetUnitMap.sort(TargetDistanceOrderNear(prev)); next = tempTargetUnitMap.begin(); --t; } } break; } case TARGET_ALL_ENEMY_IN_AREA: { FillAreaTargets(targetUnitMap, radius, PUSH_DEST_CENTER, SPELL_TARGETS_AOE_DAMAGE); float minDist = -1; #if SUPPORTED_CLIENT_BUILD > CLIENT_BUILD_1_8_4 switch (m_spellInfo->Id) { // Shadow Storm case 14297: case 26546: case 26555: minDist = 20.0f; break; } #endif if (minDist > 0) { for (UnitList::iterator itr = targetUnitMap.begin(), next; itr != targetUnitMap.end(); itr = next) { next = itr; ++next; if (!*itr) continue; if ((*itr)->IsWithinDist(m_caster, minDist)) targetUnitMap.erase(itr); } } if (SelectClosestTargets && unMaxTargets && targetUnitMap.size() > unMaxTargets) { targetUnitMap.sort(TargetDistanceOrderNear(m_caster)); UnitList::iterator itr = targetUnitMap.begin(); advance(itr, unMaxTargets); targetUnitMap.erase(itr, targetUnitMap.end()); } break; } case TARGET_AREAEFFECT_INSTANT: { SpellTargets targetB = SPELL_TARGETS_AOE_DAMAGE; // Select friendly targets for positive effect if (IsPositiveEffect(m_spellInfo, effIndex)) targetB = SPELL_TARGETS_FRIENDLY; UnitList tempTargetUnitMap; SpellScriptTargetBounds bounds = sSpellMgr.GetSpellScriptTargetBounds(m_spellInfo->Id); // fill real target list if no spell script target defined FillAreaTargets(bounds.first != bounds.second ? tempTargetUnitMap : targetUnitMap, radius, PUSH_DEST_CENTER, bounds.first != bounds.second ? SPELL_TARGETS_ALL : targetB); if (!tempTargetUnitMap.empty()) { for (UnitList::const_iterator iter = tempTargetUnitMap.begin(); iter != tempTargetUnitMap.end(); ++iter) { if ((*iter)->GetTypeId() != TYPEID_UNIT) continue; for (SpellScriptTarget::const_iterator i_spellST = bounds.first; i_spellST != bounds.second; ++i_spellST) { // only creature entries supported for this target type if (i_spellST->second.type == SPELL_TARGET_TYPE_GAMEOBJECT) continue; if ((*iter)->GetEntry() == i_spellST->second.targetEntry) { if (i_spellST->second.type == SPELL_TARGET_TYPE_DEAD && ((Creature*)(*iter))->IsCorpse()) targetUnitMap.push_back((*iter)); else if (i_spellST->second.type == SPELL_TARGET_TYPE_CREATURE && (*iter)->isAlive()) targetUnitMap.push_back((*iter)); break; } } } } // exclude caster targetUnitMap.remove(m_caster); break; } case TARGET_AREAEFFECT_CUSTOM: { if (m_spellInfo->Effect[effIndex] == SPELL_EFFECT_PERSISTENT_AREA_AURA) break; else if (m_spellInfo->Effect[effIndex] == SPELL_EFFECT_SUMMON || m_spellInfo->Effect[effIndex] == SPELL_EFFECT_SUMMON_OBJECT_WILD) { targetUnitMap.push_back(m_caster); break; } UnitList tempTargetUnitMap; SpellScriptTargetBounds bounds = sSpellMgr.GetSpellScriptTargetBounds(m_spellInfo->Id); // fill real target list if no spell script target defined FillAreaTargets(bounds.first != bounds.second ? tempTargetUnitMap : targetUnitMap, radius, PUSH_DEST_CENTER, SPELL_TARGETS_ALL); if (!tempTargetUnitMap.empty()) { for (UnitList::const_iterator iter = tempTargetUnitMap.begin(); iter != tempTargetUnitMap.end(); ++iter) { if ((*iter)->GetTypeId() != TYPEID_UNIT) continue; for (SpellScriptTarget::const_iterator i_spellST = bounds.first; i_spellST != bounds.second; ++i_spellST) { // only creature entries supported for this target type if (i_spellST->second.type == SPELL_TARGET_TYPE_GAMEOBJECT) continue; if ((*iter)->GetEntry() == i_spellST->second.targetEntry) { if (i_spellST->second.type == SPELL_TARGET_TYPE_DEAD && ((Creature*)(*iter))->IsCorpse()) targetUnitMap.push_back((*iter)); else if (i_spellST->second.type == SPELL_TARGET_TYPE_CREATURE && (*iter)->isAlive()) targetUnitMap.push_back((*iter)); break; } } } } else { // remove not targetable units if spell has no script targets for (UnitList::iterator itr = targetUnitMap.begin(); itr != targetUnitMap.end();) { if (!(*itr)->isTargetableForAttack(m_spellInfo->AttributesEx3 & SPELL_ATTR_EX3_CAST_ON_DEAD, m_caster->IsPlayer())) targetUnitMap.erase(itr++); else ++itr; } } break; } case TARGET_AREAEFFECT_GO_AROUND_SOURCE: // Despawn Ice Block (sapphiron) if (m_spellInfo->Id == 30132) { float x, y, z; m_caster->GetPosition(x, y, z); std::list tempTargetGOList; // search all GO's with entry, within range of caster MaNGOS::GameObjectEntryInPosRangeCheck go_check(*m_caster, 181247/*Ice BLock*/, x , y, z, radius); MaNGOS::GameObjectListSearcher checker(tempTargetGOList, go_check); Cell::VisitGridObjects(m_caster, checker, radius); if (!tempTargetGOList.empty()) { for (std::list::iterator iter = tempTargetGOList.begin(); iter != tempTargetGOList.end(); ++iter) AddGOTarget(*iter, effIndex); } } break; case TARGET_AREAEFFECT_GO_AROUND_DEST: { // It may be possible to fill targets for some spell effects // automatically (SPELL_EFFECT_WMO_REPAIR(88) for example) but // for some/most spells we clearly need/want to limit with spell_target_script // Some spells untested, for affected GO type 33. May need further adjustments for spells related. SpellScriptTargetBounds bounds = sSpellMgr.GetSpellScriptTargetBounds(m_spellInfo->Id); std::list tempTargetGOList; for (SpellScriptTarget::const_iterator i_spellST = bounds.first; i_spellST != bounds.second; ++i_spellST) { if (i_spellST->second.type == SPELL_TARGET_TYPE_GAMEOBJECT) { // search all GO's with entry, within range of m_destN MaNGOS::GameObjectEntryInPosRangeCheck go_check(*m_caster, i_spellST->second.targetEntry, m_targets.m_destX, m_targets.m_destY, m_targets.m_destZ, radius); MaNGOS::GameObjectListSearcher checker(tempTargetGOList, go_check); Cell::VisitGridObjects(m_caster, checker, radius); } } if (!tempTargetGOList.empty()) { for (std::list::iterator iter = tempTargetGOList.begin(); iter != tempTargetGOList.end(); ++iter) AddGOTarget(*iter, effIndex); } break; } case TARGET_ALL_ENEMY_IN_AREA_INSTANT: { // targets the ground, not the units in the area switch (m_spellInfo->Effect[effIndex]) { case SPELL_EFFECT_PERSISTENT_AREA_AURA: break; case SPELL_EFFECT_SUMMON: targetUnitMap.push_back(m_caster); break; default: FillAreaTargets(targetUnitMap, radius, PUSH_DEST_CENTER, SPELL_TARGETS_AOE_DAMAGE); break; } break; } case TARGET_DUELVSPLAYER_COORDINATES: { if (Unit* currentTarget = m_targets.getUnitTarget()) m_targets.setDestination(currentTarget->GetPositionX(), currentTarget->GetPositionY(), currentTarget->GetPositionZ()); break; } case TARGET_ALL_PARTY_AROUND_CASTER: case TARGET_ALL_PARTY_AROUND_CASTER_2: case TARGET_ALL_PARTY: FillRaidOrPartyTargets(targetUnitMap, m_caster, radius, false, true, true); break; case TARGET_ALL_RAID_AROUND_CASTER: FillRaidOrPartyTargets(targetUnitMap, m_caster, radius, true, true, false); break; case TARGET_SINGLE_FRIEND: case TARGET_SINGLE_FRIEND_2: if (m_targets.getUnitTarget()) targetUnitMap.push_back(m_targets.getUnitTarget()); break; case TARGET_CASTER_COORDINATES: { // Check original caster is GO - set its coordinates as dst cast if (WorldObject *caster = GetCastingObject()) m_targets.setDestination(caster->GetPositionX(), caster->GetPositionY(), caster->GetPositionZ()); break; } case TARGET_ALL_HOSTILE_UNITS_AROUND_CASTER: FillAreaTargets(targetUnitMap, radius, PUSH_SELF_CENTER, SPELL_TARGETS_HOSTILE); break; case TARGET_ALL_FRIENDLY_UNITS_AROUND_CASTER: // selected friendly units (for casting objects) around casting object FillAreaTargets(targetUnitMap, radius, PUSH_SELF_CENTER, SPELL_TARGETS_FRIENDLY, GetCastingObject()); break; case TARGET_ALL_FRIENDLY_UNITS_IN_AREA: FillAreaTargets(targetUnitMap, radius, PUSH_DEST_CENTER, SPELL_TARGETS_FRIENDLY); break; // TARGET_SINGLE_PARTY means that the spells can only be casted on a party member and not on the caster (some seals, fire shield from imp, etc..) case TARGET_SINGLE_PARTY: { Unit *target = m_targets.getUnitTarget(); // Those spells apparently can't be casted on the caster. if (target && target != m_caster) { // Can only be casted on group's members or its pets Group *pGroup = nullptr; Unit* owner = m_caster->GetCharmerOrOwner(); Unit *targetOwner = target->GetCharmerOrOwner(); if (owner) { if (owner->GetTypeId() == TYPEID_PLAYER) { if (target == owner) { targetUnitMap.push_back(target); break; } pGroup = ((Player*)owner)->GetGroup(); } } else if (m_caster->GetTypeId() == TYPEID_PLAYER) { if (targetOwner == m_caster && target->GetTypeId() == TYPEID_UNIT && ((Creature*)target)->IsPet()) { targetUnitMap.push_back(target); break; } pGroup = ((Player*)m_caster)->GetGroup(); } if (pGroup) { // Our target can also be a player's pet who's grouped with us or our pet. But can't be controlled player if (targetOwner) { if (targetOwner->GetTypeId() == TYPEID_PLAYER && target->GetTypeId() == TYPEID_UNIT && (((Creature*)target)->IsPet()) && target->GetOwnerGuid() == targetOwner->GetObjectGuid() && pGroup->IsMember(((Player*)targetOwner)->GetObjectGuid())) targetUnitMap.push_back(target); } // 1Our target can be a player who is on our group else if (target->GetTypeId() == TYPEID_PLAYER && pGroup->IsMember(((Player*)target)->GetObjectGuid())) targetUnitMap.push_back(target); } } break; } case TARGET_GAMEOBJECT: if (m_targets.getGOTarget()) AddGOTarget(m_targets.getGOTarget(), effIndex); break; case TARGET_IN_FRONT_OF_CASTER: { SpellNotifyPushType pushType = PUSH_IN_FRONT; switch (m_spellInfo->SpellVisual) // Some spell require a different target fill { case 3879: pushType = PUSH_IN_BACK; break; case 7441: pushType = PUSH_IN_FRONT_15; break; case 7619: // anub impale pushType = PUSH_IN_FRONT_15; break; } FillAreaTargets(targetUnitMap, radius, pushType, SPELL_TARGETS_AOE_DAMAGE); break; } case TARGET_LARGE_FRONTAL_CONE: { switch (m_spellInfo->Id) { case 24820: case 24821: case 24822: case 24823: case 24835: case 24836: case 24837: case 24838: { UnitList tempTargetUnitMap; FillAreaTargets(tempTargetUnitMap, radius, PUSH_SELF_CENTER, SPELL_TARGETS_AOE_DAMAGE); for (UnitList::const_iterator itr = tempTargetUnitMap.begin(); itr != tempTargetUnitMap.end(); ++itr) { float angle; float arc; switch (m_spellInfo->Id) { case 24820: angle = 0.0f * M_PI_F / 4.0f; arc = 2.0f * M_PI_F / 3.0f; break; case 24821: angle = 1.0f * M_PI_F / 4.0f; arc = 2.0f * M_PI_F / 3.0f; break; case 24822: angle = 2.0f * M_PI_F / 4.0f; arc = 2.0f * M_PI_F / 3.0f; break; case 24823: angle = 3.0f * M_PI_F / 4.0f; arc = 2.0f * M_PI_F / 3.0f; break; case 24835: angle = -4.0f * M_PI_F / 4.0f; arc = 2.0f * M_PI_F / 3.0f; break; case 24836: angle = -3.0f * M_PI_F / 4.0f; arc = 2.0f * M_PI_F / 3.0f; break; case 24837: angle = -2.0f * M_PI_F / 4.0f; arc = 2.0f * M_PI_F / 3.0f; break; case 24838: angle = -1.0f * M_PI_F / 4.0f; arc = 2.0f * M_PI_F / 3.0f; break; default: angle = 0.0f; arc = M_PI_F / 2.0f; break; } if (m_caster->HasInArc(arc, *itr, angle)) targetUnitMap.push_back(*itr); } break; } default: FillAreaTargets(targetUnitMap, radius, PUSH_IN_FRONT_90, SPELL_TARGETS_AOE_DAMAGE); break; } break; } case TARGET_NARROW_FRONTAL_CONE: FillAreaTargets(targetUnitMap, radius, PUSH_IN_FRONT_15, SPELL_TARGETS_AOE_DAMAGE); break; case TARGET_UNIT_TARGET_ANY: { Unit *target = m_targets.getUnitTarget(); // This target should be checked depending on the effect // (check for PvP ON/OFF, ...) if (target) targetUnitMap.push_back(target); break; } case TARGET_GAMEOBJECT_ITEM: if (m_targets.getGOTargetGuid()) AddGOTarget(m_targets.getGOTarget(), effIndex); else if (m_targets.getItemTarget()) AddItemTarget(m_targets.getItemTarget(), effIndex); break; case TARGET_MASTER: if (Unit* owner = m_caster->GetCharmerOrOwner()) targetUnitMap.push_back(owner); break; case TARGET_ALL_ENEMY_IN_AREA_CHANNELED: // targets the ground, not the units in the area if (m_spellInfo->Effect[effIndex] != SPELL_EFFECT_PERSISTENT_AREA_AURA) FillAreaTargets(targetUnitMap, radius, PUSH_DEST_CENTER, SPELL_TARGETS_AOE_DAMAGE); break; case TARGET_AREAEFFECT_PARTY: { Unit* owner = m_caster->GetCharmerOrOwner(); Player *pTarget = nullptr; if (owner) { targetUnitMap.push_back(m_caster); if (owner->GetTypeId() == TYPEID_PLAYER) pTarget = (Player*)owner; } else if (m_caster->GetTypeId() == TYPEID_PLAYER) { if (Unit* target = m_targets.getUnitTarget()) { if (target->GetTypeId() != TYPEID_PLAYER) { if (((Creature*)target)->IsPet()) { Unit *targetOwner = target->GetOwner(); if (targetOwner->GetTypeId() == TYPEID_PLAYER) pTarget = (Player*)targetOwner; } } else pTarget = (Player*)target; } } Group* pGroup = pTarget ? pTarget->GetGroup() : NULL; if (pGroup) { uint8 subgroup = pTarget->GetSubGroup(); for (GroupReference *itr = pGroup->GetFirstMember(); itr != nullptr; itr = itr->next()) { Player* target = itr->getSource(); // IsHostileTo check duel and controlled by enemy if (target && target->GetSubGroup() == subgroup && target->getLevel() + 10 >= m_spellInfo->spellLevel && !m_caster->IsHostileTo(target)) { if (pTarget->IsWithinDistInMap(target, radius)) targetUnitMap.push_back(target); if (Pet* pet = target->GetPet()) if (pTarget->IsWithinDistInMap(pet, radius)) targetUnitMap.push_back(pet); } } } else if (owner) { if (m_caster->IsWithinDistInMap(owner, radius)) targetUnitMap.push_back(owner); } else if (pTarget) { targetUnitMap.push_back(pTarget); if (Pet* pet = pTarget->GetPet()) if (m_caster->IsWithinDistInMap(pet, radius)) targetUnitMap.push_back(pet); } break; } case TARGET_SCRIPT: { if (m_targets.getUnitTarget()) targetUnitMap.push_back(m_targets.getUnitTarget()); if (m_targets.getItemTarget()) AddItemTarget(m_targets.getItemTarget(), effIndex); break; } case TARGET_SELF_FISHING: targetUnitMap.push_back(m_caster); break; case TARGET_CHAIN_HEAL: { Unit* pUnitTarget = m_targets.getUnitTarget(); if (!pUnitTarget) break; if (EffectChainTarget <= 1) targetUnitMap.push_back(pUnitTarget); else { unMaxTargets = EffectChainTarget; float max_range = radius + unMaxTargets * CHAIN_SPELL_JUMP_RADIUS; UnitList tempTargetUnitMap; FillAreaTargets(tempTargetUnitMap, max_range, PUSH_SELF_CENTER, SPELL_TARGETS_FRIENDLY); if (m_caster != pUnitTarget && std::find(tempTargetUnitMap.begin(), tempTargetUnitMap.end(), m_caster) == tempTargetUnitMap.end()) tempTargetUnitMap.push_front(m_caster); tempTargetUnitMap.sort(TargetDistanceOrderNear(pUnitTarget)); if (tempTargetUnitMap.empty()) break; if (*tempTargetUnitMap.begin() == pUnitTarget) tempTargetUnitMap.erase(tempTargetUnitMap.begin()); targetUnitMap.push_back(pUnitTarget); uint32 t = unMaxTargets - 1; Unit *prev = pUnitTarget; auto next = tempTargetUnitMap.begin(); while (t && next != tempTargetUnitMap.end()) { if (!prev->IsWithinDist(*next, CHAIN_SPELL_JUMP_RADIUS)) break; if (!(m_spellInfo->AttributesEx2 & SPELL_ATTR_EX2_IGNORE_LOS) && !prev->IsWithinLOSInMap(*next)) { ++next; continue; } if ((*next)->GetHealth() == (*next)->GetMaxHealth()) { next = tempTargetUnitMap.erase(next); continue; } prev = *next; targetUnitMap.push_back(prev); tempTargetUnitMap.erase(next); tempTargetUnitMap.sort(TargetDistanceOrderNear(prev)); next = tempTargetUnitMap.begin(); --t; } } break; } case TARGET_CURRENT_ENEMY_COORDINATES: { Unit* currentTarget = m_targets.getUnitTarget(); if (currentTarget) { targetUnitMap.push_back(currentTarget); if (m_spellInfo->Id == 28863) // Void Zone creature often end up below ground m_targets.setDestination(currentTarget->GetPositionX(), currentTarget->GetPositionY(), currentTarget->GetPositionZ()+0.3f); else if (m_spellInfo->Id != 18392) // Onyxia's Fireball - without a destination the spell uses its original spell visuals m_targets.setDestination(currentTarget->GetPositionX(), currentTarget->GetPositionY(), currentTarget->GetPositionZ()); } break; } case TARGET_AREAEFFECT_PARTY_AND_CLASS: { Player* targetPlayer = m_targets.getUnitTarget() && m_targets.getUnitTarget()->GetTypeId() == TYPEID_PLAYER ? (Player*)m_targets.getUnitTarget() : NULL; Group* pGroup = targetPlayer ? targetPlayer->GetGroup() : NULL; if (pGroup) { for (GroupReference *itr = pGroup->GetFirstMember(); itr != nullptr; itr = itr->next()) { Player* Target = itr->getSource(); // IsHostileTo check duel and controlled by enemy if (Target && targetPlayer->IsWithinDistInMap(Target, radius) && targetPlayer->getClass() == Target->getClass() && !m_caster->IsHostileTo(Target)) targetUnitMap.push_back(Target); } } else if (m_targets.getUnitTarget()) targetUnitMap.push_back(m_targets.getUnitTarget()); break; } case TARGET_TABLE_X_Y_Z_COORDINATES: { SpellTargetPosition const* st = sSpellMgr.GetSpellTargetPosition(m_spellInfo->Id); if (st) { // teleportspells are handled in another way if (m_spellInfo->Effect[effIndex] == SPELL_EFFECT_TELEPORT_UNITS) break; if (st->target_mapId == m_caster->GetMapId()) m_targets.setDestination(st->target_X, st->target_Y, st->target_Z); else sLog.outError("SPELL: wrong map (%u instead %u) target coordinates for spell ID %u", st->target_mapId, m_caster->GetMapId(), m_spellInfo->Id); } else sLog.outError("SPELL: unknown target coordinates for spell ID %u", m_spellInfo->Id); break; } case TARGET_DYNAMIC_OBJECT_FRONT: case TARGET_DYNAMIC_OBJECT_BEHIND: case TARGET_DYNAMIC_OBJECT_LEFT_SIDE: case TARGET_DYNAMIC_OBJECT_RIGHT_SIDE: case TARGET_MINION: { if (targetMode == TARGET_MINION && m_spellInfo->Effect[effIndex] == SPELL_EFFECT_DUEL) break; if (!(m_targets.m_targetMask & TARGET_FLAG_DEST_LOCATION)) { // General override, we don't want to use max spell range here. // Note: 0.0 radius is also for index 36. It is possible that 36 must be defined as // "at the base of", in difference to 0 which appear to be "directly in front of". // TODO: some summoned will make caster be half inside summoned object. Need to fix // that in the below code (nearpoint vs closepoint, etc). if (m_spellInfo->EffectRadiusIndex[effIndex] == 0) radius = 0.0f; float angle = m_caster->GetOrientation(); switch (targetMode) { case TARGET_MINION: case TARGET_DYNAMIC_OBJECT_FRONT: break; case TARGET_DYNAMIC_OBJECT_BEHIND: angle += M_PI_F; break; case TARGET_DYNAMIC_OBJECT_LEFT_SIDE: angle += M_PI_F / 2; break; case TARGET_DYNAMIC_OBJECT_RIGHT_SIDE: angle -= M_PI_F / 2; break; } float x, y, z; m_caster->GetNearPoint(m_caster, x, y, z, 0.0f, radius, angle); // For some reason all the creature Blink spells use this target type instead of the player one. // Prevent them from teleporting to places that they can't normally walk to like under the map. if (m_spellInfo->Effect[effIndex] == SPELL_EFFECT_LEAP) if (!m_caster->GetMap()->GetWalkHitPosition(m_caster->GetTransport(), x, y, z, x, y, z, NAV_GROUND | NAV_WATER, 1.0f, false) || (abs(m_caster->GetPositionZ() - z) > 5.0f)) m_caster->GetPosition(x, y, z); m_targets.setDestination(x, y, z); } targetUnitMap.push_back(m_caster); break; } case TARGET_EFFECT_SELECT: { // add here custom effects that need default target. // FOR EVERY TARGET TYPE THERE IS A DIFFERENT FILL!! switch (m_spellInfo->Effect[effIndex]) { case SPELL_EFFECT_DUMMY: { switch (m_spellInfo->Id) { case 20577: // Cannibalize { WorldObject* result = FindCorpseUsing (); if (result) { switch (result->GetTypeId()) { case TYPEID_UNIT: case TYPEID_PLAYER: targetUnitMap.push_back((Unit*)result); break; case TYPEID_CORPSE: m_targets.setCorpseTarget((Corpse*)result); break; } } else { // clear cooldown at fail if (m_caster->GetTypeId() == TYPEID_PLAYER) ((Player*)m_caster)->RemoveSpellCooldown(m_spellInfo->Id, true); SendCastResult(SPELL_FAILED_NO_EDIBLE_CORPSES); finish(false); } break; } default: if (m_targets.getUnitTarget()) targetUnitMap.push_back(m_targets.getUnitTarget()); break; } // Add AoE target-mask to self, if no target-dest provided already if ((m_targets.m_targetMask & TARGET_FLAG_DEST_LOCATION) == 0) m_targets.setDestination(m_caster->GetPositionX(), m_caster->GetPositionY(), m_caster->GetPositionZ()); break; } case SPELL_EFFECT_BIND: case SPELL_EFFECT_PARRY: case SPELL_EFFECT_BLOCK: case SPELL_EFFECT_CREATE_ITEM: case SPELL_EFFECT_TRIGGER_SPELL: case SPELL_EFFECT_TRIGGER_MISSILE: case SPELL_EFFECT_LEARN_SPELL: case SPELL_EFFECT_SKILL_STEP: case SPELL_EFFECT_PROFICIENCY: case SPELL_EFFECT_SUMMON_POSSESSED: case SPELL_EFFECT_SUMMON_OBJECT_WILD: case SPELL_EFFECT_SELF_RESURRECT: case SPELL_EFFECT_REPUTATION: case SPELL_EFFECT_ADD_HONOR: case SPELL_EFFECT_SEND_TAXI: if (m_targets.getUnitTarget()) targetUnitMap.push_back(m_targets.getUnitTarget()); // Triggered spells have additional spell targets - cast them even if no explicit unit target is given (required for spell 50516 for example) else if (m_spellInfo->Effect[effIndex] == SPELL_EFFECT_TRIGGER_SPELL) targetUnitMap.push_back(m_caster); break; case SPELL_EFFECT_SUMMON_PLAYER: if (m_targets.getUnitTarget()) targetUnitMap.push_back(m_targets.getUnitTarget()); break; case SPELL_EFFECT_RESURRECT: case SPELL_EFFECT_RESURRECT_NEW: if (m_targets.getUnitTarget()) targetUnitMap.push_back(m_targets.getUnitTarget()); if (m_targets.getCorpseTargetGuid()) { if (Corpse *corpse = m_caster->GetMap()->GetCorpse(m_targets.getCorpseTargetGuid())) if (Player* owner = ObjectAccessor::FindPlayer(corpse->GetOwnerGuid())) targetUnitMap.push_back(owner); } break; case SPELL_EFFECT_SUMMON: /*[-ZERO] if (m_spellInfo->EffectMiscValueB[effIndex] == SUMMON_TYPE_POSESSED || m_spellInfo->EffectMiscValueB[effIndex] == SUMMON_TYPE_POSESSED2) { if(m_targets.getUnitTarget()) targetUnitMap.push_back(m_targets.getUnitTarget()); } else */ targetUnitMap.push_back(m_caster); break; case SPELL_EFFECT_TRANS_DOOR: { if (m_spellInfo->rangeIndex != SPELL_RANGE_IDX_SELF_ONLY) { float tmp_x, tmp_y, tmp_z; m_caster->GetRandomPoint(m_caster->GetPositionX(), m_caster->GetPositionY(), m_caster->GetPositionZ(), 5.0f, tmp_x, tmp_y, tmp_z); m_targets.setDestination(tmp_x, tmp_y, tmp_z); } targetUnitMap.push_back(m_caster); break; } case SPELL_EFFECT_SUMMON_WILD: { if (!m_originalCasterGUID.IsEmpty() && m_originalCasterGUID.IsGameObject()) { if (GameObject* pSummoner = m_caster->GetMap()->GetGameObject(m_originalCasterGUID)) m_targets.setDestination(pSummoner->GetPositionX(), pSummoner->GetPositionY(), pSummoner->GetPositionZ()); } // no break } case SPELL_EFFECT_SUMMON_CHANGE_ITEM: case SPELL_EFFECT_SUMMON_GUARDIAN: case SPELL_EFFECT_ADD_FARSIGHT: case SPELL_EFFECT_STUCK: case SPELL_EFFECT_DESTROY_ALL_TOTEMS: case SPELL_EFFECT_SUMMON_DEMON: case SPELL_EFFECT_SKILL: targetUnitMap.push_back(m_caster); break; case SPELL_EFFECT_PERSISTENT_AREA_AURA: if (Unit* currentTarget = m_targets.getUnitTarget()) m_targets.setDestination(currentTarget->GetPositionX(), currentTarget->GetPositionY(), currentTarget->GetPositionZ()); break; case SPELL_EFFECT_LEARN_PET_SPELL: if (Pet* pet = m_caster->GetPet()) targetUnitMap.push_back(pet); break; case SPELL_EFFECT_ENCHANT_ITEM: case SPELL_EFFECT_ENCHANT_ITEM_TEMPORARY: case SPELL_EFFECT_DISENCHANT: case SPELL_EFFECT_FEED_PET: if (m_targets.getItemTarget()) AddItemTarget(m_targets.getItemTarget(), effIndex); break; case SPELL_EFFECT_APPLY_AURA: switch (m_spellInfo->EffectApplyAuraName[effIndex]) { case SPELL_AURA_ADD_FLAT_MODIFIER: // some spell mods auras have 0 target modes instead expected TARGET_SELF(1) (and present for other ranks for same spell for example) case SPELL_AURA_ADD_PCT_MODIFIER: targetUnitMap.push_back(m_caster); break; default: // apply to target in other case if (m_targets.getUnitTarget()) targetUnitMap.push_back(m_targets.getUnitTarget()); break; } break; case SPELL_EFFECT_APPLY_AREA_AURA_PARTY: // AreaAura if (m_spellInfo->Attributes == 0x9050000 || m_spellInfo->Attributes == 0x10000) SetTargetMap(effIndex, TARGET_AREAEFFECT_PARTY, targetUnitMap); break; case SPELL_EFFECT_SKIN_PLAYER_CORPSE: if (m_targets.getUnitTarget()) targetUnitMap.push_back(m_targets.getUnitTarget()); break; case SPELL_EFFECT_TELEPORT_UNITS_FACE_CASTER: if (!(m_targets.m_targetMask & TARGET_FLAG_DEST_LOCATION)) m_targets.setDestination(m_caster->GetPositionX(), m_caster->GetPositionY(), m_caster->GetPositionZ()); break; default: break; } break; } case TARGET_DEST_CASTER_FRONT_LEAP: { Unit* pUnitTarget = m_targets.getUnitTarget(); if (!pUnitTarget) break; float dis = GetSpellRadius(sSpellRadiusStore.LookupEntry(m_spellInfo->EffectRadiusIndex[effIndex])); float x, y, z; float srcX, srcY, srcZ; float zSearchDist = 20.0f; // Falling case float ground = 0.0f; pUnitTarget->GetSafePosition(x, y, z); pUnitTarget->GetSafePosition(srcX, srcY, srcZ); float waterLevel = pUnitTarget->GetTerrain()->GetWaterLevel(x, y, z, &ground); x += dis * cos(pUnitTarget->GetOrientation()); y += dis * sin(pUnitTarget->GetOrientation()); // Underwater blink case if (waterLevel != VMAP_INVALID_HEIGHT_VALUE && waterLevel > ground) { if (z < ground) z = ground; // If blinking up to the surface, limit z position (do not teleport out of water) if (z > waterLevel && (z - srcZ) > 1.0f) { float t = (waterLevel - srcZ) / (z - srcZ); x = (x - srcX) * t + srcX; y = (y - srcY) * t + srcY; z = waterLevel; } pUnitTarget->GetMap()->GetLosHitPosition(srcX, srcY, srcZ, x, y, z, -0.5f); ground = pUnitTarget->GetMap()->GetHeight(x, y, z); if (ground < z) { m_targets.setDestination(x, y, z); break; } // If we are leaving water, rather use pathfinding, but increase z-range position research. zSearchDist = 20.0f; } if (!pUnitTarget->GetMap()->GetWalkHitPosition(pUnitTarget->GetTransport(), srcX, srcY, srcZ, x, y, z, NAV_GROUND | NAV_WATER, zSearchDist, false)) { x = srcX; y = srcY; z = srcZ; } m_targets.setDestination(x, y, z); } default: //sLog.outError( "SPELL: Unknown implicit target (%u) for spell ID %u", targetMode, m_spellInfo->Id ); break; } if (unMaxTargets && targetUnitMap.size() > unMaxTargets) { // make sure one unit is always removed per iteration uint32 removed_utarget = 0; for (UnitList::iterator itr = targetUnitMap.begin(), next; itr != targetUnitMap.end(); itr = next) { next = itr; ++next; if (!*itr) continue; if ((*itr) == m_targets.getUnitTarget()) { targetUnitMap.erase(itr); removed_utarget = 1; // break; } } // remove random units from the map while (targetUnitMap.size() > unMaxTargets - removed_utarget) { uint32 poz = urand(0, targetUnitMap.size() - 1); for (auto itr = targetUnitMap.begin(); itr != targetUnitMap.end(); ++itr, --poz) { if (!*itr) continue; if (!poz) { targetUnitMap.erase(itr); break; } } } // the player's target will always be added to the map if (removed_utarget && m_targets.getUnitTarget()) targetUnitMap.push_back(m_targets.getUnitTarget()); } } bool IsAcceptableAutorepeatError(SpellCastResult result) { switch (result) { case SPELL_FAILED_MOVING: // Let's toggle auto attack while moving case SPELL_CAST_OK: return true; } return false; } SpellCastResult Spell::prepare(SpellCastTargets targets, Aura* triggeredByAura, uint32 chance) { m_targets = std::move(targets); return prepare(triggeredByAura, chance); } SpellCastResult Spell::prepare(Aura* triggeredByAura, uint32 chance) { m_spellState = SPELL_STATE_PREPARING; m_delayed = m_spellInfo->speed > 0.0f || (m_spellInfo->IsCCSpell() && m_targets.getUnitTarget() && m_targets.getUnitTarget()->IsPlayer()); if (m_caster->GetTransport()) { m_castPositionX = m_caster->GetTransOffsetX(); m_castPositionY = m_caster->GetTransOffsetY(); m_castPositionZ = m_caster->GetTransOffsetZ(); m_castOrientation = m_caster->GetTransOffsetO(); } else { m_castPositionX = m_caster->GetPositionX(); m_castPositionY = m_caster->GetPositionY(); m_castPositionZ = m_caster->GetPositionZ(); m_castOrientation = m_caster->GetOrientation(); } if (triggeredByAura) { m_triggeredByAuraSpell = triggeredByAura->GetSpellProto(); m_triggeredByAuraBasePoints = triggeredByAura->GetBasePoints(); } try { // create and add update event for this spell SpellEvent* Event = new SpellEvent(this); m_caster->m_Events.AddEvent(Event, m_caster->m_Events.CalculateTime(1)); //Prevent casting at cast another spell (ServerSide check) if (!m_IsTriggeredSpell && m_caster->IsNonMeleeSpellCasted(false, true, true)) { if (!m_originalCasterGUID.IsGameObject()) { SendCastResult(SPELL_FAILED_SPELL_IN_PROGRESS); finish(false); return SPELL_FAILED_SPELL_IN_PROGRESS; } } if (sObjectMgr.IsSpellDisabled(m_spellInfo->Id)) { SendCastResult(SPELL_FAILED_SPELL_UNAVAILABLE); finish(false); return SPELL_FAILED_SPELL_UNAVAILABLE; } // Fill cost data m_powerCost = CalculatePowerCost(m_spellInfo, m_caster, this, m_CastItem); if (Player* pPlayerCaster = m_caster->ToPlayer()) if (pPlayerCaster->HasOption(PLAYER_CHEAT_NO_POWER)) m_powerCost = 0; if (!IsChannelingVisual()) { SpellCastResult result = CheckCast(true); Unit* pTarget = m_targets.getUnitTarget(); if (result != SPELL_CAST_OK || (IsAutoRepeat() && m_caster == pTarget)) { if (!IsAutoRepeat() || !IsAcceptableAutorepeatError(result)) { DEBUG_FILTER_LOG(LOG_FILTER_SPELL_CAST, "Spell %u failed for reason 0x%x (target %u)", m_spellInfo->Id, result, pTarget ? pTarget->GetGUIDLow() : 0); if (triggeredByAura && !triggeredByAura->GetHolder()->IsPassive()) { SendChannelUpdate(0); triggeredByAura->GetHolder()->SetAuraDuration(0); } SendCastResult(result); //SendInterrupted(0); finish(false); return result; } } } // Roll chance to cast from script (must be after cast checks, this is why its here) if (chance) { if (chance <= urand(0, 100)) { finish(false); return SPELL_FAILED_TRY_AGAIN; } } // Prepare data for triggers prepareDataForTriggerSystem(); // calculate cast time (calculated after first CheckCast check to prevent charge counting for first CheckCast fail) m_casttime = GetSpellCastTime(m_spellInfo, this); if (Player* pPlayerCaster = m_caster->ToPlayer()) if (pPlayerCaster->HasOption(PLAYER_CHEAT_NO_CAST_TIME)) m_casttime = 0; m_duration = CalculateSpellDuration(m_spellInfo, m_caster); // set timer base at cast time ReSetTimer(); // If timer = 0, it's an instant cast spell and will be casted on the next tick. // Cast completion will remove all any stealth/invis auras if (m_timer) { // World of Warcraft Client Patch 1.10.0 (2006-03-28) // - Stealth and Invisibility effects will now be canceled at the // beginning of an action(spellcast, ability use etc...), rather than // at the completion of the action. #if SUPPORTED_CLIENT_BUILD > CLIENT_BUILD_1_9_4 RemoveStealthAuras(); #endif // If using a game object we need to remove any remaining invis auras. Should only // ever be Gnomish Cloaking Device, since it's a special case and not removed on // opcode receive if (m_caster->IsPlayer() && m_targets.getGOTarget()) m_caster->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_ON_CAST_SPELL); } OnSpellLaunch(); bool channeled = m_channeled; // [Nostalrius] Stop pets casting channeled spells ! (succubus seduce ...) if (m_timer && channeled) if (m_caster->IsPet()) m_caster->StopMoving(); // This is used so that creatures face the target on which they are casting if (m_setCreatureTarget = (m_caster->IsCreature() && (channeled || (!m_IsTriggeredSpell && m_timer)) && m_targets.getUnitTarget() && IsExplicitlySelectedUnitTarget(m_spellInfo->EffectImplicitTargetA[0]))) static_cast(m_caster)->SetCastingTarget(m_targets.getUnitTarget()); // add non-triggered (with cast time and without) if (!m_IsTriggeredSpell || channeled) m_caster->SetCurrentCastedSpell(this); if (!m_IsTriggeredSpell) { // will show cast bar SendSpellStart(); TriggerGlobalCooldown(); // Cast on self -> execute NOW //if (!m_timer && m_caster->IsPlayer() && !m_targets.m_targetMask && !IsAreaOfEffectSpell(m_spellInfo)) // cast(true); } // execute triggered without cast time explicitly in call point else if (m_timer == 0) cast(true); // else triggered with cast time will execute execute at next tick or later // without adding to cast type slot // will not show cast bar but will show effects at casting time etc } catch (std::runtime_error &e) { sLog.outInfo("[Spell/Crash] 'prepare()' [%u:%s:%u:{%f:%f:%f}]", m_spellInfo->Id, m_caster->GetName(), m_caster->GetGUIDLow(), m_castPositionX, m_castPositionY, m_castPositionZ); sLog.outInfo(e.what()); finish(false); return SPELL_FAILED_UNKNOWN; } return SPELL_CAST_OK; } void Spell::cancel() { if (m_spellState == SPELL_STATE_FINISHED) return; // channeled spells don't display interrupted message even if they are interrupted, possible other cases with no "Interrupted" message bool sendInterrupt = m_channeled && m_spellState != SPELL_STATE_PREPARING ? false : true; m_autoRepeat = false; switch (m_spellState) { case SPELL_STATE_PREPARING: CancelGlobalCooldown(); if (m_caster->GetTypeId() == TYPEID_PLAYER) m_caster->ToPlayer()->RestoreSpellMods(this); //(no break) case SPELL_STATE_DELAYED: { SendInterrupted(0); if (sendInterrupt && !GetDelayStart()) SendCastResult(SPELL_FAILED_INTERRUPTED); } break; case SPELL_STATE_CASTING: { for (TargetList::const_iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) { if (ihit->deleted) continue; if (ihit->missCondition == SPELL_MISS_NONE) { Unit* unit = m_caster->GetObjectGuid() == (*ihit).targetGUID ? m_caster : ObjectAccessor::GetUnit(*m_caster, ihit->targetGUID); if (unit && unit->isAlive()) unit->RemoveAurasByCasterSpell(m_spellInfo->Id, m_caster->GetObjectGuid()); } } SendChannelUpdate(0); SendInterrupted(0); if (m_caster->GetTypeId() == TYPEID_PLAYER) { // spell is canceled-take mods and clear list m_caster->ToPlayer()->RemoveSpellMods(this); // summoning rituals, if a user has cancelled inform the go // Don't use m_targets.getGOTarget(), as it may be a dangling pointer since we don't // update the target pointers here (only want to check a single target pointer). We // can use it as a speed-up to see if this spell had a GO target at some point though if (m_targets.getGOTarget()) { if (GameObject *go = m_caster->GetMap()->GetGameObject(m_targets.getGOTargetGuid())) { if (go->GetGoType() == GAMEOBJECT_TYPE_SUMMONING_RITUAL && go->getLootState() != GO_JUST_DEACTIVATED && go->HasUniqueUser(m_caster->ToPlayer())) { go->RemoveUniqueUse(m_caster->ToPlayer()); } } } } if (sendInterrupt) SendCastResult(SPELL_FAILED_INTERRUPTED); } break; default: { } break; } finish(false); m_caster->RemoveDynObject(m_spellInfo->Id); m_caster->RemoveGameObject(m_spellInfo->Id, true); } void Spell::cast(bool skipCheck) { if (m_spellInfo->Id <= 0 || m_spellInfo->Id > MAX_SPELL_ID) return; SpellEntry const* spellInfo = sSpellMgr.GetSpellEntry(m_spellInfo->Id); if (!spellInfo) return; SetExecutedCurrently(true); if (!m_caster->CheckAndIncreaseCastCounter()) { if (m_triggeredByAuraSpell) sLog.outError("Spell %u triggered by aura spell %u too deep in cast chain for cast. Cast not allowed for prevent overflow stack crash.", m_spellInfo->Id, m_triggeredByAuraSpell->Id); else sLog.outError("Spell %u too deep in cast chain for cast. Cast not allowed for prevent overflow stack crash.", m_spellInfo->Id); SendInterrupted(2); SendCastResult(SPELL_FAILED_ERROR); finish(false); SetExecutedCurrently(false); return; } // update pointers base at GUIDs to prevent access to already nonexistent object UpdatePointers(); // cancel at lost main target unit if (!m_targets.getUnitTarget() && m_targets.getUnitTargetGuid() && m_targets.getUnitTargetGuid() != m_caster->GetObjectGuid()) { SendInterrupted(2); cancel(); m_caster->DecreaseCastCounter(); SetExecutedCurrently(false); return; } // Ivina : Added the case when caster is charmed and not controlled. if ((m_caster->GetTypeId() != TYPEID_PLAYER) || ((m_caster->GetCharmerGuid()) && (!m_caster->hasUnitState(UNIT_STAT_POSSESSED)))) { if (m_targets.getUnitTarget() && m_targets.getUnitTarget() != m_caster) m_caster->SetInFront(m_targets.getUnitTarget()); } SpellCastResult castResult = SPELL_CAST_OK; if (!skipCheck && !IsChannelingVisual()) { // Nostalrius - compute power cost once again at cast finished // (in case of mana reduction buff proc while casting) m_powerCost = CalculatePowerCost(m_spellInfo, m_caster, this, m_CastItem); castResult = CheckPower(); if (castResult != SPELL_CAST_OK) { SendInterrupted(2); SendCastResult(castResult); //restore spell mods if (m_caster->GetTypeId() == TYPEID_PLAYER) m_caster->ToPlayer()->RestoreSpellMods(this); finish(false); m_caster->DecreaseCastCounter(); SetExecutedCurrently(false); return; } // triggered cast called from Spell::prepare where it was already checked castResult = CheckCast(false); if (castResult != SPELL_CAST_OK) { SendInterrupted(2); SendCastResult(castResult); finish(false); m_caster->DecreaseCastCounter(); SetExecutedCurrently(false); //restore spell mods if (m_caster->GetTypeId() == TYPEID_PLAYER) m_caster->ToPlayer()->RestoreSpellMods(this); return; } } m_caster->SetLastCastedSpell(m_spellInfo->Id, m_isClientStarted); // different triggred (for caster) and precast (casted before apply effect to target) cases switch (m_spellInfo->SpellFamilyName) { case SPELLFAMILY_GENERIC: { // Bandages if (m_spellInfo->Mechanic == MECHANIC_BANDAGE) AddPrecastSpell(11196); // Recently Bandaged // Divine Shield, Divine Protection (Blessing of Protection in paladin switch case) else if (m_spellInfo->Mechanic == MECHANIC_INVULNERABILITY) AddPrecastSpell(25771); // Forbearance switch (m_spellInfo->Id) { case 26374: // Elune's Candle { static const uint32 OmenSpells[] = { 26622, 26623, 26624, 26625, 26649 }; uint32 spell = 26636; Unit* target = m_targets.getUnitTarget(); if (target && target->ToUnit()) { if (target->GetEntry() == 15467) spell = OmenSpells[urand(0, 4)]; if (target->GetEntry() == 15466) spell = 26624; } m_caster->CastSpell(target, spell, true); } break; } break; } case SPELLFAMILY_WARRIOR: break; case SPELLFAMILY_PRIEST: { // Power Word: Shield // Nostalrius : Ivina : removed 27779 from cases = priest T0/T0.5 shield proc. if (m_spellInfo->IsFitToFamily() && (m_spellInfo->Id != 27779)) AddPrecastSpell(6788); // Weakened Soul switch (m_spellInfo->Id) { case 15237: AddTriggeredSpell(23455); break;// Holy Nova, rank 1 case 15430: AddTriggeredSpell(23458); break;// Holy Nova, rank 2 case 15431: AddTriggeredSpell(23459); break;// Holy Nova, rank 3 case 27799: AddTriggeredSpell(27803); break;// Holy Nova, rank 4 case 27800: AddTriggeredSpell(27804); break;// Holy Nova, rank 5 case 27801: AddTriggeredSpell(27805); break;// Holy Nova, rank 6 default: break; } break; } case SPELLFAMILY_PALADIN: { // Blessing of Protection (Divine Shield, Divine Protection in generic switch case) if (m_spellInfo->Mechanic == MECHANIC_INVULNERABILITY && m_spellInfo->Id != 25771) AddPrecastSpell(25771); // Forbearance break; } default: break; } // traded items have trade slot instead of guid in m_itemTargetGUID // set to real guid to be sent later to the client m_targets.updateTradeSlotItem(); FillTargetMap(); if (m_channeled) HandleAddTargetTriggerAuras(); if (m_spellState == SPELL_STATE_FINISHED) // stop cast if spell marked as finish somewhere in FillTargetMap { m_caster->DecreaseCastCounter(); SetExecutedCurrently(false); if (m_caster->GetTypeId() == TYPEID_PLAYER) m_caster->ToPlayer()->RestoreSpellMods(this); return; } // CAST SPELL // Remove any remaining invis auras on cast completion, should only be gnomish cloaking device if (!m_IsTriggeredSpell && !m_spellInfo->HasAttribute(SPELL_ATTR_EX_NOT_BREAK_STEALTH)) m_caster->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_ON_CAST_SPELL); SendSpellCooldown(); TakePower(); TakeReagents(); // we must remove reagents before HandleEffects to allow place crafted item in same slot TakeAmmo(); SendCastResult(castResult); InitializeDamageMultipliers(); // AoE case. Trigger spells for caster now. if (m_procAttacker & PROC_FLAG_SUCCESSFUL_AOE && m_canTrigger) { uint32 procAttacker = 0; // Blizzard case. Should trigger at launch for clearcast. if (m_spellInfo->IsFitToFamily()) procAttacker = m_procAttacker; else procAttacker = (m_procAttacker & PROC_FLAG_ON_TRAP_ACTIVATION); uint32 procAttackerFlags = procAttacker; if (!IsTriggered()) { procAttackerFlags |= (PROC_FLAG_SUCCESSFUL_SPELL_CAST | PROC_FLAG_SUCCESSFUL_AOE); if (m_powerCost > 0 && m_spellInfo->powerType == POWER_MANA) procAttackerFlags |= PROC_FLAG_SUCCESSFUL_MANA_SPELL_CAST; } m_caster->ProcDamageAndSpell(nullptr, procAttackerFlags, PROC_FLAG_NONE, PROC_EX_NORMAL_HIT, 1, m_attackType, m_spellInfo, this); } // Shaman totems. Trigger spell cast procs, but not others if (IsTotemSummonSpell(m_spellInfo) && m_canTrigger) { uint32 procAttackerFlags = PROC_FLAG_SUCCESSFUL_SPELL_CAST | PROC_FLAG_SUCCESSFUL_MANA_SPELL_CAST; m_caster->ProcDamageAndSpell(m_caster, procAttackerFlags, PROC_FLAG_NONE, PROC_EX_NORMAL_HIT, 1, m_attackType, m_spellInfo, this); } // Okay, everything is prepared. Now we need to distinguish between immediate and evented delayed spells if (m_delayed) { SendSpellGo(); // we must send smsg_spell_go packet before m_castItem delete in TakeCastItem()... // Remove used for cast item if need (it can be already NULL after TakeReagents call // in case delayed spell remove item at cast delay start TakeCastItem(); // fill initial spell damage from caster for delayed casted spells for (TargetList::iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) HandleDelayedSpellLaunch(&(*ihit)); // Okay, maps created, now prepare flags m_immediateHandled = false; m_spellState = SPELL_STATE_DELAYED; SetDelayStart(0); } else { // Immediate spell, no big deal try { handle_immediate(); } catch (std::runtime_error &e) { sLog.outInfo("[Spell/Crash] 'handle_immediate()' [%u:%s:%u:%u]", m_spellInfo->Id, m_caster->GetName(), m_caster->GetGUIDLow(), m_caster->GetMapId()); sLog.outInfo(e.what()); SetExecutedCurrently(false); return; } } if (IsMeleeAttackResetSpell() && !(m_spellInfo->AttributesEx2 & SPELL_ATTR_EX2_NOT_RESET_AUTO_ACTIONS)) { m_caster->resetAttackTimer(BASE_ATTACK); if (m_caster->haveOffhandWeapon()) m_caster->resetAttackTimer(OFF_ATTACK); } m_caster->DecreaseCastCounter(); SetExecutedCurrently(false); } void Spell::handle_immediate() { bool sendGoBefore = true; if (IsSpellHaveEffect(m_spellInfo, SPELL_EFFECT_INSTAKILL) || IsSpellHaveEffect(m_spellInfo, SPELL_EFFECT_SPAWN)) sendGoBefore = false; if (sendGoBefore) SendSpellGo(); // process immediate effects (items, ground, etc.) also initialize some variables _handle_immediate_phase(); // start channeling if applicable (after _handle_immediate_phase for get persistent effect dynamic object for channel target if (m_channeled && m_duration) { m_spellState = SPELL_STATE_CASTING; SendChannelStart(m_duration); if (m_caster->GetTypeId() == TYPEID_PLAYER) m_caster->ToPlayer()->RemoveSpellMods(this); } m_targetNum = 0; for (auto ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) { if (m_destroyed == true || ihit == m_UniqueTargetInfo.end() || m_UniqueTargetInfo.size() == 0) break; if (ihit->deleted == true) continue; ++m_targetNum; DoAllEffectOnTarget(&(*ihit)); // a channeled spell could be interrupted already because the aura on target // was diminished to duration=0 see Spell::DoSpellHitOnUnit if (IsChanneledSpell(m_spellInfo) && m_spellState != SPELL_STATE_CASTING) return; } for (auto ihit = m_UniqueGOTargetInfo.begin(); ihit != m_UniqueGOTargetInfo.end(); ++ihit) { if (m_destroyed == true || ihit == m_UniqueGOTargetInfo.end() || m_UniqueGOTargetInfo.size() == 0) break; if (ihit->deleted == true) continue; DoAllEffectOnTarget(&(*ihit)); } // Effets sur le corps. if (m_targets.getCorpseTargetGuid()) if (corpseTarget = m_caster->GetMap()->GetCorpse(m_targets.getCorpseTargetGuid())) for (int i = 0; i < 3; ++i) HandleEffects(nullptr, nullptr, nullptr, SpellEffectIndex(i)); // spell is finished, perform some last features of the spell here _handle_finish_phase(); SendSpellCooldown(); // Nostalrius: Send spell_go once effects are done (fix visual bug with EFFECT_INSTAKILL for example: 11504) if (!sendGoBefore) SendSpellGo(); // we must send smsg_spell_go packet before m_castItem delete in TakeCastItem()... // Remove used for cast item if need (it can be already NULL after TakeReagents call TakeCastItem(); if (m_spellState != SPELL_STATE_CASTING) finish(true); // successfully finish spell cast (not last in case autorepeat or channel spell) } uint64 Spell::handle_delayed(uint64 t_offset) { uint64 next_time = 0; if (!m_immediateHandled) { _handle_immediate_phase(); m_immediateHandled = true; } m_targetNum = 0; // now recheck units targeting correctness (need before any effects apply to prevent adding immunity at first effect not allow apply second spell effect and similar cases) for (auto ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) { if (!ihit->processed) { if (ihit->timeDelay <= t_offset) { ++m_targetNum; CheckAtDelay(&(*ihit)); DoAllEffectOnTarget(&(*ihit)); } else if (next_time == 0 || ihit->timeDelay < next_time) next_time = ihit->timeDelay; } } // now recheck gameobject targeting correctness for (auto ighit = m_UniqueGOTargetInfo.begin(); ighit != m_UniqueGOTargetInfo.end(); ++ighit) { if (!ighit->processed) { if (ighit->timeDelay <= t_offset) DoAllEffectOnTarget(&(*ighit)); else if (next_time == 0 || ighit->timeDelay < next_time) next_time = ighit->timeDelay; } } // All targets passed - need finish phase if (next_time == 0) { // spell is finished, perform some last features of the spell here _handle_finish_phase(); finish(true); // successfully finish spell cast // return zero, spell is finished now return 0; } else { // spell is unfinished, return next execution time return next_time; } } void Spell::_handle_immediate_phase() { // handle some immediate features of the spell here HandleThreatSpells(); m_needSpellLog = IsNeedSendToClient(); for (int j = 0; j < MAX_EFFECT_INDEX; ++j) { if (m_spellInfo->Effect[j] == 0) continue; // apply Send Event effect to ground in case empty target lists if (m_spellInfo->Effect[j] == SPELL_EFFECT_SEND_EVENT && !HaveTargetsForEffect(SpellEffectIndex(j))) { HandleEffects(nullptr, nullptr, nullptr, SpellEffectIndex(j)); continue; } // Don't do spell log, if is school damage spell if (m_spellInfo->Effect[j] == SPELL_EFFECT_SCHOOL_DAMAGE || m_spellInfo->Effect[j] == 0) m_needSpellLog = false; } // initialize Diminishing Returns Data m_diminishLevel = DIMINISHING_LEVEL_1; m_diminishGroup = DIMINISHING_NONE; // process items for (auto ihit = m_UniqueItemInfo.begin(); ihit != m_UniqueItemInfo.end(); ++ihit) DoAllEffectOnTarget(&(*ihit)); // process ground for (int j = 0; j < MAX_EFFECT_INDEX; ++j) { // persistent area auras target only the ground if (m_spellInfo->Effect[j] == SPELL_EFFECT_PERSISTENT_AREA_AURA) HandleEffects(nullptr, nullptr, nullptr, SpellEffectIndex(j)); } } void Spell::_handle_finish_phase() { // spell log if (m_needSpellLog) SendLogExecute(); } void Spell::SendSpellCooldown() { // (1) have infinity cooldown but set at aura apply, (2) passive cooldown at triggering if (m_spellInfo->Attributes & (SPELL_ATTR_DISABLED_WHILE_ACTIVE | SPELL_ATTR_PASSIVE)) return; if (m_caster->ToPlayer() && m_caster->ToPlayer()->HasOption(PLAYER_CHEAT_NO_COOLDOWN)) return; m_caster->AddSpellAndCategoryCooldowns(m_spellInfo, m_CastItem ? m_CastItem->GetEntry() : 0, this); } void Spell::update(uint32 difftime) { // update pointers based at it's GUIDs UpdatePointers(); if (m_targets.getUnitTargetGuid() && !m_targets.getUnitTarget()) { cancel(); return; } // check if the player caster has moved before the spell finished float nowPosX, nowPosY, nowPosZ, nowO; if (m_caster->GetTransport()) { nowPosX = m_caster->GetTransOffsetX(); nowPosY = m_caster->GetTransOffsetY(); nowPosZ = m_caster->GetTransOffsetZ(); nowO = m_caster->GetTransOffsetO(); } else { nowPosX = m_caster->GetPositionX(); nowPosY = m_caster->GetPositionY(); nowPosZ = m_caster->GetPositionZ(); nowO = m_caster->GetOrientation(); } if ((m_caster->GetTypeId() == TYPEID_PLAYER && m_timer != 0) && (fabs(m_castPositionX - nowPosX) > 0.5f || fabs(m_castPositionY - nowPosY) > 0.5f || fabs(m_castPositionZ - nowPosZ) > 0.5f) && (m_spellInfo->Effect[EFFECT_INDEX_0] != SPELL_EFFECT_STUCK || !((Player*)m_caster)->m_movementInfo.HasMovementFlag(MOVEFLAG_FALLINGFAR)) && ((m_spellInfo->Id != 24322) && (m_spellInfo->Id != 24323))) { // always cancel for channeled spells if (m_spellState == SPELL_STATE_CASTING) cancel(); // don't cancel for melee, autorepeat, triggered and instant spells else if (!IsNextMeleeSwingSpell() && !IsAutoRepeat() && !m_IsTriggeredSpell && (m_spellInfo->InterruptFlags & SPELL_INTERRUPT_FLAG_MOVEMENT)) cancel(); } // summoning ritual if (GameObject* pGo = m_targets.getGOTarget()) { if (GameObjectInfo const* pInfo = pGo->GetGOInfo()) // triggered spell if (pGo->GetGoType() == GAMEOBJECT_TYPE_SUMMONING_RITUAL && m_spellInfo->Id == pInfo->summoningRitual.spellId && // too many helpers cancelled (pGo->GetUniqueUseCount() < pInfo->summoningRitual.reqParticipants|| // the warlock cancelled !pInfo->summoningRitual.ritualPersistent && !pGo->GetOwner())) { cancel(); pGo->SetGoState(GO_STATE_READY); if (!pInfo->summoningRitual.ritualPersistent) pGo->Delete(); } } // visual spell and deleted ritual object else if (IsChannelingVisual()) cancel(); switch (m_spellState) { case SPELL_STATE_PREPARING: { if (m_timer) { // check for incapacitating states if (m_caster->GetTypeId() == TYPEID_PLAYER) { if (m_caster->hasUnitState(UNIT_STAT_CAN_NOT_REACT)) cancel(); } if (difftime >= m_timer) m_timer = 0; else m_timer -= difftime; } if (m_timer == 0 && !IsNextMeleeSwingSpell() && !IsAutoRepeat()) { RemoveStealthAuras(); cast(); } } break; case SPELL_STATE_CASTING: { if (m_timer > 0) { if (m_caster->GetTypeId() == TYPEID_PLAYER && (m_spellInfo->Id != 24322) && (m_spellInfo->Id != 24323)) { // check if player has jumped before the channeling finished if (((Player*)m_caster)->m_movementInfo.HasMovementFlag(MOVEFLAG_JUMPING)) cancel(); // check for incapacitating player states if (m_caster->hasUnitState(UNIT_STAT_CAN_NOT_REACT)) cancel(); // check if player has turned if flag is set if (m_spellInfo->ChannelInterruptFlags & CHANNEL_FLAG_TURNING && m_castOrientation != nowO) cancel(); } else if (m_caster->GetTypeId() == TYPEID_UNIT && ((Creature*)m_caster)->IsPet()) { // for succubus case if (m_caster->hasUnitState(UNIT_STAT_CAN_NOT_REACT)) cancel(); } // check if there are alive targets left if (!HasValidUnitPresentInTargetList()) { SendChannelUpdate(0); finish(); } // Must update target holder since the target will not update it. Used to keep it in sync // If the cast is interrupted mid-iteration (i.e. unit dies), break // Note that we cannot ensure that only 1 aura holder is removed from the list at a time // during iteration, since some creature scripts force all summoned adds to despawn on // death, which means that multiple units will have their auras removed at once. So // we maintain an iter that points to the next holder and increment it on removals m_channeledUpdateIterator = m_channeledHolders.begin(); SpellAuraHolderList::iterator curr; while (m_channeledUpdateIterator != m_channeledHolders.end()) { // Store current and increment, since m_channeledUpdateIterator // may be changed externally but we still need a way to step in // this loop curr = m_channeledUpdateIterator; ++m_channeledUpdateIterator; SpellAuraHolder *holder = *curr; // Holder deleted before updating, but not removed from list. Clear usage // and remove if (holder->IsDeleted()) { Unit *target = m_targets.getUnitTarget(); sLog.outError("[Spell] - Channeled update still maintains ref to deleted holder, caster: %s. Target: %s", m_caster->GetGuidStr().c_str(), target ? target->GetGuidStr().c_str() : ""); // TODO: Is this a leak if we don't delete it here? Unit probably removed from world holder->SetInUse(false); m_channeledHolders.erase(curr); continue; } holder->UpdateHolder(difftime); // Spell cast was interrupted on holder update. Unit likely died, targetted buff was // dispelled, or unit ran out of range. Return since we cleaned up in // SendChannelUpdate(0) on cancel. Similarly, if AoE and unit died the holder // was already removed. if (m_spellState == SPELL_STATE_FINISHED) return; // We can handle typical remove modes here AuraRemoveMode remove = holder->GetRemoveMode(); if ((holder->GetAuraDuration() == 0 && !holder->IsDeleted()) || (holder->IsDeleted() && (remove == AURA_REMOVE_BY_RANGE || remove == AURA_REMOVE_BY_GROUP))) { holder->SetInUse(false); m_channeledHolders.erase(curr); } } if (difftime >= m_timer) m_timer = 0; else m_timer -= difftime; } if (m_timer == 0) { SendChannelUpdate(0); // channeled spell processed independently for quest targeting // cast at creature (or GO) quest objectives update at successful cast channel finished // ignore autorepeat/melee casts for speed (not exist quest for spells (hm... ) if (!IsAutoRepeat() && !IsNextMeleeSwingSpell()) { if (Player* p = m_caster->GetCharmerOrOwnerPlayerOrPlayerItself()) { for (TargetList::const_iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) { if (ihit->deleted) continue; TargetInfo const& target = *ihit; if (!target.targetGUID.IsCreature()) continue; Unit* unit = m_caster->GetObjectGuid() == target.targetGUID ? m_caster : ObjectAccessor::GetUnit(*m_caster, target.targetGUID); if (unit == nullptr) continue; p->RewardPlayerAndGroupAtCast(unit, m_spellInfo->Id); } for (GOTargetList::const_iterator ihit = m_UniqueGOTargetInfo.begin(); ihit != m_UniqueGOTargetInfo.end(); ++ihit) { if (ihit->deleted) continue; GOTargetInfo const& target = *ihit; GameObject* go = m_caster->GetMap()->GetGameObject(target.targetGUID); if (!go) continue; p->RewardPlayerAndGroupAtCast(go, m_spellInfo->Id); } } } finish(); } } break; default: { } break; } } void Spell::HandleAddTargetTriggerAuras() { // handle SPELL_AURA_ADD_TARGET_TRIGGER auras auto const& targetTriggers = m_caster->GetAurasByType(SPELL_AURA_ADD_TARGET_TRIGGER); for (auto i = targetTriggers.begin(); i != targetTriggers.end(); ++i) { if (!(*i)->isAffectedOnSpell(m_spellInfo)) continue; for (TargetList::const_iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) { if (ihit->deleted) continue; Unit* target = nullptr; if (ihit->missCondition == SPELL_MISS_NONE) target = m_caster->GetObjectGuid() == ihit->targetGUID ? m_caster : ObjectAccessor::GetUnit(*m_caster, ihit->targetGUID); else if (ihit->missCondition == SPELL_MISS_REFLECT && ihit->reflectResult == SPELL_MISS_NONE) target = m_caster; if (!target) continue; if (target && target->isAlive()) { SpellEntry const *auraSpellInfo = (*i)->GetSpellProto(); SpellEffectIndex auraSpellIdx = (*i)->GetEffIndex(); // Calculate chance at that moment (can be depend for example from combo points) int32 auraBasePoints = (*i)->GetBasePoints(); int32 chance = m_caster->CalculateSpellDamage(target, auraSpellInfo, auraSpellIdx, &auraBasePoints); if ((m_caster->IsPlayer() && m_caster->ToPlayer()->HasOption(PLAYER_CHEAT_ALWAYS_PROC)) || roll_chance_i(chance)) m_caster->CastSpell(target, auraSpellInfo->EffectTriggerSpell[auraSpellIdx], true, nullptr, (*i)); } } } } void Spell::finish(bool ok) { m_successCast = ok; if (!m_caster) return; if (m_spellState == SPELL_STATE_FINISHED) return; m_spellState = SPELL_STATE_FINISHED; // Clear the creature's casting target so it faces victim if (m_setCreatureTarget) static_cast(m_caster)->ClearCastingTarget(); if (!ok && m_caster->GetTypeId() == TYPEID_PLAYER && m_caster->getClass() == CLASS_WARLOCK) { // Nostalrius: unstun du pet si cast de l'invocation interrompu. if (m_caster->GetPetGuid()) if (Pet* pet = ((Player*)m_caster)->GetPet()) pet->RemoveAurasDueToSpell(29825); // Fix a client problem with ritual of doom, by itself it disables // the spell during cast and then the spell stays disabled // Ignore the spell when it's triggered (ritual helper) if (m_spellInfo->Id == 18540 && !m_IsTriggeredSpell && !m_caster->HasSpellCooldown(m_spellInfo->Id)) m_caster->ToPlayer()->SendClearCooldown(18540, m_caster); } // Restore pet movement after spell (succubus after seduce) if (CharmInfo* ci = m_caster->GetCharmInfo()) { ci->SetIsAtStay(false); ci->SetIsCommandFollow(false); ci->SetIsFollowing(false); ci->SetIsReturning(false); } // other code related only to successfully finished spells if (!ok) { CancelGlobalCooldown(); if (m_caster->GetTypeId() == TYPEID_PLAYER) m_caster->ToPlayer()->RestoreSpellMods(this); return; } if (!m_channeled) HandleAddTargetTriggerAuras(); // Heal caster for all health leech from all targets if (m_healthLeech) m_caster->DealHeal(m_caster, uint32(m_healthLeech), m_spellInfo); /*if (IsRangedAttackResetSpell()) m_caster->resetAttackTimer(RANGED_ATTACK);*/ // Clear combo at finish state if (m_caster->GetTypeId() == TYPEID_PLAYER && NeedsComboPoints(m_spellInfo)) { // Not drop combopoints if negative spell and if any miss on enemy exist bool needDrop = true; if (!IsPositiveSpell(m_spellInfo)) { for (TargetList::const_iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) { if (ihit->deleted) continue; if (ihit->missCondition != SPELL_MISS_NONE && ihit->targetGUID != m_caster->GetObjectGuid()) { needDrop = false; break; } } } if (needDrop) ((Player*)m_caster)->ClearComboPoints(); } // call triggered spell only at successful cast (after clear combo points -> for add some if need) if (!m_TriggerSpells.empty()) CastTriggerSpells(); // Gnomish Death Ray if (m_spellInfo->Id == 13278) { if (Unit* const pTarget = m_targets.getUnitTarget()) { SpellEntry const *DRLaunchEntry = sSpellMgr.GetSpellEntry(13279); int32 DRdamage = m_caster->CalculateSpellDamage(pTarget, DRLaunchEntry, EFFECT_INDEX_0, m_currentBasePoints, nullptr); m_caster->CastCustomSpell(pTarget, 13279, &DRdamage, nullptr, nullptr, true, m_CastItem); } } if (m_caster->GetTypeId() == TYPEID_PLAYER) { m_caster->ToPlayer()->RemoveSpellMods(this); // summoning ritual triggered spell successfull hit if (GameObject* pGo = m_targets.getGOTarget()) if (pGo->GetGoType() == GAMEOBJECT_TYPE_SUMMONING_RITUAL) if (m_spellInfo->Id == pGo->GetGOInfo()->summoningRitual.spellId) pGo->FinishRitual(); } // Stop Attack for some spells if (m_spellInfo->Attributes & SPELL_ATTR_STOP_ATTACK_TARGET) { m_caster->AttackStop(); m_caster->InterruptSpell(CURRENT_AUTOREPEAT_SPELL); } else if ((m_spellInfo->AttributesEx & SPELL_ATTR_EX_MELEE_COMBAT_START)) { // Pets should initiate melee combat on spell with this flag. (Growl) if (Pet* pPet = m_caster->ToPet()) if (pPet->AI() && pPet->GetCharmInfo()) if (Unit* const pTarget = m_targets.getUnitTarget()) { pPet->GetCharmInfo()->SetIsCommandAttack(true); pPet->AI()->AttackStart(pTarget); } } } void Spell::SendCastResult(SpellCastResult result) { if (m_caster->GetTypeId() != TYPEID_PLAYER) return; if (((Player*)m_caster)->GetSession()->PlayerLoading()) // don't send cast results at loading time return; if (result == SPELL_CAST_OK && m_IsTriggeredSpell) return; SendCastResult((Player*)m_caster, m_spellInfo, result); } void Spell::SendCastResult(Player* caster, SpellEntry const* spellInfo, SpellCastResult result) { WorldPacket data(SMSG_CAST_RESULT, (4 + 1 + 1)); data << uint32(spellInfo->Id); if (result != SPELL_CAST_OK) { data << uint8(2); // status = fail data << uint8(IsPassiveSpell(spellInfo) ? SPELL_FAILED_DONT_REPORT : result); // problem switch (result) { case SPELL_FAILED_REQUIRES_SPELL_FOCUS: data << uint32(spellInfo->RequiresSpellFocus); break; case SPELL_FAILED_REQUIRES_AREA: data << uint32(sSpellMgr.GetRequiredAreaForSpell(spellInfo->Id)); break; case SPELL_FAILED_EQUIPPED_ITEM_CLASS: data << uint32(spellInfo->EquippedItemClass); data << uint32(spellInfo->EquippedItemSubClassMask); data << uint32(spellInfo->EquippedItemInventoryTypeMask); break; default: break; } } else data << uint8(0); caster->GetSession()->SendPacket(&data); } void Spell::SendSpellStart() { if (!IsNeedSendToClient()) return; DEBUG_FILTER_LOG(LOG_FILTER_SPELL_CAST, "Sending SMSG_SPELL_START id=%u", m_spellInfo->Id); uint32 castFlags = CAST_FLAG_UNKNOWN2; if (IsRangedSpell()) castFlags |= CAST_FLAG_AMMO; // Youfie : Sandtrap : dans le combatlog il n'y a plus de debuff avant l'apparition du Sand trap // Seul truc pas Blizz-like : c'est le joueur qui caste le sort, et pas Kurinaxx ; faudra fix dans le script de Kurinaxx quand le core sera debug pour le supporter if (m_spellInfo->Id == 25648) castFlags = CAST_FLAG_HIDDEN_COMBATLOG; WorldPacket data(SMSG_SPELL_START, (8 + 8 + 4 + 2 + 4)); if (m_CastItem) #if SUPPORTED_CLIENT_BUILD > CLIENT_BUILD_1_8_4 data << m_CastItem->GetPackGUID(); else data << m_caster->GetPackGUID(); data << m_caster->GetPackGUID(); #else data << m_CastItem->GetGUID(); else data << m_caster->GetGUID(); data << m_caster->GetGUID(); #endif data << uint32(m_spellInfo->Id); // spellId data << uint16(castFlags); // cast flags data << uint32(m_timer); // delay? data << m_targets; if (castFlags & CAST_FLAG_AMMO) // projectile info WriteAmmoToPacket(&data); m_caster->SendObjectMessageToSet(&data, true); } void Spell::SendSpellGo(bool bSendToCaster) { // not send invisible spell casting if (!IsNeedSendToClient()) return; uint32 castFlags = CAST_FLAG_UNKNOWN9; if (IsRangedSpell()) castFlags |= CAST_FLAG_AMMO; // arrows/bullets visual WorldPacket data(SMSG_SPELL_GO, 53); // guess size #if SUPPORTED_CLIENT_BUILD > CLIENT_BUILD_1_8_4 if (m_CastItem) data << m_CastItem->GetPackGUID(); else data << m_caster->GetPackGUID(); #else if (m_CastItem) data << m_CastItem->GetGUID(); else data << m_caster->GetGUID(); #endif // (HACK) Don't display cast animation for Flametongue Weapon proc // TODO - figure out the rule for why some procs should or should not have cast animations // e.g. rogue poison proc should have animation #if SUPPORTED_CLIENT_BUILD > CLIENT_BUILD_1_8_4 if (m_spellInfo->Id == 10444) data << PackedGuid(); else data << m_caster->GetPackGUID(); #else if (m_spellInfo->Id == 10444) data << uint64(); else data << m_caster->GetGUID(); #endif data << uint32(m_spellInfo->Id); // spellId data << uint16(castFlags); // cast flags WriteSpellGoTargets(&data); data << m_targets; if (castFlags & CAST_FLAG_AMMO) // projectile info WriteAmmoToPacket(&data); m_caster->SendObjectMessageToSet(&data, bSendToCaster); } void Spell::WriteAmmoToPacket(WorldPacket* data) { uint32 ammoInventoryType = 0; uint32 ammoDisplayID = 0; if (m_caster->GetTypeId() == TYPEID_PLAYER) { Item *pItem = ((Player*)m_caster)->GetWeaponForAttack(RANGED_ATTACK); if (pItem) { ammoInventoryType = pItem->GetProto()->InventoryType; if (ammoInventoryType == INVTYPE_THROWN) ammoDisplayID = pItem->GetProto()->DisplayInfoID; else { uint32 ammoID = ((Player*)m_caster)->GetUInt32Value(PLAYER_AMMO_ID); if (ammoID) { ItemPrototype const *pProto = ObjectMgr::GetItemPrototype(ammoID); if (pProto) { ammoDisplayID = pProto->DisplayInfoID; ammoInventoryType = pProto->InventoryType; } } } } } else { for (uint8 i = 0; i < MAX_VIRTUAL_ITEM_SLOT; ++i) { // see Creature::SetVirtualItem for structure data if (uint32 item_class = m_caster->GetByteValue(UNIT_VIRTUAL_ITEM_INFO + (i * 2) + 0, VIRTUAL_ITEM_INFO_0_OFFSET_CLASS)) { if (item_class == ITEM_CLASS_WEAPON) { switch (m_caster->GetByteValue(UNIT_VIRTUAL_ITEM_INFO + (i * 2) + 0, VIRTUAL_ITEM_INFO_0_OFFSET_SUBCLASS)) { case ITEM_SUBCLASS_WEAPON_THROWN: ammoDisplayID = m_caster->GetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_DISPLAY + i); ammoInventoryType = m_caster->GetByteValue(UNIT_VIRTUAL_ITEM_INFO + (i * 2) + 0, VIRTUAL_ITEM_INFO_0_OFFSET_INVENTORYTYPE); break; case ITEM_SUBCLASS_WEAPON_BOW: case ITEM_SUBCLASS_WEAPON_CROSSBOW: ammoDisplayID = 5996; // is this need fixing? ammoInventoryType = INVTYPE_AMMO; break; case ITEM_SUBCLASS_WEAPON_GUN: ammoDisplayID = 5998; // is this need fixing? ammoInventoryType = INVTYPE_AMMO; break; } if (ammoDisplayID) break; } } } } *data << uint32(ammoDisplayID); *data << uint32(ammoInventoryType); } void Spell::WriteSpellGoTargets(WorldPacket* data) { // This function also fill data for channeled spells: // m_needAliveTargetMask req for stop channelig if one target die for (TargetList::iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) { if ((*ihit).effectMask == 0) // No effect apply - all immuned add state // possibly SPELL_MISS_IMMUNE2 for this?? ihit->missCondition = SPELL_MISS_IMMUNE2; } uint32 hit = 0; size_t hitPos = data->wpos(); *data << (uint8)0; // placeholder for (TargetList::iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) { if (ihit->missCondition == SPELL_MISS_NONE) { *data << ihit->targetGUID; // in 1.12.1 expected all targets m_needAliveTargetMask |= ihit->effectMask; ++hit; } } for (GOTargetList::const_iterator ighit = m_UniqueGOTargetInfo.begin(); ighit != m_UniqueGOTargetInfo.end(); ++ighit) { *data << ighit->targetGUID; // Always hits ++hit; } uint32 miss = 0; size_t missPos = data->wpos(); *data << (uint8)0; // placeholder for (auto ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) { if (ihit->missCondition != SPELL_MISS_NONE) // Add only miss { *data << (ihit->targetGUID); *data << uint8(ihit->missCondition); if (ihit->missCondition == SPELL_MISS_REFLECT) *data << uint8(ihit->reflectResult); ++miss; } } // Reset m_needAliveTargetMask for non channeled spell if (!m_channeled) m_needAliveTargetMask = 0; data->put(hitPos, (uint8)hit); data->put(missPos, (uint8)miss); } void Spell::SendLogExecute() { WorldPacket data(SMSG_SPELLLOGEXECUTE, (8 + 4 + 4 + 4 + 4 + 8)); #if SUPPORTED_CLIENT_BUILD > CLIENT_BUILD_1_8_4 data << m_caster->GetPackGUID(); #else data << m_caster->GetGUID(); #endif data << uint32(m_spellInfo->Id); uint32 effectCount = 0; for (uint32 i = 0; i < MAX_EFFECT_INDEX; ++i) { if (!m_executeLogInfo[i].empty()) effectCount++; } if (!effectCount) return; data << uint32(effectCount); for (uint32 i = 0; i < MAX_EFFECT_INDEX; ++i) { if (m_executeLogInfo[i].empty()) continue; data << uint32(m_spellInfo->Effect[i]); data << uint32(m_executeLogInfo[i].size()); for (uint32 j = 0; j < m_executeLogInfo[i].size(); ++j) { ExecuteLogInfo info = m_executeLogInfo[i][j]; switch (m_spellInfo->Effect[i]) { case SPELL_EFFECT_POWER_DRAIN: data << info.targetGuid; data << info.powerDrain.amount; data << info.powerDrain.power; data << info.powerDrain.multiplier; break; case SPELL_EFFECT_HEAL: case SPELL_EFFECT_HEAL_MAX_HEALTH: data << info.targetGuid; data << info.heal.amount; data << info.heal.critical; break; case SPELL_EFFECT_ENERGIZE: data << info.targetGuid; data << info.energize.amount; data << info.energize.powerType; break; case SPELL_EFFECT_ADD_EXTRA_ATTACKS: data << info.targetGuid; data << info.extraAttacks.count; break; case SPELL_EFFECT_CREATE_ITEM: data << info.createItem.itemEntry; break; case SPELL_EFFECT_INTERRUPT_CAST: data << info.targetGuid; data << info.interruptCast.spellId; break; case SPELL_EFFECT_FEED_PET: data << info.feedPet.itemEntry; break; case SPELL_EFFECT_DURABILITY_DAMAGE: data << info.targetGuid; data << info.durabilityDamage.itemEntry; data << info.durabilityDamage.unk; break; case SPELL_EFFECT_INSTAKILL: case SPELL_EFFECT_RESURRECT: case SPELL_EFFECT_DISPEL: case SPELL_EFFECT_THREAT: case SPELL_EFFECT_DISTRACT: case SPELL_EFFECT_SANCTUARY: case SPELL_EFFECT_THREAT_ALL: case SPELL_EFFECT_DISPEL_MECHANIC: case SPELL_EFFECT_RESURRECT_NEW: case SPELL_EFFECT_ATTACK_ME: case SPELL_EFFECT_SKIN_PLAYER_CORPSE: case SPELL_EFFECT_MODIFY_THREAT_PERCENT: case SPELL_EFFECT_126: case SPELL_EFFECT_OPEN_LOCK: case SPELL_EFFECT_OPEN_LOCK_ITEM: case SPELL_EFFECT_DISMISS_PET: case SPELL_EFFECT_TRANS_DOOR: case SPELL_EFFECT_SUMMON: case SPELL_EFFECT_SUMMON_PET: case SPELL_EFFECT_SUMMON_WILD: case SPELL_EFFECT_SUMMON_GUARDIAN: case SPELL_EFFECT_SUMMON_TOTEM_SLOT1: case SPELL_EFFECT_SUMMON_TOTEM_SLOT2: case SPELL_EFFECT_SUMMON_TOTEM_SLOT3: case SPELL_EFFECT_SUMMON_TOTEM_SLOT4: case SPELL_EFFECT_SUMMON_POSSESSED: case SPELL_EFFECT_SUMMON_TOTEM: case SPELL_EFFECT_SUMMON_CRITTER: case SPELL_EFFECT_SUMMON_OBJECT_WILD: case SPELL_EFFECT_SUMMON_OBJECT_SLOT1: case SPELL_EFFECT_SUMMON_OBJECT_SLOT2: case SPELL_EFFECT_SUMMON_OBJECT_SLOT3: case SPELL_EFFECT_SUMMON_OBJECT_SLOT4: case SPELL_EFFECT_SUMMON_DEMON: data << info.targetGuid; break; default: return; } } } m_caster->SendMessageToSet(&data, true); } void Spell::SendInterrupted(uint8 result) { // Nostalrius : fix animation de cast a l'interruption d'un sort // Ce premier paquet ne sert apparement a rien ... // Ce second paquet informe les joueurs aux alentours que le sort a ete interrompu. WorldPacket data(SMSG_SPELL_FAILED_OTHER, (8 + 4)); data << m_caster->GetObjectGuid(); // Pareil que pour SMSG_SPELL_FAILURE data << m_spellInfo->Id; m_caster->SendObjectMessageToSet(&data, true); } void Spell::SendChannelUpdate(uint32 time) { if (!m_channeled || m_spellState == SPELL_STATE_FINISHED) return; if (!time) { // Reset farsight for some possessing auras of possessed summoned (as they might work with different aura types) if (m_spellInfo->Attributes & SPELL_ATTR_EX_FARSIGHT && m_caster->GetTypeId() == TYPEID_PLAYER && m_caster->GetCharmGuid() && !IsSpellHaveAura(m_spellInfo, SPELL_AURA_MOD_POSSESS) && !IsSpellHaveAura(m_spellInfo, SPELL_AURA_MOD_POSSESS_PET)) { Player* player = (Player*)m_caster; // These Auras are applied to self, so get the possessed first Unit* possessed = player->GetCharm(); player->SetCharm(nullptr); if (possessed) player->SetClientControl(possessed, 0); player->SetMover(nullptr); player->GetCamera().ResetView(); player->RemovePetActionBar(); if (possessed) { possessed->clearUnitState(UNIT_STAT_POSSESSED); possessed->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_POSSESSED); possessed->SetCharmerGuid(ObjectGuid()); // TODO - Requires more specials for target? // Some possessed might want to despawn? if (possessed->GetUInt32Value(UNIT_CREATED_BY_SPELL) == m_spellInfo->Id && possessed->GetTypeId() == TYPEID_UNIT) ((Creature*)possessed)->ForcedDespawn(); } } // Free channeled holders now so they can be safely removed, no handling for // external removal. Nothing else should be modifying this list during this iteration SpellAuraHolderList::iterator iter = m_channeledHolders.begin(); while (iter != m_channeledHolders.end()) { SpellAuraHolder *holder = *iter; holder->SetInUse(false); // Remove any left over auras on all targets at channel end if they // have not already been marked as deleted. Cleans up non-expired // aoe targets if (!holder->IsDeleted()) { if (Unit *unit = holder->GetTarget()) unit->RemoveSpellAuraHolder(holder, AURA_REMOVE_BY_CHANNEL); } iter = m_channeledHolders.erase(iter); } m_channeledUpdateIterator = m_channeledHolders.end(); // Remove single target auras on caster now if they expired m_caster->RemoveAurasByCasterSpell(m_spellInfo->Id, m_caster->GetObjectGuid(), AURA_REMOVE_BY_CHANNEL); ObjectGuid target_guid = m_caster->GetChannelObjectGuid(); if (target_guid != m_caster->GetObjectGuid() && target_guid.IsUnit()) { if (Unit* target = ObjectAccessor::GetUnit(*m_caster, target_guid)) { // Remove single target auras on target now if they expired target->RemoveAurasByCasterSpell(m_spellInfo->Id, m_caster->GetObjectGuid(), AURA_REMOVE_BY_CHANNEL); } } // Remove dynamic objects if they are still alive. Persistent channel objects // live until the cast has finished. Technically don't need to explicitly remove // but no harm m_caster->RemoveDynObject(m_spellInfo->Id); m_caster->RemoveGameObject(m_spellInfo->Id, true); } // Only modify/send values if we are the current channeled spell ! if (m_caster->GetCurrentSpell(CURRENT_CHANNELED_SPELL) && m_caster->GetCurrentSpell(CURRENT_CHANNELED_SPELL) != this) return; if (!time) { // Reset of channel values has to be done after a few delay. // Else, we have some visual bugs (arcane projectile, last tick) ChannelResetEvent* event = new ChannelResetEvent(m_caster); m_caster->m_Events.AddEvent(event, m_caster->m_Events.CalculateTime(0)); } else if (m_caster->GetTypeId() == TYPEID_PLAYER) { WorldPacket data(MSG_CHANNEL_UPDATE, 4); data << uint32(time); ((Player*)m_caster)->SendDirectMessage(&data); } } void Spell::SendChannelStart(uint32 duration) { WorldObject* target = nullptr; // select dynobject created by first effect if any if (m_spellInfo->Effect[EFFECT_INDEX_0] == SPELL_EFFECT_PERSISTENT_AREA_AURA) target = m_caster->GetDynObject(m_spellInfo->Id, EFFECT_INDEX_0); // select first not resisted target from target list for _0_ effect else if (!m_UniqueTargetInfo.empty()) { for (TargetList::const_iterator itr = m_UniqueTargetInfo.begin(); itr != m_UniqueTargetInfo.end(); ++itr) { if (itr->deleted) continue; if ((itr->effectMask & (1 << EFFECT_INDEX_0)) && itr->reflectResult == SPELL_MISS_NONE && itr->targetGUID != m_caster->GetObjectGuid()) { target = ObjectAccessor::GetUnit(*m_caster, itr->targetGUID); break; } } } else if (!m_UniqueGOTargetInfo.empty()) { for (GOTargetList::const_iterator itr = m_UniqueGOTargetInfo.begin(); itr != m_UniqueGOTargetInfo.end(); ++itr) { if (itr->deleted) continue; if (itr->effectMask & (1 << EFFECT_INDEX_0)) { target = m_caster->GetMap()->GetGameObject(itr->targetGUID); break; } } } if (m_caster->GetTypeId() == TYPEID_PLAYER) { WorldPacket data(MSG_CHANNEL_START, (4 + 4)); data << uint32(m_spellInfo->Id); data << uint32(duration); ((Player*)m_caster)->SendDirectMessage(&data); } m_timer = duration; if (target) m_caster->SetChannelObjectGuid(target->GetObjectGuid()); m_caster->SetUInt32Value(UNIT_CHANNEL_SPELL, m_spellInfo->Id); } void Spell::SendResurrectRequest(Player* target) { // Both players and NPCs can resurrect using spells - have a look at creature 28487 for example // However, the packet structure differs slightly const char* sentName = m_caster->GetTypeId() == TYPEID_PLAYER ? "" : m_caster->GetNameForLocaleIdx(target->GetSession()->GetSessionDbLocaleIndex()); WorldPacket data(SMSG_RESURRECT_REQUEST, (8 + 4 + strlen(sentName) + 1 + 1 + 1)); data << m_caster->GetObjectGuid(); data << uint32(strlen(sentName) + 1); data << sentName; data << uint8(0); data << uint8(m_caster->GetTypeId() == TYPEID_PLAYER ? 0 : 1); target->GetSession()->SendPacket(&data); } void Spell::SendPlaySpellVisual(uint32 SpellID) { if (m_caster->GetTypeId() != TYPEID_PLAYER) return; WorldPacket data(SMSG_PLAY_SPELL_VISUAL, 8 + 4); data << m_caster->GetObjectGuid(); data << uint32(SpellID); // spell visual id? ((Player*)m_caster)->GetSession()->SendPacket(&data); } void Spell::TakeCastItem() { if (!m_CastItem || m_caster->GetTypeId() != TYPEID_PLAYER) return; // not remove cast item at triggered spell (equipping, weapon damage, etc) if (m_IsTriggeredSpell && !(m_targets.m_targetMask & TARGET_FLAG_TRADE_ITEM)) return; ItemPrototype const *proto = m_CastItem->GetProto(); if (!proto) { // This code is to avoid a crash // I'm not sure, if this is really an error, but I guess every item needs a prototype sLog.outError("Cast item (%s) has no item prototype", m_CastItem->GetGuidStr().c_str()); return; } bool expendable = false; bool withoutCharges = false; for (int i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i) { if (proto->Spells[i].SpellId) { // item has limited charges if (proto->Spells[i].SpellCharges) { if (proto->Spells[i].SpellCharges < 0) expendable = true; int32 charges = m_CastItem->GetSpellCharges(i); // item has charges left if (charges) { (charges > 0) ? --charges : ++charges; // abs(charges) less at 1 after use if (proto->Stackable < 2) m_CastItem->SetSpellCharges(i, charges); m_CastItem->SetState(ITEM_CHANGED, (Player*)m_caster); } // all charges used withoutCharges = (charges == 0); } } } if (expendable && withoutCharges) { uint32 count = 1; Item* castItem = m_CastItem; // prevent crash at access to deleted m_targets.getItemTarget ClearCastItem(); // Destroying the item involved in a spell interrupts the spell. So delete the item once we cleared the spell cast item. ((Player*)m_caster)->DestroyItemCount(castItem, count, true); } } void Spell::TakePower() { if (m_CastItem || m_triggeredByAuraSpell || IsChannelingVisual()) return; // health as power used if (m_spellInfo->powerType == POWER_HEALTH) { m_caster->ModifyHealth(-(int32)m_powerCost); return; } if (m_spellInfo->powerType >= MAX_POWERS) { sLog.outError("Spell::TakePower: Unknown power type '%d'", m_spellInfo->powerType); return; } Powers powerType = Powers(m_spellInfo->powerType); m_caster->ModifyPower(powerType, -(int32)m_powerCost); // Set the five second timer if (powerType == POWER_MANA && m_powerCost > 0) m_caster->SetLastManaUse(); } void Spell::TakeReagents() { if (m_caster->GetTypeId() != TYPEID_PLAYER) return; if (IgnoreItemRequirements()) // reagents used in triggered spell removed by original spell or don't must be removed. return; Player* p_caster = (Player*)m_caster; for (uint32 x = 0; x < MAX_SPELL_REAGENTS; ++x) { if (m_spellInfo->Reagent[x] <= 0) continue; uint32 itemid = m_spellInfo->Reagent[x]; uint32 itemcount = m_spellInfo->ReagentCount[x]; // if CastItem is also spell reagent if (m_CastItem) { ItemPrototype const *proto = m_CastItem->GetProto(); if (proto && proto->ItemId == itemid) { for (int s = 0; s < MAX_ITEM_PROTO_SPELLS; ++s) { // CastItem will be used up and does not count as reagent int32 charges = m_CastItem->GetSpellCharges(s); if (proto->Spells[s].SpellCharges < 0 && abs(charges) < 2 && itemcount > 1) { ++itemcount; break; } } m_CastItem = nullptr; } } // if getItemTarget is also spell reagent if (m_targets.getItemTargetEntry() == itemid) m_targets.setItemTarget(nullptr); p_caster->DestroyItemCount(itemid, itemcount, true); } } void Spell::TakeAmmo() { // Some ranged attacks dont take any ammo switch (m_spellInfo->Id) { case 2094: // Blind case 13099: // Net-o-Matic case 13119: // Net-o-Matic case 23577: // Expose Weakness return; } if (m_attackType == RANGED_ATTACK && m_caster->GetTypeId() == TYPEID_PLAYER) { Item *pItem = ((Player*)m_caster)->GetWeaponForAttack(RANGED_ATTACK, true, false); // wands don't have ammo if (!pItem || pItem->GetProto()->SubClass == ITEM_SUBCLASS_WEAPON_WAND) { m_caster->SetLastAttackType(2); return; } if ((m_spellInfo->Id == 75) || (m_spellInfo->Id == 2764)) { m_caster->SetLastAttackType(2); } if (pItem->GetProto()->InventoryType == INVTYPE_THROWN) { if (pItem->GetMaxStackCount() == 1) { // decrease durability for non-stackable throw weapon ((Player*)m_caster)->DurabilityPointLossForEquipSlot(EQUIPMENT_SLOT_RANGED); } else { // decrease items amount for stackable throw weapon uint32 count = 1; ((Player*)m_caster)->DestroyItemCount(pItem, count, true); } } else if (uint32 ammo = ((Player*)m_caster)->GetUInt32Value(PLAYER_AMMO_ID)) ((Player*)m_caster)->DestroyItemCount(ammo, 1, true); } } void Spell::HandleThreatSpells() { if (m_UniqueTargetInfo.empty()) return; SpellThreatEntry const* threatEntry = sSpellMgr.GetSpellThreatEntry(m_spellInfo->Id); if (!threatEntry || (!threatEntry->threat && threatEntry->ap_bonus == 0.0f)) return; float threat = threatEntry->threat; if (threatEntry->ap_bonus != 0.0f) threat += threatEntry->ap_bonus * m_caster->GetTotalAttackPowerValue(GetWeaponAttackType(m_spellInfo)); bool positive = true; uint8 effectMask = 0; for (int i = 0; i < MAX_EFFECT_INDEX; ++i) if (m_spellInfo->Effect[i]) effectMask |= (1 << i); if (m_negativeEffectMask & effectMask) { // can only handle spells with clearly defined positive/negative effect, check at spell_threat loading probably not perfect // so abort when only some effects are negative. if ((m_negativeEffectMask & effectMask) != effectMask) { DEBUG_FILTER_LOG(LOG_FILTER_SPELL_CAST, "Spell %u, rank %u, is not clearly positive or negative, ignoring bonus threat", m_spellInfo->Id, sSpellMgr.GetSpellRank(m_spellInfo->Id)); return; } positive = false; } for (TargetList::const_iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) { if (ihit->missCondition != SPELL_MISS_NONE) continue; Unit* target = m_caster->GetObjectGuid() == ihit->targetGUID ? m_caster : ObjectAccessor::GetUnit(*m_caster, ihit->targetGUID); if (!target) continue; // positive spells distribute threat among all units that are in combat with target, like healing if (positive) target->getHostileRefManager().threatAssist(m_caster /*real_caster ??*/, threat, m_spellInfo); // for negative spells threat gets distributed among affected targets else { if (!target->CanHaveThreatList()) continue; target->AddThreat(m_caster, threat, false, GetSpellSchoolMask(m_spellInfo), m_spellInfo); } } DEBUG_FILTER_LOG(LOG_FILTER_SPELL_CAST, "Spell %u added an additional %f threat for %s %u target(s)", m_spellInfo->Id, threat, positive ? "assisting" : "harming", uint32(m_UniqueTargetInfo.size())); } void Spell::HandleEffects(Unit *pUnitTarget, Item *pItemTarget, GameObject *pGOTarget, SpellEffectIndex i, float DamageMultiplier) { // a summoning ritual visual has no effect if (IsChannelingVisual()) return; unitTarget = pUnitTarget; itemTarget = pItemTarget; gameObjTarget = pGOTarget; uint8 eff = m_spellInfo->Effect[i]; damage = int32(CalculateDamage(i, unitTarget) * DamageMultiplier); DEBUG_FILTER_LOG(LOG_FILTER_SPELL_CAST, "Spell %u Effect%d : %u Targets: %s, %s, %s", m_spellInfo->Id, i, eff, unitTarget ? unitTarget->GetGuidStr().c_str() : "-", itemTarget ? itemTarget->GetGuidStr().c_str() : "-", gameObjTarget ? gameObjTarget->GetGuidStr().c_str() : "-"); if (eff < TOTAL_SPELL_EFFECTS) (*this.*SpellEffects[eff])(i); else sLog.outError("WORLD: Spell %u has effect %d at index %u > TOTAL_SPELL_EFFECTS", m_spellInfo->Id, eff, i); } void Spell::AddTriggeredSpell(uint32 spellId) { SpellEntry const *spellInfo = sSpellMgr.GetSpellEntry(spellId); if (!spellInfo) { sLog.outError("Spell::AddTriggeredSpell: unknown spell id %u used as triggred spell for spell %u)", spellId, m_spellInfo->Id); return; } m_TriggerSpells.push_back(spellInfo); } void Spell::AddPrecastSpell(uint32 spellId) { SpellEntry const *spellInfo = sSpellMgr.GetSpellEntry(spellId); if (!spellInfo) { sLog.outError("Spell::AddPrecastSpell: unknown spell id %u used as pre-cast spell for spell %u)", spellId, m_spellInfo->Id); return; } m_preCastSpells.push_back(spellInfo); } void Spell::CastTriggerSpells() { for (SpellInfoList::const_iterator si = m_TriggerSpells.begin(); si != m_TriggerSpells.end(); ++si) { Spell* spell = new Spell(m_caster, (*si), true, m_originalCasterGUID); spell->prepare(m_targets); // use original spell original targets } } void Spell::CastPreCastSpells(Unit* target) { for (SpellInfoList::const_iterator si = m_preCastSpells.begin(); si != m_preCastSpells.end(); ++si) m_caster->CastSpell(target, (*si), true, m_CastItem); } void Spell::AddChanneledAuraHolder(SpellAuraHolder *holder) { if (!holder || !holder->IsChanneled()) return; // Set and hold in use until clean up to prevent any delete calls destroying // the object before we can handle it holder->SetInUse(true); m_channeledHolders.push_back(holder); } // Occurs when an aura should be removed from handling due to deletion or expiration void Spell::RemoveChanneledAuraHolder(SpellAuraHolder *holder, AuraRemoveMode mode) { // AURA_REMOVE_BY_CHANNEL comes from here, don't want to double erase // AURA_REMOVE_BY_RANGE and AURA_REMOVE_BY_GROUP are handled in Spell::update if (!holder || mode == AURA_REMOVE_BY_CHANNEL || mode == AURA_REMOVE_BY_GROUP || mode == AURA_REMOVE_BY_RANGE) return; SpellAuraHolderList::iterator iter = std::find(m_channeledHolders.begin(), m_channeledHolders.end(), holder); if (iter != m_channeledHolders.end()) { (*iter)->SetInUse(false); // If removing the aura currently being updated, increment the // update iter. If this is the last element, the update iterator // will be set to end() and Spell::Update will finish. If not, // it will seemlessly handle the case of multiple auras being // removed in a single update. // Micro opt - update iter in removal rather than increment if (iter == m_channeledUpdateIterator) m_channeledUpdateIterator = m_channeledHolders.erase(iter); else m_channeledHolders.erase(iter); } } SpellCastResult Spell::CheckCast(bool strict) { // Cheat du joueur ? if (m_caster->IsPlayer() && m_caster->ToPlayer()->HasOption(PLAYER_CHEAT_NO_CHECK_CAST)) return SPELL_CAST_OK; //sLog.outString("CheckCast de %u %s%s%s sur %s", // m_spellInfo->Id, strict ? "[strict]" : "", m_IsTriggeredSpell ? "[triggered]" : "", m_triggeredByAuraSpell ? "[triggeredByAura]" : "", m_targets.getUnitTargetGuid().GetString().c_str()); // Quel sort peut-on faire lancer a un mob que l'on CM ? if (m_caster->hasUnitState(UNIT_STAT_POSSESSED)) { if (m_spellInfo->Category == 21) // Enrager return SPELL_FAILED_NOT_READY; } // Fin Nostalrius // Prevent casting while sitting unless the spell allows it if (!m_IsTriggeredSpell && m_caster->IsSitState() && !(m_spellInfo->Attributes & SPELL_ATTR_CASTABLE_WHILE_SITTING)) return SPELL_FAILED_NOT_STANDING; /* Check cooldowns to prevent cheating (ignore passive spells, that client side visual only) If the cast is an item cast, check the spell proto on the item for the category cooldown to check, rather than this spell's category. This is due to bad categories in the default Spell DBC. */ uint32 spellCat = m_spellInfo->Category; if (m_CastItem) { // Find correct item category matching the current spell on item // used when item spells have custom categories due to wrong category // on spell ItemPrototype const* proto = m_CastItem->GetProto(); for (int i = 0; i < MAX_ITEM_PROTO_SPELLS; i++) { if (proto->Spells[i].SpellId == m_spellInfo->Id) { spellCat = proto->Spells[i].SpellCategory; break; } } } if (m_caster->GetTypeId() == TYPEID_PLAYER && !(m_spellInfo->Attributes & SPELL_ATTR_PASSIVE) && !m_IsTriggeredSpell && (m_caster->HasSpellCooldown(m_spellInfo->Id) || m_caster->HasSpellCategoryCooldown(spellCat))) { if (m_triggeredByAuraSpell || m_spellInfo->Attributes & SPELL_ATTR_DISABLED_WHILE_ACTIVE) return SPELL_FAILED_DONT_REPORT; else return SPELL_FAILED_NOT_READY; } // check death state to prevent cheating ("deathbug") if (!m_caster->isAlive() && !(m_spellInfo->Attributes & SPELL_ATTR_PASSIVE) && !((m_spellInfo->Attributes & SPELL_ATTR_CASTABLE_WHILE_DEAD) || (m_IsTriggeredSpell && !m_triggeredByAuraSpell))) { if (m_triggeredByAuraSpell) return SPELL_FAILED_DONT_REPORT; else return SPELL_FAILED_CASTER_DEAD; } // check global cooldown if (!m_IsTriggeredSpell) { if (strict && HasGlobalCooldown()) { // Activated spells will get stuck if we return SPELL_FAILED_NOT_READY during GCD if (m_spellInfo->Attributes & SPELL_ATTR_DISABLED_WHILE_ACTIVE) return SPELL_FAILED_DONT_REPORT; else return SPELL_FAILED_NOT_READY; } // only allow triggered spells if at an ended battleground if (m_caster->GetTypeId() == TYPEID_PLAYER) if (BattleGround * bg = ((Player*)m_caster)->GetBattleGround()) if (bg->GetStatus() == STATUS_WAIT_LEAVE) return SPELL_FAILED_DONT_REPORT; if (strict) { if (m_caster->isInCombat() && IsNonCombatSpell(m_spellInfo)) return SPELL_FAILED_AFFECTING_COMBAT; // only check at first call, Stealth auras are already removed at second call // for now, ignore triggered spells //if (strict) //{ // Cannot be used in this stance/form SpellCastResult shapeError = GetErrorAtShapeshiftedCast(m_spellInfo, m_caster->GetShapeshiftForm()); if (shapeError != SPELL_CAST_OK) return shapeError; if ((m_spellInfo->Attributes & SPELL_ATTR_ONLY_STEALTHED) && !(m_caster->HasStealthAura())) return SPELL_FAILED_ONLY_STEALTHED; } } if (m_caster->GetTypeId() == TYPEID_PLAYER && !((Player*)m_caster)->IsGameMaster() && sWorld.getConfig(CONFIG_BOOL_VMAP_INDOOR_CHECK) && VMAP::VMapFactory::createOrGetVMapManager()->isLineOfSightCalcEnabled()) { if (m_spellInfo->Attributes & SPELL_ATTR_OUTDOORS_ONLY && !m_caster->GetTerrain()->IsOutdoors(m_caster->GetPositionX(), m_caster->GetPositionY(), m_caster->GetPositionZ())) return SPELL_FAILED_ONLY_OUTDOORS; if (m_spellInfo->Attributes & SPELL_ATTR_INDOORS_ONLY && m_caster->GetTerrain()->IsOutdoors(m_caster->GetPositionX(), m_caster->GetPositionY(), m_caster->GetPositionZ())) return SPELL_FAILED_ONLY_INDOORS; } // caster state requirements if (m_spellInfo->CasterAuraState && !m_caster->HasAuraState(AuraState(m_spellInfo->CasterAuraState))) return SPELL_FAILED_CASTER_AURASTATE; if (m_caster->GetTypeId() == TYPEID_PLAYER) { // Autorepeat spells, if cast start when moving, are delayed until moving stop in spell update code. if (m_caster->IsMoving()) { // skip stuck spell to allow use it in falling case and apply spell limitations at movement if ((!m_caster->m_movementInfo.HasMovementFlag(MOVEFLAG_FALLINGFAR) || m_spellInfo->Effect[EFFECT_INDEX_0] != SPELL_EFFECT_STUCK) && (IsAutoRepeat() || m_spellInfo->AuraInterruptFlags & AURA_INTERRUPT_FLAG_NOT_SEATED)) return SPELL_FAILED_MOVING; } if (!m_IsTriggeredSpell && NeedsComboPoints(m_spellInfo) && (!m_targets.getUnitTarget() || m_targets.getUnitTarget()->GetObjectGuid() != ((Player*)m_caster)->GetComboTargetGuid())) // warrior not have real combo-points at client side but use this way for mark allow Overpower use return m_caster->getClass() == CLASS_WARRIOR ? SPELL_FAILED_BAD_TARGETS : SPELL_FAILED_NO_COMBO_POINTS; switch (m_spellInfo->Id) { // Morsure de mangouste case 1495: case 14269: case 14270: case 14271: if (m_targets.getUnitTargetGuid() != m_caster->GetReactiveTarget(REACTIVE_DEFENSE)) return SPELL_FAILED_BAD_TARGETS; break; // Contre-attaque case 19306: case 20909: case 20910: if (m_targets.getUnitTargetGuid() != m_caster->GetReactiveTarget(REACTIVE_HUNTER_PARRY)) return SPELL_FAILED_BAD_TARGETS; break; // Reindeer Transformation only castable while mounted case 25860: if (!m_caster->HasAuraType(SPELL_AURA_MOUNTED)) return SPELL_FAILED_ONLY_MOUNTED; break; } // Loatheb Corrupted Mind spell failed if (!m_IsCastByItem && !m_IsTriggeredSpell) { Unit::AuraList const& auraClassScripts = m_caster->GetAurasByType(SPELL_AURA_OVERRIDE_CLASS_SCRIPTS); for (auto itr = auraClassScripts.begin(); itr != auraClassScripts.end(); ++itr) { if ((*itr)->GetModifier()->m_miscvalue == 4327) { if ((m_spellInfo->SpellFamilyName == SPELLFAMILY_DRUID || m_spellInfo->SpellFamilyName == SPELLFAMILY_PRIEST || m_spellInfo->SpellFamilyName == SPELLFAMILY_SHAMAN || m_spellInfo->SpellFamilyName == SPELLFAMILY_PALADIN) && (IsHealSpell(m_spellInfo) || m_spellInfo->IsDispel() || // don't really care if the target is non-friendly, there's nothing to purge on Loatheb m_spellInfo->IsFitToFamily() || // prevent holy nova cast (casted spell is damage part, heal is triggered) m_spellInfo->IsFitToFamily())) // prevent PW:Shield cast { return SPELL_FAILED_FIZZLE; } } } } } if (Unit *target = m_targets.getUnitTarget()) { // Nostalrius - Un sort plus puissant est deja actif if (IsSpellAppliesAura(m_spellInfo) && !IsAreaOfEffectSpell(m_spellInfo) && IsPositiveSpell(m_spellInfo) && target->HasMorePowerfullSpellActive(m_spellInfo)) return SPELL_FAILED_AURA_BOUNCED; #if SUPPORTED_CLIENT_BUILD > CLIENT_BUILD_1_10_2 // Swiftmend if (m_spellInfo->Id == 18562) // future versions have special aura state for this { if (!target->GetAura(SPELL_AURA_PERIODIC_HEAL, SPELLFAMILY_DRUID, UI64LIT(0x50))) return SPELL_FAILED_TARGET_AURASTATE; } #endif if (!m_IsTriggeredSpell && IsDeathOnlySpell(m_spellInfo) && target->isAlive()) return SPELL_FAILED_TARGET_NOT_DEAD; // Check spell min target level if ((m_spellInfo->MinTargetLevel > 0) && (int32(target->getLevel()) < m_spellInfo->MinTargetLevel)) return SPELL_FAILED_LOWLEVEL; // Check spell max target level if ((m_spellInfo->MaxTargetLevel > 0) && (int32(target->getLevel()) > m_spellInfo->MaxTargetLevel)) return SPELL_FAILED_HIGHLEVEL; #if SUPPORTED_CLIENT_BUILD <= CLIENT_BUILD_1_11_2 // World of Warcraft Client Patch 1.12.0 (2006-08-22) // - Pickpocket can now be used on targets that are in combat, as long as the rogue remains stealthed. if ((m_spellInfo->AttributesEx & SPELL_ATTR_EX_IS_PICKPOCKET) && target->isInCombat()) return SPELL_FAILED_TARGET_IN_COMBAT; #endif bool non_caster_target = target != m_caster && !IsSpellWithCasterSourceTargetsOnly(m_spellInfo); if (non_caster_target) { // Not allow casting on flying player unless its a ritual of summoning if (target->IsTaxiFlying() && m_spellInfo->Id != 7720) return SPELL_FAILED_BAD_TARGETS; if (!m_IsTriggeredSpell) { if (!(m_spellInfo->AttributesEx2 & SPELL_ATTR_EX2_IGNORE_LOS) && !m_caster->IsWithinLOSInMap(target)) return SPELL_FAILED_LINE_OF_SIGHT; // check if target is in combat if (strict && non_caster_target && (m_spellInfo->AttributesEx & SPELL_ATTR_EX_NOT_IN_COMBAT_TARGET) && target->isInCombat()) return SPELL_FAILED_TARGET_AFFECTING_COMBAT; // auto selection spell rank implemented in WorldSession::HandleCastSpellOpcode // this case can be triggered if rank not found (too low-level target for first rank) if (m_caster->GetTypeId() == TYPEID_PLAYER && !m_CastItem && !m_IsTriggeredSpell) { // spell expected to be auto-downranking in cast handle, so must be same if (m_spellInfo != sSpellMgr.SelectAuraRankForLevel(m_spellInfo, target->getLevel())) return SPELL_FAILED_LOWLEVEL; } if (strict && m_spellInfo->AttributesEx3 & SPELL_ATTR_EX3_TARGET_ONLY_PLAYER && target->GetTypeId() != TYPEID_PLAYER && !IsAreaOfEffectSpell(m_spellInfo)) return SPELL_FAILED_BAD_TARGETS; } // Can't help a friend in duel | sorry, not enough time to figure out why this is necessary if (IsPositiveSpell(m_spellInfo) && m_spellInfo->Id != 28697) { Player* casterOwner = m_caster->GetCharmerOrOwnerPlayerOrPlayerItself(); Player* targetOwner = target->GetCharmerOrOwnerPlayerOrPlayerItself(); if (m_spellInfo->Id == 7266 && targetOwner && targetOwner->duel && casterOwner && !casterOwner->IsInDuelWith(targetOwner)) { return SPELL_FAILED_TARGET_DUELING; } if (targetOwner && casterOwner != targetOwner && targetOwner->duel && targetOwner->duel->startTime && casterOwner && !casterOwner->IsInDuelWith(targetOwner)) return SPELL_FAILED_TARGET_DUELING; } } else if (m_caster == target) { if (m_caster->GetTypeId() == TYPEID_PLAYER && m_caster->IsInWorld()) { // Additional check for some spells // If 0 spell effect empty - client not send target data (need use selection) // TODO: check it on next client version if (m_targets.m_targetMask == TARGET_FLAG_SELF && m_spellInfo->EffectImplicitTargetA[EFFECT_INDEX_1] == TARGET_CHAIN_DAMAGE) { target = m_caster->GetMap()->GetUnit(((Player *)m_caster)->GetSelectionGuid()); if (!target) return SPELL_FAILED_BAD_TARGETS; m_targets.setUnitTarget(target); } } // Some special spells with non-caster only mode // Fire Shield if (m_spellInfo->SpellFamilyName == SPELLFAMILY_WARLOCK && m_spellInfo->SpellIconID == 16) return SPELL_FAILED_BAD_TARGETS; // Arcane Missiles if (m_caster == target && m_spellInfo->SpellFamilyName == SPELLFAMILY_MAGE && m_spellInfo->SpellFamilyFlags == 0x40800) return SPELL_FAILED_BAD_TARGETS; } // check pet presents for (int j = 0; j < MAX_EFFECT_INDEX; ++j) { if (m_spellInfo->EffectImplicitTargetA[j] == TARGET_PET) { Pet* pet = m_caster->GetPet(); if (!pet) { if (m_triggeredByAuraSpell) // not report pet not existence for triggered spells return SPELL_FAILED_DONT_REPORT; else return SPELL_FAILED_NO_PET; } else if (!pet->isAlive()) { if (m_triggeredByAuraSpell) return SPELL_FAILED_DONT_REPORT; else return SPELL_FAILED_TARGETS_DEAD; } else if (!(m_spellInfo->AttributesEx2 & SPELL_ATTR_EX2_IGNORE_LOS) && !pet->IsWithinLOSInMap(m_caster)) return SPELL_FAILED_LINE_OF_SIGHT; break; } } //check creature type //ignore self casts (including area casts when caster selected as target) if (non_caster_target) { if (!CheckTargetCreatureType(target)) { if (target->GetTypeId() == TYPEID_PLAYER) return SPELL_FAILED_TARGET_IS_PLAYER; else return SPELL_FAILED_BAD_TARGETS; } // simple cases bool explicit_target_mode = false; bool target_hostile = false; bool target_hostile_checked = false; bool target_friendly_checked = false; for (int k = 0; k < MAX_EFFECT_INDEX; ++k) { if (IsExplicitPositiveTarget(m_spellInfo->EffectImplicitTargetA[k])) { if (!target_hostile_checked) { target_hostile_checked = true; target_hostile = m_caster->IsHostileTo(target); } if (target_hostile) return SPELL_FAILED_BAD_TARGETS; explicit_target_mode = true; } else if (IsExplicitNegativeTarget(m_spellInfo->EffectImplicitTargetA[k])) { if (!target_friendly_checked) { target_friendly_checked = true; if (!m_caster->IsValidAttackTarget(target)) return SPELL_FAILED_BAD_TARGETS; } explicit_target_mode = true; } } // TODO: this check can be applied and for player to prevent cheating when IsPositiveSpell will return always correct result. // check target for pet/charmed casts (not self targeted), self targeted cast used for area effects and etc // Nostalrius : pas de status positif / negatif pour les sorts de dispel (marche en offensif comme defensif) if (!explicit_target_mode && m_caster->GetTypeId() == TYPEID_UNIT && m_caster->GetCharmerOrOwnerGuid() && !IsSpellHaveEffect(m_spellInfo, SPELL_EFFECT_DISPEL)) { // check correctness positive/negative cast target (pet cast real check and cheating check) if (IsPositiveSpell(m_spellInfo)) { if (!target_hostile_checked) target_hostile = m_caster->IsHostileTo(target); if (target_hostile) return SPELL_FAILED_BAD_TARGETS; } else { if (!target_friendly_checked && m_caster->IsFriendlyTo(target)) return SPELL_FAILED_BAD_TARGETS; } } } if (IsPositiveSpell(m_spellInfo->Id, m_caster, target)) if (target->IsImmuneToSpell(m_spellInfo, target == m_caster)) return SPELL_FAILED_TARGET_AURASTATE; //Must be behind the target. if (IsFromBehindOnlySpell(m_spellInfo) && target->HasInArc(M_PI_F, m_caster)) { SendInterrupted(2); return SPELL_FAILED_NOT_BEHIND; } //Target must be facing you. if ((m_spellInfo->Attributes == 0x150010) && !target->HasInArc(M_PI_F, m_caster)) { SendInterrupted(2); return SPELL_FAILED_NOT_INFRONT; } } else if (GameObject* goTarget = m_targets.getGOTarget()) { if (goTarget->AI()) { SpellCastResult goAIresult = goTarget->AI()->CheckSpellCast(m_caster, m_spellInfo->Id); if (goAIresult != SPELL_CAST_OK) return goAIresult; } // Require LOS to loot chests // For example, check LoS when opening a gobj (spell 6477). // Prevents abuse with object id 165554 for example. if (goTarget->GetGoType() == GAMEOBJECT_TYPE_CHEST) { if (!m_IsTriggeredSpell && !(m_spellInfo->AttributesEx2 & SPELL_ATTR_EX2_IGNORE_LOS) && !goTarget->GetGOInfo()->chest.minSuccessOpens // don't check for gathering nodes, too many are halfway in walls && !goTarget->HasFlag(GAMEOBJECT_FLAGS, GO_FLAG_INTERACT_COND) // don't check for quest items && !(goTarget->GetEntry() == 160845) // exemption for Dark Coffer && !m_caster->IsWithinLOSInMap(goTarget, false)) return SPELL_FAILED_LINE_OF_SIGHT; } } else if (m_targets.IsEmpty()) { // check LOS for ground targeted AOE spells like Blizzard, Flamestrike ... if (m_caster->GetTypeId() == TYPEID_PLAYER && m_targets.m_targetMask & TARGET_FLAG_DEST_LOCATION) { if (m_targets.m_destX && m_targets.m_destY && m_targets.m_destZ) { if (!m_caster->IsWithinLOS(m_targets.m_destX, m_targets.m_destY, m_targets.m_destZ)) return SPELL_FAILED_LINE_OF_SIGHT; } } } // Nostalrius: impossible to cast spells while banned / feared / confused ... // Except divine shields, pvp trinkets for example // Stoneform is explicitly excluded, it should be allowed to cast always // TODO: This condition allows an antifear item to be used while stuned for example. if (!m_IsTriggeredSpell && !IsSpellAppliesAura(m_spellInfo, SPELL_AURA_SCHOOL_IMMUNITY) && !IsSpellAppliesAura(m_spellInfo, SPELL_AURA_MECHANIC_IMMUNITY) && m_spellInfo->Id != 20594 && // Stoneform m_caster->hasUnitState(UNIT_STAT_ISOLATED | UNIT_STAT_STUNNED | UNIT_STAT_PENDING_STUNNED | UNIT_STAT_CONFUSED | UNIT_STAT_FLEEING)) return SPELL_FAILED_DONT_REPORT; // zone check SpellCastResult locRes = sSpellMgr.GetSpellAllowedInLocationError(m_spellInfo, m_caster, m_caster->GetCharmerOrOwnerPlayerOrPlayerItself()); if (locRes != SPELL_CAST_OK) return locRes; // not let players cast spells at mount (and let do it to creatures) if (m_caster->IsMounted() && m_caster->GetTypeId() == TYPEID_PLAYER && !m_IsTriggeredSpell && !IsPassiveSpell(m_spellInfo) && !(m_spellInfo->Attributes & SPELL_ATTR_CASTABLE_WHILE_MOUNTED)) { if (m_caster->IsTaxiFlying()) return SPELL_FAILED_NOT_ON_TAXI; else if (m_caster->GetTypeId() == TYPEID_PLAYER) { m_caster->Unmount(); m_caster->RemoveSpellsCausingAura(SPELL_AURA_MOUNTED); } else return SPELL_FAILED_NOT_MOUNTED; } // always (except passive spells) check items (focus object can be required for any type casts) if (!IsPassiveSpell(m_spellInfo)) { SpellCastResult castResult = CheckItems(); if (castResult != SPELL_CAST_OK) return castResult; } // Database based targets from spell_target_script if (m_UniqueTargetInfo.empty()) // skip second CheckCast apply (for delayed spells for example) { for (int j = 0; j < MAX_EFFECT_INDEX; ++j) { if (m_spellInfo->EffectImplicitTargetA[j] == TARGET_SCRIPT || (m_spellInfo->EffectImplicitTargetB[j] == TARGET_SCRIPT && m_spellInfo->EffectImplicitTargetA[j] != TARGET_SELF) || m_spellInfo->EffectImplicitTargetA[j] == TARGET_SCRIPT_COORDINATES || m_spellInfo->EffectImplicitTargetB[j] == TARGET_SCRIPT_COORDINATES || m_spellInfo->EffectImplicitTargetA[j] == TARGET_FOCUS_OR_SCRIPTED_GAMEOBJECT) { SpellScriptTargetBounds bounds = sSpellMgr.GetSpellScriptTargetBounds(m_spellInfo->Id); if (bounds.first == bounds.second) { if (m_spellInfo->EffectImplicitTargetA[j] == TARGET_SCRIPT || m_spellInfo->EffectImplicitTargetB[j] == TARGET_SCRIPT) sLog.outErrorDb("Spell entry %u, effect %i has EffectImplicitTargetA/EffectImplicitTargetB = TARGET_SCRIPT, but creature are not defined in `spell_script_target`", m_spellInfo->Id, j); if (m_spellInfo->EffectImplicitTargetA[j] == TARGET_SCRIPT_COORDINATES || m_spellInfo->EffectImplicitTargetB[j] == TARGET_SCRIPT_COORDINATES) sLog.outErrorDb("Spell entry %u, effect %i has EffectImplicitTargetA/EffectImplicitTargetB = TARGET_SCRIPT_COORDINATES, but gameobject or creature are not defined in `spell_script_target`", m_spellInfo->Id, j); if (m_spellInfo->EffectImplicitTargetA[j] == TARGET_FOCUS_OR_SCRIPTED_GAMEOBJECT) sLog.outErrorDb("Spell entry %u, effect %i has EffectImplicitTargetA/EffectImplicitTargetB = TARGET_FOCUS_OR_SCRIPTED_GAMEOBJECT, but gameobject are not defined in `spell_script_target`", m_spellInfo->Id, j); } SpellRangeEntry const* srange = sSpellRangeStore.LookupEntry(m_spellInfo->rangeIndex); float range = GetSpellMaxRange(srange); Creature* targetExplicit = nullptr; // used for cases where a target is provided (by script for example) Creature* creatureScriptTarget = nullptr; GameObject* goScriptTarget = nullptr; for (auto i_spellST = bounds.first; i_spellST != bounds.second; ++i_spellST) { switch (i_spellST->second.type) { case SPELL_TARGET_TYPE_GAMEOBJECT: { GameObject* p_GameObject = nullptr; if (i_spellST->second.targetEntry) { MaNGOS::NearestGameObjectEntryFitConditionInObjectRangeCheck go_check(*m_caster, i_spellST->second.targetEntry, range, i_spellST->second.conditionId); MaNGOS::GameObjectLastSearcher checker(p_GameObject, go_check); Cell::VisitGridObjects(m_caster, checker, range); if (p_GameObject) { // remember found target and range, next attempt will find more near target with another entry creatureScriptTarget = nullptr; goScriptTarget = p_GameObject; range = go_check.GetLastRange(); } } else if (focusObject) // Focus Object { float frange = m_caster->GetDistance(focusObject); if (range >= frange) { creatureScriptTarget = nullptr; goScriptTarget = focusObject; range = frange; } } break; } case SPELL_TARGET_TYPE_CREATURE: case SPELL_TARGET_TYPE_DEAD: default: { Creature *p_Creature = nullptr; // check if explicit target is provided and check it up against database valid target entry/state if (Unit* pTarget = m_targets.getUnitTarget()) { if (pTarget->GetTypeId() == TYPEID_UNIT && pTarget->GetEntry() == i_spellST->second.targetEntry) { if (i_spellST->second.type == SPELL_TARGET_TYPE_DEAD && ((Creature*)pTarget)->IsCorpse()) { // always use spellMaxRange, in case GetLastRange returned different in a previous pass if (pTarget->IsWithinDistInMap(m_caster, GetSpellMaxRange(srange))) targetExplicit = (Creature*)pTarget; } else if (i_spellST->second.type == SPELL_TARGET_TYPE_CREATURE && pTarget->isAlive()) { // always use spellMaxRange, in case GetLastRange returned different in a previous pass if (pTarget->IsWithinDistInMap(m_caster, GetSpellMaxRange(srange))) targetExplicit = (Creature*)pTarget; } } } // no target provided or it was not valid, so use closest in range if (!targetExplicit) { MaNGOS::NearestCreatureEntryFitConditionInObjectRangeCheck u_check(*m_caster, i_spellST->second.targetEntry, i_spellST->second.type != SPELL_TARGET_TYPE_DEAD, range, i_spellST->second.conditionId); MaNGOS::CreatureLastSearcher searcher(p_Creature, u_check); // Visit all, need to find also Pet* objects Cell::VisitAllObjects(m_caster, searcher, range); range = u_check.GetLastRange(); } // always prefer provided target if it's valid if (targetExplicit) creatureScriptTarget = targetExplicit; else if (p_Creature) creatureScriptTarget = p_Creature; if (creatureScriptTarget) goScriptTarget = nullptr; break; } } } if (creatureScriptTarget) { // store coordinates for TARGET_SCRIPT_COORDINATES if (m_spellInfo->EffectImplicitTargetA[j] == TARGET_SCRIPT_COORDINATES || m_spellInfo->EffectImplicitTargetB[j] == TARGET_SCRIPT_COORDINATES) { m_targets.setDestination(creatureScriptTarget->GetPositionX(), creatureScriptTarget->GetPositionY(), creatureScriptTarget->GetPositionZ()); if (m_spellInfo->EffectImplicitTargetA[j] == TARGET_SCRIPT_COORDINATES && m_spellInfo->Effect[j] != SPELL_EFFECT_PERSISTENT_AREA_AURA) AddUnitTarget(creatureScriptTarget, SpellEffectIndex(j)); } // store explicit target for TARGET_SCRIPT else { if (m_spellInfo->EffectImplicitTargetA[j] == TARGET_SCRIPT || m_spellInfo->EffectImplicitTargetB[j] == TARGET_SCRIPT) { // not sure if every TARGET_SCRIPT requires the same, but this one definetely is checked wrong if (m_spellInfo->Id == 16053 && creatureScriptTarget->GetHealthPercent() < 11) m_targets.setUnitTarget(creatureScriptTarget); AddUnitTarget(creatureScriptTarget, SpellEffectIndex(j)); } } } else if (goScriptTarget) { // store coordinates for TARGET_SCRIPT_COORDINATES if (m_spellInfo->EffectImplicitTargetA[j] == TARGET_SCRIPT_COORDINATES || m_spellInfo->EffectImplicitTargetB[j] == TARGET_SCRIPT_COORDINATES) { m_targets.setDestination(goScriptTarget->GetPositionX(), goScriptTarget->GetPositionY(), goScriptTarget->GetPositionZ()); if (m_spellInfo->EffectImplicitTargetA[j] == TARGET_SCRIPT_COORDINATES && m_spellInfo->Effect[j] != SPELL_EFFECT_PERSISTENT_AREA_AURA) AddGOTarget(goScriptTarget, SpellEffectIndex(j)); } // store explicit target for TARGET_FOCUS_OR_SCRIPTED_GAMEOBJECT else { if (m_spellInfo->EffectImplicitTargetA[j] == TARGET_FOCUS_OR_SCRIPTED_GAMEOBJECT || m_spellInfo->EffectImplicitTargetB[j] == TARGET_FOCUS_OR_SCRIPTED_GAMEOBJECT) AddGOTarget(goScriptTarget, SpellEffectIndex(j)); } } //Missing DB Entry or targets for this spellEffect. else { /* For TARGET_FOCUS_OR_SCRIPTED_GAMEOBJECT makes DB targets optional not required for now * TODO: Makes more research for this target type */ if (m_spellInfo->EffectImplicitTargetA[j] != TARGET_FOCUS_OR_SCRIPTED_GAMEOBJECT) { // not report target not existence for triggered spells if (m_triggeredByAuraSpell || m_IsTriggeredSpell) return SPELL_FAILED_DONT_REPORT; else return SPELL_FAILED_BAD_TARGETS; } } } } } if (!m_IsTriggeredSpell) { if (!m_triggeredByAuraSpell) { SpellCastResult castResult = CheckRange(strict); if (castResult != SPELL_CAST_OK) return castResult; if (Player* playerCaster = m_caster->ToPlayer()) if (BattleGround* bg = playerCaster->GetBattleGround()) { castResult = bg->CheckSpellCast(playerCaster, m_spellInfo); if (castResult != SPELL_CAST_OK) return castResult; } } SpellCastResult castResult = CheckPower(); if (castResult != SPELL_CAST_OK) return castResult; // triggered spell not affected by stun/etc castResult = CheckCasterAuras(); if (castResult != SPELL_CAST_OK) return castResult; } // All spells that require target to be below 20% have this. if (m_spellInfo->TargetAuraState == AURA_STATE_HEALTHLESS_20_PERCENT) { if (!m_targets.getUnitTarget()) return SPELL_FAILED_BAD_IMPLICIT_TARGETS; if (!m_targets.getUnitTarget()->HasAuraState(AURA_STATE_HEALTHLESS_20_PERCENT)) return SPELL_FAILED_BAD_TARGETS; } for (int i = 0; i < MAX_EFFECT_INDEX; ++i) { // for effects of spells that have only one target switch (m_spellInfo->Effect[i]) { case SPELL_EFFECT_DUMMY: { if (m_spellInfo->SpellIconID == 156) // Holy Shock { // spell different for friends and enemies // hart version required facing if (m_targets.getUnitTarget() && !m_caster->IsFriendlyTo(m_targets.getUnitTarget()) && !m_caster->HasInArc(M_PI_F, m_targets.getUnitTarget())) return SPELL_FAILED_UNIT_NOT_INFRONT; } else if (m_spellInfo->Id == 13278) // Gnomish Death Ray { if (!m_targets.getUnitTarget() || m_caster->IsFriendlyTo(m_targets.getUnitTarget())) return SPELL_FAILED_BAD_TARGETS; } break; } case SPELL_EFFECT_SCHOOL_DAMAGE: { // Conflagrate if (m_spellInfo->IsFitToFamily()) { if (!m_targets.getUnitTarget()) return SPELL_FAILED_BAD_IMPLICIT_TARGETS; // for caster applied auras only bool found = false; auto const & mPeriodic = m_targets.getUnitTarget()->GetAurasByType(SPELL_AURA_PERIODIC_DAMAGE); for (auto i = mPeriodic.begin(); i != mPeriodic.end(); ++i) { // Immolate if ((*i)->GetSpellProto()->IsFitToFamily() && (*i)->GetCasterGuid() == m_caster->GetObjectGuid()) { found = true; break; } } if (!found) return SPELL_FAILED_TARGET_AURASTATE; } break; } case SPELL_EFFECT_TAMECREATURE: { // Spell can be triggered, we need to check original caster prior to caster Unit* caster = GetAffectiveCaster(); if (!caster || caster->GetTypeId() != TYPEID_PLAYER || !m_targets.getUnitTarget() || m_targets.getUnitTarget()->GetTypeId() == TYPEID_PLAYER) return SPELL_FAILED_BAD_TARGETS; Player* plrCaster = (Player*)caster; bool gmmode = m_triggeredBySpellInfo == nullptr; if (gmmode && !ChatHandler(plrCaster).FindCommand("npc tame")) { plrCaster->SendPetTameFailure(PETTAME_UNKNOWNERROR); return SPELL_FAILED_DONT_REPORT; } if (plrCaster->getClass() != CLASS_HUNTER && !gmmode) { plrCaster->SendPetTameFailure(PETTAME_UNITSCANTTAME); return SPELL_FAILED_DONT_REPORT; } Creature* target = (Creature*)m_targets.getUnitTarget(); if (target->IsPet() || target->isCharmed()) { plrCaster->SendPetTameFailure(PETTAME_CREATUREALREADYOWNED); return SPELL_FAILED_DONT_REPORT; } if (target->getLevel() > plrCaster->getLevel() && !gmmode) { plrCaster->SendPetTameFailure(PETTAME_TOOHIGHLEVEL); return SPELL_FAILED_DONT_REPORT; } if (!target->GetCreatureInfo()->isTameable()) { plrCaster->SendPetTameFailure(PETTAME_NOTTAMEABLE); return SPELL_FAILED_DONT_REPORT; } if (plrCaster->GetPetGuid() || plrCaster->GetCharmGuid() || sCharacterDatabaseCache.GetCharacterPetByOwner(plrCaster->GetGUIDLow())) { plrCaster->SendPetTameFailure(PETTAME_ANOTHERSUMMONACTIVE); return SPELL_FAILED_DONT_REPORT; } break; } case SPELL_EFFECT_LEARN_SPELL: { if (m_spellInfo->EffectImplicitTargetA[i] != TARGET_PET) break; Pet* pet = m_caster->GetPet(); if (!pet) return SPELL_FAILED_NO_PET; SpellEntry const *learn_spellproto = sSpellMgr.GetSpellEntry(m_spellInfo->EffectTriggerSpell[i]); if (!learn_spellproto) return SPELL_FAILED_NOT_KNOWN; if (!pet->CanTakeMoreActiveSpells(learn_spellproto->Id)) return SPELL_FAILED_TOO_MANY_SKILLS; if (m_spellInfo->spellLevel > pet->getLevel()) return SPELL_FAILED_LOWLEVEL; if (!pet->HasTPForSpell(learn_spellproto->Id)) return SPELL_FAILED_TRAINING_POINTS; break; } case SPELL_EFFECT_LEARN_PET_SPELL: { Pet* pet = m_caster->GetPet(); if (!pet) return SPELL_FAILED_NO_PET; SpellEntry const *learn_spellproto = sSpellMgr.GetSpellEntry(m_spellInfo->EffectTriggerSpell[i]); if (!learn_spellproto) return SPELL_FAILED_NOT_KNOWN; if (!pet->CanTakeMoreActiveSpells(learn_spellproto->Id)) return SPELL_FAILED_TOO_MANY_SKILLS; if (m_spellInfo->spellLevel > pet->getLevel()) return SPELL_FAILED_LOWLEVEL; if (!pet->HasTPForSpell(learn_spellproto->Id)) return SPELL_FAILED_TRAINING_POINTS; break; } case SPELL_EFFECT_FEED_PET: { if (m_caster->GetTypeId() != TYPEID_PLAYER) return SPELL_FAILED_BAD_TARGETS; Item* foodItem = m_targets.getItemTarget(); if (!foodItem) return SPELL_FAILED_BAD_TARGETS; Pet* pet = m_caster->GetPet(); if (!pet) return SPELL_FAILED_NO_PET; if (!pet->HaveInDiet(foodItem->GetProto())) return SPELL_FAILED_WRONG_PET_FOOD; if (!pet->GetCurrentFoodBenefitLevel(foodItem->GetProto()->ItemLevel)) return SPELL_FAILED_FOOD_LOWLEVEL; if (pet->isInCombat()) return SPELL_FAILED_AFFECTING_COMBAT; break; } case SPELL_EFFECT_POWER_BURN: case SPELL_EFFECT_POWER_DRAIN: { // Can be area effect, Check only for players and not check if target - caster (spell can have multiply drain/burn effects) if (m_caster->GetTypeId() == TYPEID_PLAYER) if (Unit* target = m_targets.getUnitTarget()) if (target != m_caster && int32(target->getPowerType()) != m_spellInfo->EffectMiscValue[i]) return SPELL_FAILED_BAD_TARGETS; break; } case SPELL_EFFECT_CHARGE: { if (m_caster->hasUnitState(UNIT_STAT_ROOT | UNIT_STAT_PENDING_ROOT)) return SPELL_FAILED_ROOTED; // Pas de chemin disponible ? if (strict) { const SpellRangeEntry *spellRange = sSpellRangeStore.LookupEntry(m_spellInfo->rangeIndex); Unit* unitTarget = m_targets.getUnitTarget(); if (unitTarget && spellRange && spellRange->maxRange > 0.0f) { float x, y, z; unitTarget->GetPosition(x, y, z); if (!m_caster->IsInWater()) { float posX, posY, posZ; m_caster->GetPosition(posX, posY, posZ); float ground_Z = m_caster->GetMap()->GetHeight(posX, posY, posZ, true); float z_diff = fabs(ground_Z - posZ); PathInfo path(m_caster); path.SetTransport(m_caster->GetTransport()); path.calculate(x, y, z); // On est sur terre, le chemin doit etre valide. if (z_diff < 1.6f && (path.getPathType() & (PATHFIND_INCOMPLETE | PATHFIND_NOPATH))) return SPELL_FAILED_NOPATH; // Dans tous les cas, on interdit un chemin trop long. if (path.Length() > ((spellRange->maxRange + 1.0f) * 1.5f)) return SPELL_FAILED_NOPATH; } } } break; } case SPELL_EFFECT_SKINNING: { if (m_caster->GetTypeId() != TYPEID_PLAYER || !m_targets.getUnitTarget() || m_targets.getUnitTarget()->GetTypeId() != TYPEID_UNIT) return SPELL_FAILED_BAD_TARGETS; if (!m_targets.getUnitTarget()->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SKINNABLE)) return SPELL_FAILED_TARGET_UNSKINNABLE; Creature* creature = (Creature*)m_targets.getUnitTarget(); if (!creature->IsSkinnableBy(m_caster->ToPlayer())) return SPELL_FAILED_TARGET_NOT_LOOTED; // If the player isn't in loot range, the skins are lost forever. // This only happens on large mobs with disjointed models (i.e. Devilsaurs). if (!creature->IsWithinDistInMap(m_caster, INTERACTION_DISTANCE)) return SPELL_FAILED_OUT_OF_RANGE; uint32 skill = creature->GetCreatureInfo()->GetRequiredLootSkill(); int32 skillValue = ((Player*)m_caster)->GetSkillValue(skill); int32 TargetLevel = m_targets.getUnitTarget()->getLevel(); int32 ReqValue = (skillValue < 100 ? (TargetLevel - 10) * 10 : TargetLevel * 5); if (ReqValue > skillValue) return SPELL_FAILED_LOW_CASTLEVEL; if (creature->GetCreatureType() != CREATURE_TYPE_CRITTER && (creature->lootForSkin || !creature->loot.isLooted())) { /* if (creature->GetEntry() == 10184) // Onyxia, Y U NO SKIN ? { sLog.nostalrius("[OnyxiaSkin] Instance %u Player %s", m_caster->GetInstanceId(), m_caster->GetName()); sLog.nostalrius("[OnyxiaSkin] Remaining gold: %u. creature->lootForSkin:%u", creature->loot.gold, creature->lootForSkin); for (LootItemList::const_iterator it = creature->loot.items.begin(); it != creature->loot.items.end(); ++it) sLog.nostalrius("[OnyxiaSkin] Remaining item: %u [Cond%u:Looted%u:Blocked%u:RollWinner%u]", it->itemid, it->conditionId, it->is_looted, it->is_blocked, it->lootOwner.GetCounter()); } else */ return SPELL_FAILED_TARGET_NOT_LOOTED; } // chance for fail at orange skinning attempt if ((m_selfContainer && (*m_selfContainer) == this) && skillValue < sWorld.GetConfigMaxSkillValue() && (ReqValue < 0 ? 0 : ReqValue) > irand(skillValue - 25, skillValue + 37)) return SPELL_FAILED_TRY_AGAIN; break; } case SPELL_EFFECT_OPEN_LOCK_ITEM: case SPELL_EFFECT_OPEN_LOCK: { if (m_caster->GetTypeId() != TYPEID_PLAYER) // only players can open locks, gather etc. return SPELL_FAILED_BAD_TARGETS; // we need a go target in case of TARGET_GAMEOBJECT (for other targets acceptable GO and items) if (m_spellInfo->EffectImplicitTargetA[i] == TARGET_GAMEOBJECT) { if (!m_targets.getGOTarget()) return SPELL_FAILED_BAD_TARGETS; } // get the lock entry uint32 lockId = 0; if (GameObject* go = m_targets.getGOTarget()) { // In BattleGround players can use only flags and banners if (((Player*)m_caster)->InBattleGround()) { if (go->GetGoState() != GO_STATE_READY) return SPELL_FAILED_BAD_TARGETS; if(!((Player*)m_caster)->CanUseBattleGroundObject()) return SPELL_FAILED_TRY_AGAIN; } lockId = go->GetGOInfo()->GetLockId(); if (!lockId) return SPELL_FAILED_ALREADY_OPEN; if (!go->IsUseRequirementMet()) return SPELL_FAILED_TRY_AGAIN; // Prevent looting chests while totally immune if (go->GetGoType() == GAMEOBJECT_TYPE_CHEST && m_caster->ToPlayer()->IsTotalImmune()) return SPELL_FAILED_DAMAGE_IMMUNE; } else if (Item* item = m_targets.getItemTarget()) { // not own (trade?) Player* itemOwner = item->GetOwner(); Player* itemTrader = itemOwner->GetTrader(); if (itemOwner != m_caster && itemTrader != m_caster) return SPELL_FAILED_ITEM_GONE; lockId = item->GetProto()->LockID; // if already unlocked if (!lockId || item->HasFlag(ITEM_FIELD_FLAGS, ITEM_DYNFLAG_UNLOCKED)) return SPELL_FAILED_ALREADY_OPEN; } else return SPELL_FAILED_BAD_TARGETS; SkillType skillId = SKILL_NONE; int32 reqSkillValue = 0; int32 skillValue = 0; // check lock compatibility SpellCastResult res = CanOpenLock(SpellEffectIndex(i), lockId, skillId, reqSkillValue, skillValue); if (res != SPELL_CAST_OK) { DEBUG_FILTER_LOG(LOG_FILTER_SPELL_CAST, "CanOpenLock fail : erreur %x0x. (Lock%u/Skill%u)", res, lockId, skillId); return res; } // chance for fail at orange mining/herb/LockPicking gathering attempt // second check prevent fail at rechecks if ((skillId == SKILL_HERBALISM || skillId == SKILL_MINING || skillId == SKILL_LOCKPICKING) && (m_selfContainer && (*m_selfContainer) == this)) { bool canFailAtMax = skillId != SKILL_HERBALISM && skillId != SKILL_MINING; // chance for failure in orange gather / lockpick (gathering skill can't fail at maxskill) if ((canFailAtMax || skillValue < sWorld.GetConfigMaxSkillValue()) && reqSkillValue > irand(skillValue - 25, skillValue + 37)) return SPELL_FAILED_TRY_AGAIN; } break; } case SPELL_EFFECT_ACTIVATE_OBJECT: { if (m_spellInfo->Id == 15958) { if (m_UniqueGOTargetInfo.size()) { ObjectGuid eggGuid = m_UniqueGOTargetInfo.back().targetGUID; if (GameObject* pRookeryEgg = m_caster->GetMap()->GetGameObject(eggGuid)) { if (pRookeryEgg->GetGoState() != GO_STATE_ACTIVE_ALTERNATIVE) return SPELL_FAILED_BAD_TARGETS; } else return SPELL_FAILED_BAD_IMPLICIT_TARGETS; } else return SPELL_FAILED_BAD_IMPLICIT_TARGETS; } else if (m_spellInfo->Id == 16447) { if (!m_UniqueGOTargetInfo.size()) return SPELL_FAILED_BAD_IMPLICIT_TARGETS; } break; } case SPELL_EFFECT_SUMMON_DEAD_PET: { Creature *pet = m_caster->GetPet(); if (!pet) return SPELL_FAILED_NO_PET; if (pet->isAlive()) return SPELL_FAILED_ALREADY_HAVE_SUMMON; break; } // Don't make this check for SPELL_EFFECT_SUMMON_CRITTER, SPELL_EFFECT_SUMMON_WILD or SPELL_EFFECT_SUMMON_GUARDIAN. // These won't show up in m_caster->GetPetGUID() case SPELL_EFFECT_SUMMON: case SPELL_EFFECT_SUMMON_PHANTASM: case SPELL_EFFECT_SUMMON_DEMON: if (m_caster->GetPetGuid()) return SPELL_FAILED_ALREADY_HAVE_SUMMON; // no break case SPELL_EFFECT_SUMMON_POSSESSED: if (m_caster->GetCharmGuid()) return SPELL_FAILED_ALREADY_HAVE_CHARM; break; case SPELL_EFFECT_SUMMON_PET: { // In Vanilla old pets were unsummoned as soon as you began summoning a new one. if (!m_caster->UnsummonOldPetBeforeNewSummon(m_spellInfo->EffectMiscValue[i])) return SPELL_FAILED_ALREADY_HAVE_SUMMON; if (m_caster->GetCharmGuid()) return SPELL_FAILED_ALREADY_HAVE_CHARM; break; } case SPELL_EFFECT_TRANS_DOOR: { if (m_caster->getClass() == CLASS_WARLOCK && m_spellInfo->Id == 698) { if (m_caster->GetTypeId() != TYPEID_PLAYER) return SPELL_FAILED_BAD_TARGETS; if (!((Player*)m_caster)->GetSelectionGuid()) return SPELL_FAILED_BAD_TARGETS; Player* target = sObjectMgr.GetPlayer(((Player*)m_caster)->GetSelectionGuid()); if (!target || ((Player*)m_caster) == target || !target->IsInSameRaidWith((Player*)m_caster)) return SPELL_FAILED_BAD_TARGETS; if (target->isInCombat()) return SPELL_FAILED_TARGET_IN_COMBAT; // check if our map is dungeon const MapEntry *mapEntry = sMapStorage.LookupEntry(m_caster->GetMapId()); if (mapEntry && mapEntry->IsDungeon()) { if (m_caster->GetMap() != target->GetMap()) return SPELL_FAILED_TARGET_NOT_IN_INSTANCE; if (mapEntry->levelMin > target->getLevel()) return SPELL_FAILED_LOWLEVEL; if (mapEntry->levelMax && mapEntry->levelMax < target->getLevel()) return SPELL_FAILED_HIGHLEVEL; } else if (m_caster->ToPlayer()->InBattleGround()) return SPELL_FAILED_NOT_HERE; } break; } case SPELL_EFFECT_SUMMON_PLAYER: { if (m_caster->GetTypeId() != TYPEID_PLAYER) return SPELL_FAILED_BAD_TARGETS; if (!m_targets.getUnitTarget()) return SPELL_FAILED_BAD_TARGETS; if (!m_targets.getUnitTarget()->IsPlayer()) return SPELL_FAILED_BAD_TARGETS; Player* target = (Player*)m_targets.getUnitTarget(); if (!target || ((Player*)m_caster) == target || !target->IsInSameRaidWith((Player*)m_caster)) return SPELL_FAILED_BAD_TARGETS; // check if our map is dungeon const MapEntry *mapEntry = sMapStorage.LookupEntry(m_caster->GetMapId()); if (mapEntry && mapEntry->IsDungeon()) { if (m_caster->GetMap() != target->GetMap()) return SPELL_FAILED_TARGET_NOT_IN_INSTANCE; if (mapEntry->levelMin > target->getLevel()) return SPELL_FAILED_LOWLEVEL; if (mapEntry->levelMax && mapEntry->levelMax < target->getLevel()) return SPELL_FAILED_HIGHLEVEL; } else if (m_caster->ToPlayer()->InBattleGround()) return SPELL_FAILED_NOT_HERE; break; } case SPELL_EFFECT_LEAP: case SPELL_EFFECT_TELEPORT_UNITS_FACE_CASTER: { // not allow use this effect at battleground until battleground start (Nostalrius: Blizzlike) if (m_caster->GetTypeId() == TYPEID_PLAYER) if (BattleGround const *bg = ((Player*)m_caster)->GetBattleGround()) if (bg->GetStatus() != STATUS_IN_PROGRESS) return SPELL_FAILED_TRY_AGAIN; // Blizzlike: no blink on transport (client does not handle it) if (m_caster->GetTransport()) return SPELL_FAILED_NOT_ON_TRANSPORT; break; } case SPELL_EFFECT_DUEL: { Unit* target = m_targets.getUnitTarget(); if (target && target->GetTransport() != m_caster->GetTransport()) return SPELL_FAILED_NOT_ON_TRANSPORT; break; } case SPELL_EFFECT_SCRIPT_EFFECT: { // Black Qiraji Battle Tank if (m_spellInfo->Id == 26656) { if (m_caster->IsMounted()) { m_caster->RemoveSpellsCausingAura(SPELL_AURA_MOUNTED); return SPELL_FAILED_DONT_REPORT; } if (m_caster->IsInWater()) return SPELL_FAILED_ONLY_ABOVEWATER; if (m_caster->GetTypeId() == TYPEID_PLAYER && ((Player*)m_caster)->GetTransport()) return SPELL_FAILED_NO_MOUNTS_ALLOWED; if (m_caster->GetMapId() != 531 && m_caster->GetTypeId() == TYPEID_PLAYER && !sMapStorage.LookupEntry(m_caster->GetMapId())->IsMountAllowed() && !m_IsTriggeredSpell) return SPELL_FAILED_NO_MOUNTS_ALLOWED; if (m_caster->GetAreaId() == 35) return SPELL_FAILED_NO_MOUNTS_ALLOWED; if (m_caster->IsInDisallowedMountForm()) return SPELL_FAILED_NOT_SHAPESHIFT; if (m_caster->GetMapId() == 531) break; } } default: break; } } if (m_spellInfo->IsNonPeriodicDispel() && !m_IsTriggeredSpell) // Buff Abolir le poison non concerne par exemple (Debuff a chaque tic) { uint32 dispelMask = 0; bool bFoundOneDispell = false; // Compute Dispel Mask for (int i = 0; i < MAX_EFFECT_INDEX; ++i) { if (m_spellInfo->Effect[i] != SPELL_EFFECT_DISPEL) continue; // Create dispel mask by dispel type uint32 dispel_type = m_spellInfo->EffectMiscValue[i]; dispelMask |= GetDispellMask(DispelType(dispel_type)); } Unit* unit_target = m_targets.getUnitTarget(); bool friendly_dispel = unit_target && unit_target->IsFriendlyTo(m_caster); if (unit_target && // Check immune for offensive dispel (!unit_target->IsImmuneToSchoolMask(GetSpellSchoolMask(m_spellInfo)) || friendly_dispel)) { if (!friendly_dispel && !m_caster->IsValidAttackTarget(unit_target)) return SPELL_FAILED_BAD_TARGETS; auto const& auras = unit_target->GetSpellAuraHolderMap(); for (auto itr = auras.begin(); itr != auras.end(); ++itr) { SpellAuraHolder *holder = itr->second; if ((1 << holder->GetSpellProto()->Dispel) & dispelMask) { if (holder->GetSpellProto()->Dispel == DISPEL_MAGIC || holder->GetSpellProto()->Dispel == DISPEL_POISON) { bool positive = holder->IsPositive(); // do not remove positive auras if friendly target // do not remove negative auras if non-friendly target // when removing charm auras ignore hostile reaction from the charm if (!friendly_dispel && !positive && IsCharmSpell(holder->GetSpellProto())) if (CharmInfo *charm = unit_target->GetCharmInfo()) if (FactionTemplateEntry const* ft = charm->GetOriginalFactionTemplate()) if (charm->GetOriginalFactionTemplate()->IsFriendlyTo(*m_caster->getFactionTemplateEntry())) bFoundOneDispell = true; if (positive == friendly_dispel) continue; } if (holder->GetSpellProto()->Dispel == DISPEL_POISON) { switch (m_spellInfo->Id) { case 7932: // Anti-Venom if (holder->GetSpellProto()->spellLevel > 25) return SPELL_FAILED_HIGHLEVEL; break; case 7933: // Strong Anti-Venom if (holder->GetSpellProto()->spellLevel > 35) return SPELL_FAILED_HIGHLEVEL; break; case 23786: // Powerful Anti-Venom if (holder->GetSpellProto()->spellLevel > 60) return SPELL_FAILED_HIGHLEVEL; break; } } bFoundOneDispell = true; break; } } } // World of Warcraft Client Patch 1.10.0 (2006-03-28) // - All spells / abilities that remove existing effects(e.g.Dispel Magic, // Cleanse, Remove Curse etc...) will now verify that there is an effect // that can be removed before casting. #if SUPPORTED_CLIENT_CLIENT > CLIENT_BUILD_1_9_4 if (!bFoundOneDispell) { return SPELL_FAILED_NOTHING_TO_DISPEL; } #endif } for (int i = 0; i < MAX_EFFECT_INDEX; ++i) { switch (m_spellInfo->EffectApplyAuraName[i]) { case SPELL_AURA_MOD_POSSESS: { if (m_caster->GetTypeId() != TYPEID_PLAYER) return SPELL_FAILED_BAD_TARGETS; if (m_targets.getUnitTarget() == m_caster) return SPELL_FAILED_BAD_TARGETS; if (m_caster->GetPetGuid()) return SPELL_FAILED_ALREADY_HAVE_SUMMON; if (m_caster->GetCharmGuid()) return SPELL_FAILED_ALREADY_HAVE_CHARM; if (m_caster->GetCharmerGuid()) #if SUPPORTED_CLIENT_BUILD >= CLIENT_BUILD_1_11_2 return SPELL_FAILED_CHARMED; #else return SPELL_FAILED_FIZZLE; #endif if (!m_targets.getUnitTarget()) return SPELL_FAILED_BAD_IMPLICIT_TARGETS; if (m_targets.getUnitTarget()->GetCharmerGuid()) #if SUPPORTED_CLIENT_BUILD >= CLIENT_BUILD_1_11_2 return SPELL_FAILED_CHARMED; #else return SPELL_FAILED_FIZZLE; #endif if (m_spellInfo->Id != 530) // Spell for ".possess" command. if (int32(m_targets.getUnitTarget()->getLevel()) > CalculateDamage(SpellEffectIndex(i), m_targets.getUnitTarget())) return SPELL_FAILED_HIGHLEVEL; break; } case SPELL_AURA_MOD_CHARM: { if (m_targets.getUnitTarget() == m_caster) return SPELL_FAILED_BAD_TARGETS; if (m_caster->GetPetGuid()) return SPELL_FAILED_ALREADY_HAVE_SUMMON; if (m_caster->GetCharmGuid()) return SPELL_FAILED_ALREADY_HAVE_CHARM; if (m_caster->GetCharmerGuid()) #if SUPPORTED_CLIENT_BUILD >= CLIENT_BUILD_1_11_2 return SPELL_FAILED_CHARMED; #else return SPELL_FAILED_FIZZLE; #endif if (!m_targets.getUnitTarget()) return SPELL_FAILED_BAD_IMPLICIT_TARGETS; if (m_targets.getUnitTarget()->GetCharmerGuid()) #if SUPPORTED_CLIENT_BUILD >= CLIENT_BUILD_1_11_2 return SPELL_FAILED_CHARMED; #else return SPELL_FAILED_FIZZLE; #endif if (int32(m_targets.getUnitTarget()->getLevel()) > CalculateDamage(SpellEffectIndex(i), m_targets.getUnitTarget())) return SPELL_FAILED_HIGHLEVEL; // Mob tapped par un autre groupe. if (Player* caster = m_caster->ToPlayer()) if (Creature* crea = m_targets.getUnitTarget()->ToCreature()) if (crea->GetLootGroupRecipientId() || crea->GetLootRecipientGuid()) if (!crea->IsTappedBy(caster)) return SPELL_FAILED_CANT_CAST_ON_TAPPED; break; } case SPELL_AURA_MOD_POSSESS_PET: { if (m_caster->GetTypeId() != TYPEID_PLAYER) return SPELL_FAILED_BAD_TARGETS; if (m_caster->GetCharmGuid()) return SPELL_FAILED_ALREADY_HAVE_CHARM; if (m_caster->GetCharmerGuid()) #if SUPPORTED_CLIENT_BUILD >= CLIENT_BUILD_1_11_2 return SPELL_FAILED_CHARMED; #else return SPELL_FAILED_FIZZLE; #endif Pet* pet = m_caster->GetPet(); if (!pet) return SPELL_FAILED_NO_PET; if (pet->GetCharmerGuid()) #if SUPPORTED_CLIENT_BUILD >= CLIENT_BUILD_1_11_2 return SPELL_FAILED_CHARMED; #else return SPELL_FAILED_FIZZLE; #endif break; } case SPELL_AURA_MOUNTED: { if (m_caster->IsInWater()) return SPELL_FAILED_ONLY_ABOVEWATER; if (m_caster->GetTypeId() == TYPEID_PLAYER && ((Player*)m_caster)->GetTransport()) return SPELL_FAILED_NO_MOUNTS_ALLOWED; /// Specific case for Temple of Ahn'Qiraj mounts as they are usable only in AQ40 and are the only mounts allowed here /// TBC and above handle this by using m_spellInfo->AreaId bool isAQ40Mount = false; switch (m_spellInfo->Id) { case 25863: // spell used by the Black Qiraji Crystal script when mounting inside AQ40 case 25953: // spells of the 4 regular AQ40 mounts case 26054: case 26055: case 26056: if (m_caster->GetMapId() == 531) { isAQ40Mount = true; break; } else return SPELL_FAILED_NOT_HERE; default: if ((m_caster->GetMapId() == 531 && m_caster->GetTerrain()->IsOutdoors(m_caster->GetPositionX(), m_caster->GetPositionY(), m_caster->GetPositionZ()))) isAQ40Mount = true; break; } // Ignore map check if spell have AreaId. AreaId already checked and this prevent special mount spells if (!isAQ40Mount && m_caster->GetTypeId() == TYPEID_PLAYER && !sMapStorage.LookupEntry(m_caster->GetMapId())->IsMountAllowed() && !m_IsTriggeredSpell) //[-ZERO] && !m_spellInfo->AreaId) return SPELL_FAILED_NO_MOUNTS_ALLOWED; if (m_caster->GetAreaId() == 35) return SPELL_FAILED_NO_MOUNTS_ALLOWED; if (m_caster->IsInDisallowedMountForm()) return SPELL_FAILED_NOT_SHAPESHIFT; break; } case SPELL_AURA_RANGED_ATTACK_POWER_ATTACKER_BONUS: { if (!m_targets.getUnitTarget()) return SPELL_FAILED_BAD_IMPLICIT_TARGETS; // can be casted at non-friendly unit or own pet/charm if (m_caster->IsFriendlyTo(m_targets.getUnitTarget())) return SPELL_FAILED_TARGET_FRIENDLY; break; } case SPELL_AURA_PERIODIC_MANA_LEECH: { if (!m_targets.getUnitTarget() && !IsAreaEffectTarget(Targets(m_spellInfo->EffectImplicitTargetA[i])) && !IsAreaEffectTarget(Targets(m_spellInfo->EffectImplicitTargetB[i]))) return SPELL_FAILED_BAD_IMPLICIT_TARGETS; if (m_caster->GetTypeId() != TYPEID_PLAYER || m_CastItem) break; if (m_targets.getUnitTarget()->getPowerType() != POWER_MANA) return SPELL_FAILED_BAD_TARGETS; break; } case SPELL_AURA_MOD_SHAPESHIFT: { if (m_caster->HasAura(23397)) // Ustaag : Nefarian Warrior Class Call return SPELL_FAILED_NOT_SHAPESHIFT; break; } case SPELL_AURA_MOD_STALKED: { if (!m_targets.getUnitTarget()) return SPELL_FAILED_BAD_IMPLICIT_TARGETS; // Example: Hunter's mark #1130 if (m_spellInfo->EffectImplicitTargetA[i] == TARGET_UNIT_TARGET_ANY && !m_caster->IsValidAttackTarget(m_targets.getUnitTarget()) && !m_spellInfo->IsFitToFamily()) // exception for mind vision return SPELL_FAILED_BAD_TARGETS; break; } default: break; } } // check trade slot case (last, for allow catch any another cast problems) if (m_targets.m_targetMask & TARGET_FLAG_TRADE_ITEM) { if (m_caster->GetTypeId() != TYPEID_PLAYER) return SPELL_FAILED_NOT_TRADING; Player *pCaster = ((Player*)m_caster); TradeData* my_trade = pCaster->GetTradeData(); if (!my_trade) return SPELL_FAILED_NOT_TRADING; TradeSlots slot = TradeSlots(m_targets.getItemTargetGuid().GetRawValue()); if (slot != TRADE_SLOT_NONTRADED) return SPELL_FAILED_ITEM_NOT_READY; // if trade not complete then remember it in trade data if (!my_trade->IsInAcceptProcess()) { // Spell will be casted at completing the trade. Silently ignore at this place my_trade->SetSpell(m_spellInfo->Id, m_CastItem); return SPELL_FAILED_DONT_REPORT; } } // all ok return SPELL_CAST_OK; } SpellCastResult Spell::CheckPetCast(Unit* target) { if (!m_caster->isAlive()) return SPELL_FAILED_CASTER_DEAD; if (m_caster->IsNonMeleeSpellCasted(false)) //prevent spellcast interruption by another spellcast return SPELL_FAILED_SPELL_IN_PROGRESS; if (m_caster->isInCombat() && IsNonCombatSpell(m_spellInfo)) return SPELL_FAILED_AFFECTING_COMBAT; if (m_caster->GetTypeId() == TYPEID_UNIT && (((Creature*)m_caster)->IsPet() || m_caster->isCharmed())) { //dead owner (pets still alive when owners ressed?) if (m_caster->GetCharmerOrOwner() && !m_caster->GetCharmerOrOwner()->isAlive()) return SPELL_FAILED_CASTER_DEAD; if (!target && m_targets.getUnitTarget()) target = m_targets.getUnitTarget(); bool need = false; for (int i = 0; i < MAX_EFFECT_INDEX; ++i) { if (m_spellInfo->EffectImplicitTargetA[i] == TARGET_CHAIN_DAMAGE || m_spellInfo->EffectImplicitTargetA[i] == TARGET_SINGLE_FRIEND || m_spellInfo->EffectImplicitTargetA[i] == TARGET_SINGLE_FRIEND_2 || m_spellInfo->EffectImplicitTargetA[i] == TARGET_UNIT_TARGET_ANY || m_spellInfo->EffectImplicitTargetA[i] == TARGET_SINGLE_PARTY || m_spellInfo->EffectImplicitTargetA[i] == TARGET_CURRENT_ENEMY_COORDINATES) { need = true; if (!target) return SPELL_FAILED_BAD_IMPLICIT_TARGETS; break; } } if (need) m_targets.setUnitTarget(target); Unit* _target = m_targets.getUnitTarget(); if (_target) //for target dead/target not valid { if (!_target->isTargetableForAttack(false, m_caster->IsPlayer())) return SPELL_FAILED_BAD_TARGETS; // guessed error // SPELL_EFFECT_DISPEL -> Positive or negative depending on the target if (IsPositiveSpell(m_spellInfo) && !IsSpellHaveEffect(m_spellInfo, SPELL_EFFECT_DISPEL)) { if (m_caster->IsHostileTo(_target)) return SPELL_FAILED_BAD_TARGETS; } else { bool checkValidAttackTarget = true; for (int j = 0; j < MAX_EFFECT_INDEX; ++j) { if (m_spellInfo->EffectImplicitTargetA[j] == TARGET_UNIT_TARGET_ANY || m_spellInfo->EffectImplicitTargetA[j] == TARGET_IN_FRONT_OF_CASTER || m_spellInfo->EffectImplicitTargetA[j] == TARGET_CASTER_COORDINATES) checkValidAttackTarget = false; } if (checkValidAttackTarget && !m_caster->IsValidAttackTarget(_target)) return SPELL_FAILED_BAD_TARGETS; } } //cooldown if (m_caster->HasSpellCooldown(m_spellInfo->Id) || m_caster->HasSpellCategoryCooldown(m_spellInfo->Category)) return SPELL_FAILED_NOT_READY; } return CheckCast(true); } SpellCastResult Spell::CheckCasterAuras() const { // Flag drop spells totally immuned to caster auras // FIXME: find more nice check for all totally immuned spells // AttributesEx3 & 0x10000000? if (m_spellInfo->Id == 23336 || // Alliance Flag Drop m_spellInfo->Id == 23334) // Horde Flag Drop return SPELL_CAST_OK; uint8 school_immune = 0; uint32 mechanic_immune = 0; uint32 dispel_immune = 0; // Check if the spell grants school or mechanic immunity. // We use bitmasks so the loop is done only once and not on every aura check below. if (m_spellInfo->AttributesEx & SPELL_ATTR_EX_DISPEL_AURAS_ON_IMMUNITY) { for (int i = 0; i < MAX_EFFECT_INDEX; ++i) { if (m_spellInfo->EffectApplyAuraName[i] == SPELL_AURA_SCHOOL_IMMUNITY) school_immune |= uint32(m_spellInfo->EffectMiscValue[i]); else if (m_spellInfo->EffectApplyAuraName[i] == SPELL_AURA_MECHANIC_IMMUNITY) mechanic_immune |= 1 << uint32(m_spellInfo->EffectMiscValue[i] - 1); else if (m_spellInfo->EffectApplyAuraName[i] == SPELL_AURA_MECHANIC_IMMUNITY_MASK) mechanic_immune |= uint32(m_spellInfo->EffectMiscValue[i]); else if (m_spellInfo->EffectApplyAuraName[i] == SPELL_AURA_DISPEL_IMMUNITY) dispel_immune |= GetDispellMask(DispelType(m_spellInfo->EffectMiscValue[i])); } } // Check whether the cast should be prevented by any state you might have. SpellCastResult prevented_reason = SPELL_CAST_OK; // Have to check if there is a stun aura. Otherwise will have problems with ghost aura apply while logging out uint32 unitflag = m_caster->GetUInt32Value(UNIT_FIELD_FLAGS); // Get unit state if (unitflag & UNIT_FLAG_STUNNED && !(mechanic_immune & (1 << (MECHANIC_STUN - 1u)))) prevented_reason = SPELL_FAILED_STUNNED; else if (unitflag & UNIT_FLAG_CONFUSED && !(mechanic_immune & CONFUSED_MECHANIC_MASK)) prevented_reason = SPELL_FAILED_CONFUSED; else if (unitflag & UNIT_FLAG_FLEEING && !(mechanic_immune & (1 << (MECHANIC_FEAR - 1u)))) prevented_reason = SPELL_FAILED_FLEEING; else if (m_spellInfo->PreventionType == SPELL_PREVENTION_TYPE_SILENCE && (unitflag & UNIT_FLAG_SILENCED || m_caster->IsSpellProhibited(m_spellInfo))) // Nostalrius : fix counterspell for mobs. prevented_reason = SPELL_FAILED_SILENCED; else if (unitflag & UNIT_FLAG_PACIFIED && m_spellInfo->PreventionType == SPELL_PREVENTION_TYPE_PACIFY) prevented_reason = SPELL_FAILED_PACIFIED; // Attr must make flag drop spell totally immune from all effects if (prevented_reason != SPELL_CAST_OK) { if (school_immune || mechanic_immune || dispel_immune) { //Checking auras is needed now, because you are prevented by some state but the spell grants immunity. auto const& auras = m_caster->GetSpellAuraHolderMap(); for (auto itr = auras.begin(); itr != auras.end(); ++itr) { SpellAuraHolder *holder = itr->second; SpellEntry const * pEntry = holder->GetSpellProto(); if ((GetSpellSchoolMask(pEntry) & school_immune) && !(pEntry->AttributesEx & SPELL_ATTR_EX_UNAFFECTED_BY_SCHOOL_IMMUNE)) continue; if ((1 << (pEntry->Dispel)) & dispel_immune) continue; for (int32 i = 0; i < MAX_EFFECT_INDEX; ++i) { Aura *aura = holder->GetAuraByEffectIndex(SpellEffectIndex(i)); if (!aura) continue; if (GetSpellMechanicMask(pEntry, 1 << i) & mechanic_immune) continue; // Make a second check for spell failed so the right SPELL_FAILED message is returned. // That is needed when your casting is prevented by multiple states and you are only immune to some of them. switch (aura->GetModifier()->m_auraname) { case SPELL_AURA_MOD_STUN: if (!(mechanic_immune & (1 << (MECHANIC_STUN - 1u)))) return SPELL_FAILED_STUNNED; break; case SPELL_AURA_MOD_CONFUSE: if (!(mechanic_immune & CONFUSED_MECHANIC_MASK)) return SPELL_FAILED_CONFUSED; break; case SPELL_AURA_MOD_FEAR: if (!(mechanic_immune & (1 << (MECHANIC_FEAR - 1u)))) return SPELL_FAILED_FLEEING; break; case SPELL_AURA_MOD_SILENCE: case SPELL_AURA_MOD_PACIFY: case SPELL_AURA_MOD_PACIFY_SILENCE: if (m_spellInfo->PreventionType == SPELL_PREVENTION_TYPE_PACIFY) return SPELL_FAILED_PACIFIED; else if (m_spellInfo->PreventionType == SPELL_PREVENTION_TYPE_SILENCE) return SPELL_FAILED_SILENCED; break; default: break; } } } } // You are prevented from casting and the spell casted does not grant immunity. Return a failed error. else return prevented_reason; } return SPELL_CAST_OK; } bool Spell::CanAutoCast(Unit* target) { ObjectGuid targetguid = target->GetObjectGuid(); // Nostalrius - par exemple roder ne doit pas se declencher si on envoie le pet attaquer. // Sinon le pet revient a cause de cet attribut : if (m_spellInfo->Attributes & SPELL_ATTR_STOP_ATTACK_TARGET && m_caster->getVictim()) return false; // 2947 - Fire Shield, rank 1 enUS // When set to auto-cast, the Imp will cast this on any party members within 30 yds if they receive a melee attack. if (m_spellInfo->IsFitToFamily() && m_spellInfo->SpellVisual == 289) if (!target->getAttackerForHelper()) return false; if (!IsSpellHaveEffect(m_spellInfo, SPELL_EFFECT_SCHOOL_DAMAGE)) { for (int j = 0; j < MAX_EFFECT_INDEX; ++j) { if (m_spellInfo->Effect[j] == SPELL_EFFECT_APPLY_AURA) { if (m_spellInfo->StackAmount <= 1) { if (target->HasAura(m_spellInfo->Id, SpellEffectIndex(j))) return false; } } else if (IsAreaAuraEffect(m_spellInfo->Effect[j])) { if (target->HasAura(m_spellInfo->Id, SpellEffectIndex(j))) return false; } } } // Dont waste mana to heal someone already full life. if (IsHealSpell(m_spellInfo) && target->GetMaxHealth() == target->GetHealth()) return false; SpellCastResult result = CheckPetCast(target); if (result == SPELL_CAST_OK || result == SPELL_FAILED_UNIT_NOT_INFRONT) { FillTargetMap(); //check if among target units, our WANTED target is as well (->only self cast spells return false) for (TargetList::const_iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) if (!ihit->deleted && ihit->targetGUID == targetguid) return true; } return false; //target invalid } SpellCastResult Spell::CheckRange(bool strict) { Unit *target = m_targets.getUnitTarget(); // Pour 'TARGET_PET', 'getUnitTarget' renvoit le caster >.< for (int i = 0; i < MAX_EFFECT_INDEX; ++i) if (m_spellInfo->EffectImplicitTargetA[i] == TARGET_PET) if (m_caster) if (Pet* pPet = m_caster->GetPet()) target = pPet; // special range cases switch (m_spellInfo->rangeIndex) { // self cast doesn't need range checking -- also for Starshards fix case SPELL_RANGE_IDX_SELF_ONLY: return SPELL_CAST_OK; // combat range spells are treated differently case SPELL_RANGE_IDX_COMBAT: { if (target) { if (target == m_caster || IsNextMeleeSwingSpell()) return SPELL_CAST_OK; // Requires target forward for these spells if (!m_caster->HasInArc(M_PI_F, target)) return SPELL_FAILED_UNIT_NOT_INFRONT; float range_mod = 1.0f; if (Player* modOwner = m_caster->GetSpellModOwner()) { float base = ATTACK_DISTANCE; range_mod += modOwner->ApplySpellMod(m_spellInfo->Id, SPELLMOD_RANGE, base, this); } // with additional 5 dist for non stricted case (some melee spells have delay in apply return m_caster->CanReachWithMeleeSpellAttack(target, range_mod) ? SPELL_CAST_OK : SPELL_FAILED_OUT_OF_RANGE; } break; // let continue in generic way for no target } } // Add up to ~5 yds "give" for non strict (landing) check and leeway bonus if both units are moving float const range_mod = (strict ? (m_caster->IsPlayer() ? 1.25f : 0.0f) : (m_caster->IsPlayer() ? 6.25f : 2.25f)) + m_caster->GetLeewayBonusRange(target); SpellRangeEntry const* srange = sSpellRangeStore.LookupEntry(m_spellInfo->rangeIndex); float max_range = GetSpellMaxRange(srange); float min_range = GetSpellMinRange(srange); if (Player* modOwner = m_caster->GetSpellModOwner()) modOwner->ApplySpellMod(m_spellInfo->Id, SPELLMOD_RANGE, max_range, this); max_range += range_mod; GameObject* go = m_targets.getGOTarget(); // Check range for gobjects (lock picking) if (go) { float const dist = m_caster->GetDistance(go); if (dist > max_range) return SPELL_FAILED_OUT_OF_RANGE; } if (target && target != m_caster) { float const dist = m_caster->GetCombatDistance(target); if (dist > max_range) return SPELL_FAILED_OUT_OF_RANGE; if (min_range && dist < min_range) return SPELL_FAILED_TOO_CLOSE; if (m_caster->GetTypeId() == TYPEID_PLAYER && (m_spellInfo->Custom & SPELL_CUSTOM_FROM_FRONT) && !m_caster->HasInArc(M_PI_F, target)) return SPELL_FAILED_UNIT_NOT_INFRONT; } // TODO verify that such spells really use bounding radius if (m_targets.m_targetMask == TARGET_FLAG_DEST_LOCATION && m_targets.m_destX != 0 && m_targets.m_destY != 0 && m_targets.m_destZ != 0) { if (!m_caster->IsWithinDist3d(m_targets.m_destX, m_targets.m_destY, m_targets.m_destZ, max_range)) return SPELL_FAILED_OUT_OF_RANGE; if (min_range && m_caster->IsWithinDist3d(m_targets.m_destX, m_targets.m_destY, m_targets.m_destZ, min_range)) return SPELL_FAILED_TOO_CLOSE; } return SPELL_CAST_OK; } uint32 Spell::CalculatePowerCost(SpellEntry const* spellInfo, Unit* caster, Spell* spell, Item* castItem) { // item cast not used power if (castItem) return 0; // Spell drain all exist power on cast (Only paladin lay of Hands) if (spellInfo->AttributesEx & SPELL_ATTR_EX_DRAIN_ALL_POWER) { // If power type - health drain all if (spellInfo->powerType == POWER_HEALTH) return caster->GetHealth(); // Else drain all power if (spellInfo->powerType < MAX_POWERS) return caster->GetPower(Powers(spellInfo->powerType)); sLog.outError("Spell::CalculateManaCost: Unknown power type '%d' in spell %d", spellInfo->powerType, spellInfo->Id); return 0; } // Base powerCost int32 powerCost = spellInfo->manaCost; // PCT cost from total amount if (spellInfo->ManaCostPercentage) { switch (spellInfo->powerType) { // health as power used case POWER_HEALTH: powerCost += spellInfo->ManaCostPercentage * caster->GetCreateHealth() / 100; break; case POWER_MANA: powerCost += spellInfo->ManaCostPercentage * caster->GetCreateMana() / 100; break; case POWER_RAGE: case POWER_FOCUS: case POWER_ENERGY: case POWER_HAPPINESS: powerCost += spellInfo->ManaCostPercentage * caster->GetMaxPower(Powers(spellInfo->powerType)) / 100; break; default: sLog.outError("Spell::CalculateManaCost: Unknown power type '%d' in spell %d", spellInfo->powerType, spellInfo->Id); return 0; } } SpellSchools school = GetFirstSchoolInMask(spell ? spell->m_spellSchoolMask : GetSpellSchoolMask(spellInfo)); // Flat mod from caster auras by spell school powerCost += caster->GetInt32Value(UNIT_FIELD_POWER_COST_MODIFIER + school); // Shiv - costs 20 + weaponSpeed*10 energy (apply only to non-triggered spell with energy cost) if (spellInfo->AttributesEx4 & SPELL_ATTR_EX4_SPELL_VS_EXTEND_COST) powerCost += caster->GetAttackTime(OFF_ATTACK) / 100; // Apply cost mod by spell if (spell) if (Player* modOwner = caster->GetSpellModOwner()) modOwner->ApplySpellMod(spellInfo->Id, SPELLMOD_COST, powerCost, spell); if (spellInfo->Attributes & SPELL_ATTR_LEVEL_DAMAGE_CALCULATION) powerCost = int32(powerCost / (1.117f * spellInfo->spellLevel / caster->getLevel() - 0.1327f)); // PCT mod from user auras by school powerCost = int32(powerCost * (1.0f + caster->GetFloatValue(UNIT_FIELD_POWER_COST_MULTIPLIER + school))); if (powerCost < 0) powerCost = 0; return powerCost; } SpellCastResult Spell::CheckPower() const { // item cast not used power if (m_CastItem || m_IsTriggeredSpell) return SPELL_CAST_OK; // health as power used - need check health amount if (m_spellInfo->powerType == POWER_HEALTH) { if (m_caster->GetHealth() <= m_powerCost) return SPELL_FAILED_CASTER_AURASTATE; return SPELL_CAST_OK; } // Check valid power type if (m_spellInfo->powerType >= MAX_POWERS) { sLog.outError("Spell::CheckMana: Unknown power type '%d'", m_spellInfo->powerType); return SPELL_FAILED_DONT_REPORT; } // Check power amount Powers powerType = Powers(m_spellInfo->powerType); if (m_caster->GetPower(powerType) < m_powerCost) return SPELL_FAILED_NO_POWER; return SPELL_CAST_OK; } bool Spell::IgnoreItemRequirements() const { if (m_IsTriggeredSpell) { /// Not own traded item (in trader trade slot) req. reagents including triggered spell case if (Item* targetItem = m_targets.getItemTarget()) if (targetItem->GetOwnerGuid() != m_caster->GetObjectGuid()) return false; /// Some triggered spells have same reagents that have master spell /// expected in test: master spell have reagents in first slot then triggered don't must use own if (m_triggeredBySpellInfo && !m_triggeredBySpellInfo->Reagent[0]) return false; return true; } return false; } SpellCastResult Spell::CheckItems() { // Check creature casts again here, even though they are checked in CreatureAI // Need to do this for some melee attacks which are channeled, and then triggered // eg. 13736 if (Creature *creature = m_caster->ToCreature()) { // If the unit is disarmed and the skill requires a weapon, it cannot be cast if (creature->HasWeapon() && !creature->CanUseEquippedWeapon(BASE_ATTACK)) { for (int i = 0; i < MAX_EFFECT_INDEX; i++) { if (m_spellInfo->Effect[i] == SPELL_EFFECT_WEAPON_DAMAGE || m_spellInfo->Effect[i] == SPELL_EFFECT_WEAPON_DAMAGE_NOSCHOOL) return SPELL_FAILED_EQUIPPED_ITEM; } } } if (m_caster->GetTypeId() != TYPEID_PLAYER) return SPELL_CAST_OK; Player* p_caster = (Player*)m_caster; // cast item checks if (m_CastItem) { if (m_CastItem->IsInTrade()) return SPELL_FAILED_ITEM_GONE; uint32 itemid = m_CastItem->GetEntry(); if (!p_caster->HasItemCount(itemid, 1)) return SPELL_FAILED_ITEM_NOT_READY; ItemPrototype const *proto = m_CastItem->GetProto(); if (!proto) return SPELL_FAILED_ITEM_NOT_READY; for (int i = 0; i < 5; ++i) if (proto->Spells[i].SpellCharges) if (m_CastItem->GetSpellCharges(i) == 0) return SPELL_FAILED_NO_CHARGES_REMAIN; // World of Warcraft Client Patch 1.11.0 (2006-06-20) // - Rejuvenation Potions: Any type of potion or consumable that grants // mana and healing will no longer be consumable unless either your // health or your mana are below maximum. #if SUPPORTED_CLIENT_BUILD > CLIENT_BUILD_1_10_2 // consumable cast item checks if (proto->Class == ITEM_CLASS_CONSUMABLE && m_targets.getUnitTarget()) { // such items should only fail if there is no suitable effect at all - see Rejuvenation Potions for example SpellCastResult failReason = SPELL_CAST_OK; for (int i = 0; i < MAX_EFFECT_INDEX; ++i) { // skip check, pet not required like checks, and for TARGET_PET m_targets.getUnitTarget() is not the real target but the caster if (m_spellInfo->EffectImplicitTargetA[i] == TARGET_PET) continue; if (m_spellInfo->Effect[i] == SPELL_EFFECT_HEAL) { if (m_targets.getUnitTarget()->GetHealth() == m_targets.getUnitTarget()->GetMaxHealth()) { failReason = SPELL_FAILED_ALREADY_AT_FULL_HEALTH; continue; } else { failReason = SPELL_CAST_OK; break; } } // Mana Potion, Rage Potion, Thistle Tea(Rogue), ... if (m_spellInfo->Effect[i] == SPELL_EFFECT_ENERGIZE) { if (m_spellInfo->EffectMiscValue[i] < 0 || m_spellInfo->EffectMiscValue[i] >= MAX_POWERS) { failReason = SPELL_FAILED_ALREADY_AT_FULL_POWER; continue; } Powers power = Powers(m_spellInfo->EffectMiscValue[i]); if (m_targets.getUnitTarget()->GetPower(power) == m_targets.getUnitTarget()->GetMaxPower(power)) { failReason = SPELL_FAILED_ALREADY_AT_FULL_POWER; continue; } else { failReason = SPELL_CAST_OK; break; } } } if (failReason != SPELL_CAST_OK) return failReason; } #endif } // check target item (for triggered case not report error) if (m_targets.getItemTargetGuid()) { if (m_caster->GetTypeId() != TYPEID_PLAYER) return m_IsTriggeredSpell && !(m_targets.m_targetMask & TARGET_FLAG_TRADE_ITEM) ? SPELL_FAILED_DONT_REPORT : SPELL_FAILED_BAD_TARGETS; if (!m_targets.getItemTarget()) return m_IsTriggeredSpell && !(m_targets.m_targetMask & TARGET_FLAG_TRADE_ITEM) ? SPELL_FAILED_DONT_REPORT : SPELL_FAILED_ITEM_GONE; if (!m_targets.getItemTarget()->IsFitToSpellRequirements(m_spellInfo)) return m_IsTriggeredSpell && !(m_targets.m_targetMask & TARGET_FLAG_TRADE_ITEM) ? SPELL_FAILED_DONT_REPORT : SPELL_FAILED_EQUIPPED_ITEM_CLASS; } // if not item target then required item must be equipped (for triggered case not report error) else if (Player* pPlayer = m_caster->ToPlayer()) { Item const* ignore = nullptr; if (m_attackType == BASE_ATTACK) ignore = pPlayer->GetWeaponForAttack(OFF_ATTACK); else if (m_attackType == OFF_ATTACK) ignore = pPlayer->GetWeaponForAttack(BASE_ATTACK); if (!pPlayer->HasItemFitToSpellReqirements(m_spellInfo, ignore)) { return m_IsTriggeredSpell ? SPELL_FAILED_DONT_REPORT : SPELL_FAILED_EQUIPPED_ITEM_CLASS; } } // check spell focus object if (m_spellInfo->RequiresSpellFocus) { const float MAX_GAMEOBJECT_FOCUS_DISTANCE = 10.0f; // Max in DB is actually 7 GameObject* ok = nullptr; MaNGOS::GameObjectFocusCheck go_check(m_caster, m_spellInfo->RequiresSpellFocus); MaNGOS::GameObjectSearcher checker(ok, go_check); Cell::VisitGridObjects(m_caster, checker, MAX_GAMEOBJECT_FOCUS_DISTANCE); if (!ok) return SPELL_FAILED_REQUIRES_SPELL_FOCUS; focusObject = ok; // game object found in range } // check reagents (ignore triggered spells with reagents processed by original spell) and special reagent ignore case. if (!IgnoreItemRequirements()) { { for (uint32 i = 0; i < MAX_SPELL_REAGENTS; ++i) { if (m_spellInfo->Reagent[i] <= 0) continue; uint32 itemid = m_spellInfo->Reagent[i]; uint32 itemcount = m_spellInfo->ReagentCount[i]; // if CastItem is also spell reagent if (m_CastItem && m_CastItem->GetEntry() == itemid) { ItemPrototype const *proto = m_CastItem->GetProto(); if (!proto) return SPELL_FAILED_ITEM_NOT_READY; for (int s = 0; s < MAX_ITEM_PROTO_SPELLS; ++s) { // CastItem will be used up and does not count as reagent int32 charges = m_CastItem->GetSpellCharges(s); if (proto->Spells[s].SpellCharges < 0 && abs(charges) < 2 && itemcount > 1) { ++itemcount; break; } } } if (!p_caster->HasItemCount(itemid, itemcount)) return SPELL_FAILED_ITEM_NOT_READY; } } // check totem-item requirements (items presence in inventory) uint32 totems = MAX_SPELL_TOTEMS; for (int i = 0; i < MAX_SPELL_TOTEMS ; ++i) { if (m_spellInfo->Totem[i] != 0) { if (p_caster->HasItemCount(m_spellInfo->Totem[i], 1)) { totems -= 1; continue; } } else totems -= 1; } if (totems != 0) return SPELL_FAILED_ITEM_GONE; //[-ZERO] not sure of it } switch (m_spellInfo->Id) { case 6201: // Healthstone creating spells case 6202: case 5699: case 11729: case 11730: { if (!p_caster) break; uint32 rank = 0; uint32 itemtype; Unit::AuraList const& mDummyAuras = p_caster->GetAurasByType(SPELL_AURA_DUMMY); for (Unit::AuraList::const_iterator i = mDummyAuras.begin(); i != mDummyAuras.end(); ++i) { if ((*i)->GetId() == 18692) { rank = 1; break; } else if ((*i)->GetId() == 18693) { rank = 2; break; } } static uint32 const itypes[5][3] = { { 5512, 19004, 19005}, // Minor Healthstone { 5511, 19006, 19007}, // Lesser Healthstone { 5509, 19008, 19009}, // Healthstone { 5510, 19010, 19011}, // Greater Healthstone { 9421, 19012, 19013}, // Major Healthstone }; switch (m_spellInfo->Id) { case 6201: itemtype = itypes[0][rank]; break; // Minor Healthstone case 6202: itemtype = itypes[1][rank]; break; // Lesser Healthstone case 5699: itemtype = itypes[2][rank]; break; // Healthstone case 11729: itemtype = itypes[3][rank]; break; // Greater Healthstone case 11730: itemtype = itypes[4][rank]; break; // Major Healthstone } ItemPosCountVec dest; InventoryResult msg = p_caster->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, itemtype, 1); if (msg != EQUIP_ERR_OK) { p_caster->SendEquipError(msg, nullptr, nullptr, itemtype); return SPELL_FAILED_DONT_REPORT; } } } for (int i = 0; i < MAX_EFFECT_INDEX; ++i) { switch (m_spellInfo->Effect[i]) { case SPELL_EFFECT_CREATE_ITEM: { if (!m_IsTriggeredSpell && m_spellInfo->EffectItemType[i]) { ItemPosCountVec dest; Unit* createItemTarget = p_caster; if (m_spellInfo->Id == 24726) createItemTarget = m_targets.getUnitTarget(); // Quelle idee d'ajouter un item a un mob ... if (createItemTarget->GetTypeId() != TYPEID_PLAYER) return SPELL_FAILED_DONT_REPORT; InventoryResult msg = createItemTarget->ToPlayer()->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, m_spellInfo->EffectItemType[i], 1); if (msg != EQUIP_ERR_OK) { createItemTarget->ToPlayer() ->SendEquipError(msg, nullptr, nullptr, m_spellInfo->EffectItemType[i]); return SPELL_FAILED_DONT_REPORT; } } break; } case SPELL_EFFECT_ENCHANT_ITEM: { Item* targetItem = m_targets.getItemTarget(); if (!targetItem) return SPELL_FAILED_ITEM_GONE; if (targetItem->GetProto()->ItemLevel < m_spellInfo->baseLevel) return SPELL_FAILED_LOWLEVEL; // Not allow enchant in trade slot for some enchant type if (targetItem->GetOwner() != m_caster) { uint32 enchant_id = m_spellInfo->EffectMiscValue[i]; SpellItemEnchantmentEntry const *pEnchant = sSpellItemEnchantmentStore.LookupEntry(enchant_id); if (!pEnchant) return SPELL_FAILED_ERROR; if (pEnchant->slot & ENCHANTMENT_CAN_SOULBOUND) return SPELL_FAILED_NOT_TRADEABLE; } break; } case SPELL_EFFECT_ENCHANT_ITEM_TEMPORARY: { Item *item = m_targets.getItemTarget(); if (!item) return SPELL_FAILED_ITEM_GONE; // Not allow enchant in trade slot for some enchant type if (item->GetOwner() != m_caster) { uint32 enchant_id = m_spellInfo->EffectMiscValue[i]; SpellItemEnchantmentEntry const *pEnchant = sSpellItemEnchantmentStore.LookupEntry(enchant_id); if (!pEnchant) return SPELL_FAILED_ERROR; if (pEnchant->slot & ENCHANTMENT_CAN_SOULBOUND) return SPELL_FAILED_NOT_TRADEABLE; } break; } case SPELL_EFFECT_ENCHANT_HELD_ITEM: // check item existence in effect code (not output errors at offhand hold item effect to main hand for example break; case SPELL_EFFECT_DISENCHANT: { if (!m_targets.getItemTarget() || m_targets.getItemTarget()->HasGeneratedLoot()) return SPELL_FAILED_CANT_BE_DISENCHANTED; // prevent disenchanting in trade slot if (m_targets.getItemTarget()->GetOwnerGuid() != m_caster->GetObjectGuid()) return SPELL_FAILED_CANT_BE_DISENCHANTED; ItemPrototype const* itemProto = m_targets.getItemTarget()->GetProto(); if (!itemProto) return SPELL_FAILED_CANT_BE_DISENCHANTED; // must have disenchant loot (other static req. checked at item prototype loading) if (!itemProto->DisenchantID || (itemProto->Flags & ITEM_FLAG_NO_DISENCHANT)) return SPELL_FAILED_CANT_BE_DISENCHANTED; break; } case SPELL_EFFECT_WEAPON_DAMAGE: case SPELL_EFFECT_WEAPON_DAMAGE_NOSCHOOL: { if (m_caster->GetTypeId() != TYPEID_PLAYER) return SPELL_FAILED_TARGET_NOT_PLAYER; if (m_attackType != RANGED_ATTACK) break; Item *pItem = ((Player*)m_caster)->GetWeaponForAttack(m_attackType, true, false); if (!pItem) return SPELL_FAILED_EQUIPPED_ITEM; switch (pItem->GetProto()->SubClass) { case ITEM_SUBCLASS_WEAPON_THROWN: { uint32 ammo = pItem->GetEntry(); if (!((Player*)m_caster)->HasItemCount(ammo, 1)) return SPELL_FAILED_NO_AMMO; }; break; case ITEM_SUBCLASS_WEAPON_GUN: case ITEM_SUBCLASS_WEAPON_BOW: case ITEM_SUBCLASS_WEAPON_CROSSBOW: { uint32 ammo = ((Player*)m_caster)->GetUInt32Value(PLAYER_AMMO_ID); if (!ammo) return SPELL_FAILED_NO_AMMO; ItemPrototype const *ammoProto = ObjectMgr::GetItemPrototype(ammo); if (!ammoProto) return SPELL_FAILED_NO_AMMO; if (ammoProto->Class != ITEM_CLASS_PROJECTILE) return SPELL_FAILED_NO_AMMO; // check ammo ws. weapon compatibility switch (pItem->GetProto()->SubClass) { case ITEM_SUBCLASS_WEAPON_BOW: case ITEM_SUBCLASS_WEAPON_CROSSBOW: if (ammoProto->SubClass != ITEM_SUBCLASS_ARROW) return SPELL_FAILED_NO_AMMO; break; case ITEM_SUBCLASS_WEAPON_GUN: if (ammoProto->SubClass != ITEM_SUBCLASS_BULLET) return SPELL_FAILED_NO_AMMO; break; default: return SPELL_FAILED_NO_AMMO; } if (!((Player*)m_caster)->HasItemCount(ammo, 1)) return SPELL_FAILED_NO_AMMO; }; break; case ITEM_SUBCLASS_WEAPON_WAND: break; default: break; } break; } default: break; } } return SPELL_CAST_OK; } void Spell::Delayed() { if (!m_caster || m_caster->GetTypeId() != TYPEID_PLAYER) return; if (m_spellState == SPELL_STATE_DELAYED) return; // spell is active and can't be time-backed // spells not loosing casting time ( slam, dynamites, bombs.. ) if (!(m_spellInfo->InterruptFlags & SPELL_INTERRUPT_FLAG_DAMAGE)) return; if (m_caster->IsPlayer() && m_caster->ToPlayer()->HasOption(PLAYER_CHEAT_UNRANDOMIZE)) return; //check resist chance int32 resistChance = 100; //must be initialized to 100 for percent modifiers ((Player*)m_caster)->ApplySpellMod(m_spellInfo->Id, SPELLMOD_NOT_LOSE_CASTING_TIME, resistChance, this); resistChance += m_caster->GetTotalAuraModifier(SPELL_AURA_RESIST_PUSHBACK) - 100; if (roll_chance_i(resistChance)) return; int32 delaytime = GetNextDelayAtDamageMsTime(); if (int32(m_timer) + delaytime > m_casttime) { delaytime = m_casttime - m_timer; m_timer = m_casttime; } else m_timer += delaytime; DETAIL_FILTER_LOG(LOG_FILTER_SPELL_CAST, "Spell %u partially interrupted for (%d) ms at damage", m_spellInfo->Id, delaytime); WorldPacket data(SMSG_SPELL_DELAYED, 8 + 4); data << ObjectGuid(m_caster->GetObjectGuid()); data << uint32(delaytime); if (m_caster->GetTypeId() == TYPEID_PLAYER) ((Player*)m_caster)->SendDirectMessage(&data); } void Spell::DelayedChannel() { if (!m_caster || m_caster->GetTypeId() != TYPEID_PLAYER || getState() != SPELL_STATE_CASTING) return; //check resist chance int32 resistChance = 100; //must be initialized to 100 for percent modifiers ((Player*)m_caster)->ApplySpellMod(m_spellInfo->Id, SPELLMOD_NOT_LOSE_CASTING_TIME, resistChance, this); resistChance += m_caster->GetTotalAuraModifier(SPELL_AURA_RESIST_PUSHBACK) - 100; if (roll_chance_i(resistChance)) return; int32 delaytime = GetNextDelayAtDamageMsTime(); if (int32(m_timer) < delaytime) { delaytime = m_timer; m_timer = 0; } else m_timer -= delaytime; DEBUG_FILTER_LOG(LOG_FILTER_SPELL_CAST, "Spell %u partially interrupted for %i ms, new duration: %u ms", m_spellInfo->Id, delaytime, m_timer); for (TargetList::const_iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) { if (ihit->deleted) continue; if ((*ihit).missCondition == SPELL_MISS_NONE) { if (Unit* unit = m_caster->GetObjectGuid() == ihit->targetGUID ? m_caster : ObjectAccessor::GetUnit(*m_caster, ihit->targetGUID)) unit->DelaySpellAuraHolder(m_spellInfo->Id, delaytime, m_caster->GetObjectGuid()); } } for (int j = 0; j < MAX_EFFECT_INDEX; ++j) { // partially interrupt persistent area auras if (DynamicObject* dynObj = m_caster->GetDynObject(m_spellInfo->Id, SpellEffectIndex(j))) dynObj->Delay(delaytime); } if (m_timer == 0) m_caster->InterruptSpell(CURRENT_CHANNELED_SPELL); else SendChannelUpdate(m_timer); } void Spell::UpdateOriginalCasterPointer() { if (m_originalCasterGUID == m_caster->GetObjectGuid()) m_originalCaster = m_caster; else if (m_originalCasterGUID.IsGameObject()) { GameObject* go = m_caster->IsInWorld() ? m_caster->GetMap()->GetGameObject(m_originalCasterGUID) : NULL; m_originalCaster = go ? go->GetOwner() : NULL; } else { Unit* unit = ObjectAccessor::GetUnit(*m_caster, m_originalCasterGUID); m_originalCaster = unit && unit->IsInWorld() ? unit : NULL; } } void Spell::UpdatePointers() { UpdateOriginalCasterPointer(); m_targets.Update(m_caster); } bool Spell::CheckTargetCreatureType(Unit* target) const { if (target->HasAura(8179)) // Totem de glebe absorbe tout (pour dispel le buff quand il absorbe un sort) return true; uint32 spellCreatureTargetMask = m_spellInfo->TargetCreatureType; // Curse of Doom : not find another way to fix spell target check :/ if (m_spellInfo->Id == 603) // in 1.12 "Curse of doom" have only 1 rank. { // not allow cast at player if (target->GetCharmerOrOwnerPlayerOrPlayerItself()) return false; spellCreatureTargetMask = 0x7FF; } // Dismiss Pet and Taming Lesson skipped if (m_spellInfo->Id == 2641 || m_spellInfo->Id == 23356) spellCreatureTargetMask = 0; if (spellCreatureTargetMask) { uint32 TargetCreatureType = target->GetCreatureTypeMask(); return !TargetCreatureType || (spellCreatureTargetMask & TargetCreatureType); } return true; } CurrentSpellTypes Spell::GetCurrentContainer() const { if (IsNextMeleeSwingSpell()) return (CURRENT_MELEE_SPELL); else if (IsAutoRepeat()) return (CURRENT_AUTOREPEAT_SPELL); else if (m_channeled) return (CURRENT_CHANNELED_SPELL); else return (CURRENT_GENERIC_SPELL); } bool Spell::CheckTarget(Unit* target, SpellEffectIndex eff) { if (target != m_caster && IsPositiveSpell(m_spellInfo)) { Player* casterOwner = m_caster->GetCharmerOrOwnerPlayerOrPlayerItself(); Player* targetOwner = target->GetCharmerOrOwnerPlayerOrPlayerItself(); if (targetOwner && casterOwner != targetOwner && targetOwner->duel && casterOwner && !casterOwner->IsInDuelWith(targetOwner)) return false; } // Check targets for creature type mask and remove not appropriate (skip explicit self target case, maybe need other explicit targets) if (m_spellInfo->EffectImplicitTargetA[eff] != TARGET_SELF) { if (!CheckTargetCreatureType(target)) return false; } // Check targets for not_selectable unit flag and remove // A player can cast spells on his pet (or other controlled unit) though in any state if (target != m_caster && target->GetCharmerOrOwnerGuid() != m_caster->GetObjectGuid()) { // any unattackable target skipped if (target->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE)) return false; // unselectable targets skipped in all cases except TARGET_SCRIPT targeting // in case TARGET_SCRIPT target selected by server always and can't be cheated if ((!m_IsTriggeredSpell || target != m_targets.getUnitTarget()) && target->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE) && m_spellInfo->EffectImplicitTargetA[eff] != TARGET_SCRIPT && m_spellInfo->EffectImplicitTargetB[eff] != TARGET_SCRIPT && m_spellInfo->EffectImplicitTargetA[eff] != TARGET_AREAEFFECT_INSTANT && m_spellInfo->EffectImplicitTargetB[eff] != TARGET_AREAEFFECT_INSTANT && m_spellInfo->EffectImplicitTargetA[eff] != TARGET_AREAEFFECT_CUSTOM && m_spellInfo->EffectImplicitTargetB[eff] != TARGET_AREAEFFECT_CUSTOM) return false; } // Check player targets and remove if in GM mode or GM invisibility (for not self casting case) if (target != m_caster && target->GetTypeId() == TYPEID_PLAYER) { if (((Player*)target)->GetVisibility() == VISIBILITY_OFF) return false; if (((Player*)target)->IsGameMaster() && !IsPositiveSpell(m_spellInfo)) return false; } // Check targets for LOS visibility (except spells without range limitations ) switch (m_spellInfo->Effect[eff]) { case SPELL_EFFECT_SUMMON_PLAYER: // from anywhere break; case SPELL_EFFECT_DUMMY: if (m_spellInfo->Id != 20577) // Cannibalize break; // no break. Cannibalize checks corpse target LOS. // fall through case SPELL_EFFECT_RESURRECT: case SPELL_EFFECT_RESURRECT_NEW: // player far away, maybe his corpse near? if (target != m_caster && !(m_spellInfo->AttributesEx2 & SPELL_ATTR_EX2_IGNORE_LOS) && !target->IsWithinLOSInMap(m_caster)) { if (!m_targets.getCorpseTargetGuid()) return false; Corpse *corpse = m_caster->GetMap()->GetCorpse(m_targets.getCorpseTargetGuid()); if (!corpse) return false; if (target->GetObjectGuid() != corpse->GetOwnerGuid()) return false; if (!(m_spellInfo->AttributesEx2 & SPELL_ATTR_EX2_IGNORE_LOS) && !corpse->IsWithinLOSInMap(m_caster)) return false; } // all ok by some way or another, skip normal check break; default: // normal case // Get GO cast coordinates if original caster -> GO if (target != m_caster) if (WorldObject *caster = GetCastingObject()) if (!(m_spellInfo->AttributesEx2 & SPELL_ATTR_EX2_IGNORE_LOS) && !target->IsWithinLOSInMap(caster)) return false; break; } if (target->GetTypeId() != TYPEID_PLAYER && m_spellInfo->AttributesEx3 & SPELL_ATTR_EX3_TARGET_ONLY_PLAYER && m_spellInfo->EffectImplicitTargetA[eff] != TARGET_SCRIPT && m_spellInfo->EffectImplicitTargetA[eff] != TARGET_SELF) return false; return true; } bool Spell::IsNeedSendToClient() const { return !IsChannelingVisual() && (m_spellInfo->SpellVisual != 0 || m_channeled || m_spellInfo->speed > 0.0f || (!m_triggeredByAuraSpell && !m_IsTriggeredSpell)); } bool Spell::IsTriggeredSpellWithRedundentData() const { return m_triggeredByAuraSpell || m_triggeredBySpellInfo || // possible not need after above check? m_IsTriggeredSpell && (m_spellInfo->manaCost || m_spellInfo->ManaCostPercentage); } bool Spell::HaveTargetsForEffect(SpellEffectIndex effect) const { for (auto itr = m_UniqueTargetInfo.begin(); itr != m_UniqueTargetInfo.end(); ++itr) if (!itr->deleted && itr->effectMask & (1 << effect)) return true; for (auto itr = m_UniqueGOTargetInfo.begin(); itr != m_UniqueGOTargetInfo.end(); ++itr) if (!itr->deleted && itr->effectMask & (1 << effect)) return true; for (auto itr = m_UniqueItemInfo.begin(); itr != m_UniqueItemInfo.end(); ++itr) if (!itr->deleted && itr->effectMask & (1 << effect)) return true; return false; } SpellEvent::SpellEvent(Spell* spell) : BasicEvent() { m_Spell = spell; } SpellEvent::~SpellEvent() { if (m_Spell->getState() != SPELL_STATE_FINISHED) m_Spell->cancel(); m_Spell->Delete(); } bool SpellEvent::Execute(uint64 e_time, uint32 p_time) { // update spell if it is not finished if (m_Spell->getState() != SPELL_STATE_FINISHED) m_Spell->update(p_time); // check spell state to process switch (m_Spell->getState()) { case SPELL_STATE_FINISHED: { // spell was finished, check deletable state if (m_Spell->IsDeletable()) { // check, if we do have unfinished triggered spells return true; // spell is deletable, finish event } // event will be re-added automatically at the end of routine) } break; case SPELL_STATE_CASTING: { // this spell is in channeled state, process it on the next update // event will be re-added automatically at the end of routine) } break; case SPELL_STATE_DELAYED: { // first, check, if we have just started if (m_Spell->GetDelayStart() != 0) { // no, we aren't, do the typical update // check, if we have channeled spell on our hands if (IsChanneledSpell(m_Spell->m_spellInfo)) { // evented channeled spell is processed separately, casted once after delay, and not destroyed till finish // check, if we have casting anything else except this channeled spell and autorepeat if (m_Spell->GetCaster()->IsNonMeleeSpellCasted(false, true, true)) { // another non-melee non-delayed spell is casted now, abort m_Spell->cancel(); } else { // do the action (pass spell to channeling state) try { m_Spell->SetExecutedCurrently(true); m_Spell->handle_immediate(); m_Spell->SetExecutedCurrently(false); } catch (std::runtime_error &e) { sLog.outInfo("[Spell/Crash] 'handle_immediate()' [%u:%s]", m_Spell->m_spellInfo->Id, m_Spell->GetCaster()->GetName()); sLog.outInfo(e.what()); m_Spell->SetExecutedCurrently(false); return false; } } // event will be re-added automatically at the end of routine) } else { // run the spell handler and think about what we can do next try { uint64 t_offset = e_time - m_Spell->GetDelayStart(); uint64 n_offset = m_Spell->handle_delayed(t_offset); if (n_offset) { // re-add us to the queue m_Spell->GetCaster()->m_Events.AddEvent(this, m_Spell->GetDelayStart() + n_offset, false); return false; // event not complete } // event complete // finish update event will be re-added automatically at the end of routine) } catch (std::runtime_error &e) { sLog.outInfo("[Spell/Crash] 'handle_immediate()' [%u:%s]", m_Spell->m_spellInfo->Id, m_Spell->GetCaster()->GetName()); sLog.outInfo(e.what()); return false; } } } else { // delaying had just started, record the moment m_Spell->SetDelayStart(e_time); // re-plan the event for the delay moment m_Spell->GetCaster()->m_Events.AddEvent(this, e_time + m_Spell->GetDelayMoment(), false); return false; // event not complete } } break; default: { // all other states // event will be re-added automatically at the end of routine) } break; } // spell processing not complete, plan event on the next update interval m_Spell->GetCaster()->m_Events.AddEvent(this, e_time + 1, false); return false; // event not complete } void SpellEvent::Abort(uint64 /*e_time*/) { // oops, the spell we try to do is aborted if (m_Spell->getState() != SPELL_STATE_FINISHED) m_Spell->cancel(); } bool SpellEvent::IsDeletable() const { return m_Spell->IsDeletable(); } SpellCastResult Spell::CanOpenLock(SpellEffectIndex effIndex, uint32 lockId, SkillType& skillId, int32& reqSkillValue, int32& skillValue) { if (!lockId) // possible case for GO and maybe for items. return SPELL_CAST_OK; // Get LockInfo LockEntry const *lockInfo = sLockStore.LookupEntry(lockId); if (!lockInfo) return SPELL_FAILED_BAD_TARGETS; for (int j = 0; j < 8; ++j) { switch (lockInfo->Type[j]) { // check key item (many fit cases can be) case LOCK_KEY_ITEM: if (lockInfo->Index[j] && m_CastItem && m_CastItem->GetEntry() == lockInfo->Index[j]) return SPELL_CAST_OK; break; // check key skill (only single first fit case can be) case LOCK_KEY_SKILL: { // wrong locktype, skip if (uint32(m_spellInfo->EffectMiscValue[effIndex]) != lockInfo->Index[j]) continue; skillId = SkillByLockType(LockType(lockInfo->Index[j])); if ((skillId != SKILL_NONE) || (LockType(lockInfo->Index[j]) == LOCKTYPE_BLASTING)) // Ustaag : ajout pour prise en compte des charges de Seaforium (item ingenieur) { // skill bonus provided by casting spell (mostly item spells) // add the damage modifier from the spell casted (cheat lock / skeleton key etc.) (use m_currentBasePoints, CalculateDamage returns wrong value) uint32 spellSkillBonus = uint32(m_currentBasePoints[effIndex]); reqSkillValue = lockInfo->Skill[j]; // castitem check: rogue using skeleton keys. the skill values should not be added in this case. skillValue = m_CastItem || m_caster->GetTypeId() != TYPEID_PLAYER ? 0 : ((Player*)m_caster)->GetSkillValue(skillId); skillValue += spellSkillBonus; if (skillValue < reqSkillValue) return SPELL_FAILED_LOW_CASTLEVEL; } return SPELL_CAST_OK; } } } // No requirement met = locked. // Rq: Lock 85 = no requirement at all = always locked return SPELL_FAILED_BAD_TARGETS; } class SpellNotifierCreatureAndPlayer { public: Spell::UnitList *i_data; Spell &i_spell; SpellNotifyPushType i_push_type; float i_radius; SpellTargets i_TargetType; WorldObject* i_originalCaster; WorldObject* i_castingObject; bool i_playerControlled; float i_centerX; float i_centerY; float GetCenterX() const { return i_centerX; } float GetCenterY() const { return i_centerY; } SpellNotifierCreatureAndPlayer(Spell &spell, Spell::UnitList &data, float radius, SpellNotifyPushType type, SpellTargets TargetType = SPELL_TARGETS_NOT_FRIENDLY, WorldObject* originalCaster = nullptr) : i_data(&data), i_spell(spell), i_push_type(type), i_radius(radius), i_TargetType(TargetType), i_originalCaster(originalCaster), i_castingObject(i_spell.GetCastingObject()) { if (!i_originalCaster) i_originalCaster = i_spell.GetAffectiveCasterObject(); i_playerControlled = i_originalCaster ? i_originalCaster->IsControlledByPlayer() : false; switch (i_push_type) { case PUSH_IN_FRONT: case PUSH_IN_FRONT_90: case PUSH_IN_FRONT_15: case PUSH_IN_BACK: case PUSH_SELF_CENTER: if (i_castingObject) { i_centerX = i_castingObject->GetPositionX(); i_centerY = i_castingObject->GetPositionY(); } break; case PUSH_DEST_CENTER: i_centerX = i_spell.m_targets.m_destX; i_centerY = i_spell.m_targets.m_destY; break; case PUSH_TARGET_CENTER: if (Unit* target = i_spell.m_targets.getUnitTarget()) { i_centerX = target->GetPositionX(); i_centerY = target->GetPositionY(); } break; default: sLog.outError("SpellNotifierCreatureAndPlayer: unsupported PUSH_* case %u.", i_push_type); } } template void Visit(GridRefManager &m) { MANGOS_ASSERT(i_data); if (!i_originalCaster || !i_castingObject) return; for (typename GridRefManager::iterator itr = m.begin(); itr != m.end(); ++itr) { // The template is only defined for Player and Creature maps. If it is extended // in the future, we should swap to WorldObject. Furthermore, we will have to // ensure all the checks are not using invalid casts. Unit* unit = itr->getSource(); // there are still more spells which can be casted on dead, but // they are no AOE and don't have such a nice SPELL_ATTR flag if (!unit->IsInMap(i_originalCaster)) continue; if (i_TargetType != SPELL_TARGETS_ALL) { if (!unit->isAttackableByAOE(i_spell.m_spellInfo->AttributesEx3 & SPELL_ATTR_EX3_CAST_ON_DEAD, i_playerControlled)) continue; } switch (i_TargetType) { case SPELL_TARGETS_HOSTILE: if (!i_originalCaster->IsHostileTo(unit)) continue; break; case SPELL_TARGETS_NOT_FRIENDLY: if (i_originalCaster->IsFriendlyTo(unit)) continue; break; case SPELL_TARGETS_NOT_HOSTILE: if (i_originalCaster->IsHostileTo(unit)) continue; break; case SPELL_TARGETS_FRIENDLY: if (!i_originalCaster->IsFriendlyTo(unit)) continue; break; case SPELL_TARGETS_AOE_DAMAGE: { if (unit->IsCreature() && static_cast(unit)->IsImmuneToAoe()) continue; if (i_originalCaster->IsFriendlyTo(unit)) continue; Unit* casterUnit = i_originalCaster->ToUnit(); if (!casterUnit && i_originalCaster->ToGameObject()) casterUnit = i_originalCaster->ToGameObject()->GetOwner(); if (casterUnit) { if (!casterUnit->IsValidAttackTarget(unit)) continue; // Negative AoE from non flagged players cannot target other players if (Player *attackedPlayer = unit->GetCharmerOrOwnerPlayerOrPlayerItself()) if (Player* casterPlayer = casterUnit->ToPlayer()) if (!casterPlayer->IsPvP() && !(casterPlayer->IsFFAPvP() && attackedPlayer->IsFFAPvP()) && !casterPlayer->IsInDuelWith(attackedPlayer)) continue; } else if (GameObject* gobj = i_originalCaster->ToGameObject()) { if (gobj->IsFriendlyTo(unit)) continue; } } break; case SPELL_TARGETS_ALL: break; default: continue; } // we don't need to check InMap here, it's already done some lines above switch (i_push_type) { case PUSH_IN_FRONT: if (i_castingObject->isInFront(unit, i_radius, 2 * M_PI_F / 3)) i_data->push_back(unit); break; case PUSH_IN_FRONT_90: if (i_castingObject->isInFront(unit, i_radius, M_PI_F / 2)) i_data->push_back(unit); break; case PUSH_IN_FRONT_15: if (i_castingObject->isInFront(unit, i_radius, M_PI_F / 12)) i_data->push_back(unit); break; case PUSH_IN_BACK: // 75 if (i_castingObject->isInBack(unit, i_radius, 5 * M_PI_F / 12)) i_data->push_back(unit); break; case PUSH_SELF_CENTER: if (i_castingObject->IsWithinDist(unit, i_radius)) i_data->push_back(unit); break; case PUSH_DEST_CENTER: if (itr->getSource()->IsWithinDist3d(i_spell.m_targets.m_destX, i_spell.m_targets.m_destY, i_spell.m_targets.m_destZ, i_radius)) i_data->push_back(unit); break; case PUSH_TARGET_CENTER: if (i_spell.m_targets.getUnitTarget() && i_spell.m_targets.getUnitTarget()->IsWithinDist(unit, i_radius)) i_data->push_back(unit); break; } } } #ifdef WIN32 template<> void Visit(CorpseMapType &) {} template<> void Visit(GameObjectMapType &) {} template<> void Visit(DynamicObjectMapType &) {} template<> void Visit(CameraMapType &) {} #endif }; #ifndef WIN32 template<> inline void SpellNotifierCreatureAndPlayer::Visit(CorpseMapType&) {} template<> inline void SpellNotifierCreatureAndPlayer::Visit(GameObjectMapType&) {} template<> inline void SpellNotifierCreatureAndPlayer::Visit(DynamicObjectMapType&) {} template<> inline void SpellNotifierCreatureAndPlayer::Visit(CameraMapType&) {} #endif /** * Fill target list by units around (x,y) points at radius distance * @param targetUnitMap Reference to target list that filled by function * @param x X coordinates of center point for target search * @param y Y coordinates of center point for target search * @param radius Radius around (x,y) for target search * @param pushType Additional rules for target area selection (in front, angle, etc) * @param spellTargets Additional rules for target selection base at hostile/friendly state to original spell caster * @param originalCaster If provided set alternative original caster, if =NULL then used Spell::GetAffectiveObject() return */ void Spell::FillAreaTargets(UnitList &targetUnitMap, float radius, SpellNotifyPushType pushType, SpellTargets spellTargets, WorldObject* originalCaster /*=NULL*/) { SpellNotifierCreatureAndPlayer notifier(*this, targetUnitMap, radius, pushType, spellTargets, originalCaster); Cell::VisitAllObjects(notifier.GetCenterX(), notifier.GetCenterY(), m_caster->GetMap(), notifier, radius); } void Spell::FillRaidOrPartyTargets(UnitList &TagUnitMap, Unit* target, float radius, bool raid, bool withPets, bool withcaster) const { Player *pTarget = target->GetCharmerOrOwnerPlayerOrPlayerItself(); Group *pGroup = pTarget ? pTarget->GetGroup() : NULL; if (pGroup) { uint8 subgroup = pTarget->GetSubGroup(); for (GroupReference *itr = pGroup->GetFirstMember(); itr != nullptr; itr = itr->next()) { Player* target = itr->getSource(); // IsHostileTo check duel and controlled by enemy if (target && (raid || subgroup == target->GetSubGroup()) && target->getLevel() + 10 >= m_spellInfo->spellLevel && !m_caster->IsHostileTo(target)) { if (target == m_caster && withcaster || target != m_caster && m_caster->IsWithinDistInMap(target, radius)) TagUnitMap.push_back(target); if (withPets) if (Pet* pet = target->GetPet()) if (pet == m_caster && withcaster || pet != m_caster && m_caster->IsWithinDistInMap(pet, radius)) TagUnitMap.push_back(pet); } } } else { Unit* ownerOrSelf = pTarget ? pTarget : target->GetCharmerOrOwnerOrSelf(); if (ownerOrSelf == m_caster && withcaster || ownerOrSelf != m_caster && m_caster->IsWithinDistInMap(ownerOrSelf, radius)) TagUnitMap.push_back(ownerOrSelf); if (withPets) if (Pet* pet = ownerOrSelf->GetPet()) if (pet == m_caster && withcaster || pet != m_caster && m_caster->IsWithinDistInMap(pet, radius)) TagUnitMap.push_back(pet); } } WorldObject* Spell::GetAffectiveCasterObject() const { if (!m_originalCasterGUID) return m_caster; if (m_originalCasterGUID.IsGameObject() && m_caster->IsInWorld()) return m_caster->GetMap()->GetGameObject(m_originalCasterGUID); return m_originalCaster; } WorldObject* Spell::GetCastingObject() const { if (m_originalCasterGUID.IsGameObject()) return m_caster->IsInWorld() ? m_caster->GetMap()->GetGameObject(m_originalCasterGUID) : NULL; return m_caster; } void Spell::ClearCastItem() { if (m_CastItem == m_targets.getItemTarget()) m_targets.setItemTarget(nullptr); m_CastItem = nullptr; } bool Spell::HasGlobalCooldown() const { return m_caster->GetGlobalCooldownMgr().HasGlobalCooldown(m_spellInfo); } void Spell::TriggerGlobalCooldown() { if (Player* pPlayer = m_caster->ToPlayer()) if (pPlayer->HasOption(PLAYER_CHEAT_NO_COOLDOWN)) return; int32 gcd = m_spellInfo->StartRecoveryTime; if (!gcd) return; // global cooldown can't leave range 1..1.5 secs (if it it) // exist some spells (mostly not player directly casted) that have < 1 sec and > 1.5 sec global cooldowns // but its as test show not affected any spell mods. if (gcd >= 1000 && gcd <= 1500) { // apply haste rating #if SUPPORTED_CLIENT_BUILD >= CLIENT_BUILD_1_12_1 gcd = int32(float(gcd) * m_caster->GetFloatValue(UNIT_MOD_CAST_SPEED)); #else gcd = int32(float(gcd) * (1.0f + m_caster->GetInt32Value(UNIT_MOD_CAST_SPEED)/100.0f)); #endif if (gcd < 1000) gcd = 1000; else if (gcd > 1500) gcd = 1500; } // global cooldown only for player or controlled units if (m_caster->GetCharmInfo() || m_caster->GetTypeId() == TYPEID_PLAYER) m_caster->GetGlobalCooldownMgr().AddGlobalCooldown(m_spellInfo, gcd); } void Spell::CancelGlobalCooldown() { if (!m_spellInfo->StartRecoveryTime) return; // cancel global cooldown when interrupting current cast if (m_caster->GetCurrentSpell(CURRENT_GENERIC_SPELL) != this) return; // global cooldown have only player or controlled units if (m_caster->GetCharmInfo() || m_caster->GetTypeId() == TYPEID_PLAYER) m_caster->GetGlobalCooldownMgr().CancelGlobalCooldown(m_spellInfo); } void Spell::ResetEffectDamageAndHeal() { m_damage = 0; m_healing = 0; m_absorbed = 0; } void Spell::SetClientStarted(bool bisClientStarted) { m_isClientStarted = bisClientStarted; } void Spell::OnSpellLaunch() { if (!m_caster || !m_caster->IsInWorld()) return; // Make sure the player is sending a valid GO target and lock ID. SPELL_EFFECT_OPEN_LOCK // can succeed with a lockId of 0 if (m_spellInfo->Id == 21651) { if (GameObject *go = m_targets.getGOTarget()) { LockEntry const *lockInfo = sLockStore.LookupEntry(go->GetGOInfo()->GetLockId()); if (lockInfo && lockInfo->Index[1] == LOCKTYPE_SLOW_OPEN) { Spell *visual = new Spell(m_caster, sSpellMgr.GetSpellEntry(24390), true); visual->prepare(); } } } unitTarget = m_targets.getUnitTarget(); // Gestion de la charge if (!unitTarget || !unitTarget->IsInWorld()) return; bool isCharge = false; for (int i = 0; i < 3; ++i) if (m_spellInfo->Effect[i] == SPELL_EFFECT_CHARGE) isCharge = true; if (!isCharge) return; // Delay attack, otherwise player makes instant attack after cast if (m_caster->GetTypeId() == TYPEID_PLAYER) { m_caster->setAttackTimer(BASE_ATTACK, m_caster->getAttackTimer(BASE_ATTACK) + 200 + 40 * m_caster->GetDistance(unitTarget)); m_caster->setAttackTimer(OFF_ATTACK, m_caster->getAttackTimer(OFF_ATTACK) + 200 + 40 * m_caster->GetDistance(unitTarget)); } bool triggerAutoAttack = unitTarget != m_caster && !IsPositiveSpell(m_spellInfo) && !(m_spellInfo->Attributes & SPELL_ATTR_STOP_ATTACK_TARGET); m_caster->GetMotionMaster()->MoveCharge(unitTarget, sWorld.getConfig(CONFIG_UINT32_SPELLS_CCDELAY), triggerAutoAttack); } bool Spell::HasModifierApplied(SpellModifier* mod) { for (std::list::const_iterator it = m_appliedMods.begin(); it != m_appliedMods.end(); ++it) if ((*it) == mod) return true; return false; } void Spell::RemoveStealthAuras() { // stealth must be removed at cast starting (at show channel bar) // skip triggered spell (item equip spell casting and other not explicit character casts/item uses) if (!m_IsTriggeredSpell && !(m_spellInfo->AttributesEx & SPELL_ATTR_EX_NOT_BREAK_STEALTH) && m_caster->GetTypeId() == TYPEID_PLAYER && m_spellInfo->SpellIconID != 250 // Camouflage fufu && m_spellInfo->SpellIconID != 103 // Roder ou Camouflage elfe de la nuit && m_spellInfo->SpellIconID != 252 // Disparition ) { bool doUnaura = m_caster->HasAuraType(SPELL_AURA_MOD_STEALTH); if (doUnaura && m_caster->GetTypeId() == TYPEID_PLAYER && m_spellInfo->SpellIconID == 249 // Assommer ) { // Gestion de assommer ameliore if (m_caster->HasAura(14076)) // Rang 1 doUnaura = (urand(0, 99) > 30); else if (m_caster->HasAura(14094)) // Rang 2 doUnaura = (urand(0, 99) > 60); else if (m_caster->HasAura(14095)) // Rang 3 doUnaura = (urand(0, 99) > 90); } if (doUnaura) m_caster->RemoveSpellsCausingAura(SPELL_AURA_MOD_STEALTH); } if (!m_IsTriggeredSpell) m_caster->RemoveSpellsCausingAura(SPELL_AURA_FEIGN_DEATH); } void Spell::Delete() const { if (IsDeletable()) delete this; else sLog.outInfo("[CRASH] Deleting in-use spell. SpellID=%u", m_spellInfo->Id); } bool ChannelResetEvent::Execute(uint64 e_time, uint32) { Abort(e_time); return true; } void ChannelResetEvent::Abort(uint64 e_time) { Spell* currSpell = caster->GetCurrentSpell(CURRENT_CHANNELED_SPELL); if (!currSpell || currSpell->getState() == SPELL_STATE_FINISHED) { caster->SetChannelObjectGuid(ObjectGuid()); caster->SetUInt32Value(UNIT_CHANNEL_SPELL, 0); if (caster->IsPlayer()) { WorldPacket data(MSG_CHANNEL_UPDATE, 4); data << uint32(0); ((Player*)caster)->SendDirectMessage(&data); } } }