Skip to content
Fetching contributors…
Cannot retrieve contributors at this time
executable file 7347 lines (6400 sloc) 285 KB
/*
* Copyright (C) 2008-2012 TrinityCore <http://www.trinitycore.org/>
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Common.h"
#include "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 "Totem.h"
#include "Spell.h"
#include "DynamicObject.h"
#include "Group.h"
#include "UpdateData.h"
#include "MapManager.h"
#include "ObjectAccessor.h"
#include "CellImpl.h"
#include "SharedDefines.h"
#include "LootMgr.h"
#include "VMapFactory.h"
#include "Battleground.h"
#include "Util.h"
#include "TemporarySummon.h"
#include "Vehicle.h"
#include "SpellAuraEffects.h"
#include "ScriptMgr.h"
#include "ConditionMgr.h"
#include "DisableMgr.h"
#include "SpellScript.h"
#include "InstanceScript.h"
#include "SpellInfo.h"
extern pEffect SpellEffects[TOTAL_SPELL_EFFECTS];
SpellCastTargets::SpellCastTargets() : m_elevation(0), m_speed(0)
{
m_objectTarget = NULL;
m_itemTarget = NULL;
m_objectTargetGUID = 0;
m_itemTargetGUID = 0;
m_itemTargetEntry = 0;
m_srcTransGUID = 0;
m_srcTransOffset.Relocate(0, 0, 0, 0);
m_srcPos.Relocate(0, 0, 0, 0);
m_dstTransGUID = 0;
m_dstTransOffset.Relocate(0, 0, 0, 0);
m_dstPos.Relocate(0, 0, 0, 0);
m_strTarget = "";
m_targetMask = 0;
}
SpellCastTargets::~SpellCastTargets()
{
}
void SpellCastTargets::Read(ByteBuffer& data, Unit* caster)
{
data >> m_targetMask;
if (m_targetMask == TARGET_FLAG_NONE)
return;
if (m_targetMask & (TARGET_FLAG_UNIT | TARGET_FLAG_UNIT_MINIPET | TARGET_FLAG_GAMEOBJECT | TARGET_FLAG_CORPSE_ENEMY | TARGET_FLAG_CORPSE_ALLY))
data.readPackGUID(m_objectTargetGUID);
if (m_targetMask & (TARGET_FLAG_ITEM | TARGET_FLAG_TRADE_ITEM))
data.readPackGUID(m_itemTargetGUID);
if (m_targetMask & TARGET_FLAG_SOURCE_LOCATION)
{
data.readPackGUID(m_srcTransGUID);
if (m_srcTransGUID)
data >> m_srcTransOffset.PositionXYZStream();
else
data >> m_srcPos.PositionXYZStream();
}
else
{
m_srcTransGUID = caster->GetTransGUID();
if (m_srcTransGUID)
m_srcTransOffset.Relocate(caster->GetTransOffsetX(), caster->GetTransOffsetY(), caster->GetTransOffsetZ(), caster->GetTransOffsetO());
else
m_srcPos.Relocate(caster);
}
if (m_targetMask & TARGET_FLAG_DEST_LOCATION)
{
data.readPackGUID(m_dstTransGUID);
if (m_dstTransGUID)
data >> m_dstTransOffset.PositionXYZStream();
else
data >> m_dstPos.PositionXYZStream();
}
else
{
m_dstTransGUID = caster->GetTransGUID();
if (m_dstTransGUID)
m_dstTransOffset.Relocate(caster->GetTransOffsetX(), caster->GetTransOffsetY(), caster->GetTransOffsetZ(), caster->GetTransOffsetO());
else
m_dstPos.Relocate(caster);
}
if (m_targetMask & TARGET_FLAG_STRING)
data >> m_strTarget;
Update(caster);
}
void SpellCastTargets::Write(ByteBuffer& data)
{
data << uint32(m_targetMask);
if (m_targetMask & (TARGET_FLAG_UNIT | TARGET_FLAG_CORPSE_ALLY | TARGET_FLAG_GAMEOBJECT | TARGET_FLAG_CORPSE_ENEMY | TARGET_FLAG_UNIT_MINIPET))
data.appendPackGUID(m_objectTargetGUID);
if (m_targetMask & (TARGET_FLAG_ITEM | TARGET_FLAG_TRADE_ITEM))
{
if (m_itemTarget)
data.append(m_itemTarget->GetPackGUID());
else
data << uint8(0);
}
if (m_targetMask & TARGET_FLAG_SOURCE_LOCATION)
{
data.appendPackGUID(m_srcTransGUID); // relative position guid here - transport for example
if (m_srcTransGUID)
data << m_srcTransOffset.PositionXYZStream();
else
data << m_srcPos.PositionXYZStream();
}
if (m_targetMask & TARGET_FLAG_DEST_LOCATION)
{
data.appendPackGUID(m_dstTransGUID); // relative position guid here - transport for example
if (m_dstTransGUID)
data << m_dstTransOffset.PositionXYZStream();
else
data << m_dstPos.PositionXYZStream();
}
if (m_targetMask & TARGET_FLAG_STRING)
data << m_strTarget;
}
uint64 SpellCastTargets::GetUnitTargetGUID() const
{
switch (GUID_HIPART(m_objectTargetGUID))
{
case HIGHGUID_PLAYER:
case HIGHGUID_VEHICLE:
case HIGHGUID_UNIT:
case HIGHGUID_PET:
return m_objectTargetGUID;
default:
return 0LL;
}
}
Unit* SpellCastTargets::GetUnitTarget() const
{
if (m_objectTarget)
return m_objectTarget->ToUnit();
return NULL;
}
void SpellCastTargets::SetUnitTarget(Unit* target)
{
if (!target)
return;
m_objectTarget = target;
m_objectTargetGUID = target->GetGUID();
m_targetMask |= TARGET_FLAG_UNIT;
}
uint64 SpellCastTargets::GetGOTargetGUID() const
{
switch (GUID_HIPART(m_objectTargetGUID))
{
case HIGHGUID_TRANSPORT:
case HIGHGUID_MO_TRANSPORT:
case HIGHGUID_GAMEOBJECT:
return m_objectTargetGUID;
default:
return 0LL;
}
}
GameObject* SpellCastTargets::GetGOTarget() const
{
if (m_objectTarget)
return m_objectTarget->ToGameObject();
return NULL;
}
void SpellCastTargets::SetGOTarget(GameObject* target)
{
if (!target)
return;
m_objectTarget = target;
m_objectTargetGUID = target->GetGUID();
m_targetMask |= TARGET_FLAG_GAMEOBJECT;
}
uint64 SpellCastTargets::GetCorpseTargetGUID() const
{
switch (GUID_HIPART(m_objectTargetGUID))
{
case HIGHGUID_CORPSE:
return m_objectTargetGUID;
default:
return 0LL;
}
}
Corpse* SpellCastTargets::GetCorpseTarget() const
{
if (m_objectTarget)
return m_objectTarget->ToCorpse();
return NULL;
}
WorldObject* SpellCastTargets::GetObjectTarget() const
{
return m_objectTarget;
}
uint64 SpellCastTargets::GetObjectTargetGUID() const
{
return m_objectTargetGUID;
}
void SpellCastTargets::RemoveObjectTarget()
{
m_objectTarget = NULL;
m_objectTargetGUID = 0LL;
m_targetMask &= ~(TARGET_FLAG_UNIT_MASK | TARGET_FLAG_CORPSE_MASK | TARGET_FLAG_GAMEOBJECT_MASK);
}
void SpellCastTargets::SetItemTarget(Item* item)
{
if (!item)
return;
m_itemTarget = item;
m_itemTargetGUID = item->GetGUID();
m_itemTargetEntry = item->GetEntry();
m_targetMask |= TARGET_FLAG_ITEM;
}
void SpellCastTargets::SetTradeItemTarget(Player* caster)
{
m_itemTargetGUID = uint64(TRADE_SLOT_NONTRADED);
m_itemTargetEntry = 0;
m_targetMask |= TARGET_FLAG_TRADE_ITEM;
Update(caster);
}
void SpellCastTargets::UpdateTradeSlotItem()
{
if (m_itemTarget && (m_targetMask & TARGET_FLAG_TRADE_ITEM))
{
m_itemTargetGUID = m_itemTarget->GetGUID();
m_itemTargetEntry = m_itemTarget->GetEntry();
}
}
Position const* SpellCastTargets::GetSrc() const
{
return &m_srcPos;
}
void SpellCastTargets::SetSrc(float x, float y, float z)
{
m_srcPos.Relocate(x, y, z);
m_srcTransGUID = 0;
m_targetMask |= TARGET_FLAG_SOURCE_LOCATION;
}
void SpellCastTargets::SetSrc(Position const& pos)
{
m_srcPos.Relocate(pos);
m_srcTransGUID = 0;
m_targetMask |= TARGET_FLAG_SOURCE_LOCATION;
}
void SpellCastTargets::SetSrc(WorldObject const& wObj)
{
uint64 guid = wObj.GetTransGUID();
m_srcTransGUID = guid;
m_srcTransOffset.Relocate(wObj.GetTransOffsetX(), wObj.GetTransOffsetY(), wObj.GetTransOffsetZ(), wObj.GetTransOffsetO());
m_srcPos.Relocate(wObj);
m_targetMask |= TARGET_FLAG_SOURCE_LOCATION;
}
void SpellCastTargets::ModSrc(Position const& pos)
{
ASSERT(m_targetMask & TARGET_FLAG_SOURCE_LOCATION);
if (m_srcTransGUID)
{
Position offset;
m_srcPos.GetPositionOffsetTo(pos, offset);
m_srcTransOffset.RelocateOffset(offset);
}
m_srcPos.Relocate(pos);
}
void SpellCastTargets::RemoveSrc()
{
m_targetMask &= ~(TARGET_FLAG_SOURCE_LOCATION);
}
WorldLocation const* SpellCastTargets::GetDst() const
{
return &m_dstPos;
}
void SpellCastTargets::SetDst(float x, float y, float z, float orientation, uint32 mapId)
{
m_dstPos.Relocate(x, y, z, orientation);
m_dstTransGUID = 0;
m_targetMask |= TARGET_FLAG_DEST_LOCATION;
if (mapId != MAPID_INVALID)
m_dstPos.m_mapId = mapId;
}
void SpellCastTargets::SetDst(Position const& pos)
{
m_dstPos.Relocate(pos);
m_dstTransGUID = 0;
m_targetMask |= TARGET_FLAG_DEST_LOCATION;
}
void SpellCastTargets::SetDst(WorldObject const& wObj)
{
uint64 guid = wObj.GetTransGUID();
m_dstTransGUID = guid;
m_dstTransOffset.Relocate(wObj.GetTransOffsetX(), wObj.GetTransOffsetY(), wObj.GetTransOffsetZ(), wObj.GetTransOffsetO());
m_dstPos.Relocate(wObj);
m_targetMask |= TARGET_FLAG_DEST_LOCATION;
}
void SpellCastTargets::SetDst(SpellCastTargets const& spellTargets)
{
m_dstTransGUID = spellTargets.m_dstTransGUID;
m_dstTransOffset.Relocate(spellTargets.m_dstTransOffset);
m_dstPos.Relocate(spellTargets.m_dstPos);
m_targetMask |= TARGET_FLAG_DEST_LOCATION;
}
void SpellCastTargets::ModDst(Position const& pos)
{
ASSERT(m_targetMask & TARGET_FLAG_DEST_LOCATION);
if (m_dstTransGUID)
{
Position offset;
m_dstPos.GetPositionOffsetTo(pos, offset);
m_dstTransOffset.RelocateOffset(offset);
}
m_dstPos.Relocate(pos);
}
void SpellCastTargets::RemoveDst()
{
m_targetMask &= ~(TARGET_FLAG_DEST_LOCATION);
}
void SpellCastTargets::Update(Unit* caster)
{
m_objectTarget = m_objectTargetGUID ? ((m_objectTargetGUID == caster->GetGUID()) ? caster : ObjectAccessor::GetWorldObject(*caster, m_objectTargetGUID)) : NULL;
m_itemTarget = NULL;
if (caster->GetTypeId() == TYPEID_PLAYER)
{
Player* player = caster->ToPlayer();
if (m_targetMask & TARGET_FLAG_ITEM)
m_itemTarget = player->GetItemByGuid(m_itemTargetGUID);
else if (m_targetMask & TARGET_FLAG_TRADE_ITEM)
if (m_itemTargetGUID == TRADE_SLOT_NONTRADED) // here it is not guid but slot. Also prevents hacking slots
if (TradeData* pTrade = player->GetTradeData())
m_itemTarget = pTrade->GetTraderData()->GetItem(TRADE_SLOT_NONTRADED);
if (m_itemTarget)
m_itemTargetEntry = m_itemTarget->GetEntry();
}
// update positions by transport move
if (HasSrc() && m_srcTransGUID)
{
if (WorldObject* transport = ObjectAccessor::GetWorldObject(*caster, m_srcTransGUID))
{
m_srcPos.Relocate(transport);
m_srcPos.RelocateOffset(m_srcTransOffset);
}
}
if (HasDst() && m_dstTransGUID)
{
if (WorldObject* transport = ObjectAccessor::GetWorldObject(*caster, m_dstTransGUID))
{
m_dstPos.Relocate(transport);
m_dstPos.RelocateOffset(m_dstTransOffset);
}
}
}
void SpellCastTargets::OutDebug() const
{
if (!m_targetMask)
sLog->outString("No targets");
sLog->outString("target mask: %u", m_targetMask);
if (m_targetMask & (TARGET_FLAG_UNIT_MASK | TARGET_FLAG_CORPSE_MASK | TARGET_FLAG_GAMEOBJECT_MASK))
sLog->outString("Object target: " UI64FMTD, m_objectTargetGUID);
if (m_targetMask & TARGET_FLAG_ITEM)
sLog->outString("Item target: " UI64FMTD, m_itemTargetGUID);
if (m_targetMask & TARGET_FLAG_TRADE_ITEM)
sLog->outString("Trade item target: " UI64FMTD, m_itemTargetGUID);
if (m_targetMask & TARGET_FLAG_SOURCE_LOCATION)
sLog->outString("Source location: transport guid:" UI64FMTD " trans offset: %s position: %s", m_srcTransGUID, m_srcTransOffset.ToString().c_str(), m_srcPos.ToString().c_str());
if (m_targetMask & TARGET_FLAG_DEST_LOCATION)
sLog->outString("Destination location: transport guid:" UI64FMTD " trans offset: %s position: %s", m_dstTransGUID, m_dstTransOffset.ToString().c_str(), m_dstPos.ToString().c_str());
if (m_targetMask & TARGET_FLAG_STRING)
sLog->outString("String: %s", m_strTarget.c_str());
sLog->outString("speed: %f", m_speed);
sLog->outString("elevation: %f", m_elevation);
}
SpellValue::SpellValue(SpellInfo const* proto)
{
for (uint32 i = 0; i < MAX_SPELL_EFFECTS; ++i)
EffectBasePoints[i] = proto->Effects[i].BasePoints;
MaxAffectedTargets = proto->MaxAffectedTargets;
RadiusMod = 1.0f;
AuraStackAmount = 1;
}
Spell::Spell(Unit* caster, SpellInfo const* info, TriggerCastFlags triggerFlags, uint64 originalCasterGUID, bool skipCheck) :
m_spellInfo(sSpellMgr->GetSpellForDifficultyFromSpell(info, caster)),
m_caster((info->AttributesEx6 & SPELL_ATTR6_CAST_BY_CHARMER && caster->GetCharmerOrOwner()) ? caster->GetCharmerOrOwner() : caster)
, m_spellValue(new SpellValue(m_spellInfo))
{
m_customError = SPELL_CUSTOM_ERROR_NONE;
m_skipCheck = skipCheck;
m_selfContainer = NULL;
m_referencedFromCurrentSpell = false;
m_executedCurrently = false;
m_needComboPoints = m_spellInfo->NeedsComboPoints();
m_comboPointGain = 0;
m_delayStart = 0;
m_delayAtDamageCount = 0;
m_applyMultiplierMask = 0;
m_auraScaleMask = 0;
// Get data for type of attack
switch (m_spellInfo->DmgClass)
{
case SPELL_DAMAGE_CLASS_MELEE:
if (m_spellInfo->AttributesEx3 & SPELL_ATTR3_REQ_OFFHAND)
m_attackType = OFF_ATTACK;
else
m_attackType = BASE_ATTACK;
break;
case SPELL_DAMAGE_CLASS_RANGED:
m_attackType = m_spellInfo->IsRangedWeaponSpell() ? RANGED_ATTACK : BASE_ATTACK;
break;
default:
// Wands
if (m_spellInfo->AttributesEx2 & SPELL_ATTR2_AUTOREPEAT_FLAG)
m_attackType = RANGED_ATTACK;
else
m_attackType = BASE_ATTACK;
break;
}
m_spellSchoolMask = info->GetSchoolMask(); // Can be override for some spell (wand shoot for example)
if (m_attackType == RANGED_ATTACK)
// wand case
if ((m_caster->getClassMask() & CLASSMASK_WAND_USERS) != 0 && m_caster->GetTypeId() == TYPEID_PLAYER)
if (Item* pItem = m_caster->ToPlayer()->GetWeaponForAttack(RANGED_ATTACK))
m_spellSchoolMask = SpellSchoolMask(1 << pItem->GetTemplate()->Damage[0].DamageType);
if (originalCasterGUID)
m_originalCasterGUID = originalCasterGUID;
else
m_originalCasterGUID = m_caster->GetGUID();
if (m_originalCasterGUID == m_caster->GetGUID())
m_originalCaster = m_caster;
else
{
m_originalCaster = ObjectAccessor::GetUnit(*m_caster, m_originalCasterGUID);
if (m_originalCaster && !m_originalCaster->IsInWorld())
m_originalCaster = NULL;
}
m_spellState = SPELL_STATE_NULL;
_triggeredCastFlags = triggerFlags;
if (info->AttributesEx4 & SPELL_ATTR4_TRIGGERED)
_triggeredCastFlags = TRIGGERED_FULL_MASK;
m_CastItem = NULL;
m_castItemGUID = 0;
unitTarget = NULL;
itemTarget = NULL;
gameObjTarget = NULL;
focusObject = NULL;
m_cast_count = 0;
m_glyphIndex = 0;
m_preCastSpell = 0;
m_triggeredByAuraSpell = NULL;
m_spellAura = NULL;
//Auto Shot & Shoot (wand)
m_autoRepeat = m_spellInfo->IsAutoRepeatRangedSpell();
m_runesState = 0;
m_powerCost = 0; // setup to correct value in Spell::prepare, must not be used before.
m_casttime = 0; // setup to correct value in Spell::prepare, must not be used before.
m_timer = 0; // will set to castime in prepare
m_channelTargetEffectMask = 0;
// Determine if spell can be reflected back to the caster
// Patch 1.2 notes: Spell Reflection no longer reflects abilities
m_canReflect = m_spellInfo->DmgClass == SPELL_DAMAGE_CLASS_MAGIC && !(m_spellInfo->Attributes & SPELL_ATTR0_ABILITY)
&& !(m_spellInfo->AttributesEx & SPELL_ATTR1_CANT_BE_REFLECTED) && !(m_spellInfo->Attributes & SPELL_ATTR0_UNAFFECTED_BY_INVULNERABILITY)
&& !m_spellInfo->IsPassive() && !m_spellInfo->IsPositive();
CleanupTargetList();
CleanupEffectExecuteData();
}
Spell::~Spell()
{
// unload scripts
while (!m_loadedScripts.empty())
{
std::list<SpellScript*>::iterator itr = m_loadedScripts.begin();
(*itr)->_Unload();
delete (*itr);
m_loadedScripts.erase(itr);
}
if (m_referencedFromCurrentSpell && m_selfContainer && *m_selfContainer == this)
{
// Clean the reference to avoid later crash.
// If this error is repeating, we may have to add an ASSERT to better track down how we get into this case.
sLog->outError("SPELL: deleting spell for spell ID %u. However, spell still referenced.", m_spellInfo->Id);
*m_selfContainer = NULL;
}
if (m_caster && m_caster->GetTypeId() == TYPEID_PLAYER)
ASSERT(m_caster->ToPlayer()->m_spellModTakingSpell != this);
delete m_spellValue;
CheckEffectExecuteData();
}
void Spell::InitExplicitTargets(SpellCastTargets const& targets)
{
m_targets = targets;
// this function tries to correct spell explicit targets for spell
// client doesn't send explicit targets correctly sometimes - we need to fix such spells serverside
// this also makes sure that we correctly send explicit targets to client (removes redundant data)
uint32 neededTargets = m_spellInfo->GetExplicitTargetMask();
if (WorldObject* target = m_targets.GetObjectTarget())
{
// check if object target is valid with needed target flags
// for unit case allow corpse target mask because player with not released corpse is a unit target
if ((target->ToUnit() && !(neededTargets & (TARGET_FLAG_UNIT_MASK | TARGET_FLAG_CORPSE_MASK)))
|| (target->ToGameObject() && !(neededTargets & TARGET_FLAG_GAMEOBJECT_MASK))
|| (target->ToCorpse() && !(neededTargets & TARGET_FLAG_CORPSE_MASK)))
m_targets.RemoveObjectTarget();
}
else
{
// try to select correct unit target if not provided by client or by serverside cast
if (neededTargets & (TARGET_FLAG_UNIT_MASK))
{
Unit* unit = NULL;
// try to use player selection as a target
if (Player* playerCaster = m_caster->ToPlayer())
{
// selection has to be found and to be valid target for the spell
if (Unit* selectedUnit = ObjectAccessor::GetUnit(*m_caster, playerCaster->GetSelection()))
if (m_spellInfo->CheckExplicitTarget(m_caster, selectedUnit) == SPELL_CAST_OK)
unit = selectedUnit;
}
// try to use attacked unit as a target
else if ((m_caster->GetTypeId() == TYPEID_UNIT) && neededTargets & (TARGET_FLAG_UNIT_ENEMY | TARGET_FLAG_UNIT))
unit = m_caster->getVictim();
// didn't find anything - let's use self as target
if (!unit && neededTargets & (TARGET_FLAG_UNIT_RAID | TARGET_FLAG_UNIT_PARTY | TARGET_FLAG_UNIT_ALLY))
unit = m_caster;
m_targets.SetUnitTarget(unit);
}
}
// check if spell needs dst target
if (neededTargets & TARGET_FLAG_DEST_LOCATION)
{
// and target isn't set
if (!m_targets.HasDst())
{
// try to use unit target if provided
if (WorldObject* target = targets.GetObjectTarget())
m_targets.SetDst(*target);
// or use self if not available
else
m_targets.SetDst(*m_caster);
}
}
else
m_targets.RemoveDst();
if (neededTargets & TARGET_FLAG_SOURCE_LOCATION)
{
if (!targets.HasSrc())
m_targets.SetSrc(*m_caster);
}
else
m_targets.RemoveSrc();
}
void Spell::SelectExplicitTargets()
{
// here go all explicit target changes made to explicit targets after spell prepare phase is finished
if (Unit* target = m_targets.GetUnitTarget())
{
// check for explicit target redirection, for Grounding Totem for example
if (m_spellInfo->GetExplicitTargetMask() & TARGET_FLAG_UNIT_ENEMY
|| (m_spellInfo->GetExplicitTargetMask() & TARGET_FLAG_UNIT && !m_spellInfo->IsPositive()))
{
Unit* redirect;
switch (m_spellInfo->DmgClass)
{
case SPELL_DAMAGE_CLASS_MAGIC:
redirect = m_caster->GetMagicHitRedirectTarget(target, m_spellInfo);
break;
case SPELL_DAMAGE_CLASS_MELEE:
case SPELL_DAMAGE_CLASS_RANGED:
redirect = m_caster->GetMeleeHitRedirectTarget(target, m_spellInfo);
break;
default:
redirect = NULL;
break;
}
if (redirect && (redirect != target))
m_targets.SetUnitTarget(redirect);
}
}
}
void Spell::SelectSpellTargets()
{
// select targets for cast phase
SelectExplicitTargets();
uint32 processedAreaEffectsMask = 0;
for (uint32 i = 0; i < MAX_SPELL_EFFECTS; ++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->Effects[i].IsEffect())
continue;
// set expected type of implicit targets to be sent to client
uint32 implicitTargetMask = GetTargetFlagMask(m_spellInfo->Effects[i].TargetA.GetObjectType()) | GetTargetFlagMask(m_spellInfo->Effects[i].TargetB.GetObjectType());
if (implicitTargetMask & TARGET_FLAG_UNIT)
m_targets.SetTargetFlag(TARGET_FLAG_UNIT);
if (implicitTargetMask & (TARGET_FLAG_GAMEOBJECT | TARGET_FLAG_GAMEOBJECT_ITEM))
m_targets.SetTargetFlag(TARGET_FLAG_GAMEOBJECT);
SelectEffectImplicitTargets(SpellEffIndex(i), m_spellInfo->Effects[i].TargetA, processedAreaEffectsMask);
SelectEffectImplicitTargets(SpellEffIndex(i), m_spellInfo->Effects[i].TargetB, processedAreaEffectsMask);
// Select targets of effect based on effect type
// those are used when no valid target could be added for spell effect based on spell target type
// some spell effects use explicit target as a default target added to target map (like SPELL_EFFECT_LEARN_SPELL)
// some spell effects add target to target map only when target type specified (like SPELL_EFFECT_WEAPON)
// some spell effects don't add anything to target map (confirmed with sniffs) (like SPELL_EFFECT_DESTROY_ALL_TOTEMS)
SelectEffectTypeImplicitTargets(i);
if (m_spellInfo->IsChanneled())
{
uint8 mask = (1 << i);
for (std::list<TargetInfo>::iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit)
{
if (ihit->effectMask & mask)
{
m_channelTargetEffectMask |= mask;
break;
}
}
}
else if (m_auraScaleMask)
{
bool checkLvl = !m_UniqueTargetInfo.empty();
for (std::list<TargetInfo>::iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end();)
{
// remove targets which did not pass min level check
if (m_auraScaleMask && ihit->effectMask == m_auraScaleMask)
{
// Do not check for selfcast
if (!ihit->scaleAura && ihit->targetGUID != m_caster->GetGUID())
{
m_UniqueTargetInfo.erase(ihit++);
continue;
}
}
++ihit;
}
if (checkLvl && m_UniqueTargetInfo.empty())
{
SendCastResult(SPELL_FAILED_LOWLEVEL);
finish(false);
}
}
}
if (m_targets.HasDst())
{
if (m_targets.HasTraj())
{
float speed = m_targets.GetSpeedXY();
if (speed > 0.0f)
m_delayMoment = (uint64)floor(m_targets.GetDist2d() / speed * 1000.0f);
}
else if (m_spellInfo->Speed > 0.0f)
{
float dist = m_caster->GetDistance(*m_targets.GetDst());
m_delayMoment = (uint64) floor(dist / m_spellInfo->Speed * 1000.0f);
}
}
}
void Spell::SelectEffectImplicitTargets(SpellEffIndex effIndex, SpellImplicitTargetInfo const& targetType, uint32& processedEffectMask)
{
if (!targetType.GetTarget())
return;
uint32 effectMask = 1 << effIndex;
// set the same target list for all effects
// some spells appear to need this, however this requires more research
switch (targetType.GetSelectionCategory())
{
case TARGET_SELECT_CATEGORY_NEARBY:
case TARGET_SELECT_CATEGORY_CONE:
case TARGET_SELECT_CATEGORY_AREA:
// targets for effect already selected
if (effectMask & processedEffectMask)
return;
// choose which targets we can select at once
for (uint32 j = effIndex + 1; j < MAX_SPELL_EFFECTS; ++j)
if (GetSpellInfo()->Effects[effIndex].TargetA.GetTarget() == GetSpellInfo()->Effects[j].TargetA.GetTarget() &&
GetSpellInfo()->Effects[effIndex].TargetB.GetTarget() == GetSpellInfo()->Effects[j].TargetB.GetTarget() &&
GetSpellInfo()->Effects[effIndex].ImplicitTargetConditions == GetSpellInfo()->Effects[j].ImplicitTargetConditions &&
GetSpellInfo()->Effects[effIndex].CalcRadius(m_caster) == GetSpellInfo()->Effects[j].CalcRadius(m_caster))
effectMask |= 1 << j;
processedEffectMask |= effectMask;
break;
default:
break;
}
switch(targetType.GetSelectionCategory())
{
case TARGET_SELECT_CATEGORY_CHANNEL:
SelectImplicitChannelTargets(effIndex, targetType);
break;
case TARGET_SELECT_CATEGORY_NEARBY:
SelectImplicitNearbyTargets(effIndex, targetType, effectMask);
break;
case TARGET_SELECT_CATEGORY_CONE:
SelectImplicitConeTargets(effIndex, targetType, effectMask);
break;
case TARGET_SELECT_CATEGORY_AREA:
SelectImplicitAreaTargets(effIndex, targetType, effectMask);
break;
case TARGET_SELECT_CATEGORY_DEFAULT:
switch (targetType.GetObjectType())
{
case TARGET_OBJECT_TYPE_SRC:
switch(targetType.GetReferenceType())
{
case TARGET_REFERENCE_TYPE_CASTER:
m_targets.SetSrc(*m_caster);
break;
default:
ASSERT(false && "Spell::SelectEffectImplicitTargets: received not implemented select target reference type for TARGET_TYPE_OBJECT_SRC");
break;
}
break;
case TARGET_OBJECT_TYPE_DEST:
switch(targetType.GetReferenceType())
{
case TARGET_REFERENCE_TYPE_CASTER:
SelectImplicitCasterDestTargets(effIndex, targetType);
break;
case TARGET_REFERENCE_TYPE_TARGET:
SelectImplicitTargetDestTargets(effIndex, targetType);
break;
case TARGET_REFERENCE_TYPE_DEST:
SelectImplicitDestDestTargets(effIndex, targetType);
break;
default:
ASSERT(false && "Spell::SelectEffectImplicitTargets: received not implemented select target reference type for TARGET_TYPE_OBJECT_DEST");
break;
}
break;
default:
switch(targetType.GetReferenceType())
{
case TARGET_REFERENCE_TYPE_CASTER:
SelectImplicitCasterObjectTargets(effIndex, targetType);
break;
case TARGET_REFERENCE_TYPE_TARGET:
SelectImplicitTargetObjectTargets(effIndex, targetType);
break;
default:
ASSERT(false && "Spell::SelectEffectImplicitTargets: received not implemented select target reference type for TARGET_TYPE_OBJECT");
break;
}
break;
}
break;
case TARGET_SELECT_CATEGORY_NYI:
sLog->outDebug(LOG_FILTER_SPELLS_AURAS, "SPELL: target type %u, found in spellID %u, effect %u is not implemented yet!", m_spellInfo->Id, effIndex, targetType.GetTarget());
break;
default:
ASSERT(false && "Spell::SelectEffectImplicitTargets: received not implemented select target category");
break;
}
}
void Spell::SelectImplicitChannelTargets(SpellEffIndex effIndex, SpellImplicitTargetInfo const& targetType)
{
if (targetType.GetReferenceType() != TARGET_REFERENCE_TYPE_CASTER)
{
ASSERT(false && "Spell::SelectImplicitChannelTargets: received not implemented target reference type");
return;
}
Spell* channeledSpell = m_originalCaster->GetCurrentSpell(CURRENT_CHANNELED_SPELL);
if (!channeledSpell)
{
sLog->outDebug(LOG_FILTER_SPELLS_AURAS, "Spell::SelectImplicitChannelTargets: cannot find channel spell for spell ID %u, effect %u", m_spellInfo->Id, effIndex);
return;
}
switch (targetType.GetTarget())
{
case TARGET_UNIT_CHANNEL_TARGET:
// unit target may be no longer avalible - teleported out of map for example
if (Unit* target = Unit::GetUnit(*m_caster, channeledSpell->m_targets.GetUnitTargetGUID()))
AddUnitTarget(target, 1 << effIndex);
else
sLog->outDebug(LOG_FILTER_SPELLS_AURAS, "SPELL: cannot find channel spell target for spell ID %u, effect %u", m_spellInfo->Id, effIndex);
break;
case TARGET_DEST_CHANNEL_TARGET:
if (channeledSpell->m_targets.HasDst())
m_targets.SetDst(channeledSpell->m_targets);
else if (WorldObject* target = ObjectAccessor::GetWorldObject(*m_caster, channeledSpell->m_targets.GetObjectTargetGUID()))
m_targets.SetDst(*target);
else
sLog->outDebug(LOG_FILTER_SPELLS_AURAS, "SPELL: cannot find channel spell destination for spell ID %u, effect %u", m_spellInfo->Id, effIndex);
break;
case TARGET_DEST_CHANNEL_CASTER:
m_targets.SetDst(*channeledSpell->GetCaster());
break;
default:
ASSERT(false && "Spell::SelectImplicitChannelTargets: received not implemented target type");
break;
}
}
void Spell::SelectImplicitNearbyTargets(SpellEffIndex effIndex, SpellImplicitTargetInfo const& targetType, uint32 effMask)
{
if (targetType.GetReferenceType() != TARGET_REFERENCE_TYPE_CASTER)
{
ASSERT(false && "Spell::SelectImplicitNearbyTargets: received not implemented target reference type");
return;
}
float range = 0.0f;
switch (targetType.GetCheckType())
{
case TARGET_CHECK_ENEMY:
range = m_spellInfo->GetMaxRange(false, m_caster, this);
break;
case TARGET_CHECK_ALLY:
case TARGET_CHECK_PARTY:
case TARGET_CHECK_RAID:
case TARGET_CHECK_RAID_CLASS:
range = m_spellInfo->GetMaxRange(true, m_caster, this);
break;
case TARGET_CHECK_ENTRY:
case TARGET_CHECK_DEFAULT:
range = m_spellInfo->GetMaxRange(m_spellInfo->IsPositive(), m_caster, this);
break;
default:
ASSERT(false && "Spell::SelectImplicitNearbyTargets: received not implemented selection check type");
break;
}
ConditionList* condList = m_spellInfo->Effects[effIndex].ImplicitTargetConditions;
// handle emergency case - try to use other provided targets if no conditions provided
if (targetType.GetCheckType() == TARGET_CHECK_ENTRY && (!condList || condList->empty()))
{
sLog->outDebug(LOG_FILTER_SPELLS_AURAS, "Spell::SelectImplicitNearbyTargets: no conditions entry for target with TARGET_CHECK_ENTRY of spell ID %u, effect %u - selecting default targets", m_spellInfo->Id, effIndex);
switch (targetType.GetObjectType())
{
case TARGET_OBJECT_TYPE_GOBJ:
if (m_spellInfo->RequiresSpellFocus)
{
if (focusObject)
AddGOTarget(focusObject, effMask);
return;
}
break;
case TARGET_OBJECT_TYPE_DEST:
if (m_spellInfo->RequiresSpellFocus)
{
if (focusObject)
m_targets.SetDst(*focusObject);
return;
}
break;
default:
break;
}
}
WorldObject* target = SearchNearbyTarget(range, targetType.GetObjectType(), targetType.GetCheckType(), condList);
if (!target)
{
sLog->outDebug(LOG_FILTER_SPELLS_AURAS, "Spell::SelectImplicitNearbyTargets: cannot find nearby target for spell ID %u, effect %u", m_spellInfo->Id, effIndex);
return;
}
switch (targetType.GetObjectType())
{
case TARGET_OBJECT_TYPE_UNIT:
if (Unit* unitTarget = target->ToUnit())
AddUnitTarget(unitTarget, effMask, false);
break;
case TARGET_OBJECT_TYPE_GOBJ:
if (GameObject* gobjTarget = target->ToGameObject())
AddGOTarget(gobjTarget, effMask);
break;
case TARGET_OBJECT_TYPE_DEST:
m_targets.SetDst(*target);
break;
default:
ASSERT(false && "Spell::SelectImplicitNearbyTargets: received not implemented target object type");
break;
}
SelectImplicitChainTargets(effIndex, targetType, target, effMask);
}
void Spell::SelectImplicitConeTargets(SpellEffIndex effIndex, SpellImplicitTargetInfo const& targetType, uint32 effMask)
{
if (targetType.GetReferenceType() != TARGET_REFERENCE_TYPE_CASTER)
{
ASSERT(false && "Spell::SelectImplicitConeTargets: received not implemented target reference type");
return;
}
std::list<WorldObject*> targets;
SpellTargetObjectTypes objectType = targetType.GetObjectType();
SpellTargetCheckTypes selectionType = targetType.GetCheckType();
ConditionList* condList = m_spellInfo->Effects[effIndex].ImplicitTargetConditions;
float coneAngle = M_PI/2;
float radius = m_spellInfo->Effects[effIndex].CalcRadius(m_caster) * m_spellValue->RadiusMod;
if (uint32 containerTypeMask = GetSearcherTypeMask(objectType, condList))
{
Trinity::WorldObjectSpellConeTargetCheck check(coneAngle, radius, m_caster, m_spellInfo, selectionType, condList);
Trinity::WorldObjectListSearcher<Trinity::WorldObjectSpellConeTargetCheck> searcher(m_caster, targets, check, containerTypeMask);
SearchTargets<Trinity::WorldObjectListSearcher<Trinity::WorldObjectSpellConeTargetCheck> > (searcher, containerTypeMask, m_caster, m_caster, radius);
if (!targets.empty())
{
// Other special target selection goes here
if (uint32 maxTargets = m_spellValue->MaxAffectedTargets)
{
Unit::AuraEffectList const& Auras = m_caster->GetAuraEffectsByType(SPELL_AURA_MOD_MAX_AFFECTED_TARGETS);
for (Unit::AuraEffectList::const_iterator j = Auras.begin(); j != Auras.end(); ++j)
if ((*j)->IsAffectedOnSpell(m_spellInfo))
maxTargets += (*j)->GetAmount();
Trinity::RandomResizeList(targets, maxTargets);
}
// for compability with older code - add only unit and go targets
// TODO: remove this
std::list<Unit*> unitTargets;
std::list<GameObject*> gObjTargets;
for (std::list<WorldObject*>::iterator itr = targets.begin(); itr != targets.end(); ++itr)
{
if (Unit* unitTarget = (*itr)->ToUnit())
unitTargets.push_back(unitTarget);
else if (GameObject* gObjTarget = (*itr)->ToGameObject())
gObjTargets.push_back(gObjTarget);
}
CallScriptAfterUnitTargetSelectHandlers(unitTargets, effIndex);
for (std::list<Unit*>::iterator itr = unitTargets.begin(); itr != unitTargets.end(); ++itr)
AddUnitTarget(*itr, effMask, false);
for (std::list<GameObject*>::iterator itr = gObjTargets.begin(); itr != gObjTargets.end(); ++itr)
AddGOTarget(*itr, effMask);
}
}
}
void Spell::SelectImplicitAreaTargets(SpellEffIndex effIndex, SpellImplicitTargetInfo const& targetType, uint32 effMask)
{
Unit* referer = NULL;
switch (targetType.GetReferenceType())
{
case TARGET_REFERENCE_TYPE_SRC:
case TARGET_REFERENCE_TYPE_DEST:
case TARGET_REFERENCE_TYPE_CASTER:
referer = m_caster;
break;
case TARGET_REFERENCE_TYPE_TARGET:
referer = m_targets.GetUnitTarget();
break;
case TARGET_REFERENCE_TYPE_LAST:
{
// find last added target for this effect
for (std::list<TargetInfo>::reverse_iterator ihit = m_UniqueTargetInfo.rbegin(); ihit != m_UniqueTargetInfo.rend(); ++ihit)
{
if (ihit->effectMask & (1<<effIndex))
{
referer = ObjectAccessor::GetUnit(*m_caster, ihit->targetGUID);
break;
}
}
break;
}
default:
ASSERT(false && "Spell::SelectImplicitAreaTargets: received not implemented target reference type");
return;
}
if (!referer)
return;
Position const* center = NULL;
switch (targetType.GetReferenceType())
{
case TARGET_REFERENCE_TYPE_SRC:
center = m_targets.GetSrc();
break;
case TARGET_REFERENCE_TYPE_DEST:
center = m_targets.GetDst();
break;
case TARGET_REFERENCE_TYPE_CASTER:
case TARGET_REFERENCE_TYPE_TARGET:
case TARGET_REFERENCE_TYPE_LAST:
center = referer;
break;
default:
ASSERT(false && "Spell::SelectImplicitAreaTargets: received not implemented target reference type");
return;
}
std::list<WorldObject*> targets;
float radius = m_spellInfo->Effects[effIndex].CalcRadius(m_caster) * m_spellValue->RadiusMod;
SearchAreaTargets(targets, radius, center, referer, targetType.GetObjectType(), targetType.GetCheckType(), m_spellInfo->Effects[effIndex].ImplicitTargetConditions);
// Custom entries
// TODO: remove those
switch (m_spellInfo->Id)
{
case 46584: // Raise Dead
{
if (Player* playerCaster = m_caster->ToPlayer())
{
for (std::list<WorldObject*>::iterator itr = targets.begin(); itr != targets.end(); ++itr)
{
switch ((*itr)->GetTypeId())
{
case TYPEID_UNIT:
case TYPEID_PLAYER:
{
Unit* unitTarget = (*itr)->ToUnit();
if (unitTarget->isAlive() || !playerCaster->isHonorOrXPTarget(unitTarget)
|| ((unitTarget->GetCreatureTypeMask() & (1 << (CREATURE_TYPE_HUMANOID-1))) == 0)
|| (unitTarget->GetDisplayId() != unitTarget->GetNativeDisplayId()))
break;
AddUnitTarget(unitTarget, effMask, false);
// no break;
}
case TYPEID_CORPSE: // wont work until corpses are allowed in target lists, but at least will send dest in packet
m_targets.SetDst(*(*itr));
return; // nothing more to do here
default:
break;
}
}
}
return; // don't add targets to target map
}
// Corpse Explosion
case 49158:
case 51325:
case 51326:
case 51327:
case 51328:
// check if our target is not valid (spell can target ghoul or dead unit)
if (!(m_targets.GetUnitTarget() && m_targets.GetUnitTarget()->GetDisplayId() == m_targets.GetUnitTarget()->GetNativeDisplayId() &&
((m_targets.GetUnitTarget()->GetEntry() == 26125 && m_targets.GetUnitTarget()->GetOwnerGUID() == m_caster->GetGUID())
|| m_targets.GetUnitTarget()->isDead())))
{
// remove existing targets
CleanupTargetList();
for (std::list<WorldObject*>::iterator itr = targets.begin(); itr != targets.end(); ++itr)
{
switch ((*itr)->GetTypeId())
{
case TYPEID_UNIT:
case TYPEID_PLAYER:
if (!(*itr)->ToUnit()->isDead())
break;
AddUnitTarget((*itr)->ToUnit(), 1 << effIndex, false);
return;
default:
break;
}
}
if (m_caster->GetTypeId() == TYPEID_PLAYER)
m_caster->ToPlayer()->RemoveSpellCooldown(m_spellInfo->Id, true);
SendCastResult(SPELL_FAILED_CANT_DO_THAT_RIGHT_NOW);
finish(false);
}
return;
default:
break;
}
std::list<Unit*> unitTargets;
std::list<GameObject*> gObjTargets;
// for compability with older code - add only unit and go targets
// TODO: remove this
for (std::list<WorldObject*>::iterator itr = targets.begin(); itr != targets.end(); ++itr)
{
if (Unit* unitTarget = (*itr)->ToUnit())
unitTargets.push_back(unitTarget);
else if (GameObject* gObjTarget = (*itr)->ToGameObject())
gObjTargets.push_back(gObjTarget);
}
if (!unitTargets.empty())
{
// Special target selection for smart heals and energizes
uint32 maxSize = 0;
int32 power = -1;
switch (m_spellInfo->SpellFamilyName)
{
case SPELLFAMILY_GENERIC:
switch (m_spellInfo->Id)
{
case 52759: // Ancestral Awakening
case 71610: // Echoes of Light (Althor's Abacus normal version)
case 71641: // Echoes of Light (Althor's Abacus heroic version)
maxSize = 1;
power = POWER_HEALTH;
break;
case 54968: // Glyph of Holy Light
maxSize = m_spellInfo->MaxAffectedTargets;
power = POWER_HEALTH;
break;
case 57669: // Replenishment
// In arenas Replenishment may only affect the caster
if (m_caster->GetTypeId() == TYPEID_PLAYER && m_caster->ToPlayer()->InArena())
{
unitTargets.clear();
unitTargets.push_back(m_caster);
break;
}
maxSize = 10;
power = POWER_MANA;
break;
default:
break;
}
break;
case SPELLFAMILY_PRIEST:
if (m_spellInfo->SpellFamilyFlags[0] == 0x10000000) // Circle of Healing
{
maxSize = m_caster->HasAura(55675) ? 6 : 5; // Glyph of Circle of Healing
power = POWER_HEALTH;
}
else if (m_spellInfo->Id == 64844) // Divine Hymn
{
maxSize = 3;
power = POWER_HEALTH;
}
else if (m_spellInfo->Id == 64904) // Hymn of Hope
{
maxSize = 3;
power = POWER_MANA;
}
else
break;
// Remove targets outside caster's raid
for (std::list<Unit*>::iterator itr = unitTargets.begin() ; itr != unitTargets.end();)
{
if (!(*itr)->IsInRaidWith(m_caster))
itr = unitTargets.erase(itr);
else
++itr;
}
break;
case SPELLFAMILY_DRUID:
if (m_spellInfo->SpellFamilyFlags[1] == 0x04000000) // Wild Growth
{
maxSize = m_caster->HasAura(62970) ? 6 : 5; // Glyph of Wild Growth
power = POWER_HEALTH;
}
else if (m_spellInfo->SpellFamilyFlags[2] == 0x0100) // Starfall
{
// Remove targets not in LoS or in stealth
for (std::list<Unit*>::iterator itr = unitTargets.begin() ; itr != unitTargets.end();)
{
if ((*itr)->HasStealthAura() || (*itr)->HasInvisibilityAura() || !(*itr)->IsWithinLOSInMap(m_caster))
itr = unitTargets.erase(itr);
else
++itr;
}
break;
}
else
break;
// Remove targets outside caster's raid
for (std::list<Unit*>::iterator itr = unitTargets.begin() ; itr != unitTargets.end();)
if (!(*itr)->IsInRaidWith(m_caster))
itr = unitTargets.erase(itr);
else
++itr;
break;
default:
break;
}
if (maxSize && power != -1)
{
if (Powers(power) == POWER_HEALTH)
{
if (unitTargets.size() > maxSize)
{
unitTargets.sort(Trinity::HealthPctOrderPred());
unitTargets.resize(maxSize);
}
}
else
{
for (std::list<Unit*>::iterator itr = unitTargets.begin() ; itr != unitTargets.end();)
if ((*itr)->getPowerType() != (Powers)power)
itr = unitTargets.erase(itr);
else
++itr;
if (unitTargets.size() > maxSize)
{
unitTargets.sort(Trinity::PowerPctOrderPred((Powers)power));
unitTargets.resize(maxSize);
}
}
}
// Other special target selection goes here
if (uint32 maxTargets = m_spellValue->MaxAffectedTargets)
{
Unit::AuraEffectList const& Auras = m_caster->GetAuraEffectsByType(SPELL_AURA_MOD_MAX_AFFECTED_TARGETS);
for (Unit::AuraEffectList::const_iterator j = Auras.begin(); j != Auras.end(); ++j)
if ((*j)->IsAffectedOnSpell(m_spellInfo))
maxTargets += (*j)->GetAmount();
if (m_spellInfo->Id == 5246) //Intimidating Shout
unitTargets.remove(m_targets.GetUnitTarget());
Trinity::RandomResizeList(unitTargets, maxTargets);
}
CallScriptAfterUnitTargetSelectHandlers(unitTargets, effIndex);
for (std::list<Unit*>::iterator itr = unitTargets.begin(); itr != unitTargets.end(); ++itr)
AddUnitTarget(*itr, effMask, false);
}
if (!gObjTargets.empty())
{
if (uint32 maxTargets = m_spellValue->MaxAffectedTargets)
{
Unit::AuraEffectList const& Auras = m_caster->GetAuraEffectsByType(SPELL_AURA_MOD_MAX_AFFECTED_TARGETS);
for (Unit::AuraEffectList::const_iterator j = Auras.begin(); j != Auras.end(); ++j)
if ((*j)->IsAffectedOnSpell(m_spellInfo))
maxTargets += (*j)->GetAmount();
Trinity::RandomResizeList(gObjTargets, maxTargets);
}
for (std::list<GameObject*>::iterator itr = gObjTargets.begin(); itr != gObjTargets.end(); ++itr)
AddGOTarget(*itr, effMask);
}
}
void Spell::SelectImplicitCasterDestTargets(SpellEffIndex effIndex, SpellImplicitTargetInfo const& targetType)
{
switch(targetType.GetTarget())
{
case TARGET_DEST_CASTER:
m_targets.SetDst(*m_caster);
return;
case TARGET_DEST_HOME:
if (Player* playerCaster = m_caster->ToPlayer())
m_targets.SetDst(playerCaster->m_homebindX, playerCaster->m_homebindY, playerCaster->m_homebindZ, playerCaster->GetOrientation(), playerCaster->m_homebindMapId);
return;
case TARGET_DEST_DB:
if (SpellTargetPosition const* st = sSpellMgr->GetSpellTargetPosition(m_spellInfo->Id))
{
// TODO: fix this check
if (m_spellInfo->HasEffect(SPELL_EFFECT_TELEPORT_UNITS))
m_targets.SetDst(st->target_X, st->target_Y, st->target_Z, st->target_Orientation, (int32)st->target_mapId);
else if (st->target_mapId == m_caster->GetMapId())
m_targets.SetDst(st->target_X, st->target_Y, st->target_Z, st->target_Orientation);
}
else
{
sLog->outDebug(LOG_FILTER_SPELLS_AURAS, "SPELL: unknown target coordinates for spell ID %u", m_spellInfo->Id);
WorldObject* target = m_targets.GetObjectTarget();
m_targets.SetDst(target ? *target : *m_caster);
}
return;
case TARGET_DEST_CASTER_FISHING:
{
float min_dis = m_spellInfo->GetMinRange(true);
float max_dis = m_spellInfo->GetMaxRange(true);
float dis = (float)rand_norm() * (max_dis - min_dis) + min_dis;
float x, y, z, angle;
angle = (float)rand_norm() * static_cast<float>(M_PI * 35.0f / 180.0f) - static_cast<float>(M_PI * 17.5f / 180.0f);
m_caster->GetClosePoint(x, y, z, DEFAULT_WORLD_OBJECT_SIZE, dis, angle);
m_targets.SetDst(x, y, z, m_caster->GetOrientation());
return;
}
default:
break;
}
float dist;
float angle = targetType.CalcDirectionAngle();
float objSize = m_caster->GetObjectSize();
if (targetType.GetTarget() == TARGET_DEST_CASTER_SUMMON)
dist = PET_FOLLOW_DIST;
else
dist = m_spellInfo->Effects[effIndex].CalcRadius(m_caster);
if (dist < objSize)
dist = objSize;
else if (targetType.GetTarget() == TARGET_DEST_CASTER_RANDOM)
dist = objSize + (dist - objSize) * (float)rand_norm();
Position pos;
if (targetType.GetTarget() == TARGET_DEST_CASTER_FRONT_LEAP)
m_caster->GetFirstCollisionPosition(pos, dist, angle);
else
m_caster->GetNearPosition(pos, dist, angle);
m_targets.SetDst(*m_caster);
m_targets.ModDst(pos);
}
void Spell::SelectImplicitTargetDestTargets(SpellEffIndex effIndex, SpellImplicitTargetInfo const& targetType)
{
WorldObject* target = m_targets.GetObjectTarget();
switch(targetType.GetTarget())
{
case TARGET_DEST_TARGET_ENEMY:
case TARGET_DEST_TARGET_ANY:
m_targets.SetDst(*target);
return;
default:
break;
}
float angle = targetType.CalcDirectionAngle();
float objSize = target->GetObjectSize();
float dist = m_spellInfo->Effects[effIndex].CalcRadius(m_caster);
if (dist < objSize)
dist = objSize;
else if (targetType.GetTarget() == TARGET_DEST_TARGET_RANDOM)
dist = objSize + (dist - objSize) * (float)rand_norm();
Position pos;
target->GetNearPosition(pos, dist, angle);
m_targets.SetDst(*target);
m_targets.ModDst(pos);
}
void Spell::SelectImplicitDestDestTargets(SpellEffIndex effIndex, SpellImplicitTargetInfo const& targetType)
{
// set destination to caster if no dest provided
// can only happen if previous destination target could not be set for some reason
// (not found nearby target, or channel target for example
// maybe we should abort the spell in such case?
if (!m_targets.HasDst())
m_targets.SetDst(*m_caster);
switch(targetType.GetTarget())
{
case TARGET_DEST_DYNOBJ_ENEMY:
case TARGET_DEST_DYNOBJ_ALLY:
case TARGET_DEST_DYNOBJ_NONE:
case TARGET_DEST_DEST:
return;
case TARGET_DEST_TRAJ:
SelectImplicitTrajTargets();
return;
default:
break;
}
float angle = targetType.CalcDirectionAngle();
float dist = m_spellInfo->Effects[effIndex].CalcRadius(m_caster);
if (targetType.GetTarget() == TARGET_DEST_DEST_RANDOM)
dist *= (float)rand_norm();
Position pos = *m_targets.GetDst();
m_caster->MovePosition(pos, dist, angle);
m_targets.ModDst(pos);
}
void Spell::SelectImplicitCasterObjectTargets(SpellEffIndex effIndex, SpellImplicitTargetInfo const& targetType)
{
switch(targetType.GetTarget())
{
case TARGET_UNIT_CASTER:
AddUnitTarget(m_caster, 1 << effIndex, false);
break;
case TARGET_UNIT_MASTER:
if (Unit* owner = m_caster->GetCharmerOrOwner())
AddUnitTarget(owner, 1 << effIndex);
break;
case TARGET_UNIT_PET:
if (Guardian* pet = m_caster->GetGuardianPet())
AddUnitTarget(pet, 1 << effIndex);
break;
case TARGET_UNIT_SUMMONER:
if (m_caster->isSummon())
if (Unit* unit = m_caster->ToTempSummon()->GetSummoner())
AddUnitTarget(unit, 1 << effIndex);
break;
case TARGET_UNIT_VEHICLE:
if (Unit *vehicle = m_caster->GetVehicleBase())
AddUnitTarget(vehicle, 1 << effIndex);
break;
case TARGET_UNIT_PASSENGER_0:
case TARGET_UNIT_PASSENGER_1:
case TARGET_UNIT_PASSENGER_2:
case TARGET_UNIT_PASSENGER_3:
case TARGET_UNIT_PASSENGER_4:
case TARGET_UNIT_PASSENGER_5:
case TARGET_UNIT_PASSENGER_6:
case TARGET_UNIT_PASSENGER_7:
if (m_caster->GetTypeId() == TYPEID_UNIT && m_caster->ToCreature()->IsVehicle())
if (Unit *unit = m_caster->GetVehicleKit()->GetPassenger(targetType.GetTarget() - TARGET_UNIT_PASSENGER_0))
AddUnitTarget(unit, 1 << effIndex);
break;
default:
break;
}
}
void Spell::SelectImplicitTargetObjectTargets(SpellEffIndex effIndex, SpellImplicitTargetInfo const& targetType)
{
ASSERT((m_targets.GetObjectTarget() || m_targets.GetItemTarget()) && "Spell::SelectImplicitTargetObjectTargets - no explicit object or item target available!");
if (Unit* unit = m_targets.GetUnitTarget())
AddUnitTarget(unit, 1 << effIndex);
else if (GameObject* gobj = m_targets.GetGOTarget())
AddGOTarget(gobj, 1 << effIndex);
else
AddItemTarget(m_targets.GetItemTarget(), effIndex);
if (WorldObject* target = m_targets.GetObjectTarget())
SelectImplicitChainTargets(effIndex, targetType, target, 1 << effIndex);
}
void Spell::SelectImplicitChainTargets(SpellEffIndex effIndex, SpellImplicitTargetInfo const& targetType, WorldObject* target, uint32 effMask)
{
uint32 maxTargets = m_spellInfo->Effects[effIndex].ChainTarget;
if (Player* modOwner = m_caster->GetSpellModOwner())
modOwner->ApplySpellMod(m_spellInfo->Id, SPELLMOD_JUMP_TARGETS, maxTargets, this);
if (maxTargets > 1)
{
// mark damage multipliers as used
for (uint32 k = effIndex; k < MAX_SPELL_EFFECTS; ++k)
if (effMask & (1 << k))
m_damageMultipliers[k] = 1.0f;
m_applyMultiplierMask |= effMask;
std::list<WorldObject*> targets;
SearchChainTargets(targets, maxTargets - 1, target, targetType.GetObjectType(), targetType.GetCheckType()
, m_spellInfo->Effects[effIndex].ImplicitTargetConditions, targetType.GetTarget() == TARGET_UNIT_TARGET_CHAINHEAL_ALLY);
// for backward compability
std::list<Unit*> unitTargets;
for (std::list<WorldObject*>::iterator itr = targets.begin(); itr != targets.end(); ++itr)
if (Unit* unitTarget = (*itr)->ToUnit())
unitTargets.push_back(unitTarget);
CallScriptAfterUnitTargetSelectHandlers(unitTargets, effIndex);
for (std::list<Unit*>::iterator itr = unitTargets.begin(); itr != unitTargets.end(); ++itr)
AddUnitTarget(*itr, effMask, false);
}
}
float tangent(float x)
{
x = tan(x);
//if (x < std::numeric_limits<float>::max() && x > -std::numeric_limits<float>::max()) return x;
//if (x >= std::numeric_limits<float>::max()) return std::numeric_limits<float>::max();
//if (x <= -std::numeric_limits<float>::max()) return -std::numeric_limits<float>::max();
if (x < 100000.0f && x > -100000.0f) return x;
if (x >= 100000.0f) return 100000.0f;
if (x <= 100000.0f) return -100000.0f;
return 0.0f;
}
#define DEBUG_TRAJ(a) //a
void Spell::SelectImplicitTrajTargets()
{
if (!m_targets.HasTraj())
return;
float dist2d = m_targets.GetDist2d();
if (!dist2d)
return;
float srcToDestDelta = m_targets.GetDst()->m_positionZ - m_targets.GetSrc()->m_positionZ;
std::list<WorldObject*> targets;
Trinity::WorldObjectSpellTrajTargetCheck check(dist2d, m_targets.GetSrc(), m_caster, m_spellInfo);
Trinity::WorldObjectListSearcher<Trinity::WorldObjectSpellTrajTargetCheck> searcher(m_caster, targets, check, GRID_MAP_TYPE_MASK_ALL);
SearchTargets<Trinity::WorldObjectListSearcher<Trinity::WorldObjectSpellTrajTargetCheck> > (searcher, GRID_MAP_TYPE_MASK_ALL, m_caster, m_targets.GetSrc(), dist2d);
if (targets.empty())
return;
targets.sort(Trinity::ObjectDistanceOrderPred(m_caster));
float b = tangent(m_targets.GetElevation());
float a = (srcToDestDelta - dist2d * b) / (dist2d * dist2d);
if (a > -0.0001f)
a = 0;
DEBUG_TRAJ(sLog->outError("Spell::SelectTrajTargets: a %f b %f", a, b);)
float bestDist = m_spellInfo->GetMaxRange(false);
std::list<WorldObject*>::const_iterator itr = targets.begin();
for (; itr != targets.end(); ++itr)
{
if (Unit* unitTarget = (*itr)->ToUnit())
if (m_caster == *itr || m_caster->IsOnVehicle(unitTarget) || (unitTarget)->GetVehicle())//(*itr)->IsOnVehicle(m_caster))
continue;
const float size = std::max((*itr)->GetObjectSize() * 0.7f, 1.0f); // 1/sqrt(3)
// TODO: all calculation should be based on src instead of m_caster
const float objDist2d = m_targets.GetSrc()->GetExactDist2d(*itr) * cos(m_targets.GetSrc()->GetRelativeAngle(*itr));
const float dz = (*itr)->GetPositionZ() - m_targets.GetSrc()->m_positionZ;
DEBUG_TRAJ(sLog->outError("Spell::SelectTrajTargets: check %u, dist between %f %f, height between %f %f.", (*itr)->GetEntry(), objDist2d - size, objDist2d + size, dz - size, dz + size);)
float dist = objDist2d - size;
float height = dist * (a * dist + b);
DEBUG_TRAJ(sLog->outError("Spell::SelectTrajTargets: dist %f, height %f.", dist, height);)
if (dist < bestDist && height < dz + size && height > dz - size)
{
bestDist = dist > 0 ? dist : 0;
break;
}
#define CHECK_DIST {\
DEBUG_TRAJ(sLog->outError("Spell::SelectTrajTargets: dist %f, height %f.", dist, height);)\
if (dist > bestDist)\
continue;\
if (dist < objDist2d + size && dist > objDist2d - size)\
{\
bestDist = dist;\
break;\
}\
}
if (!a)
{
height = dz - size;
dist = height / b;
CHECK_DIST;
height = dz + size;
dist = height / b;
CHECK_DIST;
continue;
}
height = dz - size;
float sqrt1 = b * b + 4 * a * height;
if (sqrt1 > 0)
{
sqrt1 = sqrt(sqrt1);
dist = (sqrt1 - b) / (2 * a);
CHECK_DIST;
}
height = dz + size;
float sqrt2 = b * b + 4 * a * height;
if (sqrt2 > 0)
{
sqrt2 = sqrt(sqrt2);
dist = (sqrt2 - b) / (2 * a);
CHECK_DIST;
dist = (-sqrt2 - b) / (2 * a);
CHECK_DIST;
}
if (sqrt1 > 0)
{
dist = (-sqrt1 - b) / (2 * a);
CHECK_DIST;
}
}
if (m_targets.GetSrc()->GetExactDist2d(m_targets.GetDst()) > bestDist)
{
float x = m_targets.GetSrc()->m_positionX + cos(m_caster->GetOrientation()) * bestDist;
float y = m_targets.GetSrc()->m_positionY + sin(m_caster->GetOrientation()) * bestDist;
float z = m_targets.GetSrc()->m_positionZ + bestDist * (a * bestDist + b);
if (itr != targets.end())
{
float distSq = (*itr)->GetExactDistSq(x, y, z);
float sizeSq = (*itr)->GetObjectSize();
sizeSq *= sizeSq;
DEBUG_TRAJ(sLog->outError("Initial %f %f %f %f %f", x, y, z, distSq, sizeSq);)
if (distSq > sizeSq)
{
float factor = 1 - sqrt(sizeSq / distSq);
x += factor * ((*itr)->GetPositionX() - x);
y += factor * ((*itr)->GetPositionY() - y);
z += factor * ((*itr)->GetPositionZ() - z);
distSq = (*itr)->GetExactDistSq(x, y, z);
DEBUG_TRAJ(sLog->outError("Initial %f %f %f %f %f", x, y, z, distSq, sizeSq);)
}
}
Position trajDst;
trajDst.Relocate(x, y, z, m_caster->GetOrientation());
m_targets.ModDst(trajDst);
}
}
void Spell::SelectEffectTypeImplicitTargets(uint8 effIndex)
{
// special case for SPELL_EFFECT_SUMMON_RAF_FRIEND and SPELL_EFFECT_SUMMON_PLAYER
// TODO: this is a workaround - target shouldn't be stored in target map for those spells
switch (m_spellInfo->Effects[effIndex].Effect)
{
case SPELL_EFFECT_SUMMON_RAF_FRIEND:
case SPELL_EFFECT_SUMMON_PLAYER:
if (m_caster->GetTypeId() == TYPEID_PLAYER && m_caster->ToPlayer()->GetSelection())
{
Player* target = ObjectAccessor::FindPlayer(m_caster->ToPlayer()->GetSelection());
if (target)
AddUnitTarget(target, 1 << effIndex, false);
}
return;
default:
break;
}
// select spell implicit targets based on effect type
if (!m_spellInfo->Effects[effIndex].GetImplicitTargetType())
return;
uint32 targetMask = m_spellInfo->Effects[effIndex].GetMissingTargetMask();
if (!targetMask)
return;
switch (m_spellInfo->Effects[effIndex].GetImplicitTargetType())
{
// add explicit object target or self to the target map
case EFFECT_IMPLICIT_TARGET_EXPLICIT:
// player which not released his spirit is Unit, but target flag for it is TARGET_FLAG_CORPSE_MASK
if (targetMask & (TARGET_FLAG_UNIT_MASK | TARGET_FLAG_CORPSE_MASK))
{
if (Unit* unitTarget = m_targets.GetUnitTarget())
AddUnitTarget(unitTarget, 1 << effIndex, false);
else if (targetMask & TARGET_FLAG_CORPSE_MASK)
{
if (Corpse* corpseTarget = m_targets.GetCorpseTarget())
{
// TODO: this is a workaround - corpses should be added to spell target map too, but we can't do that so we add owner instead
if (Player* owner = ObjectAccessor::FindPlayer(corpseTarget->GetOwnerGUID()))
AddUnitTarget(owner, 1 << effIndex, false);
}
}
else //if (targetMask & TARGET_FLAG_UNIT_MASK)
{
AddUnitTarget(m_caster, 1 << effIndex, false);
}
}
if (targetMask & TARGET_FLAG_ITEM_MASK)
{
if (Item* itemTarget = m_targets.GetItemTarget())
AddItemTarget(itemTarget, 1 << effIndex);
}
if (targetMask & TARGET_FLAG_GAMEOBJECT_MASK)
{
if (GameObject* gObjTarget = m_targets.GetGOTarget())
AddGOTarget(gObjTarget, 1 << effIndex);
}
break;
// add self to the target map
case EFFECT_IMPLICIT_TARGET_CASTER:
if (targetMask & TARGET_FLAG_UNIT_MASK)
AddUnitTarget(m_caster, 1 << effIndex, false);
break;
default:
break;
}
}
uint32 Spell::GetSearcherTypeMask(SpellTargetObjectTypes objType, ConditionList* condList)
{
// this function selects which containers need to be searched for spell target
uint32 retMask = GRID_MAP_TYPE_MASK_ALL;
// filter searchers based on searched object type
switch (objType)
{
case TARGET_OBJECT_TYPE_UNIT:
case TARGET_OBJECT_TYPE_UNIT_AND_DEST:
case TARGET_OBJECT_TYPE_CORPSE:
case TARGET_OBJECT_TYPE_CORPSE_ENEMY:
case TARGET_OBJECT_TYPE_CORPSE_ALLY:
retMask &= GRID_MAP_TYPE_MASK_PLAYER | GRID_MAP_TYPE_MASK_CORPSE | GRID_MAP_TYPE_MASK_CREATURE;
break;
case TARGET_OBJECT_TYPE_GOBJ:
case TARGET_OBJECT_TYPE_GOBJ_ITEM:
retMask &= GRID_MAP_TYPE_MASK_GAMEOBJECT;
break;
default:
break;
}
if (!(m_spellInfo->AttributesEx2 & SPELL_ATTR2_CAN_TARGET_DEAD))
retMask &= ~GRID_MAP_TYPE_MASK_CORPSE;
if (m_spellInfo->AttributesEx3 & SPELL_ATTR3_ONLY_TARGET_PLAYERS)
retMask &= GRID_MAP_TYPE_MASK_CORPSE | GRID_MAP_TYPE_MASK_PLAYER;
if (m_spellInfo->AttributesEx3 & SPELL_ATTR3_ONLY_TARGET_GHOSTS)
retMask &= GRID_MAP_TYPE_MASK_PLAYER;
if (condList)
retMask &= sConditionMgr->GetSearcherTypeMaskForConditionList(*condList);
return retMask;
}
template<class SEARCHER>
void Spell::SearchTargets(SEARCHER& searcher, uint32 containerMask, Unit* referer, Position const* pos, float radius)
{
if (!containerMask)
return;
// search world and grid for possible targets
bool searchInGrid = containerMask & (GRID_MAP_TYPE_MASK_CREATURE | GRID_MAP_TYPE_MASK_GAMEOBJECT);
bool searchInWorld = containerMask & (GRID_MAP_TYPE_MASK_CREATURE | GRID_MAP_TYPE_MASK_PLAYER | GRID_MAP_TYPE_MASK_CORPSE);
if (searchInGrid || searchInWorld)
{
float x,y;
x = pos->GetPositionX();
y = pos->GetPositionY();
CellCoord p(Trinity::ComputeCellCoord(x, y));
Cell cell(p);
cell.SetNoCreate();
Map& map = *(referer->GetMap());
if (searchInWorld)
{
TypeContainerVisitor<SEARCHER, WorldTypeMapContainer> world_object_notifier(searcher);
cell.Visit(p, world_object_notifier, map, radius, x, y);
}
if (searchInGrid)
{
TypeContainerVisitor<SEARCHER, GridTypeMapContainer > grid_object_notifier(searcher);
cell.Visit(p, grid_object_notifier, map, radius, x , y);
}
}
}
WorldObject* Spell::SearchNearbyTarget(float range, SpellTargetObjectTypes objectType, SpellTargetCheckTypes selectionType, ConditionList* condList)
{
WorldObject* target = NULL;
uint32 containerTypeMask = GetSearcherTypeMask(objectType, condList);
if (!containerTypeMask)
return NULL;
Trinity::WorldObjectSpellNearbyTargetCheck check(range, m_caster, m_spellInfo, selectionType, condList);
Trinity::WorldObjectLastSearcher<Trinity::WorldObjectSpellNearbyTargetCheck> searcher(m_caster, target, check, containerTypeMask);
SearchTargets<Trinity::WorldObjectLastSearcher<Trinity::WorldObjectSpellNearbyTargetCheck> > (searcher, containerTypeMask, m_caster, m_caster, range);
return target;
}
void Spell::SearchAreaTargets(std::list<WorldObject*>& targets, float range, Position const* position, Unit* referer, SpellTargetObjectTypes objectType, SpellTargetCheckTypes selectionType, ConditionList* condList)
{
uint32 containerTypeMask = GetSearcherTypeMask(objectType, condList);
if (!containerTypeMask)
return;
Trinity::WorldObjectSpellAreaTargetCheck check(range, position, m_caster, referer, m_spellInfo, selectionType, condList);
Trinity::WorldObjectListSearcher<Trinity::WorldObjectSpellAreaTargetCheck> searcher(m_caster, targets, check, containerTypeMask);
SearchTargets<Trinity::WorldObjectListSearcher<Trinity::WorldObjectSpellAreaTargetCheck> > (searcher, containerTypeMask, m_caster, m_caster, range);
}
void Spell::SearchChainTargets(std::list<WorldObject*>& targets, uint32 chainTargets, WorldObject* target, SpellTargetObjectTypes objectType, SpellTargetCheckTypes selectType, ConditionList* condList, bool isChainHeal)
{
// max dist for jump target selection
float jumpRadius = 0.0f;
switch (m_spellInfo->DmgClass)
{
case SPELL_DAMAGE_CLASS_RANGED:
// 7.5y for multi shot
jumpRadius = 7.5f;
break;
case SPELL_DAMAGE_CLASS_MELEE:
// 5y for swipe, cleave and similar
jumpRadius = 5.0f;
break;
case SPELL_DAMAGE_CLASS_NONE:
case SPELL_DAMAGE_CLASS_MAGIC:
// 12.5y for chain heal spell since 3.2 patch
if (isChainHeal)
jumpRadius = 12.5f;
// 10y as default for magic chain spells
else
jumpRadius = 10.0f;
break;
}
// chain lightning/heal spells and similar - allow to jump at larger distance and go out of los
bool isBouncingFar = (m_spellInfo->AttributesEx4 & SPELL_ATTR4_AREA_TARGET_CHAIN
|| m_spellInfo->DmgClass == SPELL_DAMAGE_CLASS_NONE
|| m_spellInfo->DmgClass == SPELL_DAMAGE_CLASS_MAGIC);
// max dist which spell can reach
float searchRadius = jumpRadius;
if (isBouncingFar)
searchRadius *= chainTargets;
std::list<WorldObject*> tempTargets;
SearchAreaTargets(tempTargets, searchRadius, target, m_caster, objectType, selectType, condList);
tempTargets.remove(target);
// remove targets which are always invalid for chain spells
// for some spells allow only chain targets in front of caster (swipe for example)
if (!isBouncingFar)
{
for (std::list<WorldObject*>::iterator itr = tempTargets.begin(); itr != tempTargets.end();)
{
std::list<WorldObject*>::iterator checkItr = itr++;
if (!m_caster->HasInArc(static_cast<float>(M_PI), *checkItr))
tempTargets.erase(checkItr);
}
}
while (chainTargets)
{
// try to get unit for next chain jump
std::list<WorldObject*>::iterator foundItr = tempTargets.end();
// get unit with highest hp deficit in dist
if (isChainHeal)
{
uint32 maxHPDeficit = 0;
for (std::list<WorldObject*>::iterator itr = tempTargets.begin(); itr != tempTargets.end(); ++itr)
{
if (Unit* unitTarget = (*itr)->ToUnit())
{
uint32 deficit = unitTarget->GetMaxHealth() - unitTarget->GetHealth();
if ((deficit > maxHPDeficit || foundItr == tempTargets.end()) && target->IsWithinDist(unitTarget, jumpRadius) && target->IsWithinLOSInMap(unitTarget))
{
foundItr = itr;
maxHPDeficit = deficit;
}
}
}
}
// get closest object
else
{
for (std::list<WorldObject*>::iterator itr = tempTargets.begin(); itr != tempTargets.end(); ++itr)
{
if (foundItr == tempTargets.end())
{
if ((!isBouncingFar || target->IsWithinDist(*itr, jumpRadius)) && target->IsWithinLOSInMap(*itr))
foundItr = itr;
}
else if (target->GetDistanceOrder(*itr, *foundItr) && target->IsWithinLOSInMap(*itr))
foundItr = itr;
}
}
// not found any valid target - chain ends
if (foundItr == tempTargets.end())
break;
target = *foundItr;
tempTargets.erase(foundItr);
targets.push_back(target);
--chainTargets;
}
}
void Spell::prepareDataForTriggerSystem(AuraEffect const* /*triggeredByAura*/)
{
//==========================================================================================
// Now fill data for trigger system, need know:
// can spell trigger another or not (m_canTrigger)
// Create base triggers flags for Attacker and Victim (m_procAttacker, m_procVictim and m_procEx)
//==========================================================================================
m_procVictim = m_procAttacker = 0;
// 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_DONE_SPELL_MELEE_DMG_CLASS;
if (m_attackType == OFF_ATTACK)
m_procAttacker |= PROC_FLAG_DONE_OFFHAND_ATTACK;
else
m_procAttacker |= PROC_FLAG_DONE_MAINHAND_ATTACK;
m_procVictim = PROC_FLAG_TAKEN_SPELL_MELEE_DMG_CLASS;
break;
case SPELL_DAMAGE_CLASS_RANGED:
// Auto attack
if (m_spellInfo->AttributesEx2 & SPELL_ATTR2_AUTOREPEAT_FLAG)
{
m_procAttacker = PROC_FLAG_DONE_RANGED_AUTO_ATTACK;
m_procVictim = PROC_FLAG_TAKEN_RANGED_AUTO_ATTACK;
}
else // Ranged spell attack
{
m_procAttacker = PROC_FLAG_DONE_SPELL_RANGED_DMG_CLASS;
m_procVictim = PROC_FLAG_TAKEN_SPELL_RANGED_DMG_CLASS;
}
break;
default:
if (m_spellInfo->EquippedItemClass == ITEM_CLASS_WEAPON &&
m_spellInfo->EquippedItemSubClassMask & (1<<ITEM_SUBCLASS_WEAPON_WAND)
&& m_spellInfo->AttributesEx2 & SPELL_ATTR2_AUTOREPEAT_FLAG) // Wands auto attack
{
m_procAttacker = PROC_FLAG_DONE_RANGED_AUTO_ATTACK;
m_procVictim = PROC_FLAG_TAKEN_RANGED_AUTO_ATTACK;
}
// For other spells trigger procflags are set in Spell::DoAllEffectOnTarget
// Because spell positivity is dependant on target
}
m_procEx = PROC_EX_NONE;
// Hunter trap spells - activation proc for Lock and Load, Entrapment and Misdirection
if (m_spellInfo->SpellFamilyName == SPELLFAMILY_HUNTER &&
(m_spellInfo->SpellFamilyFlags[0] & 0x18 || // Freezing and Frost Trap, Freezing Arrow
m_spellInfo->Id == 57879 || // Snake Trap - done this way to avoid double proc
m_spellInfo->SpellFamilyFlags[2] & 0x00024000)) // Explosive and Immolation Trap
m_procAttacker |= PROC_FLAG_DONE_TRAP_ACTIVATION;
/* Effects which are result of aura proc from triggered spell cannot proc
to prevent chain proc of these spells */
// Hellfire Effect - trigger as DOT
if (m_spellInfo->SpellFamilyName == SPELLFAMILY_WARLOCK && m_spellInfo->SpellFamilyFlags[0] & 0x00000040)
{
m_procAttacker = PROC_FLAG_DONE_PERIODIC;
m_procVictim = PROC_FLAG_TAKEN_PERIODIC;
}
// Ranged autorepeat attack is set as triggered spell - ignore it
if (!(m_procAttacker & PROC_FLAG_DONE_RANGED_AUTO_ATTACK))
{
if (_triggeredCastFlags & TRIGGERED_DISALLOW_PROC_EVENTS &&
(m_spellInfo->AttributesEx2 & SPELL_ATTR2_TRIGGERED_CAN_TRIGGER_PROC ||
m_spellInfo->AttributesEx3 & SPELL_ATTR3_TRIGGERED_CAN_TRIGGER_PROC_2))
m_procEx |= PROC_EX_INTERNAL_CANT_PROC;
else if (_triggeredCastFlags & TRIGGERED_DISALLOW_PROC_EVENTS)
m_procEx |= PROC_EX_INTERNAL_TRIGGERED;
}
// Totem casts require spellfamilymask defined in spell_proc_event to proc
if (m_originalCaster && m_caster != m_originalCaster && m_caster->GetTypeId() == TYPEID_UNIT && m_caster->ToCreature()->isTotem() && m_caster->IsControlledByPlayer())
m_procEx |= PROC_EX_INTERNAL_REQ_FAMILY;
}
void Spell::CleanupTargetList()
{
m_UniqueTargetInfo.clear();
m_UniqueGOTargetInfo.clear();
m_UniqueItemInfo.clear();
m_delayMoment = 0;
}
void Spell::AddUnitTarget(Unit* target, uint32 effectMask, bool checkIfValid /*= true*/)
{
for (uint32 effIndex = 0; effIndex < MAX_SPELL_EFFECTS; ++effIndex)
if (!m_spellInfo->Effects[effIndex].IsEffect() || !CheckEffectTarget(target, effIndex))
effectMask &= ~(1 << effIndex);
// no effects left
if (!effectMask)
return;
if (checkIfValid)
if (m_spellInfo->CheckTarget(m_caster, target, true) != SPELL_CAST_OK)
return;
// Check for effect immune skip if immuned
for (uint32 effIndex = 0; effIndex < MAX_SPELL_EFFECTS; ++effIndex)
if (target->IsImmunedToSpellEffect(m_spellInfo, effIndex))
effectMask &= ~(1 << effIndex);
uint64 targetGUID = target->GetGUID();
// Lookup target in already in list
for (std::list<TargetInfo>::iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit)
{
if (targetGUID == ihit->targetGUID) // Found in list
{
ihit->effectMask |= effectMask; // Immune effects removed from mask
ihit->scaleAura = false;
if (m_auraScaleMask && ihit->effectMask == m_auraScaleMask && m_caster != target)
{
SpellInfo const* auraSpell = sSpellMgr->GetSpellInfo(sSpellMgr->GetFirstSpellInChain(m_spellInfo->Id));
if (uint32(target->getLevel() + 10) >= auraSpell->SpellLevel)
ihit->scaleAura = true;
}
return;
}
}
// This is new target calculate data for him
// Get spell hit result on target
TargetInfo targetInfo;
targetInfo.targetGUID = targetGUID; // Store target GUID
targetInfo.effectMask = effectMask; // Store all effects not immune
targetInfo.processed = false; // Effects not apply on target
targetInfo.alive = target->isAlive();
targetInfo.damage = 0;
targetInfo.crit = false;
targetInfo.scaleAura = false;
if (m_auraScaleMask && targetInfo.effectMask == m_auraScaleMask && m_caster != target)
{
SpellInfo const* auraSpell = sSpellMgr->GetSpellInfo(sSpellMgr->GetFirstSpellInChain(m_spellInfo->Id));
if (uint32(target->getLevel() + 10) >= auraSpell->SpellLevel)
targetInfo.scaleAura = true;
}
// Calculate hit result
if (m_originalCaster)
{
targetInfo.missCondition = m_originalCaster->SpellHitResult(target, m_spellInfo, m_canReflect);
if (m_skipCheck && targetInfo.missCondition != SPELL_MISS_IMMUNE)
targetInfo.missCondition = SPELL_MISS_NONE;
}
else
targetInfo.missCondition = SPELL_MISS_EVADE; //SPELL_MISS_NONE;
// Spell have speed - need calculate incoming time
// Incoming time is zero for self casts. At least I think so.
if (m_spellInfo->Speed > 0.0f && m_caster != target)
{
// calculate spell incoming interval
// TODO: this is a hack
float dist = m_caster->GetDistance(target->GetPositionX(), target->GetPositionY(), target->GetPositionZ());
if (dist < 5.0f)
dist = 5.0f;
targetInfo.timeDelay = (uint64) floor(dist / m_spellInfo->Speed * 1000.0f);
// Calculate minimum incoming time
if (m_delayMoment == 0 || m_delayMoment > targetInfo.timeDelay)
m_delayMoment = targetInfo.timeDelay;
}
else
targetInfo.timeDelay = 0LL;
// If target reflect spell back to caster
if (targetInfo.missCondition == SPELL_MISS_REFLECT)
{
// Calculate reflected spell result on caster
targetInfo.reflectResult = m_caster->SpellHitResult(m_caster, m_spellInfo, m_canReflect);
if (targetInfo.reflectResult == SPELL_MISS_REFLECT) // Impossible reflect again, so simply deflect spell
targetInfo.reflectResult = SPELL_MISS_PARRY;
// Increase time interval for reflected spells by 1.5
targetInfo.timeDelay += targetInfo.timeDelay >> 1;
}
else
targetInfo.reflectResult = SPELL_MISS_NONE;
// Add target to list
m_UniqueTargetInfo.push_back(targetInfo);
}
void Spell::AddGOTarget(GameObject* go, uint32 effectMask)
{
for (uint32 effIndex = 0; effIndex < MAX_SPELL_EFFECTS; ++effIndex)
{
if (!m_spellInfo->Effects[effIndex].IsEffect())
effectMask &= ~(1 << effIndex);
else
{
switch (m_spellInfo->Effects[effIndex].Effect)
{
case SPELL_EFFECT_GAMEOBJECT_DAMAGE:
case SPELL_EFFECT_GAMEOBJECT_REPAIR:
case SPELL_EFFECT_GAMEOBJECT_SET_DESTRUCTION_STATE:
if (go->GetGoType() != GAMEOBJECT_TYPE_DESTRUCTIBLE_BUILDING)
effectMask &= ~(1 << effIndex);
break;
default:
break;
}
}
}
if (!effectMask)
return;
uint64 targetGUID = go->GetGUID();
// Lookup target in already in list
for (std::list<GOTargetInfo>::iterator ihit = m_UniqueGOTargetInfo.begin(); ihit != m_UniqueGOTargetInfo.end(); ++ihit)
{
if (targetGUID == ihit->targetGUID) // Found in list
{
ihit->effectMask |= effectMask; // Add only effect mask
return;
}
}
// This is new target calculate data for him
GOTargetInfo target;
target.targetGUID = targetGUID;
target.effectMask = effectMask;
target.processed = false; // Effects not apply on target
// Spell have speed - need calculate incoming time
if (m_spellInfo->Speed > 0.0f)
{
// calculate spell incoming interval
float dist = m_caster->GetDistance(go->GetPositionX(), go->GetPositionY(), go->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
target.timeDelay = 0LL;
// Add target to list
m_UniqueGOTargetInfo.push_back(target);
}
void Spell::AddGOTarget(uint64 goGUID, uint32 effectMask)
{
if (GameObject* go = m_caster->GetMap()->GetGameObject(goGUID))
AddGOTarget(go, effectMask);
}
void Spell::AddItemTarget(Item* item, uint32 effectMask)
{
for (uint32 effIndex = 0; effIndex < MAX_SPELL_EFFECTS; ++effIndex)
if (!m_spellInfo->Effects[effIndex].IsEffect())
effectMask &= ~(1 << effIndex);
// no effects left
if (!effectMask)
return;
// Lookup target in already in list
for (std::list<ItemTargetInfo>::iterator ihit = m_UniqueItemInfo.begin(); ihit != m_UniqueItemInfo.end(); ++ihit)
{
if (item == ihit->item) // Found in list
{
ihit->effectMask |= effectMask; // Add only effect mask
return;
}
}
// This is new target add data
ItemTargetInfo target;
target.item = item;
target.effectMask = effectMask;
m_UniqueItemInfo.push_back(target);
}
void Spell::DoAllEffectOnTarget(TargetInfo* target)
{
if (!target || target->processed)
return;
target->processed = true; // Target checked in apply effects procedure
// Get mask of effects for target
uint8 mask = target->effectMask;
Unit* unit = m_caster->GetGUID() == target->targetGUID ? m_caster : ObjectAccessor::GetUnit(*m_caster, target->targetGUID);
if (!unit)
{
uint8 farMask = 0;
// create far target mask
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
if (m_spellInfo->Effects[i].IsFarUnitTargetEffect())
if ((1 << i) & mask)
farMask |= (1 << i);
if (!farMask)
return;
// find unit in world
unit = ObjectAccessor::FindUnit(target->targetGUID);
if (!unit)
return;
// do far effects on the unit
// can't use default call because of threading, do stuff as fast as possible
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
if (farMask & (1 << i))
HandleEffects(unit, NULL, NULL, i, SPELL_EFFECT_HANDLE_HIT_TARGET);
return;
}
if (unit->isAlive() != target->alive)
return;
if (getState() == SPELL_STATE_DELAYED && !m_spellInfo->IsPositive() && (getMSTime() - target->timeDelay) <= unit->m_lastSanctuaryTime)
return; // No missinfo in that case
// Get original caster (if exist) and calculate damage/healing from him data
Unit* caster = m_originalCaster ? m_originalCaster : m_caster;
// Skip if m_originalCaster not avaiable
if (!caster)
return;
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
m_damage = target->damage;
m_healing = -target->damage;
// Fill base trigger info
uint32 procAttacker = m_procAttacker;
uint32 procVictim = m_procVictim;
uint32 procEx = m_procEx;
m_spellAura = NULL; // Set aura to null for every target-make sure that pointer is not used for unit without aura applied
//Spells with this flag cannot trigger if effect is casted on self
bool canEffectTrigger = !(m_spellInfo->AttributesEx3 & SPELL_ATTR3_CANT_TRIGGER_PROC) && unitTarget->CanProc() && CanExecuteTriggersOnHit(mask);
Unit* spellHitTarget = NULL;
if (missInfo == SPELL_MISS_NONE) // In case spell hit target, do all effect on that target
spellHitTarget = unit;
else if (missInfo == SPELL_MISS_REFLECT) // In case spell reflect from target, do all effect on caster (if hit)
{
if (target->reflectResult == SPELL_MISS_NONE) // If reflected spell hit caster -> do all effect on him
{
spellHitTarget = m_caster;
if (m_caster->GetTypeId() == TYPEID_UNIT)
m_caster->ToCreature()->LowerPlayerDamageReq(target->damage);
}
}
if (spellHitTarget)
{
SpellMissInfo missInfo2 = DoSpellHitOnUnit(spellHitTarget, mask, target->scaleAura);
if (missInfo2 != SPELL_MISS_NONE)
{
if (missInfo2 != SPELL_MISS_MISS)
m_caster->SendSpellMiss(unit, m_spellInfo->Id, missInfo2);
m_damage = 0;
spellHitTarget = NULL;
}
}
// Do not take combo points on dodge and miss
if (missInfo != SPELL_MISS_NONE && m_needComboPoints &&
m_targets.GetUnitTargetGUID() == target->targetGUID)
{
m_needComboPoints = false;
// Restore spell mods for a miss/dodge/parry Cold Blood
// TODO: check how broad this rule should be
if (m_caster->GetTypeId() == TYPEID_PLAYER && (missInfo == SPELL_MISS_MISS ||
missInfo == SPELL_MISS_DODGE || missInfo == SPELL_MISS_PARRY))
m_caster->ToPlayer()->RestoreSpellMods(this, 14177);
}
// Trigger info was not filled in spell::preparedatafortriggersystem - we do it now
if (canEffectTrigger && !procAttacker && !procVictim)
{
bool positive = true;
if (m_damage > 0)
positive = false;
else if (!m_healing)
{
for (uint8 i = 0; i< MAX_SPELL_EFFECTS; ++i)
// If at least one effect negative spell is negative hit
if (mask & (1<<i) && !m_spellInfo->IsPositiveEffect(i))
{
positive = false;
break;
}
}
switch (m_spellInfo->DmgClass)
{
case SPELL_DAMAGE_CLASS_MAGIC:
if (positive)
{
procAttacker |= PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_POS;
procVictim |= PROC_FLAG_TAKEN_SPELL_MAGIC_DMG_CLASS_POS;
}
else
{
procAttacker |= PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG;
procVictim |= PROC_FLAG_TAKEN_SPELL_MAGIC_DMG_CLASS_NEG;
}
break;
case SPELL_DAMAGE_CLASS_NONE:
if (positive)
{
procAttacker |= PROC_FLAG_DONE_SPELL_NONE_DMG_CLASS_POS;
procVictim |= PROC_FLAG_TAKEN_SPELL_NONE_DMG_CLASS_POS;
}
else
{
procAttacker |= PROC_FLAG_DONE_SPELL_NONE_DMG_CLASS_NEG;
procVictim |= PROC_FLAG_TAKEN_SPELL_NONE_DMG_CLASS_NEG;
}
break;
}
}
CallScriptOnHitHandlers();
// All calculated do it!
// Do healing and triggers
if (m_healing > 0)
{
bool crit = caster->isSpellCrit(unitTarget, m_spellInfo, m_spellSchoolMask);
uint32 addhealth = m_healing;
if (crit)
{
procEx |= PROC_EX_CRITICAL_HIT;
addhealth = caster->SpellCriticalHealingBonus(m_spellInfo, addhealth, NULL);
}
else
procEx |= PROC_EX_NORMAL_HIT;
// Do triggers for unit (reflect triggers passed on hit phase for correct drop charge)
if (canEffectTrigger && missInfo != SPELL_MISS_REFLECT)
caster->ProcDamageAndSpell(unitTarget, procAttacker, procVictim, procEx, addhealth, m_attackType, m_spellInfo, m_triggeredByAuraSpell);
int32 gain = caster->HealBySpell(unitTarget, m_spellInfo, addhealth, crit);
unitTarget->getHostileRefManager().threatAssist(caster, float(gain) * 0.5f, m_spellInfo);
m_healing = gain;
}
// Do damage and triggers
else if (m_damage > 0)
{
// Fill base damage struct (unitTarget - is real spell target)
SpellNonMeleeDamage damageInfo(caster, unitTarget, m_spellInfo->Id, m_spellSchoolMask);
// Add bonuses and fill damageInfo struct
caster->CalculateSpellDamageTaken(&damageInfo, m_damage, m_spellInfo, m_attackType, target->crit);
caster->DealDamageMods(damageInfo.target, damageInfo.damage, &damageInfo.absorb);
// Send log damage message to client
caster->SendSpellNonMeleeDamageLog(&damageInfo);
procEx |= createProcExtendMask(&damageInfo, missInfo);
procVictim |= PROC_FLAG_TAKEN_DAMAGE;
// Do triggers for unit (reflect triggers passed on hit phase for correct drop charge)
if (canEffectTrigger && missInfo != SPELL_MISS_REFLECT)
{
caster->ProcDamageAndSpell(unitTarget, procAttacker, procVictim, procEx, damageInfo.damage, m_attackType, m_spellInfo, m_triggeredByAuraSpell);
if (caster->GetTypeId() == TYPEID_PLAYER && (m_spellInfo->Attributes & SPELL_ATTR0_STOP_ATTACK_TARGET) == 0 &&
(m_spellInfo->DmgClass == SPELL_DAMAGE_CLASS_MELEE || m_spellInfo->DmgClass == SPELL_DAMAGE_CLASS_RANGED))
caster->ToPlayer()->CastItemCombatSpell(unitTarget, m_attackType, procVictim, procEx);
}
caster->DealSpellDamage(&damageInfo, true);
// Haunt
if (m_spellInfo->SpellFamilyName == SPELLFAMILY_WARLOCK && m_spellInfo->SpellFamilyFlags[1] & 0x40000 && m_spellAura && m_spellAura->GetEffect(1))
{
AuraEffect* aurEff = m_spellAura->GetEffect(1);
aurEff->SetAmount(CalculatePctU(aurEff->GetAmount(), damageInfo.damage));
}
m_damage = damageInfo.damage;
}
// Passive spell hits/misses or active spells only misses (only triggers)
else
{
// Fill base damage struct (unitTarget - is real spell target)
SpellNonMeleeDamage damageInfo(caster, unitTarget, m_spellInfo->Id, m_spellSchoolMask);
procEx |= createProcExtendMask(&damageInfo, missInfo);
// Do triggers for unit (reflect triggers passed on hit phase for correct drop charge)
if (canEffectTrigger && missInfo != SPELL_MISS_REFLECT)
caster->ProcDamageAndSpell(unit, procAttacker, procVictim, procEx, 0, m_attackType, m_spellInfo, m_triggeredByAuraSpell);
// Failed Pickpocket, reveal rogue
if (missInfo == SPELL_MISS_RESIST && m_spellInfo->AttributesCu & SPELL_ATTR0_CU_PICKPOCKET && unitTarget->GetTypeId() == TYPEID_UNIT)
{
m_caster->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_TALK);
if (unitTarget->ToCreature()->IsAIEnabled)
unitTarget->ToCreature()->AI()->AttackStart(m_caster);
}
}
if (missInfo != SPELL_MISS_EVADE && m_caster && !m_caster->IsFriendlyTo(unit) && !m_spellInfo->IsPositive())
{
m_caster->CombatStart(unit, !(m_spellInfo->AttributesEx3 & SPELL_ATTR3_NO_INITIAL_AGGRO));
if (m_spellInfo->AttributesCu & SPELL_ATTR0_CU_AURA_CC)
if (!unit->IsStandState())
unit->SetStandState(UNIT_STAND_STATE_STAND);
}
if (spellHitTarget)
{
//AI functions
if (spellHitTarget->GetTypeId() == TYPEID_UNIT)
{
if (spellHitTarget->ToCreature()->IsAIEnabled)
spellHitTarget->ToCreature()->AI()->SpellHit(m_caster, m_spellInfo);
// 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 (m_originalCaster && m_originalCaster->IsControlledByPlayer() && !spellHitTarget->ToCreature()->isPet() && !IsAutoRepeat() && !IsNextMeleeSwingSpell() && !IsChannelActive())
if (Player* p = m_originalCaster->GetCharmerOrOwnerPlayerOrPlayerItself())
p->CastedCreatureOrGO(spellHitTarget->GetEntry(), spellHitTarget->GetGUID(), m_spellInfo->Id);
}
if (m_caster->GetTypeId() == TYPEID_UNIT && m_caster->ToCreature()->IsAIEnabled)
m_caster->ToCreature()->AI()->SpellHitTarget(spellHitTarget, m_spellInfo);
// Needs to be called after dealing damage/healing to not remove breaking on damage auras
DoTriggersOnSpellHit(spellHitTarget, mask);
// if target is fallged for pvp also flag caster if a player
if (unit->IsPvP() && m_caster->GetTypeId() == TYPEID_PLAYER)
m_caster->ToPlayer()->UpdatePvP(true);
CallScriptAfterHitHandlers();
}
}
SpellMissInfo Spell::DoSpellHitOnUnit(Unit* unit, uint32 effectMask, bool scaleAura)
{
if (!unit || !effectMask)
return SPELL_MISS_EVADE;
// For delayed spells immunity may be applied between missile launch and hit - check immunity for that case
// disable effects to which unit is immune
for (uint32 effectNumber = 0; effectNumber < MAX_SPELL_EFFECTS; ++effectNumber)
if (effectMask & (1 << effectNumber) && unit->IsImmunedToSpellEffect(m_spellInfo, effectNumber))
effectMask &= ~(1 << effectNumber);
if (!effectMask || (m_spellInfo->Speed && (unit->IsImmunedToDamage(m_spellInfo) || unit->IsImmunedToSpell(m_spellInfo))))
return SPELL_MISS_IMMUNE;
PrepareScriptHitHandlers();
CallScriptBeforeHitHandlers();
if (unit->GetTypeId() == TYPEID_PLAYER)
{
unit->ToPlayer()->GetAchievementMgr().StartTimedAchievement(ACHIEVEMENT_TIMED_TYPE_SPELL_TARGET, m_spellInfo->Id);
unit->ToPlayer()->GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_BE_SPELL_TARGET, m_spellInfo->Id, 0, m_caster);
unit->ToPlayer()->GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_BE_SPELL_TARGET2, m_spellInfo->Id);
}
if (m_caster->GetTypeId() == TYPEID_PLAYER)
{
m_caster->ToPlayer()->GetAchievementMgr().StartTimedAchievement(ACHIEVEMENT_TIMED_TYPE_SPELL_CASTER, m_spellInfo->Id);
m_caster->ToPlayer()->GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_CAST_SPELL2, m_spellInfo->Id, 0, unit);
}
if (m_caster != unit)
{
// Recheck UNIT_FLAG_NON_ATTACKABLE for delayed spells
if (m_spellInfo->Speed > 0.0f && unit->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE) && unit->GetCharmerOrOwnerGUID() != m_caster->GetGUID())
return SPELL_MISS_EVADE;
if (m_caster->_IsValidAttackTarget(unit, m_spellInfo))
{
unit->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_HITBYSPELL);
//TODO: This is a hack. But we do not know what types of stealth should be interrupted by CC
if ((m_spellInfo->AttributesCu & SPELL_ATTR0_CU_AURA_CC) && unit->IsControlledByPlayer())
unit->RemoveAurasByType(SPELL_AURA_MOD_STEALTH);
}
else if (m_caster->IsFriendlyTo(unit))
{
// for delayed spells ignore negative spells (after duel end) for friendly targets
// TODO: this cause soul transfer bugged
if (m_spellInfo->Speed > 0.0f && unit->GetTypeId() == TYPEID_PLAYER && !m_spellInfo->IsPositive())
return SPELL_MISS_EVADE;
// assisting case, healing and resurrection
if (unit->HasUnitState(UNIT_STATE_ATTACK_PLAYER))
{
m_caster->SetContestedPvP();
if (m_caster->GetTypeId() == TYPEID_PLAYER)
m_caster->ToPlayer()->UpdatePvP(true);
}
if (unit->isInCombat() && !(m_spellInfo->AttributesEx3 & SPELL_ATTR3_NO_INITIAL_AGGRO))
{
m_caster->SetInCombatState(unit->GetCombatTimer() > 0, unit);
unit->getHostileRefManager().threatAssist(m_caster, 0.0f);
}
}
}
// 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 = GetDiminishingReturnsGroupForSpell(m_spellInfo, m_triggeredByAuraSpell);
if (m_diminishGroup)
{
m_diminishLevel = unit->GetDiminishing(m_diminishGroup);
DiminishingReturnsType type = GetDiminishingReturnsGroupType(m_diminishGroup);
// Increase Diminishing on unit, current informations for actually casts will use values above
if ((type == DRTYPE_PLAYER && unit->GetCharmerOrOwnerPlayerOrPlayerItself()) || type == DRTYPE_ALL)
unit->IncrDiminishing(m_diminishGroup);
}
uint8 aura_effmask = 0;
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
if (effectMask & (1 << i) && m_spellInfo->Effects[i].IsUnitOwnedAuraEffect())
aura_effmask |= 1 << i;
if (aura_effmask)
{
// Select rank for aura with level requirements only in specific cases
// Unit has to be target only of aura effect, both caster and target have to be players, target has to be other than unit target
SpellInfo const* aurSpellInfo = m_spellInfo;
int32 basePoints[3];
if (scaleAura)
{
aurSpellInfo = m_spellInfo->GetAuraRankForLevel(unitTarget->getLevel());
ASSERT(aurSpellInfo);
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
{
basePoints[i] = aurSpellInfo->Effects[i].BasePoints;
if (m_spellInfo->Effects[i].Effect != aurSpellInfo->Effects[i].Effect)
{
aurSpellInfo = m_spellInfo;
break;
}
}
}
if (m_originalCaster)
{
bool refresh = false;
m_spellAura = Aura::TryRefreshStackOrCreate(aurSpellInfo, effectMask, unit,
m_originalCaster, (aurSpellInfo == m_spellInfo)? &m_spellValue->EffectBasePoints[0] : &basePoints[0], m_CastItem, 0, &refresh);
if (m_spellAura)
{
// Set aura stack amount to desired value
if (m_spellValue->AuraStackAmount > 1)
{
if (!refresh)
m_spellAura->SetStackAmount(m_spellValue->AuraStackAmount);
else
m_spellAura->ModStackAmount(m_spellValue->AuraStackAmount);
}
// Now Reduce spell duration using data received at spell hit
int32 duration = m_spellAura->GetMaxDuration();
int32 limitduration = GetDiminishingReturnsLimitDuration(m_diminishGroup, aurSpellInfo);
float diminishMod = unit->ApplyDiminishingToDuration(m_diminishGroup, duration, m_originalCaster, m_diminishLevel, limitduration);
// unit is immune to aura if it was diminished to 0 duration
if (diminishMod == 0.0f)
{
m_spellAura->Remove();
bool found = false;
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
if (effectMask & (1 << i) && m_spellInfo->Effects[i].Effect != SPELL_EFFECT_APPLY_AURA)
found = true;
if (!found)
return SPELL_MISS_IMMUNE;
}
else
{
((UnitAura*)m_spellAura)->SetDiminishGroup(m_diminishGroup);
bool positive = m_spellAura->GetSpellInfo()->IsPositive();
if (AuraApplication* aurApp = m_spellAura->GetApplicationOfTarget(m_originalCaster->GetGUID()))
positive = aurApp->IsPositive();
duration = m_originalCaster->ModSpellDuration(aurSpellInfo, unit, duration, positive, effectMask);
// Haste modifies duration of channeled spells
if (m_spellInfo->IsChanneled())
{
if (m_spellInfo->AttributesEx5 & SPELL_ATTR5_HASTE_AFFECT_DURATION)
m_originalCaster->ModSpellCastTime(aurSpellInfo, duration, this);
}
// and duration of auras affected by SPELL_AURA_PERIODIC_HASTE
else if (m_originalCaster->HasAuraTypeWithAffectMask(SPELL_AURA_PERIODIC_HASTE, aurSpellInfo) || m_spellInfo->AttributesEx5 & SPELL_ATTR5_HASTE_AFFECT_DURATION)
duration = int32(duration * m_originalCaster->GetFloatValue(UNIT_MOD_CAST_SPEED));
if (duration != m_spellAura->GetMaxDuration())
{
m_spellAura->SetMaxDuration(duration);
m_spellAura->SetDuration(duration);
}
m_spellAura->_RegisterForTargets();
}
}
}
}
for (uint32 effectNumber = 0; effectNumber < MAX_SPELL_EFFECTS; ++effectNumber)
if (effectMask & (1 << effectNumber))
HandleEffects(unit, NULL, NULL, effectNumber, SPELL_EFFECT_HANDLE_HIT_TARGET);
return SPELL_MISS_NONE;
}
void Spell::DoTriggersOnSpellHit(Unit* unit, uint8 effMask)
{
// Apply additional spell effects to target
// TODO: move this code to scripts
if (m_preCastSpell)
{
// Paladin immunity shields
if (m_preCastSpell == 61988)
{
// Cast Forbearance
m_caster->CastSpell(unit, 25771, true);
// Cast Avenging Wrath Marker
unit->CastSpell(unit, 61987, true);
}
// Avenging Wrath
if (m_preCastSpell == 61987)
// Cast the serverside immunity shield marker
m_caster->CastSpell(unit, 61988, true);
if (sSpellMgr->GetSpellInfo(m_preCastSpell))
// Blizz seems to just apply aura without bothering to cast
m_caster->AddAura(m_preCastSpell, unit);
}
// handle SPELL_AURA_ADD_TARGET_TRIGGER auras
// this is executed after spell proc spells on target hit
// spells are triggered for each hit spell target
// info confirmed with retail sniffs of permafrost and shadow weaving
if (!m_hitTriggerSpells.empty())
{
int _duration = 0;
for (HitTriggerSpells::const_iterator i = m_hitTriggerSpells.begin(); i != m_hitTriggerSpells.end(); ++i)
{
if (CanExecuteTriggersOnHit(effMask, i->first) && roll_chance_i(i->second))
{
m_caster->CastSpell(unit, i->first, true);
sLog->outDebug(LOG_FILTER_SPELLS_AURAS, "Spell %d triggered spell %d by SPELL_AURA_ADD_TARGET_TRIGGER aura", m_spellInfo->Id, i->first->Id);
// SPELL_AURA_ADD_TARGET_TRIGGER auras shouldn't trigger auras without duration
// set duration of current aura to the triggered spell
if (i->first->GetDuration() == -1)
{
if (Aura* triggeredAur = unit->GetAura(i->first->Id, m_caster->GetGUID()))
{
// get duration from aura-only once
if (!_duration)
{
Aura* aur = unit->GetAura(m_spellInfo->Id, m_caster->GetGUID());
_duration = aur ? aur->GetDuration() : -1;
}
triggeredAur->SetDuration(_duration);
}
}
}
}
}
// trigger linked auras remove/apply
// TODO: remove/cleanup this, as this table is not documented and people are doing stupid things with it
if (std::vector<int32> const* spellTriggered = sSpellMgr->GetSpellLinked(m_spellInfo->Id + SPELL_LINK_HIT))
for (std::vector<int32>::const_iterator i = spellTriggered->begin(); i != spellTriggered->end(); ++i)
if (*i < 0)
unit->RemoveAurasDueToSpell(-(*i));
else
unit->CastSpell(unit, *i, true, 0, 0, m_caster->GetGUID());
}
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;
PrepareScriptHitHandlers();
CallScriptBeforeHitHandlers();
for (uint32 effectNumber = 0; effectNumber < MAX_SPELL_EFFECTS; ++effectNumber)
if (effectMask & (1 << effectNumber))
HandleEffects(NULL, NULL, go, effectNumber, SPELL_EFFECT_HANDLE_HIT_TARGET);
CallScriptOnHitHandlers();
// 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 (m_originalCaster && m_originalCaster->IsControlledByPlayer() && !IsAutoRepeat() && !IsNextMeleeSwingSpell() && !IsChannelActive())
if (Player* p = m_originalCaster->GetCharmerOrOwnerPlayerOrPlayerItself())
p->CastedCreatureOrGO(go->GetEntry(), go->GetGUID(), m_spellInfo->Id);
CallScriptAfterHitHandlers();
}
void Spell::DoAllEffectOnTarget(ItemTargetInfo* target)
{
uint32 effectMask = target->effectMask;
if (!target->item || !effectMask)
return;
PrepareScriptHitHandlers();
CallScriptBeforeHitHandlers();
for (uint32 effectNumber = 0; effectNumber < MAX_SPELL_EFFECTS; ++effectNumber)
if (effectMask & (1 << effectNumber))
HandleEffects(NULL, target->item, NULL, effectNumber, SPELL_EFFECT_HANDLE_HIT_TARGET);
CallScriptOnHitHandlers();
CallScriptAfterHitHandlers();
}
bool Spell::UpdateChanneledTargetList()
{
// Not need check return true
if (m_channelTargetEffectMask == 0)
return true;
uint8 channelTargetEffectMask = m_channelTargetEffectMask;
uint8 channelAuraMask = 0;
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
if (m_spellInfo->Effects[i].Effect == SPELL_EFFECT_APPLY_AURA)
channelAuraMask |= 1<<i;
channelAuraMask &= channelTargetEffectMask;
float range = 0;
if (channelAuraMask)
{
range = m_spellInfo->GetMaxRange(m_spellInfo->IsPositive());
if (Player* modOwner = m_caster->GetSpellModOwner())
modOwner->ApplySpellMod(m_spellInfo->Id, SPELLMOD_RANGE, range, this);
}
for (std::list<TargetInfo>::iterator ihit= m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit)
{
if (ihit->missCondition == SPELL_MISS_NONE && (channelTargetEffectMask & ihit->effectMask))
{
Unit* unit = m_caster->GetGUID() == ihit->targetGUID ? m_caster : ObjectAccessor::GetUnit(*m_caster, ihit->targetGUID);
if (!unit)
continue;
if (IsValidDeadOrAliveTarget(unit))
{
if (channelAuraMask & ihit->effectMask)
{
if (AuraApplication * aurApp = unit->GetAuraApplication(m_spellInfo->Id, m_originalCasterGUID))
{
if (m_caster != unit && !m_caster->IsWithinDistInMap(unit, range))
{
ihit->effectMask &= ~aurApp->GetEffectMask();
unit->RemoveAura(aurApp);
continue;
}
}
else // aura is dispelled
continue;
}
channelTargetEffectMask &= ~ihit->effectMask; // remove from need alive mask effect that have alive target
}
}
}
// is all effects from m_needAliveTargetMask have alive targets
return channelTargetEffectMask == 0;
}
void Spell::prepare(SpellCastTargets const* targets, AuraEffect const* triggeredByAura)
{
if (m_CastItem)
m_castItemGUID = m_CastItem->GetGUID();
else
m_castItemGUID = 0;
InitExplicitTargets(*targets);
// Fill aura scaling information
if (m_caster->IsControlledByPlayer() && !m_spellInfo->IsPassive() && m_spellInfo->SpellLevel && !m_spellInfo->IsChanneled() && !(_triggeredCastFlags & TRIGGERED_IGNORE_AURA_SCALING))
{
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
{
if (m_spellInfo->Effects[i].Effect == SPELL_EFFECT_APPLY_AURA)
{
// Change aura with ranks only if basepoints are taken from spellInfo and aura is positive
if (m_spellInfo->IsPositiveEffect(i))
{
m_auraScaleMask |= (1 << i);
if (m_spellValue->EffectBasePoints[i] != m_spellInfo->Effects[i].BasePoints)
{
m_auraScaleMask = 0;
break;
}
}
}
}
}
m_spellState = SPELL_STATE_PREPARING;
if (triggeredByAura)
m_triggeredByAuraSpell = triggeredByAura->GetSpellInfo();
// 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 (!(_triggeredCastFlags & TRIGGERED_IGNORE_CAST_IN_PROGRESS) && m_caster->IsNonMeleeSpellCasted(false, true, true) && m_cast_count)
{
SendCastResult(SPELL_FAILED_SPELL_IN_PROGRESS);
finish(false);
return;
}
if (DisableMgr::IsDisabledFor(DISABLE_TYPE_SPELL, m_spellInfo->Id, m_caster))
{
SendCastResult(SPELL_FAILED_SPELL_UNAVAILABLE);
finish(false);
return;
}
LoadScripts();
if (m_caster->GetTypeId() == TYPEID_PLAYER)
m_caster->ToPlayer()->SetSpellModTakingSpell(this, true);
// Fill cost data (not use power for item casts
m_powerCost = m_CastItem ? 0 : m_spellInfo->CalcPowerCost(m_caster, m_spellSchoolMask);
if (m_caster->GetTypeId() == TYPEID_PLAYER)
m_caster->ToPlayer()->SetSpellModTakingSpell(this, false);
// Set combo point requirement
if ((_triggeredCastFlags & TRIGGERED_IGNORE_COMBO_POINTS) || m_CastItem || !m_caster->m_movedPlayer)
m_needComboPoints = false;
SpellCastResult result = CheckCast(true);
if (result != SPELL_CAST_OK && !IsAutoRepeat()) //always cast autorepeat dummy for triggering
{
// Periodic auras should be interrupted when aura triggers a spell which can't be cast
// for example bladestorm aura should be removed on disarm as of patch 3.3.5
// channeled periodic spells should be affected by this (arcane missiles, penance, etc)
// a possible alternative sollution for those would be validating aura target on unit state change
if (triggeredByAura && triggeredByAura->IsPeriodic() && !triggeredByAura->GetBase()->IsPassive())
{
SendChannelUpdate(0);
triggeredByAura->GetBase()->SetDuration(0);
}
SendCastResult(result);
finish(false);
return;
}
// Prepare data for triggers
prepareDataForTriggerSystem(triggeredByAura);
if (m_caster->GetTypeId() == TYPEID_PLAYER)
m_caster->ToPlayer()->SetSpellModTakingSpell(this, true);
// calculate cast time (calculated after first CheckCast check to prevent charge counting for first CheckCast fail)
m_casttime = m_spellInfo->CalcCastTime(m_caster, this);
if (m_caster->GetTypeId() == TYPEID_PLAYER)
m_caster->ToPlayer()->SetSpellModTakingSpell(this, false);
// don't allow channeled spells / spells with cast time to be casted while moving
// (even if they are interrupted on moving, spells with almost immediate effect get to have their effect processed before movement interrupter kicks in)
if ((m_spellInfo->IsChanneled() || m_casttime) && m_caster->GetTypeId() == TYPEID_PLAYER && m_caster->isMoving() && m_spellInfo->InterruptFlags & SPELL_INTERRUPT_FLAG_MOVEMENT)
{
SendCastResult(SPELL_FAILED_MOVING);
finish(false);
return;
}
// set timer base at cast time
ReSetTimer();
sLog->outDebug(LOG_FILTER_SPELLS_AURAS, "Spell::prepare: spell id %u source %u caster %d customCastFlags %u mask %u", m_spellInfo->Id, m_caster->GetEntry(), m_originalCaster ? m_originalCaster->GetEntry() : -1, _triggeredCastFlags, m_targets.GetTargetMask());
//Containers for channeled spells have to be set
//TODO:Apply this to all casted spells if needed
// Why check duration? 29350: channelled triggers channelled
if ((_triggeredCastFlags & TRIGGERED_CAST_DIRECTLY) && (!m_spellInfo->IsChanneled() || !m_spellInfo->GetMaxDuration()))
cast(true);
else
{
// 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 (!(_triggeredCastFlags & TRIGGERED_IGNORE_AURA_INTERRUPT_FLAGS) && m_spellInfo->IsBreakingStealth())
{
m_caster->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_CAST);
for (uint32 i = 0; i < MAX_SPELL_EFFECTS; ++i)
if (m_spellInfo->Effects[i].GetUsedTargetObjectType() == TARGET_OBJECT_TYPE_UNIT)
{
m_caster->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_SPELL_ATTACK);
break;
}
}
m_caster->SetCurrentCastedSpell(this);
SendSpellStart();
// set target for proper facing
if (m_casttime && !(_triggeredCastFlags & TRIGGERED_IGNORE_SET_FACING))
if (uint64 target = m_targets.GetUnitTargetGUID())
if (m_caster->GetGUID() != target && m_caster->GetTypeId() == TYPEID_UNIT)
m_caster->FocusTarget(this, target);
if (!(_triggeredCastFlags & TRIGGERED_IGNORE_GCD))
TriggerGlobalCooldown();
//item: first cast may destroy item and second cast causes crash
if (!m_casttime && !m_spellInfo->StartRecoveryTime && !m_castItemGUID && GetCurrentContainer() == CURRENT_GENERIC_SPELL)
cast(true);
}
}
void Spell::cancel()
{
if (m_spellState == SPELL_STATE_FINISHED)
return;
uint32 oldState = m_spellState;
m_spellState = SPELL_STATE_FINISHED;
m_autoRepeat = false;
switch (oldState)
{
case SPELL_STATE_PREPARING:
CancelGlobalCooldown();
if (m_caster->GetTypeId() == TYPEID_PLAYER)
m_caster->ToPlayer()->RestoreSpellMods(this);
case SPELL_STATE_DELAYED:
SendInterrupted(0);
SendCastResult(SPELL_FAILED_INTERRUPTED);
break;
case SPELL_STATE_CASTING:
for (std::list<TargetInfo>::const_iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit)
if ((*ihit).missCondition == SPELL_MISS_NONE)
if (Unit* unit = m_caster->GetGUID() == ihit->targetGUID ? m_caster : ObjectAccessor::GetUnit(*m_caster, ihit->targetGUID))
unit->RemoveOwnedAura(m_spellInfo->Id, m_originalCasterGUID, 0, AURA_REMOVE_BY_CANCEL);
SendChannelUpdate(0);
SendInterrupted(0);
SendCastResult(SPELL_FAILED_INTERRUPTED);
// spell is canceled-take mods and clear list
if (m_caster->GetTypeId() == TYPEID_PLAYER)
m_caster->ToPlayer()->RemoveSpellMods(this);
m_appliedMods.clear();
break;
default:
break;
}
SetReferencedFromCurrent(false);
if (m_selfContainer && *m_selfContainer == this)
*m_selfContainer = NULL;
m_caster->RemoveDynObject(m_spellInfo->Id);
m_caster->RemoveGameObject(m_spellInfo->Id, true);
//set state back so finish will be processed
m_spellState = oldState;
finish(false);
}
void Spell::cast(bool skipCheck)
{
// update pointers base at GUIDs to prevent access to non-existed already object
UpdatePointers();
// cancel at lost explicit target during cast
if (m_targets.GetObjectTargetGUID() && !m_targets.GetObjectTarget())
{
cancel();
return;
}
// now that we've done the basic check, now run the scripts
// should be done before the spell is actually executed
if (Player* playerCaster = m_caster->ToPlayer())
sScriptMgr->OnPlayerSpellCast(playerCaster, this, skipCheck);
SetExecutedCurrently(true);
if (m_caster->GetTypeId() != TYPEID_PLAYER && m_targets.GetUnitTarget() && m_targets.GetUnitTarget() != m_caster)
m_caster->SetInFront(m_targets.GetUnitTarget());
// Should this be done for original caster?
if (m_caster->GetTypeId() == TYPEID_PLAYER)
{
// Set spell which will drop charges for triggered cast spells
// if not successfully casted, will be remove in finish(false)
m_caster->ToPlayer()->SetSpellModTakingSpell(this, true);
}
CallScriptBeforeCastHandlers();
// skip check if done already (for instant cast spells for example)
if (!skipCheck)
{
SpellCastResult castResult = CheckCast(false);
if (castResult != SPELL_CAST_OK)
{
SendCastResult(castResult);
SendInterrupted(0);
//restore spell mods
if (m_caster->GetTypeId() == TYPEID_PLAYER)
{
m_caster->ToPlayer()->RestoreSpellMods(this);
// cleanup after mod system
// triggered spell pointer can be not removed in some cases
m_caster->ToPlayer()->SetSpellModTakingSpell(this, false);
}
finish(false);
SetExecutedCurrently(false);
return;
}
// additional check after cast bar completes (must not be in CheckCast)
// if trade not complete then remember it in trade data
if (m_targets.GetTargetMask() & TARGET_FLAG_TRADE_ITEM)
{
if (m_caster->GetTypeId() == TYPEID_PLAYER)
{
if (TradeData* my_trade = m_caster->ToPlayer()->GetTradeData())
{
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);
SendCastResult(SPELL_FAILED_DONT_REPORT);
SendInterrupted(0);
m_caster->ToPlayer()->RestoreSpellMods(this);
// cleanup after mod system
// triggered spell pointer can be not removed in some cases
m_caster->ToPlayer()->SetSpellModTakingSpell(this, false);
finish(false);
SetExecutedCurrently(false);
return;
}
}
}
}
}
SelectSpellTargets();
// Spell may be finished after target map check
if (m_spellState == SPELL_STATE_FINISHED)
{
SendInterrupted(0);
//restore spell mods
if (m_caster->GetTypeId() == TYPEID_PLAYER)
{
m_caster->ToPlayer()->RestoreSpellMods(this);
// cleanup after mod system
// triggered spell pointer can be not removed in some cases
m_caster->ToPlayer()->SetSpellModTakingSpell(this, false);
}
finish(false);
SetExecutedCurrently(false);
return;
}
PrepareTriggersExecutedOnHit();
CallScriptOnCastHandlers();
// 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();
if (m_caster->GetTypeId() == TYPEID_PLAYER)
{
if (!(_triggeredCastFlags & TRIGGERED_IGNORE_CAST_ITEM) && m_CastItem)
{
m_caster->ToPlayer()->GetAchievementMgr().StartTimedAchievement(ACHIEVEMENT_TIMED_TYPE_ITEM, m_CastItem->GetEntry());
m_caster->ToPlayer()->GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_USE_ITEM, m_CastItem->GetEntry());
}
m_caster->ToPlayer()->GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_CAST_SPELL, m_spellInfo->Id);
}
if (!(_triggeredCastFlags & TRIGGERED_IGNORE_POWER_AND_REAGENT_COST))
{
// Powers have to be taken before SendSpellGo
TakePower();
TakeReagents(); // we must remove reagents before HandleEffects to allow place crafted item in same slot
}
else if (Item* targetItem = m_targets.GetItemTarget())
{
/// Not own traded item (in trader trade slot) req. reagents including triggered spell case
if (targetItem->GetOwnerGUID() != m_caster->GetGUID())
TakeReagents();
}
// CAST SPELL
SendSpellCooldown();
PrepareScriptHitHandlers();
HandleLaunchPhase();
// we must send smsg_spell_go packet before m_castItem delete in TakeCastItem()...
SendSpellGo();
// Okay, everything is prepared. Now we need to distinguish between immediate and evented delayed spells
if ((m_spellInfo->Speed > 0.0f && !m_spellInfo->IsChanneled()) || m_spellInfo->Id == 14157)
{
// 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();
// Okay, maps created, now prepare flags
m_immediateHandled = false;
m_spellState = SPELL_STATE_DELAYED;
SetDelayStart(0);
if (m_caster->HasUnitState(UNIT_STATE_CASTING) && !m_caster->IsNonMeleeSpellCasted(false, false, true))
m_caster->ClearUnitState(UNIT_STATE_CASTING);
}
else
{
// Immediate spell, no big deal
handle_immediate();
}
CallScriptAfterCastHandlers();
if (const std::vector<int32> *spell_triggered = sSpellMgr->GetSpellLinked(m_spellInfo->Id))
{
for (std::vector<int32>::const_iterator i = spell_triggered->begin(); i != spell_triggered->end(); ++i)
if (*i < 0)
m_caster->RemoveAurasDueToSpell(-(*i));
else
m_caster->CastSpell(m_targets.GetUnitTarget() ? m_targets.GetUnitTarget() : m_caster, *i, true);
}
if (m_caster->GetTypeId() == TYPEID_PLAYER)
m_caster->ToPlayer()->SetSpellModTakingSpell(this, false);
SetExecutedCurrently(false);
}
void Spell::handle_immediate()
{
// start channeling if applicable
if (m_spellInfo->IsChanneled())
{
int32 duration = m_spellInfo->GetDuration();
if (duration)
{
// First mod_duration then haste - see Missile Barrage
// Apply duration mod
if (Player* modOwner = m_caster->GetSpellModOwner())
modOwner->ApplySpellMod(m_spellInfo->Id, SPELLMOD_DURATION, duration);
// Apply haste mods
if (m_spellInfo->AttributesEx5 & SPELL_ATTR5_HASTE_AFFECT_DURATION)
m_caster->ModSpellCastTime(m_spellInfo, duration, this);
m_spellState = SPELL_STATE_CASTING;
m_caster->AddInterruptMask(m_spellInfo->ChannelInterruptFlags);
SendChannelStart(duration);
}
else if (duration == -1)
{
m_spellState = SPELL_STATE_CASTING;
m_caster->AddInterruptMask(m_spellInfo->ChannelInterruptFlags);
SendChannelStart(duration);
}
}
PrepareTargetProcessing();
// process immediate effects (items, ground, etc.) also initialize some variables
_handle_immediate_phase();
for (std::list<TargetInfo>::iterator ihit= m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit)
DoAllEffectOnTarget(&(*ihit));
for (std::list<GOTargetInfo>::iterator ihit= m_UniqueGOTargetInfo.begin(); ihit != m_UniqueGOTargetInfo.end(); ++ihit)
DoAllEffectOnTarget(&(*ihit));
FinishTargetProcessing();
// spell is finished, perform some last features of the spell here
_handle_finish_phase();
// Remove used for cast item if need (it can be already NULL after TakeReagents call
TakeCastItem();
// handle ammo consumption for Hunter's volley spell
if (m_spellInfo->IsRangedWeaponSpell() && m_spellInfo->IsChanneled())
TakeAmmo();
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)
{
UpdatePointers();
if (m_caster->GetTypeId() == TYPEID_PLAYER)
m_caster->ToPlayer()->SetSpellModTakingSpell(this, true);
uint64 next_time = 0;
PrepareTargetProcessing();
if (!m_immediateHandled)
{
_handle_immediate_phase();
m_immediateHandled = true;
}
bool single_missile = (m_targets.HasDst());
// 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 (std::list<TargetInfo>::iterator ihit= m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit)
{
if (ihit->processed == false)
{
if (single_missile || ihit->timeDelay <= t_offset)
{
ihit->timeDelay = t_offset;
DoAllEffectOnTarget(&(*ihit));
}
else if (next_time == 0 || ihit->timeDelay < next_time)
next_time = ihit->timeDelay;
}
}
// now recheck gameobject targeting correctness
for (std::list<GOTargetInfo>::iterator ighit= m_UniqueGOTargetInfo.begin(); ighit != m_UniqueGOTargetInfo.end(); ++ighit)
{
if (ighit->processed == false)
{
if (single_missile || ighit->timeDelay <= t_offset)
DoAllEffectOnTarget(&(*ighit));
else if (next_time == 0 || ighit->timeDelay < next_time)
next_time = ighit->timeDelay;
}
}
FinishTargetProcessing();
if (m_caster->GetTypeId() == TYPEID_PLAYER)
m_caster->ToPlayer()->SetSpellModTakingSpell(this, false);
// 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()
{
m_spellAura = NULL;
// initialize Diminishing Returns Data
m_diminishLevel = DIMINISHING_LEVEL_1;
m_diminishGroup = DIMINISHING_NONE;
// handle some immediate features of the spell here
HandleThreatSpells();
PrepareScriptHitHandlers();
// handle effects with SPELL_EFFECT_HANDLE_HIT mode
for (uint32 j = 0; j < MAX_SPELL_EFFECTS; ++j)
{
// don't do anything for empty effect
if (!m_spellInfo->Effects[j].IsEffect())
continue;
// call effect handlers to handle destination hit
HandleEffects(NULL, NULL, NULL, j, SPELL_EFFECT_HANDLE_HIT);
}
// process items
for (std::list<ItemTargetInfo>::iterator ihit= m_UniqueItemInfo.begin(); ihit != m_UniqueItemInfo.end(); ++ihit)
DoAllEffectOnTarget(&(*ihit));
if (!m_originalCaster)
return;
// Handle procs on cast
// TODO: finish new proc system:P
if (m_UniqueTargetInfo.empty())
{
uint32 procAttacker = m_procAttacker;
if (!procAttacker)
{
bool positive = m_spellInfo->IsPositive();
switch (m_spellInfo->DmgClass)
{
case SPELL_DAMAGE_CLASS_MAGIC:
if (positive)
procAttacker |= PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_POS;
else
procAttacker |= PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG;
break;
case SPELL_DAMAGE_CLASS_NONE:
if (positive)
procAttacker |= PROC_FLAG_DONE_SPELL_NONE_DMG_CLASS_POS;
else
procAttacker |= PROC_FLAG_DONE_SPELL_NONE_DMG_CLASS_NEG;
break;
}
}
// Proc damage for spells which have only dest targets (2484 should proc 51486 for example)
m_originalCaster->ProcDamageAndSpell(NULL, procAttacker, 0, m_procEx | PROC_EX_NORMAL_HIT, 0, BASE_ATTACK, m_spellInfo, m_triggeredByAuraSpell);
}
}
void Spell::_handle_finish_phase()
{
if (m_caster->m_movedPlayer)
{
// Take for real after all targets are processed
if (m_needComboPoints)
m_caster->m_movedPlayer->ClearComboPoints();
// Real add combo points from effects
if (m_comboPointGain)
m_caster->m_movedPlayer->GainSpellComboPoints(m_comboPointGain);
}
if (m_caster->m_extraAttacks && GetSpellInfo()->HasEffect(SPELL_EFFECT_ADD_EXTRA_ATTACKS))
m_caster->HandleProcExtraAttackFor(m_caster->getVictim());
// TODO: trigger proc phase finish here
}
void Spell::SendSpellCooldown()
{
if (m_caster->GetTypeId() != TYPEID_PLAYER)
return;
Player* _player = (Player*)m_caster;
// mana/health/etc potions, disabled by client (until combat out as declarate)
if (m_CastItem && m_CastItem->IsPotion())
{
// need in some way provided data for Spell::finish SendCooldownEvent
_player->SetLastPotionId(m_CastItem->GetEntry());
return;
}
// have infinity cooldown but set at aura apply // do not set cooldown for triggered spells (needed by reincarnation)
if (m_spellInfo->Attributes & (SPELL_ATTR0_DISABLED_WHILE_ACTIVE | SPELL_ATTR0_PASSIVE) || (_triggeredCastFlags & TRIGGERED_IGNORE_SPELL_AND_CATEGORY_CD))
return;
_player->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())
{
sLog->outDebug(LOG_FILTER_SPELLS_AURAS, "Spell %u is cancelled due to removal of target.", m_spellInfo->Id);
cancel();
return;
}
// check if the player caster has moved before the spell finished
if ((m_caster->GetTypeId() == TYPEID_PLAYER && m_timer != 0) &&
m_caster->isMoving() && (m_spellInfo->InterruptFlags & SPELL_INTERRUPT_FLAG_MOVEMENT) &&
(m_spellInfo->Effects[0].Effect != SPELL_EFFECT_STUCK || !m_caster->HasUnitMovementFlag(MOVEMENTFLAG_FALLING)))
{
// don't cancel for melee, autorepeat, triggered and instant spells
if (!IsNextMeleeSwingSpell() && !IsAutoRepeat() && !IsTriggered())
cancel();
}
switch (m_spellState)
{
case SPELL_STATE_PREPARING:
{
if (m_timer > 0)
{
if (difftime >= (uint32)m_timer)
m_timer = 0;
else
m_timer -= difftime;
}
if (m_timer == 0 && !IsNextMeleeSwingSpell() && !IsAutoRepeat())
// don't CheckCast for instant spells - done in spell::prepare, skip duplicate checks, needed for range checks for example
cast(!m_casttime);
break;
}
case SPELL_STATE_CASTING:
{
if (m_timer)
{
// check if there are alive targets left
if (!UpdateChanneledTargetList())
{
sLog->outDebug(LOG_FILTER_SPELLS_AURAS, "Channeled spell %d is removed due to lack of targets", m_spellInfo->Id);
SendChannelUpdate(0);
finish();
}
if (m_timer > 0)
{
if (difftime >= (uint32)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 (std::list<TargetInfo>::iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit)
{
TargetInfo* target = &*ihit;
if (!IS_CRE_OR_VEH_GUID(target->targetGUID))
continue;
Unit* unit = m_caster->GetGUID() == target->targetGUID ? m_caster : ObjectAccessor::GetUnit(*m_caster, target->targetGUID);
if (unit == NULL)
continue;
p->CastedCreatureOrGO(unit->GetEntry(), unit->GetGUID(), m_spellInfo->Id);
}
for (std::list<GOTargetInfo>::iterator ihit = m_UniqueGOTargetInfo.begin(); ihit != m_UniqueGOTargetInfo.end(); ++ihit)
{
GOTargetInfo* target = &*ihit;
GameObject* go = m_caster->GetMap()->GetGameObject(target->targetGUID);
if (!go)
continue;
p->CastedCreatureOrGO(go->GetEntry(), go->GetGUID(), m_spellInfo->Id);
}
}
}
finish();
}
break;
}
default:
break;
}
}
void Spell::finish(bool ok)
{
if (!m_caster)
return;
if (m_spellState == SPELL_STATE_FINISHED)
return;
m_spellState = SPELL_STATE_FINISHED;
if (m_spellInfo->IsChanneled())
m_caster->UpdateInterruptMask();
if (m_caster->HasUnitState(UNIT_STATE_CASTING) && !m_caster->IsNonMeleeSpellCasted(false, false, true))
m_caster->ClearUnitState(UNIT_STATE_CASTING);
// Unsummon summon as possessed creatures on spell cancel
if (m_spellInfo->IsChanneled() && m_caster->GetTypeId() == TYPEID_PLAYER)
{
if (Unit* charm = m_caster->GetCharm())
if (charm->GetTypeId() == TYPEID_UNIT
&& charm->ToCreature()->HasUnitTypeMask(UNIT_MASK_PUPPET)
&& charm->GetUInt32Value(UNIT_CREATED_BY_SPELL) == m_spellInfo->Id)
((Puppet*)charm)->UnSummon();
}
if (m_caster->GetTypeId() == TYPEID_UNIT)
m_caster->ReleaseFocus(this);
if (!ok)
return;
if (m_caster->GetTypeId() == TYPEID_UNIT && m_caster->ToCreature()->isSummon())
{
// Unsummon statue
uint32 spell = m_caster->GetUInt32Value(UNIT_CREATED_BY_SPELL);
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spell);
if (spellInfo && spellInfo->SpellIconID == 2056)
{
sLog->outDebug(LOG_FILTER_SPELLS_AURAS, "Statue %d is unsummoned in spell %d finish", m_caster->GetGUIDLow(), m_spellInfo->Id);
m_caster->setDeathState(JUST_DIED);
return;
}
}
if (IsAutoActionResetSpell())
{
bool found = false;
Unit::AuraEffectList const& vIgnoreReset = m_caster->GetAuraEffectsByType(SPELL_AURA_IGNORE_MELEE_RESET);
for (Unit::AuraEffectList::const_iterator i = vIgnoreReset.begin(); i != vIgnoreReset.end(); ++i)
{
if ((*i)->IsAffectedOnSpell(m_spellInfo))
{
found = true;
break;
}
}
if (!found && !(m_spellInfo->AttributesEx2 & SPELL_ATTR2_NOT_RESET_AUTO_ACTIONS))
{
m_caster->resetAttackTimer(BASE_ATTACK);
if (m_caster->haveOffhandWeapon())
m_caster->resetAttackTimer(OFF_ATTACK);
m_caster->resetAttackTimer(RANGED_ATTACK);
}
}
// potions disabled by client, send event "not in combat" if need
if (m_caster->GetTypeId() == TYPEID_PLAYER)
{
if (!m_triggeredByAuraSpell)
m_caster->ToPlayer()->UpdatePotionCooldown(this);
// triggered spell pointer can be not set in some cases
// this is needed for proper apply of triggered spell mods
m_caster->ToPlayer()->SetSpellModTakingSpell(this, true);
// Take mods after trigger spell (needed for 14177 to affect 48664)
// mods are taken only on succesfull cast and independantly from targets of the spell
m_caster->ToPlayer()->RemoveSpellMods(this);
m_caster->ToPlayer()->SetSpellModTakingSpell(this, false);
}
// Stop Attack for some spells
if (m_spellInfo->Attributes & SPELL_ATTR0_STOP_ATTACK_TARGET)
m_caster->AttackStop();
}
void Spell::SendCastResult(SpellCastResult result)
{
if (result == SPELL_CAST_OK)
return;
if (m_caster->GetTypeId() != TYPEID_PLAYER)
return;
if (m_caster->ToPlayer()->GetSession()->PlayerLoading()) // don't send cast results at loading time
return;
SendCastResult(m_caster->ToPlayer(), m_spellInfo, m_cast_count, result, m_customError);
}
void Spell::SendCastResult(Player* caster, SpellInfo const* spellInfo, uint8 cast_count, SpellCastResult result, SpellCustomErrors customError /*= SPELL_CUSTOM_ERROR_NONE*/)
{
if (result == SPELL_CAST_OK)
return;
WorldPacket data(SMSG_CAST_FAILED, (4+1+1));
data << uint8(cast_count); // single cast or multi 2.3 (0/1)
data << uint32(spellInfo->Id);
data << uint8(result); // problem
switch (result)
{
case SPELL_FAILED_REQUIRES_SPELL_FOCUS:
data << uint32(spellInfo->RequiresSpellFocus); // SpellFocusObject.dbc id
break;
case SPELL_FAILED_REQUIRES_AREA: // AreaTable.dbc id
// hardcode areas limitation case
switch (spellInfo->Id)
{
case 41617: // Cenarion Mana Salve
case 41619: // Cenarion Healing Salve
data << uint32(3905);
break;
case 41618: // Bottled Nethergon Energy
case 41620: // Bottled Nethergon Vapor
data << uint32(3842);
break;
case 45373: // Bloodberry Elixir
data << uint32(4075);
break;
default: // default case (don't must be)
data << uint32(0);
break;
}
break;
case SPELL_FAILED_TOTEMS:
if (spellInfo->Totem[0])
data << uint32(spellInfo->Totem[0]);
if (spellInfo->Totem[1])
data << uint32(spellInfo->Totem[1]);
break;
case SPELL_FAILED_TOTEM_CATEGORY:
if (spellInfo->TotemCategory[0])
data << uint32(spellInfo->TotemCategory[0]);
if (spellInfo->TotemCategory[1])
data << uint32(spellInfo->TotemCategory[1]);
break;
case SPELL_FAILED_EQUIPPED_ITEM_CLASS:
case SPELL_FAILED_EQUIPPED_ITEM_CLASS_MAINHAND:
case SPELL_FAILED_EQUIPPED_ITEM_CLASS_OFFHAND:
data << uint32(spellInfo->EquippedItemClass);
data << uint32(spellInfo->EquippedItemSubClassMask);
break;
case SPELL_FAILED_TOO_MANY_OF_ITEM:
{
uint32 item = 0;
for (int8 x = 0; x < 3; x++)
if (spellInfo->Effects[x].ItemType)
item = spellInfo->Effects[x].ItemType;
ItemTemplate const* pProto = sObjectMgr->GetItemTemplate(item);
if (pProto && pProto->ItemLimitCategory)
data << uint32(pProto->ItemLimitCategory);
break;
}
case SPELL_FAILED_CUSTOM_ERROR:
data << uint32(customError);
break;
case SPELL_FAILED_REAGENTS:
{
uint32 missingItem = 0;
for (uint32 i = 0; i < MAX_SPELL_REAGENTS; i++)
{
if (spellInfo->Reagent[i] <= 0)
continue;
uint32 itemid = spellInfo->Reagent[i];
uint32 itemcount = spellInfo->ReagentCount[i];
if (!caster->HasItemCount(itemid, itemcount))
{
missingItem = itemid;
break;
}
}
data << uint32(missingItem); // first missing item
break;
}
default:
break;
}
caster->GetSession()->SendPacket(&data);
}
void Spell::SendSpellStart()
{
if (!IsNeedSendToClient())
return;
//sLog->outDebug(LOG_FILTER_SPELLS_AURAS, "Sending SMSG_SPELL_START id=%u", m_spellInfo->Id);
uint32 castFlags = CAST_FLAG_UNKNOWN_2;
if ((IsTriggered() && !m_spellInfo->IsAutoRepeatRangedSpell()) || m_triggeredByAuraSpell)
castFlags |= CAST_FLAG_PENDING;
if (m_spellInfo->Attributes & SPELL_ATTR0_REQ_AMMO)
castFlags |= CAST_FLAG_AMMO;
if ((m_caster->GetTypeId() == TYPEID_PLAYER ||
(m_caster->GetTypeId() == TYPEID_UNIT && m_caster->ToCreature()->isPet()))
&& m_spellInfo->PowerType != POWER_HEALTH)
castFlags |= CAST_FLAG_POWER_LEFT_SELF;
if (m_spellInfo->RuneCostID && m_spellInfo->PowerType == POWER_RUNE)
castFlags |= CAST_FLAG_UNKNOWN_19;
WorldPacket data(SMSG_SPELL_START, (8+8+4+4+2));
if (m_CastItem)
data.append(m_CastItem->GetPackGUID());
else
data.append(m_caster->GetPackGUID());
data.append(m_caster->GetPackGUID());
data << uint8(m_cast_count); // pending spell cast?
data << uint32(m_spellInfo->Id); // spellId
data << uint32(castFlags); // cast flags
data << int32(m_timer); // delay?
m_targets.Write(data);
if (castFlags & CAST_FLAG_POWER_LEFT_SELF)
data << uint32(m_caster->GetPower((Powers)m_spellInfo->PowerType));
if (castFlags & CAST_FLAG_AMMO)
WriteAmmoToPacket(&data);
if (castFlags & CAST_FLAG_UNKNOWN_23)
{
data << uint32(0);
data << uint32(0);
}
m_caster->SendMessageToSet(&data, true);
}
void Spell::SendSpellGo()
{
// not send invisible spell casting
if (!IsNeedSendToClient())
return;
//sLog->outDebug(LOG_FILTER_SPELLS_AURAS, "Sending SMSG_SPELL_GO id=%u", m_spellInfo->Id);
uint32 castFlags = CAST_FLAG_UNKNOWN_9;
// triggered spells with spell visual != 0
if ((IsTriggered() && !m_spellInfo->IsAutoRepeatRangedSpell()) || m_triggeredByAuraSpell)
castFlags |= CAST_FLAG_PENDING;
if (m_spellInfo->Attributes & SPELL_ATTR0_REQ_AMMO)
castFlags |= CAST_FLAG_AMMO; // arrows/bullets visual
if ((m_caster->GetTypeId() == TYPEID_PLAYER ||
(m_caster->GetTypeId() == TYPEID_UNIT && m_caster->ToCreature()->isPet()))
&& m_spellInfo->PowerType != POWER_HEALTH)
castFlags |= CAST_FLAG_POWER_LEFT_SELF; // should only be sent to self, but the current messaging doesn't make that possible
if ((m_caster->GetTypeId() == TYPEID_PLAYER)
&& (m_caster->getClass() == CLASS_DEATH_KNIGHT)
&& m_spellInfo->RuneCostID
&& m_spellInfo->PowerType == POWER_RUNE)
{
castFlags |= CAST_FLAG_UNKNOWN_19; // same as in SMSG_SPELL_START
castFlags |= CAST_FLAG_RUNE_LIST; // rune cooldowns list
castFlags |= CAST_FLAG_UNKNOWN_9; // ??
}
if (m_spellInfo->HasEffect(SPELL_EFFECT_ACTIVATE_RUNE))
{
castFlags |= CAST_FLAG_RUNE_LIST; // rune cooldowns list
castFlags |= CAST_FLAG_UNKNOWN_19; // same as in SMSG_SPELL_START
}
if (m_targets.HasTraj())
castFlags |= CAST_FLAG_ADJUST_MISSILE;
WorldPacket data(SMSG_SPELL_GO, 50); // guess size
if (m_CastItem)
data.append(m_CastItem->GetPackGUID());
else
data.append(m_caster->GetPackGUID());
data.append(m_caster->GetPackGUID());
data << uint8(m_cast_count); // pending spell cast?
data << uint32(m_spellInfo->Id); // spellId
data << uint32(castFlags); // cast flags
data << uint32(getMSTime()); // timestamp
WriteSpellGoTargets(&data);
m_targets.Write(data);
if (castFlags & CAST_FLAG_POWER_LEFT_SELF)
data << uint32(m_caster->GetPower((Powers)m_spellInfo->PowerType));
if (castFlags & CAST_FLAG_RUNE_LIST) // rune cooldowns list
{
//TODO: There is a crash caused by a spell with CAST_FLAG_RUNE_LIST casted by a creature
//The creature is the mover of a player, so HandleCastSpellOpcode uses it as the caster
if (Player* player = m_caster->ToPlayer())
{
uint8 runeMaskInitial = m_runesState;
uint8 runeMaskAfterCast = player->GetRunesState();
data << uint8(runeMaskInitial); // runes state before
data << uint8(runeMaskAfterCast); // runes state after
for (uint8 i = 0; i < MAX_RUNES; ++i)
{
uint8 mask = (1 << i);
if (mask & runeMaskInitial && !(mask & runeMaskAfterCast)) // usable before andon cooldown now...
{
// float casts ensure the division is performed on floats as we need float result
float baseCd = float(player->GetRuneBaseCooldown(i));
data << uint8((baseCd - float(player->GetRuneCooldown(i))) / baseCd * 255); // rune cooldown passed
}
}
}
}
if (castFlags & CAST_FLAG_ADJUST_MISSILE)
{
data << m_targets.GetElevation();
data << uint32(m_delayMoment);
}
if (castFlags & CAST_FLAG_AMMO)
WriteAmmoToPacket(&data);
if (castFlags & CAST_FLAG_UNKNOWN_20)
{
data << uint32(0);
data << uint32(0);
}
if (m_targets.GetTargetMask() & TARGET_FLAG_DEST_LOCATION)
{
data << uint8(0);
}
m_caster->SendMessageToSet(&data, true);
}
void Spell::WriteAmmoToPacket(WorldPacket* data)
{
uint32 ammoInventoryType = 0;
uint32 ammoDisplayID = 0;
if (m_caster->GetTypeId() == TYPEID_PLAYER)
{
Item* pItem = m_caster->ToPlayer()->GetWeaponForAttack(RANGED_ATTACK);
if (pItem)
{
ammoInventoryType = pItem->GetTemplate()->InventoryType;
if (ammoInventoryType == INVTYPE_THROWN)
ammoDisplayID = pItem->GetTemplate()->DisplayInfoID;
else
{
uint32 ammoID = m_caster->ToPlayer()->GetUInt32Value(PLAYER_AMMO_ID);
if (ammoID)
{
ItemTemplate const* pProto = sObjectMgr->GetItemTemplate(ammoID);
if (pProto)
{