/*
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see .
*/
#include "Unit.h"
#include "AbstractFollower.h"
#include "Battlefield.h"
#include "BattlefieldMgr.h"
#include "Battleground.h"
#include "BattlegroundPackets.h"
#include "BattlegroundScore.h"
#include "BattlePetMgr.h"
#include "CellImpl.h"
#include "CharacterCache.h"
#include "CharmInfo.h"
#include "ChatPackets.h"
#include "ChatTextBuilder.h"
#include "CombatLogPackets.h"
#include "CombatPackets.h"
#include "Common.h"
#include "ConditionMgr.h"
#include "Containers.h"
#include "Creature.h"
#include "CreatureAI.h"
#include "CreatureAIImpl.h"
#include "CreatureAIFactory.h"
#include "CreatureGroups.h"
#include "DB2Stores.h"
#include "Formulas.h"
#include "GameObjectAI.h"
#include "GameTime.h"
#include "GridNotifiersImpl.h"
#include "Group.h"
#include "InstanceScript.h"
#include "Item.h"
#include "ItemBonusMgr.h"
#include "KillRewarder.h"
#include "ListUtils.h"
#include "Log.h"
#include "Loot.h"
#include "LootMgr.h"
#include "LootPackets.h"
#include "MiscPackets.h"
#include "MotionMaster.h"
#include "MovementGenerator.h"
#include "MovementPackets.h"
#include "MoveSpline.h"
#include "MoveSplineInit.h"
#include "ObjectAccessor.h"
#include "ObjectMgr.h"
#include "Opcodes.h"
#include "OutdoorPvP.h"
#include "PartyPackets.h"
#include "Pet.h"
#include "PetPackets.h"
#include "PhasingHandler.h"
#include "Player.h"
#include "PlayerAI.h"
#include "QuestDef.h"
#include "Spell.h"
#include "ScheduledChangeAI.h"
#include "SpellAuraEffects.h"
#include "SpellAuras.h"
#include "SpellHistory.h"
#include "SpellInfo.h"
#include "SpellMgr.h"
#include "SpellPackets.h"
#include "StringConvert.h"
#include "TemporarySummon.h"
#include "Totem.h"
#include "Transport.h"
#include "Util.h"
#include "Vehicle.h"
#include "VehiclePackets.h"
#include "Vignette.h"
#include "VignettePackets.h"
#include "World.h"
#include "WorldPacket.h"
#include "WorldSession.h"
#include
#include
#include
float baseMoveSpeed[MAX_MOVE_TYPE] =
{
2.5f, // MOVE_WALK
7.0f, // MOVE_RUN
4.5f, // MOVE_RUN_BACK
4.722222f, // MOVE_SWIM
2.5f, // MOVE_SWIM_BACK
3.141594f, // MOVE_TURN_RATE
7.0f, // MOVE_FLIGHT
4.5f, // MOVE_FLIGHT_BACK
3.14f // MOVE_PITCH_RATE
};
float playerBaseMoveSpeed[MAX_MOVE_TYPE] =
{
2.5f, // MOVE_WALK
7.0f, // MOVE_RUN
4.5f, // MOVE_RUN_BACK
4.722222f, // MOVE_SWIM
2.5f, // MOVE_SWIM_BACK
3.141594f, // MOVE_TURN_RATE
7.0f, // MOVE_FLIGHT
4.5f, // MOVE_FLIGHT_BACK
3.14f // MOVE_PITCH_RATE
};
DispelableAura::DispelableAura(Aura* aura, int32 dispelChance, uint8 dispelCharges) :
_aura(aura), _chance(dispelChance), _charges(dispelCharges)
{
}
DispelableAura::~DispelableAura() = default;
bool DispelableAura::RollDispel() const
{
return roll_chance_i(_chance);
}
DamageInfo::DamageInfo(Unit* attacker, Unit* victim, uint32 damage, SpellInfo const* spellInfo, SpellSchoolMask schoolMask, DamageEffectType damageType, WeaponAttackType attackType)
: m_attacker(attacker), m_victim(victim), m_damage(damage), m_originalDamage(damage), m_spellInfo(spellInfo), m_schoolMask(schoolMask), m_damageType(damageType), m_attackType(attackType),
m_absorb(0), m_resist(0), m_block(0), m_hitMask(PROC_HIT_NONE)
{
}
DamageInfo::DamageInfo(CalcDamageInfo const& dmgInfo)
: m_attacker(dmgInfo.Attacker), m_victim(dmgInfo.Target), m_damage(dmgInfo.Damage), m_originalDamage(dmgInfo.Damage), m_spellInfo(nullptr), m_schoolMask(SpellSchoolMask(dmgInfo.DamageSchoolMask)),
m_damageType(DIRECT_DAMAGE), m_attackType(dmgInfo.AttackType), m_absorb(dmgInfo.Absorb), m_resist(dmgInfo.Resist), m_block(dmgInfo.Blocked), m_hitMask(PROC_HIT_NONE)
{
switch (dmgInfo.TargetState)
{
case VICTIMSTATE_IS_IMMUNE:
m_hitMask |= PROC_HIT_IMMUNE;
break;
case VICTIMSTATE_BLOCKS:
m_hitMask |= PROC_HIT_FULL_BLOCK;
break;
}
if (dmgInfo.HitInfo & (HITINFO_PARTIAL_ABSORB | HITINFO_FULL_ABSORB))
m_hitMask |= PROC_HIT_ABSORB;
if (dmgInfo.HitInfo & HITINFO_FULL_RESIST)
m_hitMask |= PROC_HIT_FULL_RESIST;
if (m_block)
m_hitMask |= PROC_HIT_BLOCK;
bool const damageNullified = (dmgInfo.HitInfo & (HITINFO_FULL_ABSORB | HITINFO_FULL_RESIST)) != 0 ||
(m_hitMask & (PROC_HIT_IMMUNE | PROC_HIT_FULL_BLOCK)) != 0;
switch (dmgInfo.HitOutCome)
{
case MELEE_HIT_MISS:
m_hitMask |= PROC_HIT_MISS;
break;
case MELEE_HIT_DODGE:
m_hitMask |= PROC_HIT_DODGE;
break;
case MELEE_HIT_PARRY:
m_hitMask |= PROC_HIT_PARRY;
break;
case MELEE_HIT_EVADE:
m_hitMask |= PROC_HIT_EVADE;
break;
case MELEE_HIT_BLOCK:
case MELEE_HIT_CRUSHING:
case MELEE_HIT_GLANCING:
case MELEE_HIT_NORMAL:
if (!damageNullified)
m_hitMask |= PROC_HIT_NORMAL;
break;
case MELEE_HIT_CRIT:
if (!damageNullified)
m_hitMask |= PROC_HIT_CRITICAL;
break;
}
}
DamageInfo::DamageInfo(SpellNonMeleeDamage const& spellNonMeleeDamage, DamageEffectType damageType, WeaponAttackType attackType, ProcFlagsHit hitMask)
: m_attacker(spellNonMeleeDamage.attacker), m_victim(spellNonMeleeDamage.target), m_damage(spellNonMeleeDamage.damage), m_originalDamage(spellNonMeleeDamage.originalDamage),
m_spellInfo(spellNonMeleeDamage.Spell), m_schoolMask(SpellSchoolMask(spellNonMeleeDamage.schoolMask)), m_damageType(damageType),
m_attackType(attackType), m_absorb(spellNonMeleeDamage.absorb), m_resist(spellNonMeleeDamage.resist), m_block(spellNonMeleeDamage.blocked), m_hitMask(hitMask)
{
if (spellNonMeleeDamage.blocked)
m_hitMask |= PROC_HIT_BLOCK;
if (spellNonMeleeDamage.absorb)
m_hitMask |= PROC_HIT_ABSORB;
}
void DamageInfo::ModifyDamage(int32 amount)
{
amount = std::max(amount, -static_cast(GetDamage()));
m_damage += amount;
}
void DamageInfo::AbsorbDamage(uint32 amount)
{
amount = std::min(amount, GetDamage());
m_absorb += amount;
m_damage -= amount;
m_hitMask |= PROC_HIT_ABSORB;
}
void DamageInfo::ResistDamage(uint32 amount)
{
amount = std::min(amount, GetDamage());
m_resist += amount;
m_damage -= amount;
if (!m_damage)
{
m_hitMask |= PROC_HIT_FULL_RESIST;
m_hitMask &= ~(PROC_HIT_NORMAL | PROC_HIT_CRITICAL);
}
}
void DamageInfo::BlockDamage(uint32 amount)
{
amount = std::min(amount, GetDamage());
m_block += amount;
m_damage -= amount;
m_hitMask |= PROC_HIT_BLOCK;
if (!m_damage)
{
m_hitMask |= PROC_HIT_FULL_BLOCK;
m_hitMask &= ~(PROC_HIT_NORMAL | PROC_HIT_CRITICAL);
}
}
ProcFlagsHit DamageInfo::GetHitMask() const
{
return m_hitMask;
}
HealInfo::HealInfo(Unit* healer, Unit* target, uint32 heal, SpellInfo const* spellInfo, SpellSchoolMask schoolMask)
: _healer(healer), _target(target), _heal(heal), _originalHeal(heal), _effectiveHeal(0), _absorb(0), _spellInfo(spellInfo), _schoolMask(schoolMask), _hitMask(0)
{
}
void HealInfo::AbsorbHeal(uint32 amount)
{
amount = std::min(amount, GetHeal());
_absorb += amount;
_heal -= amount;
amount = std::min(amount, GetEffectiveHeal());
_effectiveHeal -= amount;
_hitMask |= PROC_HIT_ABSORB;
}
uint32 HealInfo::GetHitMask() const
{
return _hitMask;
}
ProcEventInfo::ProcEventInfo(Unit* actor, Unit* actionTarget, Unit* procTarget,
ProcFlagsInit const& typeMask, ProcFlagsSpellType spellTypeMask,
ProcFlagsSpellPhase spellPhaseMask, ProcFlagsHit hitMask,
Spell* spell, DamageInfo* damageInfo,
HealInfo* healInfo) :
_actor(actor), _actionTarget(actionTarget), _procTarget(procTarget),
_typeMask(typeMask), _spellTypeMask(spellTypeMask),
_spellPhaseMask(spellPhaseMask), _hitMask(hitMask), _spell(spell),
_damageInfo(damageInfo), _healInfo(healInfo)
{ }
SpellInfo const* ProcEventInfo::GetSpellInfo() const
{
if (_spell)
return _spell->GetSpellInfo();
if (_damageInfo)
return _damageInfo->GetSpellInfo();
if (_healInfo)
return _healInfo->GetSpellInfo();
return nullptr;
}
SpellSchoolMask ProcEventInfo::GetSchoolMask() const
{
if (_spell)
return _spell->GetSpellInfo()->GetSchoolMask();
if (_damageInfo)
return _damageInfo->GetSchoolMask();
if (_healInfo)
return _healInfo->GetSchoolMask();
return SPELL_SCHOOL_MASK_NONE;
}
SpellNonMeleeDamage::SpellNonMeleeDamage(Unit* _attacker, Unit* _target, SpellInfo const* _spellInfo, SpellCastVisual spellVisual, uint32 _schoolMask, ObjectGuid _castId)
: target(_target), attacker(_attacker), castId(_castId), Spell(_spellInfo), SpellVisual(spellVisual), damage(0), originalDamage(0),
schoolMask(_schoolMask), absorb(0), resist(0), periodicLog(false), blocked(0), HitInfo(0), cleanDamage(0), fullBlock(false), preHitHealth(_target->GetHealth())
{
}
Unit::Unit(bool isWorldObject) :
WorldObject(isWorldObject), m_lastSanctuaryTime(0), LastCharmerGUID(), movespline(std::make_unique()),
m_ControlledByPlayer(false), m_procDeep(0), m_procChainLength(0), m_transformSpell(0),
m_removedAurasCount(0), m_interruptMask(SpellAuraInterruptFlags::None), m_interruptMask2(SpellAuraInterruptFlags2::None),
m_unitMovedByMe(nullptr), m_playerMovingMe(nullptr), m_charmer(nullptr), m_charmed(nullptr),
i_motionMaster(std::make_unique(this)), m_regenTimer(0), m_vehicle(nullptr),
m_unitTypeMask(UNIT_MASK_NONE), m_Diminishing(), m_combatManager(this),
m_threatManager(this), m_aiLocked(false), _playHoverAnim(false), _aiAnimKitId(0), _movementAnimKitId(0), _meleeAnimKitId(0),
_spellHistory(std::make_unique(this))
{
m_objectType |= TYPEMASK_UNIT;
m_objectTypeId = TYPEID_UNIT;
m_updateFlag.MovementUpdate = true;
m_entityFragments.Add(WowCS::EntityFragment::Tag_Unit, false);
m_baseAttackSpeed = { };
m_attackTimer = { };
m_modAttackSpeedPct.fill(1.0f);
m_canDualWield = false;
m_movementCounter = 0;
m_state = 0;
m_deathState = ALIVE;
m_currentSpells = { };
m_auraUpdateIterator = m_ownedAuras.end();
m_canModifyStats = false;
for (uint8 i = 0; i < UNIT_MOD_END; ++i)
{
m_auraFlatModifiersGroup[i][BASE_VALUE] = 0.0f;
m_auraFlatModifiersGroup[i][BASE_PCT_EXCLUDE_CREATE] = 100.0f;
m_auraFlatModifiersGroup[i][TOTAL_VALUE] = 0.0f;
m_auraPctModifiersGroup[i][BASE_PCT] = 1.0f;
m_auraPctModifiersGroup[i][TOTAL_PCT] = 1.0f;
}
// implement 50% base damage from offhand
m_auraPctModifiersGroup[UNIT_MOD_DAMAGE_OFFHAND][TOTAL_PCT] = 0.5f;
for (uint8 i = 0; i < MAX_ATTACK; ++i)
{
m_weaponDamage[i][MINDAMAGE] = BASE_MINDAMAGE;
m_weaponDamage[i][MAXDAMAGE] = BASE_MAXDAMAGE;
}
m_createStats = { };
m_floatStatPosBuff = { };
m_floatStatNegBuff = { };
m_attacking = nullptr;
m_modMeleeHitChance = 0.0f;
m_modRangedHitChance = 0.0f;
m_modSpellHitChance = 0.0f;
m_baseSpellCritChance = 5.0f;
m_speed_rate.fill(1.0f);
SetFlightCapabilityID(0, false);
// remove aurastates allowing special moves
m_reactiveTimer = { };
m_cleanupDone = false;
m_duringRemoveFromWorld = false;
m_serverSideVisibility.SetValue(SERVERSIDE_VISIBILITY_GHOST, GHOST_VISIBILITY_ALIVE);
_lastLiquid = nullptr;
_oldFactionId = 0;
_isWalkingBeforeCharm = false;
_instantCast = false;
_isCombatDisallowed = false;
_lastExtraAttackSpell = 0;
}
////////////////////////////////////////////////////////////
// Methods of class Unit
Unit::~Unit()
{
// set current spells as deletable
for (size_t i = 0; i < m_currentSpells.size(); ++i)
{
if (m_currentSpells[i])
{
m_currentSpells[i]->SetReferencedFromCurrent(false);
m_currentSpells[i] = nullptr;
}
}
m_Events.KillAllEvents(true);
_DeleteRemovedAuras();
ASSERT(!m_duringRemoveFromWorld);
ASSERT(!m_attacking);
ASSERT(m_attackers.empty());
ASSERT(m_sharedVision.empty());
ASSERT(m_Controlled.empty());
ASSERT(m_appliedAuras.empty());
ASSERT(m_ownedAuras.empty());
ASSERT(m_removedAuras.empty());
ASSERT(m_dynObj.empty());
ASSERT(m_gameObj.empty());
ASSERT(m_areaTrigger.empty());
ASSERT(!m_unitMovedByMe || (m_unitMovedByMe == this));
ASSERT(!m_playerMovingMe || (m_playerMovingMe == this));
}
void Unit::Update(uint32 p_time)
{
// WARNING! Order of execution here is important, do not change.
// Spells must be processed with event system BEFORE they go to _UpdateSpells.
// Or else we may have some SPELL_STATE_FINISHED spells stalled in pointers, that is bad.
WorldObject::Update(p_time);
if (!IsInWorld())
return;
_UpdateSpells(p_time);
// If this is set during update SetCantProc(false) call is missing somewhere in the code
// Having this would prevent spells from being proced, so let's crash
ASSERT(!m_procDeep);
m_combatManager.Update(p_time);
_lastDamagedTargetGuid = ObjectGuid::Empty;
if (_lastExtraAttackSpell)
{
while (!extraAttacksTargets.empty())
{
auto itr = extraAttacksTargets.begin();
ObjectGuid targetGuid = itr->first;
uint32 count = itr->second;
extraAttacksTargets.erase(itr);
if (Unit* victim = ObjectAccessor::GetUnit(*this, targetGuid))
HandleProcExtraAttackFor(victim, count);
}
_lastExtraAttackSpell = 0;
}
auto spellPausesCombatTimer = [&](CurrentSpellTypes type)
{
return GetCurrentSpell(type) && GetCurrentSpell(type)->GetSpellInfo()->HasAttribute(SPELL_ATTR6_DELAY_COMBAT_TIMER_DURING_CAST);
};
if (!spellPausesCombatTimer(CURRENT_GENERIC_SPELL) && !spellPausesCombatTimer(CURRENT_CHANNELED_SPELL))
{
if (uint32 base_att = getAttackTimer(BASE_ATTACK))
setAttackTimer(BASE_ATTACK, (p_time >= base_att ? 0 : base_att - p_time));
if (uint32 off_att = getAttackTimer(OFF_ATTACK))
setAttackTimer(OFF_ATTACK, (p_time >= off_att ? 0 : off_att - p_time));
if (uint32 ranged_att = getAttackTimer(RANGED_ATTACK))
setAttackTimer(RANGED_ATTACK, (p_time >= ranged_att ? 0 : ranged_att - p_time));
}
// update abilities available only for fraction of time
UpdateReactives(p_time);
if (IsAlive())
{
ModifyAuraState(AURA_STATE_WOUNDED_20_PERCENT, HealthBelowPct(20));
ModifyAuraState(AURA_STATE_WOUNDED_25_PERCENT, HealthBelowPct(25));
ModifyAuraState(AURA_STATE_WOUNDED_35_PERCENT, HealthBelowPct(35));
ModifyAuraState(AURA_STATE_WOUND_HEALTH_20_80, HealthBelowPct(20) || HealthAbovePct(80));
ModifyAuraState(AURA_STATE_HEALTHY_75_PERCENT, HealthAbovePct(75));
ModifyAuraState(AURA_STATE_WOUND_HEALTH_35_80, HealthBelowPct(35) || HealthAbovePct(80));
ModifyAuraState(AURA_STATE_WOUNDED_50_PERCENT, HealthBelowPct(50));
}
UpdateSplineMovement(p_time);
i_motionMaster->Update(p_time);
// Wait with the aura interrupts until we have updated our movement generators and position
if (GetTypeId() == TYPEID_PLAYER)
InterruptMovementBasedAuras();
else if (!movespline->Finalized())
InterruptMovementBasedAuras();
// All position info based actions have been executed, reset info
_positionUpdateInfo.Reset();
if (HasScheduledAIChange() && (GetTypeId() != TYPEID_PLAYER || (IsCharmed() && GetCharmerGUID().IsCreature())))
UpdateCharmAI();
RefreshAI();
}
void Unit::Heartbeat()
{
WorldObject::Heartbeat();
// SMSG_FLIGHT_SPLINE_SYNC for cyclic splines
SendFlightSplineSyncUpdate();
// Trigger heartbeat procs and generic aura behavior such as food emotes and invoking aura script hooks
TriggerAuraHeartbeat();
// Update Vignette position and visibility
if (m_vignette)
Vignettes::Update(*m_vignette, this);
}
void Unit::TriggerAuraHeartbeat()
{
for (auto const& [_, auraApplication] : m_appliedAuras)
auraApplication->GetBase()->Heartbeat();
Unit::ProcSkillsAndAuras(this, nullptr, PROC_FLAG_HEARTBEAT, PROC_FLAG_NONE, PROC_SPELL_TYPE_MASK_ALL, PROC_SPELL_PHASE_NONE, PROC_HIT_NONE, nullptr, nullptr, nullptr);
}
bool Unit::haveOffhandWeapon() const
{
if (Player const* player = ToPlayer())
return player->GetWeaponForAttack(OFF_ATTACK, true) != nullptr;
return CanDualWield();
}
void Unit::MonsterMoveWithSpeed(float x, float y, float z, float speed, bool generatePath, bool forceDestination)
{
std::function initializer = [=](Movement::MoveSplineInit& init)
{
init.MoveTo(x, y, z, generatePath, forceDestination);
init.SetVelocity(speed);
};
GetMotionMaster()->LaunchMoveSpline(std::move(initializer), 0, MOTION_PRIORITY_NORMAL, POINT_MOTION_TYPE);
}
void Unit::AtStartOfEncounter(EncounterType type)
{
switch (type)
{
case EncounterType::DungeonEncounter:
if (GetMap()->IsRaid())
RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags2::StartOfRaidEncounterAndStartOfMythicPlus);
RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags2::StartOfEncounter);
break;
case EncounterType::MythicPlusRun:
RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags2::StartOfRaidEncounterAndStartOfMythicPlus);
RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags2::EndOfRaidEncounterAndStartOfMythicPlus);
RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags2::ChallengeModeStart);
break;
case EncounterType::Battleground:
RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags2::StartOfRaidEncounterAndStartOfMythicPlus);
break;
default:
break;
}
if (IsAlive())
Unit::ProcSkillsAndAuras(this, nullptr, PROC_FLAG_ENCOUNTER_START, PROC_FLAG_NONE, PROC_SPELL_TYPE_MASK_ALL, PROC_SPELL_PHASE_NONE, PROC_HIT_NONE, nullptr, nullptr, nullptr);
}
void Unit::AtEndOfEncounter(EncounterType type)
{
switch (type)
{
case EncounterType::DungeonEncounter:
if (GetMap()->IsRaid())
RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags2::EndOfRaidEncounterAndStartOfMythicPlus);
RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags2::EndOfEncounter);
break;
default:
break;
}
GetSpellHistory()->ResetCooldowns([](SpellHistory::CooldownEntry const& cooldown)
{
SpellInfo const* spellInfo = sSpellMgr->AssertSpellInfo(cooldown.SpellId, DIFFICULTY_NONE);
return spellInfo->HasAttribute(SPELL_ATTR10_RESET_COOLDOWN_ON_ENCOUNTER_END);
}, true);
}
void Unit::UpdateSplineMovement(uint32 t_diff)
{
if (movespline->Finalized())
return;
movespline->updateState(t_diff);
bool arrived = movespline->Finalized();
if (arrived)
{
DisableSpline();
if (Optional animTier = movespline->GetAnimation())
SetAnimTier(*animTier);
}
UpdateSplinePosition();
}
void Unit::UpdateSplinePosition()
{
Movement::Location loc = movespline->ComputePosition();
if (movespline->onTransport)
{
Position& pos = m_movementInfo.transport.pos;
pos.m_positionX = loc.x;
pos.m_positionY = loc.y;
pos.m_positionZ = loc.z;
pos.SetOrientation(loc.orientation);
if (TransportBase* transport = GetDirectTransport())
transport->CalculatePassengerPosition(loc.x, loc.y, loc.z, &loc.orientation);
else
return;
}
if (HasUnitState(UNIT_STATE_LOST_CONTROL | UNIT_STATE_FOCUSING))
loc.orientation = GetOrientation();
UpdatePosition(loc.x, loc.y, loc.z, loc.orientation);
}
void Unit::SendFlightSplineSyncUpdate()
{
if (!movespline->isCyclic() || movespline->Finalized())
return;
WorldPackets::Movement::FlightSplineSync flightSplineSync;
flightSplineSync.Guid = GetGUID();
flightSplineSync.SplineDist = float(movespline->timePassed()) / movespline->Duration();
SendMessageToSet(flightSplineSync.Write(), true);
}
void Unit::InterruptMovementBasedAuras()
{
// TODO: Check if orientation transport offset changed instead of only global orientation
if (_positionUpdateInfo.Turned)
RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Turning);
if (_positionUpdateInfo.Relocated && !GetVehicle())
RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Moving);
}
void Unit::DisableSpline()
{
m_movementInfo.RemoveMovementFlag(MOVEMENTFLAG_FORWARD);
movespline->_Interrupt();
}
void Unit::resetAttackTimer(WeaponAttackType type)
{
m_attackTimer[type] = uint32(GetBaseAttackTime(type) * m_modAttackSpeedPct[type]);
}
bool Unit::IsWithinCombatRange(Unit const* obj, float dist2compare) const
{
if (!obj || !IsInMap(obj) || !InSamePhase(obj))
return false;
float dx = GetPositionX() - obj->GetPositionX();
float dy = GetPositionY() - obj->GetPositionY();
float dz = GetPositionZ() - obj->GetPositionZ();
float distsq = dx * dx + dy * dy + dz * dz;
float sizefactor = GetCombatReach() + obj->GetCombatReach();
float maxdist = dist2compare + sizefactor;
return distsq < maxdist * maxdist;
}
bool Unit::IsWithinMeleeRangeAt(Position const& pos, Unit const* obj) const
{
if (!obj || !IsInMap(obj) || !InSamePhase(obj))
return false;
float dx = pos.GetPositionX() - obj->GetPositionX();
float dy = pos.GetPositionY() - obj->GetPositionY();
float dz = pos.GetPositionZ() - obj->GetPositionZ();
float distsq = dx*dx + dy*dy + dz*dz;
float maxdist = GetMeleeRange(obj) + GetTotalAuraModifier(SPELL_AURA_MOD_AUTOATTACK_RANGE);
return distsq <= maxdist * maxdist;
}
float Unit::GetMeleeRange(Unit const* target) const
{
float range = GetCombatReach() + target->GetCombatReach() + 4.0f / 3.0f;
return std::max(range, NOMINAL_MELEE_RANGE);
}
bool Unit::IsWithinBoundaryRadius(const Unit* obj) const
{
if (!obj || !IsInMap(obj) || !InSamePhase(obj))
return false;
float objBoundaryRadius = std::max(obj->GetBoundingRadius(), MIN_MELEE_REACH);
return IsInDist(obj, objBoundaryRadius);
}
void Unit::SetVisibleAura(AuraApplication* aurApp)
{
m_visibleAuras.insert(aurApp);
m_visibleAurasToUpdate.insert(aurApp);
UpdateAuraForGroup();
}
void Unit::RemoveVisibleAura(AuraApplication* aurApp)
{
m_visibleAuras.erase(aurApp);
m_visibleAurasToUpdate.erase(aurApp);
UpdateAuraForGroup();
}
void Unit::SetVisibleAuraUpdate(AuraApplication* aurApp)
{
m_visibleAurasToUpdate.insert(aurApp);
}
void Unit::RemoveVisibleAuraUpdate(AuraApplication* aurApp)
{
m_visibleAurasToUpdate.erase(aurApp);
}
void Unit::UpdateInterruptMask()
{
m_interruptMask = SpellAuraInterruptFlags::None;
m_interruptMask2 = SpellAuraInterruptFlags2::None;
for (AuraApplication const* aurApp : m_interruptableAuras)
{
m_interruptMask |= aurApp->GetBase()->GetSpellInfo()->AuraInterruptFlags;
m_interruptMask2 |= aurApp->GetBase()->GetSpellInfo()->AuraInterruptFlags2;
}
if (Spell* spell = m_currentSpells[CURRENT_CHANNELED_SPELL])
{
if (spell->getState() == SPELL_STATE_CHANNELING)
{
m_interruptMask |= spell->m_spellInfo->ChannelInterruptFlags;
m_interruptMask2 |= spell->m_spellInfo->ChannelInterruptFlags2;
}
}
}
bool Unit::HasAuraTypeWithFamilyFlags(AuraType auraType, uint32 familyName, flag128 familyFlags) const
{
for (AuraEffect const* aura : GetAuraEffectsByType(auraType))
if (aura->GetSpellInfo()->SpellFamilyName == familyName && aura->GetSpellInfo()->SpellFamilyFlags & familyFlags)
return true;
return false;
}
bool Unit::HasBreakableByDamageAuraType(AuraType type, uint32 excludeAura) const
{
AuraEffectList const& auras = GetAuraEffectsByType(type);
for (AuraEffectList::const_iterator itr = auras.begin(); itr != auras.end(); ++itr)
if ((!excludeAura || excludeAura != (*itr)->GetSpellInfo()->Id) && //Avoid self interrupt of channeled Crowd Control spells like Seduction
(*itr)->GetSpellInfo()->HasAuraInterruptFlag(SpellAuraInterruptFlags::Damage))
return true;
return false;
}
bool Unit::HasBreakableByDamageCrowdControlAura(Unit* excludeCasterChannel) const
{
uint32 excludeAura = 0;
if (Spell* currentChanneledSpell = excludeCasterChannel ? excludeCasterChannel->GetCurrentSpell(CURRENT_CHANNELED_SPELL) : nullptr)
excludeAura = currentChanneledSpell->GetSpellInfo()->Id; //Avoid self interrupt of channeled Crowd Control spells like Seduction
return ( HasBreakableByDamageAuraType(SPELL_AURA_MOD_CONFUSE, excludeAura)
|| HasBreakableByDamageAuraType(SPELL_AURA_MOD_FEAR, excludeAura)
|| HasBreakableByDamageAuraType(SPELL_AURA_MOD_STUN, excludeAura)
|| HasBreakableByDamageAuraType(SPELL_AURA_MOD_ROOT, excludeAura)
|| HasBreakableByDamageAuraType(SPELL_AURA_MOD_ROOT_2, excludeAura)
|| HasBreakableByDamageAuraType(SPELL_AURA_TRANSFORM, excludeAura));
}
/*static*/ void Unit::DealDamageMods(Unit const* attacker, Unit const* victim, uint32& damage, uint32* absorb)
{
if (!victim || !victim->IsAlive() || victim->HasUnitState(UNIT_STATE_IN_FLIGHT) || (victim->GetTypeId() == TYPEID_UNIT && victim->ToCreature()->IsEvadingAttacks()))
{
if (absorb)
*absorb += damage;
damage = 0;
return;
}
if (attacker)
damage *= attacker->GetDamageMultiplierForTarget(victim);
}
/*static*/ AuraEffectVector Unit::CopyAuraEffectList(Unit::AuraEffectList const& list)
{
AuraEffectVector effects;
std::copy(list.begin(), list.end(), std::back_inserter(effects));
return effects;
}
/*static*/ uint32 Unit::DealDamage(Unit* attacker, Unit* victim, uint32 damage, CleanDamage const* cleanDamage, DamageEffectType damagetype, SpellSchoolMask damageSchoolMask, SpellInfo const* spellProto, bool durabilityLoss)
{
uint32 damageDone = damage;
uint32 damageTaken = damage;
if (attacker)
damageTaken = damage / victim->GetHealthMultiplierForTarget(attacker);
// call script hooks
{
uint32 tmpDamage = damageTaken;
// sparring
if (Creature* victimCreature = victim->ToCreature())
tmpDamage = victimCreature->CalculateDamageForSparring(attacker, tmpDamage);
if (UnitAI* victimAI = victim->GetAI())
victimAI->DamageTaken(attacker, tmpDamage, damagetype, spellProto);
if (UnitAI* attackerAI = attacker ? attacker->GetAI() : nullptr)
attackerAI->DamageDealt(victim, tmpDamage, damagetype);
// Hook for OnDamage Event
sScriptMgr->OnDamage(attacker, victim, tmpDamage);
// if any script modified damage, we need to also apply the same modification to unscaled damage value
if (tmpDamage != damageTaken)
{
if (attacker)
damageDone = tmpDamage * victim->GetHealthMultiplierForTarget(attacker);
else
damageDone = tmpDamage;
damageTaken = tmpDamage;
}
}
// Signal to pets that their owner was attacked - except when DOT.
if (attacker != victim && damagetype != DOT)
{
for (Unit* controlled : victim->m_Controlled)
if (Creature* cControlled = controlled->ToCreature())
if (CreatureAI* controlledAI = cControlled->AI())
controlledAI->OwnerAttackedBy(attacker);
}
if (Player* player = victim->ToPlayer())
if (player->GetCommandStatus(CHEAT_GOD))
return 0;
if (damagetype != NODAMAGE)
{
// interrupting auras with SpellAuraInterruptFlags::Damage before checking !damage (absorbed damage breaks that type of auras)
if (spellProto)
{
if (!spellProto->HasAttribute(SPELL_ATTR4_REACTIVE_DAMAGE_PROC))
victim->RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Damage, spellProto);
}
else
victim->RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Damage);
if (!damageTaken && damagetype != DOT && cleanDamage && cleanDamage->absorbed_damage)
if (victim != attacker && victim->GetTypeId() == TYPEID_PLAYER)
if (Spell* spell = victim->m_currentSpells[CURRENT_GENERIC_SPELL])
if (spell->getState() == SPELL_STATE_PREPARING && spell->m_spellInfo->InterruptFlags.HasFlag(SpellInterruptFlags::DamageAbsorb))
victim->InterruptNonMeleeSpells(false);
// We're going to call functions which can modify content of the list during iteration over it's elements
// Let's copy the list so we can prevent iterator invalidation
AuraEffectVector vCopyDamageCopy = CopyAuraEffectList(victim->GetAuraEffectsByType(SPELL_AURA_SHARE_DAMAGE_PCT));
// copy damage to casters of this aura
for (auto i = vCopyDamageCopy.begin(); i != vCopyDamageCopy.end(); ++i)
{
// Check if aura was removed during iteration - we don't need to work on such auras
if (!((*i)->GetBase()->IsAppliedOnTarget(victim->GetGUID())))
continue;
// check damage school mask
if (((*i)->GetMiscValue() & damageSchoolMask) == 0)
continue;
Unit* shareDamageTarget = (*i)->GetCaster();
if (!shareDamageTarget)
continue;
SpellInfo const* spell = (*i)->GetSpellInfo();
uint32 share = CalculatePct(damageDone, (*i)->GetAmount());
/// @todo check packets if damage is done by victim, or by attacker of victim
Unit::DealDamageMods(attacker, shareDamageTarget, share, nullptr);
Unit::DealDamage(attacker, shareDamageTarget, share, nullptr, NODAMAGE, spell->GetSchoolMask(), spell, false);
}
}
if (!damageDone)
return 0;
uint32 health = victim->GetHealth();
// duel ends when player has 1 or less hp
bool duel_hasEnded = false;
bool duel_wasMounted = false;
if (victim->GetTypeId() == TYPEID_PLAYER && victim->ToPlayer()->duel && damageTaken >= (health-1))
{
if (!attacker)
return 0;
// prevent kill only if killed in duel and killed by opponent or opponent controlled creature
if (victim->ToPlayer()->duel->Opponent == attacker->GetControllingPlayer())
damageTaken = health - 1;
duel_hasEnded = true;
}
else if (victim->IsCreature() && victim != attacker && damageTaken >= health && victim->ToCreature()->HasFlag(CREATURE_STATIC_FLAG_UNKILLABLE))
{
damageTaken = health - 1;
// If we had damage (aka health was not 1 already) trigger OnHealthDepleted
if (damageTaken > 0)
{
if (CreatureAI* victimAI = victim->ToCreature()->AI())
victimAI->OnHealthDepleted(attacker, false);
}
}
else if (victim->IsVehicle() && damageTaken >= (health-1) && victim->GetCharmer() && victim->GetCharmer()->GetTypeId() == TYPEID_PLAYER)
{
Player* victimRider = victim->GetCharmer()->ToPlayer();
if (victimRider && victimRider->duel && victimRider->duel->IsMounted)
{
if (!attacker)
return 0;
// prevent kill only if killed in duel and killed by opponent or opponent controlled creature
if (victimRider->duel->Opponent == attacker->GetControllingPlayer())
damageTaken = health - 1;
duel_wasMounted = true;
duel_hasEnded = true;
}
}
if (spellProto && spellProto->HasAttribute(SPELL_ATTR9_CANNOT_KILL_TARGET) && damageTaken >= health)
damageTaken = health - 1;
if (attacker && attacker != victim)
{
if (Player* killer = attacker->ToPlayer())
{
// in bg, count dmg if victim is also a player
if (victim->GetTypeId() == TYPEID_PLAYER && !(spellProto && spellProto->HasAttribute(SPELL_ATTR7_DO_NOT_COUNT_FOR_PVP_SCOREBOARD)))
if (Battleground* bg = killer->GetBattleground())
bg->UpdatePlayerScore(killer, SCORE_DAMAGE_DONE, damageDone);
killer->UpdateCriteria(CriteriaType::DamageDealt, health > damageDone ? damageDone : health, 0, 0, victim);
killer->UpdateCriteria(CriteriaType::HighestDamageDone, damageDone);
}
}
if (victim->GetTypeId() == TYPEID_PLAYER)
victim->ToPlayer()->UpdateCriteria(CriteriaType::HighestDamageTaken, damageTaken);
if (victim->GetTypeId() != TYPEID_PLAYER && (!victim->IsControlledByPlayer() || victim->IsVehicle()))
{
victim->ToCreature()->SetTappedBy(attacker);
if (!attacker || attacker->IsControlledByPlayer())
victim->ToCreature()->LowerPlayerDamageReq(health < damageTaken ? health : damageTaken);
}
bool killed = false;
bool skipSettingDeathState = false;
if (health <= damageTaken)
{
killed = true;
if (victim->GetTypeId() == TYPEID_PLAYER && victim != attacker)
victim->ToPlayer()->UpdateCriteria(CriteriaType::TotalDamageTaken, health);
if (damagetype != NODAMAGE && damagetype != SELF_DAMAGE && victim->HasAuraType(SPELL_AURA_SCHOOL_ABSORB_OVERKILL))
{
AuraEffectVector vAbsorbOverkill = CopyAuraEffectList(victim->GetAuraEffectsByType(SPELL_AURA_SCHOOL_ABSORB_OVERKILL));
DamageInfo damageInfo = DamageInfo(attacker, victim, damageTaken, spellProto, damageSchoolMask, damagetype,
cleanDamage ? cleanDamage->attackType : BASE_ATTACK);
for (AuraEffect* absorbAurEff : vAbsorbOverkill)
{
Aura* base = absorbAurEff->GetBase();
AuraApplication const* aurApp = base->GetApplicationOfTarget(victim->GetGUID());
if (!aurApp)
continue;
if (!(absorbAurEff->GetMiscValue() & damageInfo.GetSchoolMask()))
continue;
// cannot absorb over limit
if (damageTaken >= victim->CountPctFromMaxHealth(100 + absorbAurEff->GetMiscValueB()))
continue;
// absorb all damage by default
uint32 currentAbsorb = damageInfo.GetDamage();
// This aura type is used both by Spirit of Redemption (death not really prevented, must grant all credit immediately) and Cheat Death (death prevented)
// repurpose PreventDefaultAction for this
bool deathFullyPrevented = false;
absorbAurEff->GetBase()->CallScriptEffectAbsorbHandlers(absorbAurEff, aurApp, damageInfo, currentAbsorb, deathFullyPrevented);
// absorb must be smaller than the damage itself
currentAbsorb = std::min(currentAbsorb, damageInfo.GetDamage());
// if nothing is absorbed (for example because of a scripted cooldown) then skip this aura and proceed with dying
if (!currentAbsorb)
continue;
damageInfo.AbsorbDamage(currentAbsorb);
if (deathFullyPrevented)
killed = false;
skipSettingDeathState = true;
if (currentAbsorb)
{
WorldPackets::CombatLog::SpellAbsorbLog absorbLog;
absorbLog.Attacker = attacker ? attacker->GetGUID() : ObjectGuid::Empty;
absorbLog.Victim = victim->GetGUID();
absorbLog.Caster = base->GetCasterGUID();
absorbLog.AbsorbedSpellID = spellProto ? spellProto->Id : 0;
absorbLog.AbsorbSpellID = base->GetId();
absorbLog.Absorbed = currentAbsorb;
absorbLog.OriginalDamage = damageInfo.GetOriginalDamage();
absorbLog.LogData.Initialize(victim);
victim->SendCombatLogMessage(&absorbLog);
}
}
damageTaken = damageInfo.GetDamage();
}
}
if (spellProto && spellProto->HasAttribute(SPELL_ATTR3_NO_DURABILITY_LOSS))
durabilityLoss = false;
if (killed)
Unit::Kill(attacker, victim, durabilityLoss, skipSettingDeathState);
else
{
if (victim->GetTypeId() == TYPEID_PLAYER)
victim->ToPlayer()->UpdateCriteria(CriteriaType::TotalDamageTaken, damageTaken);
victim->ModifyHealth(-(int32)damageTaken);
if (damagetype == DIRECT_DAMAGE || damagetype == SPELL_DIRECT_DAMAGE)
victim->RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::NonPeriodicDamage, spellProto);
if (victim->GetTypeId() != TYPEID_PLAYER)
{
// Part of Evade mechanics. DoT's and Thorns / Retribution Aura do not contribute to this
if (damagetype != DOT && damageTaken > 0 && !victim->GetOwnerGUID().IsPlayer() && (!spellProto || !spellProto->HasAura(SPELL_AURA_DAMAGE_SHIELD)))
victim->ToCreature()->SetLastDamagedTime(GameTime::GetGameTime() + MAX_AGGRO_RESET_TIME);
if (attacker && (!spellProto || !spellProto->HasAttribute(SPELL_ATTR4_NO_HARMFUL_THREAT)))
victim->GetThreatManager().AddThreat(attacker, float(damageTaken), spellProto);
}
else // victim is a player
{
// random durability for items (HIT TAKEN)
if (durabilityLoss && roll_chance_f(sWorld->getRate(RATE_DURABILITY_LOSS_DAMAGE)))
{
EquipmentSlots slot = EquipmentSlots(urand(0, EQUIPMENT_SLOT_END-1));
victim->ToPlayer()->DurabilityPointLossForEquipSlot(slot);
}
}
if (attacker && attacker->GetTypeId() == TYPEID_PLAYER)
{
// random durability for items (HIT DONE)
if (durabilityLoss && roll_chance_f(sWorld->getRate(RATE_DURABILITY_LOSS_DAMAGE)))
{
EquipmentSlots slot = EquipmentSlots(urand(0, EQUIPMENT_SLOT_END-1));
attacker->ToPlayer()->DurabilityPointLossForEquipSlot(slot);
}
}
if (damagetype != NODAMAGE && damagetype != DOT)
{
if (victim != attacker && (!spellProto || !(spellProto->HasAttribute(SPELL_ATTR6_NO_PUSHBACK) || spellProto->HasAttribute(SPELL_ATTR7_DONT_CAUSE_SPELL_PUSHBACK) || spellProto->HasAttribute(SPELL_ATTR3_TREAT_AS_PERIODIC))))
{
if (Spell* spell = victim->m_currentSpells[CURRENT_GENERIC_SPELL])
{
if (spell->getState() == SPELL_STATE_PREPARING)
{
auto isCastInterrupted = [&]()
{
if (!damageTaken)
return spell->m_spellInfo->InterruptFlags.HasFlag(SpellInterruptFlags::ZeroDamageCancels);
if (victim->IsPlayer() && spell->m_spellInfo->InterruptFlags.HasFlag(SpellInterruptFlags::DamageCancelsPlayerOnly))
return true;
if (spell->m_spellInfo->InterruptFlags.HasFlag(SpellInterruptFlags::DamageCancels))
return true;
return false;
};
auto isCastDelayed = [&]()
{
if (!damageTaken)
return false;
if (victim->IsPlayer() && spell->m_spellInfo->InterruptFlags.HasFlag(SpellInterruptFlags::DamagePushbackPlayerOnly))
return true;
if (spell->m_spellInfo->InterruptFlags.HasFlag(SpellInterruptFlags::DamagePushback))
return true;
return false;
};
if (isCastInterrupted())
victim->InterruptSpell(CURRENT_GENERIC_SPELL, false, false);
else if (isCastDelayed())
spell->Delayed();
}
}
if (damageTaken && victim->IsPlayer())
if (Spell* spell = victim->m_currentSpells[CURRENT_CHANNELED_SPELL])
if (spell->getState() == SPELL_STATE_CHANNELING && spell->m_spellInfo->HasChannelInterruptFlag(SpellAuraInterruptFlags::DamageChannelDuration))
spell->DelayedChannel();
}
}
// last damage from duel opponent
if (duel_hasEnded)
{
Player* he = duel_wasMounted ? victim->GetCharmer()->ToPlayer() : victim->ToPlayer();
ASSERT_NODEBUGINFO(he && he->duel);
if (duel_wasMounted) // In this case victim == mount
victim->SetHealth(1);
else
he->SetHealth(1);
he->duel->Opponent->CombatStopWithPets(true);
he->CombatStopWithPets(true);
he->CastSpell(he, 7267, true); // beg
he->DuelComplete(DUEL_WON);
}
}
// make player victims stand up automatically
if (victim->GetStandState() && victim->IsPlayer())
victim->SetStandState(UNIT_STAND_STATE_STAND);
return damageTaken;
}
void Unit::CastStop(uint32 except_spellid)
{
for (uint32 i = CURRENT_FIRST_NON_MELEE_SPELL; i < CURRENT_MAX_SPELL; i++)
if (m_currentSpells[i] && m_currentSpells[i]->m_spellInfo->Id != except_spellid)
InterruptSpell(CurrentSpellTypes(i), false);
}
void Unit::CalculateSpellDamageTaken(SpellNonMeleeDamage* damageInfo, int32 damage, SpellInfo const* spellInfo, WeaponAttackType attackType, bool crit /*= false*/, bool blocked /*= false*/, Spell* spell /*= nullptr*/)
{
if (damage < 0)
return;
Unit* victim = damageInfo->target;
if (!victim || !victim->IsAlive())
return;
SpellSchoolMask damageSchoolMask = SpellSchoolMask(damageInfo->schoolMask);
// Spells with SPELL_ATTR4_IGNORE_DAMAGE_TAKEN_MODIFIERS ignore resilience because their damage is based off another spell's damage.
if (!spellInfo->HasAttribute(SPELL_ATTR4_IGNORE_DAMAGE_TAKEN_MODIFIERS))
{
if (Unit::IsDamageReducedByArmor(damageSchoolMask, spellInfo))
damage = Unit::CalcArmorReducedDamage(damageInfo->attacker, victim, damage, spellInfo, attackType);
// Per-school calc
switch (spellInfo->DmgClass)
{
// Melee and Ranged Spells
case SPELL_DAMAGE_CLASS_RANGED:
case SPELL_DAMAGE_CLASS_MELEE:
{
if (crit)
{
damageInfo->HitInfo |= SPELL_HIT_TYPE_CRIT;
// Calculate crit bonus
uint32 crit_bonus = damage;
// Apply crit_damage bonus for melee spells
if (Player* modOwner = GetSpellModOwner())
modOwner->ApplySpellMod(spellInfo, SpellModOp::CritDamageAndHealing, crit_bonus);
damage += crit_bonus;
// Increase crit damage from SPELL_AURA_MOD_CRIT_DAMAGE_BONUS
float critPctDamageMod = (GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_CRIT_DAMAGE_BONUS, spellInfo->GetSchoolMask()) - 1.0f) * 100;
if (critPctDamageMod != 0)
AddPct(damage, critPctDamageMod);
}
// Spell weapon based damage CAN BE crit & blocked at same time
if (blocked)
{
// double blocked amount if block is critical
uint32 value = victim->GetBlockPercent(GetLevel());
if (victim->IsBlockCritical())
{
value *= 2; // double blocked percent
value *= GetTotalAuraMultiplier(SPELL_AURA_MOD_CRITICAL_BLOCK_AMOUNT);
}
damageInfo->blocked = CalculatePct(damage, value);
if (damage <= int32(damageInfo->blocked))
{
damageInfo->blocked = uint32(damage);
damageInfo->fullBlock = true;
}
damage -= damageInfo->blocked;
}
if (CanApplyResilience())
Unit::ApplyResilience(victim, &damage);
break;
}
// Magical Attacks
case SPELL_DAMAGE_CLASS_NONE:
case SPELL_DAMAGE_CLASS_MAGIC:
{
// If crit add critical bonus
if (crit)
{
damageInfo->HitInfo |= SPELL_HIT_TYPE_CRIT;
damage = Unit::SpellCriticalDamageBonus(this, spellInfo, damage, victim);
}
if (CanApplyResilience())
Unit::ApplyResilience(victim, &damage);
break;
}
default:
break;
}
}
// Script Hook For CalculateSpellDamageTaken -- Allow scripts to change the Damage post class mitigation calculations
sScriptMgr->ModifySpellDamageTaken(damageInfo->target, damageInfo->attacker, damage, spellInfo);
// Calculate absorb resist
if (damage < 0)
damage = 0;
damageInfo->damage = damage;
damageInfo->originalDamage = damage;
DamageInfo dmgInfo(*damageInfo, SPELL_DIRECT_DAMAGE, BASE_ATTACK, PROC_HIT_NONE);
Unit::CalcAbsorbResist(dmgInfo, spell);
damageInfo->absorb = dmgInfo.GetAbsorb();
damageInfo->resist = dmgInfo.GetResist();
if (damageInfo->absorb)
damageInfo->HitInfo |= (damageInfo->damage - damageInfo->absorb == 0 ? HITINFO_FULL_ABSORB : HITINFO_PARTIAL_ABSORB);
if (damageInfo->resist)
damageInfo->HitInfo |= (damageInfo->damage - damageInfo->resist == 0 ? HITINFO_FULL_RESIST : HITINFO_PARTIAL_RESIST);
damageInfo->damage = dmgInfo.GetDamage();
}
void Unit::DealSpellDamage(SpellNonMeleeDamage const* damageInfo, bool durabilityLoss)
{
if (!damageInfo)
return;
Unit* victim = damageInfo->target;
if (!victim)
return;
if (!victim->IsAlive() || victim->HasUnitState(UNIT_STATE_IN_FLIGHT) || (victim->GetTypeId() == TYPEID_UNIT && victim->ToCreature()->IsEvadingAttacks()))
return;
if (!damageInfo->Spell)
{
TC_LOG_DEBUG("entities.unit", "Unit::DealSpellDamage has no spell");
return;
}
// Call default DealDamage
CleanDamage cleanDamage(damageInfo->cleanDamage, damageInfo->absorb, BASE_ATTACK, MELEE_HIT_NORMAL);
Unit::DealDamage(this, victim, damageInfo->damage, &cleanDamage, SPELL_DIRECT_DAMAGE, SpellSchoolMask(damageInfo->schoolMask), damageInfo->Spell, durabilityLoss);
}
/// @todo for melee need create structure as in
void Unit::CalculateMeleeDamage(Unit* victim, CalcDamageInfo* damageInfo, WeaponAttackType attackType /*= BASE_ATTACK*/)
{
damageInfo->Attacker = this;
damageInfo->Target = victim;
damageInfo->DamageSchoolMask = GetMeleeDamageSchoolMask(attackType);
damageInfo->Damage = 0;
damageInfo->OriginalDamage = 0;
damageInfo->Absorb = 0;
damageInfo->Resist = 0;
damageInfo->Blocked = 0;
damageInfo->HitInfo = 0;
damageInfo->TargetState = 0;
damageInfo->RageGained = 0;
damageInfo->AttackType = attackType;
damageInfo->ProcAttacker = PROC_FLAG_NONE;
damageInfo->ProcVictim = PROC_FLAG_NONE;
damageInfo->CleanDamage = 0;
damageInfo->HitOutCome = MELEE_HIT_EVADE;
if (!victim)
return;
if (!IsAlive() || !victim->IsAlive())
return;
// Select HitInfo/procAttacker/procVictim flag based on attack type
switch (attackType)
{
case BASE_ATTACK:
damageInfo->ProcAttacker = PROC_FLAG_DEAL_MELEE_SWING | PROC_FLAG_MAIN_HAND_WEAPON_SWING;
damageInfo->ProcVictim = PROC_FLAG_TAKE_MELEE_SWING;
break;
case OFF_ATTACK:
damageInfo->ProcAttacker = PROC_FLAG_DEAL_MELEE_SWING | PROC_FLAG_OFF_HAND_WEAPON_SWING;
damageInfo->ProcVictim = PROC_FLAG_TAKE_MELEE_SWING;
damageInfo->HitInfo = HITINFO_OFFHAND;
break;
default:
return;
}
// Physical Immune check
if (damageInfo->Target->IsImmunedToDamage(SpellSchoolMask(damageInfo->DamageSchoolMask)))
{
damageInfo->HitInfo |= HITINFO_NORMALSWING;
damageInfo->TargetState = VICTIMSTATE_IS_IMMUNE;
damageInfo->Damage = 0;
damageInfo->CleanDamage = 0;
return;
}
uint32 damage = 0;
damage += CalculateDamage(damageInfo->AttackType, false, true);
// Add melee damage bonus
damage = MeleeDamageBonusDone(damageInfo->Target, damage, damageInfo->AttackType, DIRECT_DAMAGE, nullptr, nullptr, MECHANIC_NONE, SpellSchoolMask(damageInfo->DamageSchoolMask));
damage = damageInfo->Target->MeleeDamageBonusTaken(this, damage, damageInfo->AttackType, DIRECT_DAMAGE, nullptr, SpellSchoolMask(damageInfo->DamageSchoolMask));
// Script Hook For CalculateMeleeDamage -- Allow scripts to change the Damage pre class mitigation calculations
sScriptMgr->ModifyMeleeDamage(damageInfo->Target, damageInfo->Attacker, damage);
// Calculate armor reduction
if (Unit::IsDamageReducedByArmor(SpellSchoolMask(damageInfo->DamageSchoolMask)))
{
damageInfo->Damage = Unit::CalcArmorReducedDamage(damageInfo->Attacker, damageInfo->Target, damage, nullptr, damageInfo->AttackType);
damageInfo->CleanDamage += damage - damageInfo->Damage;
}
else
damageInfo->Damage = damage;
damageInfo->HitOutCome = RollMeleeOutcomeAgainst(damageInfo->Target, damageInfo->AttackType);
switch (damageInfo->HitOutCome)
{
case MELEE_HIT_EVADE:
damageInfo->HitInfo |= HITINFO_MISS | HITINFO_SWINGNOHITSOUND;
damageInfo->TargetState = VICTIMSTATE_EVADES;
damageInfo->OriginalDamage = damageInfo->Damage;
damageInfo->Damage = 0;
damageInfo->CleanDamage = 0;
return;
case MELEE_HIT_MISS:
damageInfo->HitInfo |= HITINFO_MISS;
damageInfo->TargetState = VICTIMSTATE_INTACT;
damageInfo->OriginalDamage = damageInfo->Damage;
damageInfo->Damage = 0;
damageInfo->CleanDamage = 0;
break;
case MELEE_HIT_NORMAL:
damageInfo->TargetState = VICTIMSTATE_HIT;
damageInfo->OriginalDamage = damageInfo->Damage;
break;
case MELEE_HIT_CRIT:
{
damageInfo->HitInfo |= HITINFO_CRITICALHIT;
damageInfo->TargetState = VICTIMSTATE_HIT;
// Crit bonus calc
damageInfo->Damage *= 2;
// Increase crit damage from SPELL_AURA_MOD_CRIT_DAMAGE_BONUS
float mod = (GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_CRIT_DAMAGE_BONUS, damageInfo->DamageSchoolMask) - 1.0f) * 100;
if (mod != 0)
AddPct(damageInfo->Damage, mod);
damageInfo->OriginalDamage = damageInfo->Damage;
break;
}
case MELEE_HIT_PARRY:
damageInfo->TargetState = VICTIMSTATE_PARRY;
damageInfo->CleanDamage += damageInfo->Damage;
damageInfo->OriginalDamage = damageInfo->Damage;
damageInfo->Damage = 0;
break;
case MELEE_HIT_DODGE:
damageInfo->TargetState = VICTIMSTATE_DODGE;
damageInfo->CleanDamage += damageInfo->Damage;
damageInfo->OriginalDamage = damageInfo->Damage;
damageInfo->Damage = 0;
break;
case MELEE_HIT_BLOCK:
damageInfo->TargetState = VICTIMSTATE_HIT;
damageInfo->HitInfo |= HITINFO_BLOCK;
// 30% damage blocked, double blocked amount if block is critical
damageInfo->Blocked = CalculatePct(damageInfo->Damage, damageInfo->Target->GetBlockPercent(GetLevel()));
if (damageInfo->Target->IsBlockCritical())
{
damageInfo->Blocked *= 2;
damageInfo->Blocked *= GetTotalAuraMultiplier(SPELL_AURA_MOD_CRITICAL_BLOCK_AMOUNT);
}
damageInfo->OriginalDamage = damageInfo->Damage;
damageInfo->Damage -= damageInfo->Blocked;
damageInfo->CleanDamage += damageInfo->Blocked;
break;
case MELEE_HIT_GLANCING:
{
damageInfo->HitInfo |= HITINFO_GLANCING;
damageInfo->TargetState = VICTIMSTATE_HIT;
int32 leveldif = int32(victim->GetLevel()) - int32(GetLevel());
if (leveldif > 3)
leveldif = 3;
damageInfo->OriginalDamage = damageInfo->Damage;
float reducePercent = 1.f - leveldif * 0.1f;
damageInfo->CleanDamage += damageInfo->Damage - uint32(reducePercent * damageInfo->Damage);
damageInfo->Damage = uint32(reducePercent * damageInfo->Damage);
break;
}
case MELEE_HIT_CRUSHING:
damageInfo->HitInfo |= HITINFO_CRUSHING;
damageInfo->TargetState = VICTIMSTATE_HIT;
// 150% normal damage
damageInfo->Damage += (damageInfo->Damage / 2);
damageInfo->OriginalDamage = damageInfo->Damage;
break;
default:
break;
}
// Always apply HITINFO_AFFECTS_VICTIM in case its not a miss
if (!(damageInfo->HitInfo & HITINFO_MISS))
damageInfo->HitInfo |= HITINFO_AFFECTS_VICTIM;
int32 resilienceReduction = damageInfo->Damage;
if (CanApplyResilience())
Unit::ApplyResilience(victim, &resilienceReduction);
resilienceReduction = damageInfo->Damage - resilienceReduction;
damageInfo->Damage -= resilienceReduction;
damageInfo->CleanDamage += resilienceReduction;
damageInfo->OriginalDamage -= resilienceReduction;
// Calculate absorb resist
if (int32(damageInfo->Damage) > 0)
{
damageInfo->ProcVictim |= PROC_FLAG_TAKE_ANY_DAMAGE;
// Calculate absorb & resists
DamageInfo dmgInfo(*damageInfo);
Unit::CalcAbsorbResist(dmgInfo);
damageInfo->Absorb = dmgInfo.GetAbsorb();
damageInfo->Resist = dmgInfo.GetResist();
if (damageInfo->Absorb)
damageInfo->HitInfo |= (damageInfo->Damage - damageInfo->Absorb == 0 ? HITINFO_FULL_ABSORB : HITINFO_PARTIAL_ABSORB);
if (damageInfo->Resist)
damageInfo->HitInfo |= (damageInfo->Damage - damageInfo->Resist == 0 ? HITINFO_FULL_RESIST : HITINFO_PARTIAL_RESIST);
damageInfo->Damage = dmgInfo.GetDamage();
}
else // Impossible get negative result but....
damageInfo->Damage = 0;
}
void Unit::DealMeleeDamage(CalcDamageInfo* damageInfo, bool durabilityLoss)
{
Unit* victim = damageInfo->Target;
if (!victim->IsAlive() || victim->HasUnitState(UNIT_STATE_IN_FLIGHT) || (victim->GetTypeId() == TYPEID_UNIT && victim->ToCreature()->IsEvadingAttacks()))
return;
if (damageInfo->TargetState == VICTIMSTATE_PARRY &&
(victim->GetTypeId() != TYPEID_UNIT || (victim->ToCreature()->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_NO_PARRY_HASTEN) == 0))
{
// Get attack timers
float offtime = float(victim->getAttackTimer(OFF_ATTACK));
float basetime = float(victim->getAttackTimer(BASE_ATTACK));
// Reduce attack time
if (victim->haveOffhandWeapon() && offtime < basetime)
{
float percent20 = victim->GetBaseAttackTime(OFF_ATTACK) * 0.20f;
float percent60 = 3.0f * percent20;
if (offtime > percent20 && offtime <= percent60)
victim->setAttackTimer(OFF_ATTACK, uint32(percent20));
else if (offtime > percent60)
{
offtime -= 2.0f * percent20;
victim->setAttackTimer(OFF_ATTACK, uint32(offtime));
}
}
else
{
float percent20 = victim->GetBaseAttackTime(BASE_ATTACK) * 0.20f;
float percent60 = 3.0f * percent20;
if (basetime > percent20 && basetime <= percent60)
victim->setAttackTimer(BASE_ATTACK, uint32(percent20));
else if (basetime > percent60)
{
basetime -= 2.0f * percent20;
victim->setAttackTimer(BASE_ATTACK, uint32(basetime));
}
}
}
// Call default DealDamage
CleanDamage cleanDamage(damageInfo->CleanDamage, damageInfo->Absorb, damageInfo->AttackType, damageInfo->HitOutCome);
Unit::DealDamage(this, victim, damageInfo->Damage, &cleanDamage, DIRECT_DAMAGE, SpellSchoolMask(damageInfo->DamageSchoolMask), nullptr, durabilityLoss);
// If this is a creature and it attacks from behind it has a probability to daze it's victim
if ((damageInfo->HitOutCome == MELEE_HIT_CRIT || damageInfo->HitOutCome == MELEE_HIT_CRUSHING || damageInfo->HitOutCome == MELEE_HIT_NORMAL || damageInfo->HitOutCome == MELEE_HIT_GLANCING) &&
GetTypeId() != TYPEID_PLAYER && !ToCreature()->IsControlledByPlayer() && !victim->HasInArc(float(M_PI), this)
&& (victim->GetTypeId() == TYPEID_PLAYER || !victim->ToCreature()->isWorldBoss())&& !victim->IsVehicle())
{
// 20% base chance
float chance = 20.0f;
// there is a newbie protection, at level 10 just 7% base chance; assuming linear function
if (victim->GetLevel() < 30)
chance = 0.65f * victim->GetLevelForTarget(this) + 0.5f;
uint32 const victimDefense = victim->GetMaxSkillValueForLevel(this);
uint32 const attackerMeleeSkill = GetMaxSkillValueForLevel();
chance *= attackerMeleeSkill / float(victimDefense) * 0.16f;
// -probability is between 0% and 40%
RoundToInterval(chance, 0.0f, 40.0f);
if (roll_chance_f(chance))
CastSpell(victim, SPELL_DAZED, true);
}
if (GetTypeId() == TYPEID_PLAYER)
{
DamageInfo dmgInfo(*damageInfo);
ToPlayer()->CastItemCombatSpell(dmgInfo);
}
// Do effect if any damage done to target
if (damageInfo->Damage)
{
// We're going to call functions which can modify content of the list during iteration over it's elements
// Let's copy the list so we can prevent iterator invalidation
AuraEffectVector vDamageShieldsCopy = CopyAuraEffectList(victim->GetAuraEffectsByType(SPELL_AURA_DAMAGE_SHIELD));
for (AuraEffect const* aurEff : vDamageShieldsCopy)
{
SpellInfo const* spellInfo = aurEff->GetSpellInfo();
// Damage shield can be resisted...
SpellMissInfo missInfo = victim->SpellHitResult(this, spellInfo, false);
if (missInfo != SPELL_MISS_NONE)
{
victim->SendSpellMiss(this, spellInfo->Id, missInfo);
continue;
}
// ...or immuned
if (IsImmunedToDamage(this, spellInfo))
{
victim->SendSpellDamageImmune(this, spellInfo->Id, false);
continue;
}
uint32 damage = aurEff->GetAmount();
if (Unit* caster = aurEff->GetCaster())
{
damage = caster->SpellDamageBonusDone(this, spellInfo, damage, SPELL_DIRECT_DAMAGE, aurEff->GetSpellEffectInfo());
damage = SpellDamageBonusTaken(caster, spellInfo, damage, SPELL_DIRECT_DAMAGE);
}
DamageInfo damageInfo(this, victim, damage, spellInfo, spellInfo->GetSchoolMask(), SPELL_DIRECT_DAMAGE, BASE_ATTACK);
victim->CalcAbsorbResist(damageInfo);
damage = damageInfo.GetDamage();
Unit::DealDamageMods(victim, this, damage, nullptr);
WorldPackets::CombatLog::SpellDamageShield damageShield;
damageShield.Attacker = victim->GetGUID();
damageShield.Defender = GetGUID();
damageShield.SpellID = spellInfo->Id;
damageShield.TotalDamage = damage;
damageShield.OriginalDamage = damageInfo.GetOriginalDamage();
damageShield.OverKill = std::max(int32(damage) - int32(GetHealth()), 0);
damageShield.SchoolMask = spellInfo->SchoolMask;
damageShield.LogAbsorbed = damageInfo.GetAbsorb();
Unit::DealDamage(victim, this, damage, nullptr, SPELL_DIRECT_DAMAGE, spellInfo->GetSchoolMask(), spellInfo, true);
damageShield.LogData.Initialize(this);
victim->SendCombatLogMessage(&damageShield);
}
}
}
void Unit::HandleEmoteCommand(Emote emoteId, Player* target /*=nullptr*/, Trinity::IteratorPair spellVisualKitIds /*= {}*/, int32 sequenceVariation /*= 0*/)
{
WorldPackets::Chat::Emote packet;
packet.Guid = GetGUID();
packet.EmoteID = emoteId;
if (EmotesEntry const* emotesEntry = sEmotesStore.LookupEntry(emoteId))
if (emotesEntry->AnimID == ANIM_MOUNT_SPECIAL || emotesEntry->AnimID == ANIM_MOUNT_SELF_SPECIAL)
std::copy(spellVisualKitIds.begin(), spellVisualKitIds.end(), std::back_inserter(packet.SpellVisualKitIDs));
packet.SequenceVariation = sequenceVariation;
if (target)
target->SendDirectMessage(packet.Write());
else
SendMessageToSet(packet.Write(), true);
}
/*static*/ bool Unit::IsDamageReducedByArmor(SpellSchoolMask schoolMask, SpellInfo const* spellInfo /*= nullptr*/)
{
// only physical spells damage gets reduced by armor
if ((schoolMask & SPELL_SCHOOL_MASK_NORMAL) == 0)
return false;
return !spellInfo || !spellInfo->HasAttribute(SPELL_ATTR0_CU_IGNORE_ARMOR);
}
/*static*/ uint32 Unit::CalcArmorReducedDamage(Unit const* attacker, Unit* victim, uint32 damage, SpellInfo const* spellInfo, WeaponAttackType attackType /*= MAX_ATTACK*/, uint8 attackerLevel /*= 0*/)
{
float armor = float(victim->GetArmor());
if (attacker)
{
armor *= victim->GetArmorMultiplierForTarget(attacker);
// bypass enemy armor by SPELL_AURA_BYPASS_ARMOR_FOR_CASTER
int32 armorBypassPct = 0;
AuraEffectList const & reductionAuras = victim->GetAuraEffectsByType(SPELL_AURA_BYPASS_ARMOR_FOR_CASTER);
for (AuraEffectList::const_iterator i = reductionAuras.begin(); i != reductionAuras.end(); ++i)
if ((*i)->GetCasterGUID() == attacker->GetGUID())
armorBypassPct += (*i)->GetAmount();
armor = CalculatePct(armor, 100 - std::min(armorBypassPct, 100));
// Ignore enemy armor by SPELL_AURA_MOD_TARGET_RESISTANCE aura
armor += attacker->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_TARGET_RESISTANCE, SPELL_SCHOOL_MASK_NORMAL);
if (spellInfo)
if (Player* modOwner = attacker->GetSpellModOwner())
modOwner->ApplySpellMod(spellInfo, SpellModOp::TargetResistance, armor);
AuraEffectList const& resIgnoreAuras = attacker->GetAuraEffectsByType(SPELL_AURA_MOD_IGNORE_TARGET_RESIST);
for (AuraEffect const* aurEff : resIgnoreAuras)
{
if (aurEff->GetMiscValue() & SPELL_SCHOOL_MASK_NORMAL && aurEff->IsAffectingSpell(spellInfo))
armor = std::floor(AddPct(armor, -aurEff->GetAmount()));
}
// Apply Player CR_ARMOR_PENETRATION rating and buffs from stances\specializations etc.
if (attacker->GetTypeId() == TYPEID_PLAYER)
{
float arpPct = attacker->ToPlayer()->GetRatingBonusValue(CR_ARMOR_PENETRATION);
Item const* weapon = attacker->ToPlayer()->GetWeaponForAttack(attackType, true);
arpPct += attacker->GetTotalAuraModifier(SPELL_AURA_MOD_ARMOR_PENETRATION_PCT, [weapon](AuraEffect const* aurEff) -> bool
{
return aurEff->GetSpellInfo()->IsItemFitToSpellRequirements(weapon);
});
// no more than 100%
RoundToInterval(arpPct, 0.f, 100.f);
float maxArmorPen = 0.f;
if (victim->GetLevelForTarget(attacker) < 60)
maxArmorPen = float(400 + 85 * victim->GetLevelForTarget(attacker));
else
maxArmorPen = 400 + 85 * victim->GetLevelForTarget(attacker) + 4.5f * 85 * (victim->GetLevelForTarget(attacker) - 59);
// Cap armor penetration to this number
maxArmorPen = std::min((armor + maxArmorPen) / 3.f, armor);
// Figure out how much armor do we ignore
armor -= CalculatePct(maxArmorPen, arpPct);
}
}
if (G3D::fuzzyLe(armor, 0.0f))
return damage;
Classes attackerClass = CLASS_NONE;
Optional attackerItemLevel;
if (attacker)
{
attackerLevel = attacker->GetLevelForTarget(victim);
if (Player const* ownerPlayer = attacker->GetCharmerOrOwnerPlayerOrPlayerItself())
attackerItemLevel = ownerPlayer->m_playerData->AvgItemLevel[AsUnderlyingType(AvgItemLevelCategory::EquippedBase)];
else
attackerClass = Classes(attacker->GetClass());
}
// Expansion and ContentTuningID necessary? Does Player get a ContentTuningID too ?
float armorConstant = sDB2Manager.EvaluateExpectedStat(ExpectedStatType::ArmorConstant, attackerLevel, -2, 0, attackerClass, 0);
if (attackerItemLevel)
{
uint32 maxLevelForExpansion = GetMaxLevelForExpansion(sWorld->getIntConfig(CONFIG_EXPANSION));
if (attackerLevel == maxLevelForExpansion)
{
float itemLevelDelta = *attackerItemLevel - ASSERT_NOTNULL(sItemLevelByLevelTable.GetRow(maxLevelForExpansion))->ItemLevel;
if (uint32 curveId = sDB2Manager.GetGlobalCurveId(GlobalCurve::ArmorItemLevelDiminishing))
armorConstant *= sDB2Manager.GetCurveValueAt(curveId, itemLevelDelta);
}
}
if (!(armor + armorConstant))
return damage;
float mitigation = std::min(armor / (armor + armorConstant), 0.85f);
return uint32(std::max(damage * (1.0f - mitigation), 0.0f));
}
/*static*/ uint32 Unit::CalcSpellResistedDamage(Unit const* attacker, Unit* victim, uint32 damage, SpellSchoolMask schoolMask, SpellInfo const* spellInfo)
{
// Magic damage, check for resists
if (!(schoolMask & SPELL_SCHOOL_MASK_MAGIC))
return 0;
// Npcs can have holy resistance
if ((schoolMask & SPELL_SCHOOL_MASK_HOLY) && victim->GetTypeId() != TYPEID_UNIT)
return 0;
float const averageResist = Unit::CalculateAverageResistReduction(attacker, schoolMask, victim, spellInfo);
float discreteResistProbability[11] = { };
if (averageResist <= 0.1f)
{
discreteResistProbability[0] = 1.0f - 7.5f * averageResist;
discreteResistProbability[1] = 5.0f * averageResist;
discreteResistProbability[2] = 2.5f * averageResist;
}
else
{
for (uint32 i = 0; i < 11; ++i)
discreteResistProbability[i] = std::max(0.5f - 2.5f * std::fabs(0.1f * i - averageResist), 0.0f);
}
float roll = rand_norm();
float probabilitySum = 0.0f;
uint32 resistance = 0;
for (; resistance < 11; ++resistance)
if (roll < (probabilitySum += discreteResistProbability[resistance]))
break;
float damageResisted = damage * resistance / 10.f;
if (damageResisted > 0.0f) // if any damage was resisted
{
int32 ignoredResistance = 0;
if (attacker)
ignoredResistance += attacker->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_IGNORE_TARGET_RESIST, schoolMask);
ignoredResistance = std::min(ignoredResistance, 100);
ApplyPct(damageResisted, 100 - ignoredResistance);
// Spells with melee and magic school mask, decide whether resistance or armor absorb is higher
if (spellInfo && spellInfo->HasAttribute(SPELL_ATTR0_CU_SCHOOLMASK_NORMAL_WITH_MAGIC))
{
uint32 damageAfterArmor = Unit::CalcArmorReducedDamage(attacker, victim, damage, spellInfo, spellInfo->GetAttackType());
float armorReduction = damage - damageAfterArmor;
// pick the lower one, the weakest resistance counts
damageResisted = std::min(damageResisted, armorReduction);
}
}
damageResisted = std::max(damageResisted, 0.f);
return uint32(damageResisted);
}
/*static*/ float Unit::CalculateAverageResistReduction(WorldObject const* caster, SpellSchoolMask schoolMask, Unit const* victim, SpellInfo const* spellInfo /*= nullptr*/)
{
float victimResistance = float(victim->GetResistance(schoolMask));
if (caster)
{
// pets inherit 100% of masters penetration
if (Player const* player = caster->GetSpellModOwner())
{
victimResistance += float(player->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_TARGET_RESISTANCE, schoolMask));
victimResistance -= float(player->GetSpellPenetrationItemMod());
}
else if (Unit const* unitCaster = caster->ToUnit())
victimResistance += float(unitCaster->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_TARGET_RESISTANCE, schoolMask));
}
// holy resistance exists in pve and comes from level difference, ignore template values
if (schoolMask & SPELL_SCHOOL_MASK_HOLY)
victimResistance = 0.0f;
// Chaos Bolt exception, ignore all target resistances (unknown attribute?)
if (spellInfo && spellInfo->SpellFamilyName == SPELLFAMILY_WARLOCK && spellInfo->Id == 116858)
victimResistance = 0.0f;
victimResistance = std::max(victimResistance, 0.0f);
// level-based resistance does not apply to binary spells, and cannot be overcome by spell penetration
// gameobject caster -- should it have level based resistance?
if (caster && caster->GetTypeId() != TYPEID_GAMEOBJECT && (!spellInfo || !spellInfo->HasAttribute(SPELL_ATTR0_CU_BINARY_SPELL)))
victimResistance += std::max((float(victim->GetLevelForTarget(caster)) - float(caster->GetLevelForTarget(victim))) * 5.0f, 0.0f);
static uint32 const bossLevel = 83;
static float const bossResistanceConstant = 510.0f;
uint32 level = caster ? victim->GetLevelForTarget(caster) : victim->GetLevel();
float resistanceConstant = 0.0f;
if (level == bossLevel)
resistanceConstant = bossResistanceConstant;
else
resistanceConstant = level * 5.0f;
return victimResistance / (victimResistance + resistanceConstant);
}
/*static*/ void Unit::CalcAbsorbResist(DamageInfo& damageInfo, Spell* spell /*= nullptr*/)
{
if (!damageInfo.GetVictim() || !damageInfo.GetVictim()->IsAlive() || !damageInfo.GetDamage())
return;
uint32 resistedDamage = Unit::CalcSpellResistedDamage(damageInfo.GetAttacker(), damageInfo.GetVictim(), damageInfo.GetDamage(), damageInfo.GetSchoolMask(), damageInfo.GetSpellInfo());
// Ignore Absorption Auras
float auraAbsorbMod = 0.f;
if (Unit* attacker = damageInfo.GetAttacker())
auraAbsorbMod = attacker->GetMaxPositiveAuraModifierByMiscMask(SPELL_AURA_MOD_TARGET_ABSORB_SCHOOL, damageInfo.GetSchoolMask());
RoundToInterval(auraAbsorbMod, 0.0f, 100.0f);
int32 absorbIgnoringDamage = CalculatePct(damageInfo.GetDamage(), auraAbsorbMod);
if (spell)
spell->CallScriptOnResistAbsorbCalculateHandlers(damageInfo, resistedDamage, absorbIgnoringDamage);
damageInfo.ResistDamage(resistedDamage);
// We're going to call functions which can modify content of the list during iteration over it's elements
// Let's copy the list so we can prevent iterator invalidation
AuraEffectVector vSchoolAbsorbCopy = CopyAuraEffectList(damageInfo.GetVictim()->GetAuraEffectsByType(SPELL_AURA_SCHOOL_ABSORB));
std::sort(vSchoolAbsorbCopy.begin(), vSchoolAbsorbCopy.end(), Trinity::AbsorbAuraOrderPred());
// absorb without mana cost
for (auto itr = vSchoolAbsorbCopy.begin(); (itr != vSchoolAbsorbCopy.end()) && (damageInfo.GetDamage() > 0); ++itr)
{
AuraEffect* absorbAurEff = *itr;
// Check if aura was removed during iteration - we don't need to work on such auras
AuraApplication const* aurApp = absorbAurEff->GetBase()->GetApplicationOfTarget(damageInfo.GetVictim()->GetGUID());
if (!aurApp)
continue;
if (!(absorbAurEff->GetMiscValue() & damageInfo.GetSchoolMask()))
continue;
// get amount which can be still absorbed by the aura
int32 currentAbsorb = absorbAurEff->GetAmount();
// aura with infinite absorb amount - let the scripts handle absorbtion amount, set here to 0 for safety
if (currentAbsorb < 0)
currentAbsorb = 0;
if (!absorbAurEff->GetSpellInfo()->HasAttribute(SPELL_ATTR6_ABSORB_CANNOT_BE_IGNORE))
damageInfo.ModifyDamage(-absorbIgnoringDamage);
uint32 tempAbsorb = uint32(currentAbsorb);
bool defaultPrevented = false;
absorbAurEff->GetBase()->CallScriptEffectAbsorbHandlers(absorbAurEff, aurApp, damageInfo, tempAbsorb, defaultPrevented);
currentAbsorb = tempAbsorb;
if (!defaultPrevented)
{
// absorb must be smaller than the damage itself
currentAbsorb = RoundToInterval(currentAbsorb, 0, int32(damageInfo.GetDamage()));
damageInfo.AbsorbDamage(currentAbsorb);
tempAbsorb = currentAbsorb;
absorbAurEff->GetBase()->CallScriptEffectAfterAbsorbHandlers(absorbAurEff, aurApp, damageInfo, tempAbsorb);
// Check if our aura is using amount to count damage
if (absorbAurEff->GetAmount() >= 0)
{
// Reduce shield amount
absorbAurEff->ChangeAmount(absorbAurEff->GetAmount() - currentAbsorb);
// Aura cannot absorb anything more - remove it
if (absorbAurEff->GetAmount() <= 0)
absorbAurEff->GetBase()->Remove(AURA_REMOVE_BY_ENEMY_SPELL);
}
}
if (!absorbAurEff->GetSpellInfo()->HasAttribute(SPELL_ATTR6_ABSORB_CANNOT_BE_IGNORE))
damageInfo.ModifyDamage(absorbIgnoringDamage);
if (currentAbsorb)
{
WorldPackets::CombatLog::SpellAbsorbLog absorbLog;
absorbLog.Attacker = damageInfo.GetAttacker() ? damageInfo.GetAttacker()->GetGUID() : ObjectGuid::Empty;
absorbLog.Victim = damageInfo.GetVictim()->GetGUID();
absorbLog.Caster = absorbAurEff->GetBase()->GetCasterGUID();
absorbLog.AbsorbedSpellID = damageInfo.GetSpellInfo() ? damageInfo.GetSpellInfo()->Id : 0;
absorbLog.AbsorbSpellID = absorbAurEff->GetId();
absorbLog.Absorbed = currentAbsorb;
absorbLog.OriginalDamage = damageInfo.GetOriginalDamage();
absorbLog.LogData.Initialize(damageInfo.GetVictim());
damageInfo.GetVictim()->SendCombatLogMessage(&absorbLog);
}
}
// absorb by mana cost
AuraEffectVector vManaShieldCopy = CopyAuraEffectList(damageInfo.GetVictim()->GetAuraEffectsByType(SPELL_AURA_MANA_SHIELD));
for (auto itr = vManaShieldCopy.begin(); (itr != vManaShieldCopy.end()) && (damageInfo.GetDamage() > 0); ++itr)
{
AuraEffect* absorbAurEff = *itr;
// Check if aura was removed during iteration - we don't need to work on such auras
AuraApplication const* aurApp = absorbAurEff->GetBase()->GetApplicationOfTarget(damageInfo.GetVictim()->GetGUID());
if (!aurApp)
continue;
// check damage school mask
if (!(absorbAurEff->GetMiscValue() & damageInfo.GetSchoolMask()))
continue;
// get amount which can be still absorbed by the aura
int32 currentAbsorb = absorbAurEff->GetAmount();
// aura with infinite absorb amount - let the scripts handle absorbtion amount, set here to 0 for safety
if (currentAbsorb < 0)
currentAbsorb = 0;
if (!absorbAurEff->GetSpellInfo()->HasAttribute(SPELL_ATTR6_ABSORB_CANNOT_BE_IGNORE))
damageInfo.ModifyDamage(-absorbIgnoringDamage);
uint32 tempAbsorb = currentAbsorb;
bool defaultPrevented = false;
absorbAurEff->GetBase()->CallScriptEffectManaShieldHandlers(absorbAurEff, aurApp, damageInfo, tempAbsorb, defaultPrevented);
currentAbsorb = tempAbsorb;
if (!defaultPrevented)
{
// absorb must be smaller than the damage itself
currentAbsorb = RoundToInterval(currentAbsorb, 0, int32(damageInfo.GetDamage()));
int32 manaReduction = currentAbsorb;
// lower absorb amount by talents
if (float manaMultiplier = absorbAurEff->GetSpellEffectInfo().CalcValueMultiplier(absorbAurEff->GetCaster()))
manaReduction = int32(float(manaReduction) * manaMultiplier);
int32 manaTaken = -damageInfo.GetVictim()->ModifyPower(POWER_MANA, -manaReduction);
// take case when mana has ended up into account
currentAbsorb = currentAbsorb ? int32(float(currentAbsorb) * (float(manaTaken) / float(manaReduction))) : 0;
damageInfo.AbsorbDamage(currentAbsorb);
tempAbsorb = currentAbsorb;
absorbAurEff->GetBase()->CallScriptEffectAfterManaShieldHandlers(absorbAurEff, aurApp, damageInfo, tempAbsorb);
// Check if our aura is using amount to count damage
if (absorbAurEff->GetAmount() >= 0)
{
absorbAurEff->ChangeAmount(absorbAurEff->GetAmount() - currentAbsorb);
if ((absorbAurEff->GetAmount() <= 0))
absorbAurEff->GetBase()->Remove(AURA_REMOVE_BY_ENEMY_SPELL);
}
}
if (!absorbAurEff->GetSpellInfo()->HasAttribute(SPELL_ATTR6_ABSORB_CANNOT_BE_IGNORE))
damageInfo.ModifyDamage(absorbIgnoringDamage);
if (currentAbsorb)
{
WorldPackets::CombatLog::SpellAbsorbLog absorbLog;
absorbLog.Attacker = damageInfo.GetAttacker() ? damageInfo.GetAttacker()->GetGUID() : ObjectGuid::Empty;
absorbLog.Victim = damageInfo.GetVictim()->GetGUID();
absorbLog.Caster = absorbAurEff->GetBase()->GetCasterGUID();
absorbLog.AbsorbedSpellID = damageInfo.GetSpellInfo() ? damageInfo.GetSpellInfo()->Id : 0;
absorbLog.AbsorbSpellID = absorbAurEff->GetId();
absorbLog.Absorbed = currentAbsorb;
absorbLog.OriginalDamage = damageInfo.GetOriginalDamage();
absorbLog.LogData.Initialize(damageInfo.GetVictim());
damageInfo.GetVictim()->SendCombatLogMessage(&absorbLog);
}
}
// split damage auras - only when not damaging self
if (damageInfo.GetVictim() != damageInfo.GetAttacker())
{
// We're going to call functions which can modify content of the list during iteration over it's elements
// Let's copy the list so we can prevent iterator invalidation
AuraEffectVector vSplitDamagePctCopy = CopyAuraEffectList(damageInfo.GetVictim()->GetAuraEffectsByType(SPELL_AURA_SPLIT_DAMAGE_PCT));
for (auto itr = vSplitDamagePctCopy.begin(); itr != vSplitDamagePctCopy.end() && damageInfo.GetDamage() > 0; ++itr)
{
// Check if aura was removed during iteration - we don't need to work on such auras
AuraApplication const* aurApp = (*itr)->GetBase()->GetApplicationOfTarget(damageInfo.GetVictim()->GetGUID());
if (!aurApp)
continue;
// check damage school mask
if (!((*itr)->GetMiscValue() & damageInfo.GetSchoolMask()))
continue;
// Damage can be splitted only if aura has an alive caster
Unit* caster = (*itr)->GetCaster();
if (!caster || (caster == damageInfo.GetVictim()) || !caster->IsInWorld() || !caster->IsAlive())
continue;
uint32 splitDamage = CalculatePct(damageInfo.GetDamage(), (*itr)->GetAmount());
(*itr)->GetBase()->CallScriptEffectSplitHandlers((*itr), aurApp, damageInfo, splitDamage);
// absorb must be smaller than the damage itself
splitDamage = RoundToInterval(splitDamage, uint32(0), uint32(damageInfo.GetDamage()));
damageInfo.AbsorbDamage(splitDamage);
// check if caster is immune to damage
if (caster->IsImmunedToDamage(damageInfo.GetSchoolMask()))
{
damageInfo.GetVictim()->SendSpellMiss(caster, (*itr)->GetSpellInfo()->Id, SPELL_MISS_IMMUNE);
continue;
}
uint32 split_absorb = 0;
Unit::DealDamageMods(damageInfo.GetAttacker(), caster, splitDamage, &split_absorb);
// sparring
if (Creature* victimCreature = damageInfo.GetVictim()->ToCreature())
{
if (victimCreature->ShouldFakeDamageFrom(damageInfo.GetAttacker()))
damageInfo.ModifyDamage(damageInfo.GetDamage() * -1);
}
SpellNonMeleeDamage log(damageInfo.GetAttacker(), caster, (*itr)->GetSpellInfo(), (*itr)->GetBase()->GetSpellVisual(), damageInfo.GetSchoolMask(), (*itr)->GetBase()->GetCastId());
CleanDamage cleanDamage = CleanDamage(splitDamage, 0, BASE_ATTACK, MELEE_HIT_NORMAL);
Unit::DealDamage(damageInfo.GetAttacker(), caster, splitDamage, &cleanDamage, DIRECT_DAMAGE, damageInfo.GetSchoolMask(), (*itr)->GetSpellInfo(), false);
log.damage = splitDamage;
log.originalDamage = splitDamage;
log.absorb = split_absorb;
caster->SendSpellNonMeleeDamageLog(&log);
// break 'Fear' and similar auras
Unit::ProcSkillsAndAuras(damageInfo.GetAttacker(), caster, PROC_FLAG_NONE, PROC_FLAG_TAKE_HARMFUL_SPELL, PROC_SPELL_TYPE_DAMAGE, PROC_SPELL_PHASE_HIT, PROC_HIT_NONE, nullptr, &damageInfo, nullptr);
}
}
}
/*static*/ void Unit::CalcHealAbsorb(HealInfo& healInfo)
{
if (!healInfo.GetHeal())
return;
AuraEffectVector vHealAbsorb = CopyAuraEffectList(healInfo.GetTarget()->GetAuraEffectsByType(SPELL_AURA_SCHOOL_HEAL_ABSORB));
for (auto i = vHealAbsorb.begin(); i != vHealAbsorb.end() && healInfo.GetHeal() > 0; ++i)
{
AuraEffect* absorbAurEff = *i;
// Check if aura was removed during iteration - we don't need to work on such auras
AuraApplication const* aurApp = absorbAurEff->GetBase()->GetApplicationOfTarget(healInfo.GetTarget()->GetGUID());
if (!aurApp)
continue;
if (!(absorbAurEff->GetMiscValue() & healInfo.GetSchoolMask()))
continue;
// get amount which can be still absorbed by the aura
int32 currentAbsorb = absorbAurEff->GetAmount();
// aura with infinite absorb amount - let the scripts handle absorbtion amount, set here to 0 for safety
if (currentAbsorb < 0)
currentAbsorb = 0;
uint32 tempAbsorb = uint32(currentAbsorb);
bool defaultPrevented = false;
absorbAurEff->GetBase()->CallScriptEffectAbsorbHandlers(absorbAurEff, aurApp, healInfo, tempAbsorb, defaultPrevented);
currentAbsorb = tempAbsorb;
if (!defaultPrevented)
{
// absorb must be smaller than the heal itself
currentAbsorb = RoundToInterval(currentAbsorb, 0, int32(healInfo.GetHeal()));
healInfo.AbsorbHeal(currentAbsorb);
tempAbsorb = currentAbsorb;
absorbAurEff->GetBase()->CallScriptEffectAfterAbsorbHandlers(absorbAurEff, aurApp, healInfo, tempAbsorb);
// Check if our aura is using amount to count heal
if (absorbAurEff->GetAmount() >= 0)
{
// Reduce shield amount
absorbAurEff->ChangeAmount(absorbAurEff->GetAmount() - currentAbsorb);
// Aura cannot absorb anything more - remove it
if (absorbAurEff->GetAmount() <= 0)
absorbAurEff->GetBase()->Remove(AURA_REMOVE_BY_ENEMY_SPELL);
}
}
if (currentAbsorb)
{
WorldPackets::CombatLog::SpellHealAbsorbLog absorbLog;
absorbLog.Healer = healInfo.GetHealer() ? healInfo.GetHealer()->GetGUID() : ObjectGuid::Empty;
absorbLog.Target = healInfo.GetTarget()->GetGUID();
absorbLog.AbsorbCaster = absorbAurEff->GetBase()->GetCasterGUID();
absorbLog.AbsorbedSpellID = healInfo.GetSpellInfo() ? healInfo.GetSpellInfo()->Id : 0;
absorbLog.AbsorbSpellID = absorbAurEff->GetId();
absorbLog.Absorbed = currentAbsorb;
absorbLog.OriginalHeal = healInfo.GetOriginalHeal();
healInfo.GetTarget()->SendMessageToSet(absorbLog.Write(), true);
}
}
}
void Unit::DoMeleeAttackIfReady()
{
if (!HasUnitState(UNIT_STATE_MELEE_ATTACKING))
return;
if (HasUnitState(UNIT_STATE_CHARGING))
return;
if (IsCreature() && !ToCreature()->CanMelee())
return;
if (HasUnitState(UNIT_STATE_CASTING))
{
Spell* channeledSpell = GetCurrentSpell(CURRENT_CHANNELED_SPELL);
if (!channeledSpell || !channeledSpell->GetSpellInfo()->HasAttribute(SPELL_ATTR5_ALLOW_ACTIONS_DURING_CHANNEL))
return;
}
Unit* victim = GetVictim();
if (!victim)
return;
auto getAutoAttackError = [&]() -> Optional
{
if (!IsWithinMeleeRange(victim))
return AttackSwingErr::NotInRange;
//120 degrees of radiant range, if player is not in boundary radius
if (!IsWithinBoundaryRadius(victim) && !HasInArc(2 * float(M_PI) / 3, victim))
return AttackSwingErr::BadFacing;
return {};
};
if (isAttackReady(BASE_ATTACK))
{
Optional autoAttackError = getAutoAttackError();
if (!autoAttackError)
{
// prevent base and off attack in same time, delay attack at 0.2 sec
if (haveOffhandWeapon())
if (getAttackTimer(OFF_ATTACK) < ATTACK_DISPLAY_DELAY)
setAttackTimer(OFF_ATTACK, ATTACK_DISPLAY_DELAY);
// do attack
AttackerStateUpdate(victim, BASE_ATTACK);
resetAttackTimer(BASE_ATTACK);
}
else
setAttackTimer(BASE_ATTACK, 100);
if (Player* attackerPlayer = ToPlayer())
attackerPlayer->SetAttackSwingError(autoAttackError);
}
if (!IsInFeralForm() && haveOffhandWeapon() && isAttackReady(OFF_ATTACK))
{
Optional autoAttackError = getAutoAttackError();
if (!autoAttackError)
{
// prevent base and off attack in same time, delay attack at 0.2 sec
if (getAttackTimer(BASE_ATTACK) < ATTACK_DISPLAY_DELAY)
setAttackTimer(BASE_ATTACK, ATTACK_DISPLAY_DELAY);
// do attack
AttackerStateUpdate(victim, OFF_ATTACK);
resetAttackTimer(OFF_ATTACK);
}
else
setAttackTimer(OFF_ATTACK, 100);
}
}
// Calculates the normalized rage amount per weapon swing
static uint32 CalcMeleeAttackRageGain(Unit const* attacker, WeaponAttackType attType)
{
if (!attacker || (attType != BASE_ATTACK && attType != OFF_ATTACK))
return 0;
uint32 rage = uint32(attacker->GetBaseAttackTime(attType) / 1000.f * 1.75f);
if (attType == OFF_ATTACK)
rage /= 2;
return rage;
}
void Unit::AttackerStateUpdate(Unit* victim, WeaponAttackType attType, bool extra)
{
if (HasUnitFlag(UNIT_FLAG_PACIFIED))
return;
if (HasUnitState(UNIT_STATE_CANNOT_AUTOATTACK) && !extra)
return;
if (HasAuraType(SPELL_AURA_DISABLE_ATTACKING_EXCEPT_ABILITIES))
return;
if (!victim->IsAlive())
return;
if ((attType == BASE_ATTACK || attType == OFF_ATTACK) && !IsWithinLOSInMap(victim))
return;
AtTargetAttacked(victim, true);
RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Attacking);
if (attType != BASE_ATTACK && attType != OFF_ATTACK)
return; // ignore ranged case
if (!extra && _lastExtraAttackSpell)
_lastExtraAttackSpell = 0;
// melee attack spell cast at main hand attack only - no normal melee dmg dealt
if (attType == BASE_ATTACK && m_currentSpells[CURRENT_MELEE_SPELL] && !extra)
m_currentSpells[CURRENT_MELEE_SPELL]->cast();
else
{
// attack can be redirected to another target
victim = GetMeleeHitRedirectTarget(victim);
AuraEffectList const& meleeAttackOverrides = GetAuraEffectsByType(SPELL_AURA_OVERRIDE_AUTOATTACK_WITH_MELEE_SPELL);
uint32 meleeAttackSpellId = 0;
if (attType == BASE_ATTACK)
{
if (!meleeAttackOverrides.empty())
meleeAttackSpellId = meleeAttackOverrides.front()->GetSpellEffectInfo().TriggerSpell;
}
else
{
auto itr = std::find_if(meleeAttackOverrides.begin(), meleeAttackOverrides.end(), [&](AuraEffect const* aurEff)
{
return aurEff->GetSpellEffectInfo().MiscValue != 0;
});
if (itr != meleeAttackOverrides.end())
meleeAttackSpellId = (*itr)->GetSpellEffectInfo().MiscValue;
}
if (!meleeAttackSpellId)
{
CalcDamageInfo damageInfo;
CalculateMeleeDamage(victim, &damageInfo, attType);
// Send log damage message to client
Unit::DealDamageMods(damageInfo.Attacker, victim, damageInfo.Damage, &damageInfo.Absorb);
// sparring
if (Creature* victimCreature = victim->ToCreature())
{
if (victimCreature->ShouldFakeDamageFrom(damageInfo.Attacker))
damageInfo.HitInfo |= HITINFO_FAKE_DAMAGE;
}
// Rage reward
if (this != victim && damageInfo.HitOutCome != MELEE_HIT_MISS && GetPowerType() == POWER_RAGE)
{
if (uint32 rageReward = CalcMeleeAttackRageGain(this, attType))
{
damageInfo.HitInfo |= HITINFO_RAGE_GAIN;
damageInfo.RageGained = RewardRage(rageReward);
}
}
SendAttackStateUpdate(&damageInfo);
_lastDamagedTargetGuid = victim->GetGUID();
DealMeleeDamage(&damageInfo, true);
DamageInfo dmgInfo(damageInfo);
Unit::ProcSkillsAndAuras(damageInfo.Attacker, damageInfo.Target, damageInfo.ProcAttacker, damageInfo.ProcVictim, PROC_SPELL_TYPE_NONE, PROC_SPELL_PHASE_NONE, dmgInfo.GetHitMask(), nullptr, &dmgInfo, nullptr);
TC_LOG_DEBUG("entities.unit", "AttackerStateUpdate: {} attacked {} for {} dmg, absorbed {}, blocked {}, resisted {}.",
GetGUID().ToString(), victim->GetGUID().ToString(), damageInfo.Damage, damageInfo.Absorb, damageInfo.Blocked, damageInfo.Resist);
}
else
{
CastSpell(victim, meleeAttackSpellId, true);
uint32 hitInfo = HITINFO_AFFECTS_VICTIM | HITINFO_NO_ANIMATION;
if (attType == OFF_ATTACK)
hitInfo |= HITINFO_OFFHAND;
SendAttackStateUpdate(hitInfo, victim, 0, GetMeleeDamageSchoolMask(), 0, 0, 0, VICTIMSTATE_HIT, 0, 0);
}
}
}
void Unit::HandleProcExtraAttackFor(Unit* victim, uint32 count)
{
while (count)
{
--count;
AttackerStateUpdate(victim, BASE_ATTACK, true);
}
}
void Unit::AddExtraAttacks(uint32 count)
{
ObjectGuid targetGUID = _lastDamagedTargetGuid;
if (!targetGUID)
{
ObjectGuid selection = GetTarget();
if (!selection.IsEmpty())
targetGUID = selection; // Spell was cast directly (not triggered by aura)
else
return;
}
extraAttacksTargets[targetGUID] += count;
}
MeleeHitOutcome Unit::RollMeleeOutcomeAgainst(Unit const* victim, WeaponAttackType attType) const
{
if (victim->GetTypeId() == TYPEID_UNIT && victim->ToCreature()->IsEvadingAttacks())
return MELEE_HIT_EVADE;
// Miss chance based on melee
int32 miss_chance = int32(MeleeSpellMissChance(victim, attType, nullptr) * 100.0f);
// Critical hit chance
int32 crit_chance = int32((GetUnitCriticalChanceAgainst(attType, victim) + GetTotalAuraModifier(SPELL_AURA_MOD_AUTOATTACK_CRIT_CHANCE)) * 100.0f);
int32 dodge_chance = int32(GetUnitDodgeChance(attType, victim) * 100.0f);
int32 block_chance = int32(GetUnitBlockChance(attType, victim) * 100.0f);
int32 parry_chance = int32(GetUnitParryChance(attType, victim) * 100.0f);
// melee attack table implementation
// outcome priority:
// 1. > 2. > 3. > 4. > 5. > 6. > 7. > 8.
// MISS > DODGE > PARRY > GLANCING > BLOCK > CRIT > CRUSHING > HIT
int32 sum = 0, tmp = 0;
int32 roll = urand(0, 9999);
int32 attackerLevel = GetLevelForTarget(victim);
int32 victimLevel = victim->GetLevelForTarget(this);
// check if attack comes from behind, nobody can parry or block if attacker is behind
bool canParryOrBlock = victim->HasInArc(float(M_PI), this) || victim->HasAuraType(SPELL_AURA_IGNORE_HIT_DIRECTION);
// only creatures can dodge if attacker is behind
bool canDodge = victim->GetTypeId() != TYPEID_PLAYER || canParryOrBlock;
// if victim is casting or cc'd it can't avoid attacks
if (victim->IsNonMeleeSpellCast(false, false, true) || victim->HasUnitState(UNIT_STATE_CONTROLLED))
{
canDodge = false;
canParryOrBlock = false;
}
// 1. MISS
tmp = miss_chance;
if (tmp > 0 && roll < (sum += tmp))
return MELEE_HIT_MISS;
// always crit against a sitting target (except 0 crit chance)
if (victim->GetTypeId() == TYPEID_PLAYER && crit_chance > 0 && !victim->IsStandState())
return MELEE_HIT_CRIT;
// 2. DODGE
if (canDodge)
{
tmp = dodge_chance;
if (tmp > 0 // check if unit _can_ dodge
&& roll < (sum += tmp))
return MELEE_HIT_DODGE;
}
// 3. PARRY
if (canParryOrBlock)
{
tmp = parry_chance;
if (tmp > 0 // check if unit _can_ parry
&& roll < (sum += tmp))
return MELEE_HIT_PARRY;
}
// 4. GLANCING
// Max 40% chance to score a glancing blow against mobs that are higher level (can do only players and pets and not with ranged weapon)
if ((GetTypeId() == TYPEID_PLAYER || IsPet()) &&
victim->GetTypeId() != TYPEID_PLAYER && !victim->IsPet() &&
attackerLevel + 3 < victimLevel)
{
// cap possible value (with bonuses > max skill)
tmp = (10 + 10 * (victimLevel - attackerLevel)) * 100;
if (tmp > 0 && roll < (sum += tmp))
return MELEE_HIT_GLANCING;
}
// 5. BLOCK
if (canParryOrBlock)
{
tmp = block_chance;
if (tmp > 0 // check if unit _can_ block
&& roll < (sum += tmp))
return MELEE_HIT_BLOCK;
}
// 6.CRIT
tmp = crit_chance;
if (tmp > 0 && roll < (sum += tmp))
return MELEE_HIT_CRIT;
// 7. CRUSHING
// mobs can score crushing blows if they're 4 or more levels above victim
if (attackerLevel >= victimLevel + 4 &&
// can be from by creature (if can) or from controlled player that considered as creature
!IsControlledByPlayer() &&
!(GetTypeId() == TYPEID_UNIT && ToCreature()->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_NO_CRUSHING_BLOWS))
{
// add 2% chance per level, min. is 15%
tmp = attackerLevel - victimLevel * 1000 - 1500;
if (roll < (sum += tmp))
{
TC_LOG_DEBUG("entities.unit", "RollMeleeOutcomeAgainst: CRUSHING <{}, {})", sum-tmp, sum);
return MELEE_HIT_CRUSHING;
}
}
// 8. HIT
return MELEE_HIT_NORMAL;
}
uint32 Unit::CalculateDamage(WeaponAttackType attType, bool normalized, bool addTotalPct) const
{
float minDamage = 0.0f;
float maxDamage = 0.0f;
if (normalized || !addTotalPct)
{
CalculateMinMaxDamage(attType, normalized, addTotalPct, minDamage, maxDamage);
if (IsInFeralForm() && attType == BASE_ATTACK)
{
float minOffhandDamage = 0.0f;
float maxOffhandDamage = 0.0f;
CalculateMinMaxDamage(OFF_ATTACK, normalized, addTotalPct, minOffhandDamage, maxOffhandDamage);
minDamage += minOffhandDamage;
maxDamage += maxOffhandDamage;
}
}
else
{
switch (attType)
{
case RANGED_ATTACK:
minDamage = m_unitData->MinRangedDamage;
maxDamage = m_unitData->MaxRangedDamage;
break;
case BASE_ATTACK:
minDamage = m_unitData->MinDamage;
maxDamage = m_unitData->MaxDamage;
if (IsInFeralForm())
{
minDamage += m_unitData->MinOffHandDamage;
maxDamage += m_unitData->MaxOffHandDamage;
}
break;
case OFF_ATTACK:
minDamage = m_unitData->MinOffHandDamage;
maxDamage = m_unitData->MaxOffHandDamage;
break;
default:
break;
}
}
minDamage = std::max(0.f, minDamage);
maxDamage = std::max(0.f, maxDamage);
if (minDamage > maxDamage)
std::swap(minDamage, maxDamage);
return urand(uint32(minDamage), uint32(maxDamage));
}
void Unit::SendMeleeAttackStart(Unit* victim)
{
WorldPackets::Combat::AttackStart packet;
packet.Attacker = GetGUID();
packet.Victim = victim->GetGUID();
SendMessageToSet(packet.Write(), true);
}
void Unit::SendMeleeAttackStop(Unit* victim)
{
WorldPackets::Combat::SAttackStop attackStop;
attackStop.Attacker = GetGUID();
if (victim)
{
attackStop.Victim = victim->GetGUID();
attackStop.NowDead = !victim->IsAlive();
}
SendMessageToSet(attackStop.Write(), true);
if (victim)
TC_LOG_DEBUG("entities.unit", "{} stopped attacking {}", GetGUID().ToString(), victim->GetGUID().ToString());
else
TC_LOG_DEBUG("entities.unit", "{} stopped attacking", GetGUID().ToString());
}
bool Unit::IsBlockCritical() const
{
if (roll_chance_i(GetTotalAuraModifier(SPELL_AURA_MOD_BLOCK_CRIT_CHANCE)))
return true;
return false;
}
int32 Unit::GetMechanicResistChance(SpellInfo const* spellInfo) const
{
if (!spellInfo)
return 0;
int32 resistMech = 0;
for (SpellEffectInfo const& effect : spellInfo->GetEffects())
{
if (!effect.IsEffect())
break;
int32 effectMech = spellInfo->GetEffectMechanic(effect.EffectIndex);
if (effectMech)
{
int32 temp = GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_MECHANIC_RESISTANCE, effectMech);
if (resistMech < temp)
resistMech = temp;
}
}
return std::max(resistMech, 0);
}
bool Unit::CanUseAttackType(uint8 attacktype) const
{
switch (attacktype)
{
case BASE_ATTACK:
return !HasUnitFlag(UNIT_FLAG_DISARMED);
case OFF_ATTACK:
return !HasUnitFlag2(UNIT_FLAG2_DISARM_OFFHAND);
case RANGED_ATTACK:
return !HasUnitFlag2(UNIT_FLAG2_DISARM_RANGED);
default:
return true;
}
}
// Melee based spells hit result calculations
SpellMissInfo Unit::MeleeSpellHitResult(Unit* victim, SpellInfo const* spellInfo) const
{
if (spellInfo->HasAttribute(SPELL_ATTR3_NO_AVOIDANCE))
return SPELL_MISS_NONE;
WeaponAttackType attType = BASE_ATTACK;
// Check damage class instead of attack type to correctly handle judgements
// - they are meele, but can't be dodged/parried/deflected because of ranged dmg class
if (spellInfo->DmgClass == SPELL_DAMAGE_CLASS_RANGED)
attType = RANGED_ATTACK;
uint32 roll = urand(0, 9999);
uint32 missChance = uint32(MeleeSpellMissChance(victim, attType, spellInfo) * 100.0f);
// Roll miss
uint32 tmp = missChance;
if (roll < tmp)
return SPELL_MISS_MISS;
// Chance resist mechanic
int32 resist_chance = victim->GetMechanicResistChance(spellInfo) * 100;
tmp += resist_chance;
if (roll < tmp)
return SPELL_MISS_RESIST;
// Same spells cannot be parried/dodged
if (spellInfo->HasAttribute(SPELL_ATTR0_NO_ACTIVE_DEFENSE))
return SPELL_MISS_NONE;
bool canDodge = !spellInfo->HasAttribute(SPELL_ATTR7_NO_ATTACK_DODGE);
bool canParry = !spellInfo->HasAttribute(SPELL_ATTR7_NO_ATTACK_PARRY);
bool canBlock = !spellInfo->HasAttribute(SPELL_ATTR8_NO_ATTACK_BLOCK);
// if victim is casting or cc'd it can't avoid attacks
if (victim->IsNonMeleeSpellCast(false, false, true) || victim->HasUnitState(UNIT_STATE_CONTROLLED))
{
canDodge = false;
canParry = false;
canBlock = false;
}
// Ranged attacks can only miss, resist and deflect and get blocked
if (attType == RANGED_ATTACK)
{
canParry = false;
canDodge = false;
// only if in front
if (!victim->HasUnitState(UNIT_STATE_CONTROLLED) && (victim->HasInArc(float(M_PI), this) || victim->HasAuraType(SPELL_AURA_IGNORE_HIT_DIRECTION)))
{
int32 deflect_chance = victim->GetTotalAuraModifier(SPELL_AURA_DEFLECT_SPELLS) * 100;
tmp += deflect_chance;
if (roll < tmp)
return SPELL_MISS_DEFLECT;
}
}
// Check for attack from behind
if (!victim->HasInArc(float(M_PI), this))
{
if (!victim->HasAuraType(SPELL_AURA_IGNORE_HIT_DIRECTION))
{
// Can't dodge from behind in PvP (but its possible in PvE)
if (victim->GetTypeId() == TYPEID_PLAYER)
canDodge = false;
// Can't parry or block
canParry = false;
canBlock = false;
}
else // Only deterrence as of 3.3.5
{
if (spellInfo->HasAttribute(SPELL_ATTR0_CU_REQ_CASTER_BEHIND_TARGET))
canParry = false;
}
}
// Ignore combat result aura
AuraEffectList const& ignore = GetAuraEffectsByType(SPELL_AURA_IGNORE_COMBAT_RESULT);
for (AuraEffect const* aurEff : ignore)
{
if (!aurEff->IsAffectingSpell(spellInfo))
continue;
switch (aurEff->GetMiscValue())
{
case MELEE_HIT_DODGE:
canDodge = false;
break;
case MELEE_HIT_BLOCK:
canBlock = false;
break;
case MELEE_HIT_PARRY:
canParry = false;
break;
default:
TC_LOG_DEBUG("entities.unit", "Spell {} SPELL_AURA_IGNORE_COMBAT_RESULT has unhandled state {}", aurEff->GetId(), aurEff->GetMiscValue());
break;
}
}
if (canDodge)
{
// Roll dodge
int32 dodgeChance = int32(GetUnitDodgeChance(attType, victim) * 100.0f);
if (dodgeChance < 0)
dodgeChance = 0;
if (roll < (tmp += dodgeChance))
return SPELL_MISS_DODGE;
}
if (canParry)
{
// Roll parry
int32 parryChance = int32(GetUnitParryChance(attType, victim) * 100.0f);
if (parryChance < 0)
parryChance = 0;
tmp += parryChance;
if (roll < tmp)
return SPELL_MISS_PARRY;
}
if (canBlock)
{
int32 blockChance = int32(GetUnitBlockChance(attType, victim) * 100.0f);
if (blockChance < 0)
blockChance = 0;
tmp += blockChance;
if (roll < tmp)
return SPELL_MISS_BLOCK;
}
return SPELL_MISS_NONE;
}
float Unit::GetUnitDodgeChance(WeaponAttackType attType, Unit const* victim) const
{
int32 const levelDiff = victim->GetLevelForTarget(this) - GetLevelForTarget(victim);
float chance = 0.0f;
float levelBonus = 0.0f;
if (Player const* playerVictim = victim->ToPlayer())
chance = playerVictim->m_activePlayerData->DodgePercentage;
else
{
if (!victim->IsTotem())
{
chance = 3.0f;
chance += victim->GetTotalAuraModifier(SPELL_AURA_MOD_DODGE_PERCENT);
if (levelDiff > 0)
levelBonus = 1.5f * levelDiff;
}
}
chance += levelBonus;
// Reduce enemy dodge chance by SPELL_AURA_MOD_COMBAT_RESULT_CHANCE
chance += GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_COMBAT_RESULT_CHANCE, VICTIMSTATE_DODGE);
// reduce dodge by SPELL_AURA_MOD_ENEMY_DODGE
chance += GetTotalAuraModifier(SPELL_AURA_MOD_ENEMY_DODGE);
// Reduce dodge chance by attacker expertise rating
if (GetTypeId() == TYPEID_PLAYER)
chance -= ToPlayer()->GetExpertiseDodgeOrParryReduction(attType);
else
chance -= GetTotalAuraModifier(SPELL_AURA_MOD_EXPERTISE) / 4.0f;
return std::max(chance, 0.0f);
}
float Unit::GetUnitParryChance(WeaponAttackType attType, Unit const* victim) const
{
int32 const levelDiff = victim->GetLevelForTarget(this) - GetLevelForTarget(victim);
float chance = 0.0f;
float levelBonus = 0.0f;
if (Player const* playerVictim = victim->ToPlayer())
{
if (playerVictim->CanParry())
{
Item* tmpitem = playerVictim->GetWeaponForAttack(BASE_ATTACK, true);
if (!tmpitem)
tmpitem = playerVictim->GetWeaponForAttack(OFF_ATTACK, true);
if (tmpitem)
chance = playerVictim->m_activePlayerData->ParryPercentage;
}
}
else
{
if (!victim->IsTotem() && !(victim->ToCreature()->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_NO_PARRY))
{
chance = 6.0f;
chance += victim->GetTotalAuraModifier(SPELL_AURA_MOD_PARRY_PERCENT);
if (levelDiff > 0)
levelBonus = 1.5f * levelDiff;
}
}
chance += levelBonus;
// Reduce parry chance by attacker expertise rating
if (GetTypeId() == TYPEID_PLAYER)
chance -= ToPlayer()->GetExpertiseDodgeOrParryReduction(attType);
else
chance -= GetTotalAuraModifier(SPELL_AURA_MOD_EXPERTISE) / 4.0f;
return std::max(chance, 0.0f);
}
float Unit::GetUnitMissChance() const
{
float miss_chance = 5.0f;
return miss_chance;
}
float Unit::GetUnitBlockChance(WeaponAttackType /*attType*/, Unit const* victim) const
{
int32 const levelDiff = victim->GetLevelForTarget(this) - GetLevelForTarget(victim);
float chance = 0.0f;
float levelBonus = 0.0f;
if (Player const* playerVictim = victim->ToPlayer())
{
if (playerVictim->CanBlock())
{
Item* tmpitem = playerVictim->GetUseableItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND);
if (tmpitem && !tmpitem->IsBroken() && tmpitem->GetTemplate()->GetInventoryType() == INVTYPE_SHIELD)
chance = playerVictim->m_activePlayerData->BlockPercentage;
}
}
else
{
if (!victim->IsTotem() && !(victim->ToCreature()->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_NO_BLOCK))
{
chance = 3.0f;
chance += victim->GetTotalAuraModifier(SPELL_AURA_MOD_BLOCK_PERCENT);
if (levelDiff > 0)
levelBonus = 1.5f * levelDiff;
}
}
chance += levelBonus;
return std::max(chance, 0.0f);
}
float Unit::GetUnitCriticalChanceDone(WeaponAttackType attackType) const
{
float chance = 0.0f;
if (Player const* thisPlayer = ToPlayer())
{
switch (attackType)
{
case BASE_ATTACK:
chance = thisPlayer->m_activePlayerData->CritPercentage;
break;
case OFF_ATTACK:
chance = thisPlayer->m_activePlayerData->OffhandCritPercentage;
break;
case RANGED_ATTACK:
chance = thisPlayer->m_activePlayerData->RangedCritPercentage;
break;
// Just for good manner
default:
chance = 0.0f;
break;
}
}
else
{
if (!(ToCreature()->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_NO_CRIT))
{
chance = 5.0f;
chance += GetTotalAuraModifier(SPELL_AURA_MOD_WEAPON_CRIT_PERCENT);
chance += GetTotalAuraModifier(SPELL_AURA_MOD_CRIT_PCT);
}
}
return chance;
}
float Unit::GetUnitCriticalChanceTaken(Unit const* attacker, WeaponAttackType attackType, float critDone) const
{
float chance = critDone;
// flat aura mods
if (attackType != RANGED_ATTACK)
chance += GetTotalAuraModifier(SPELL_AURA_MOD_ATTACKER_MELEE_CRIT_CHANCE);
chance += GetTotalAuraModifier(SPELL_AURA_MOD_CRIT_CHANCE_VERSUS_TARGET_HEALTH, [this](AuraEffect const* aurEff)
{
return !HealthBelowPct(aurEff->GetMiscValueB());
});
chance += GetTotalAuraModifier(SPELL_AURA_MOD_CRIT_CHANCE_FOR_CASTER, [attacker](AuraEffect const* aurEff) -> bool
{
return aurEff->GetCasterGUID() == attacker->GetGUID();
});
if (TempSummon const* tempSummon = attacker->ToTempSummon())
{
chance += GetTotalAuraModifier(SPELL_AURA_MOD_CRIT_CHANCE_FOR_CASTER_PET, [tempSummon](AuraEffect const* aurEff) -> bool
{
return aurEff->GetCasterGUID() == tempSummon->GetSummonerGUID();
});
}
chance += GetTotalAuraModifier(SPELL_AURA_MOD_ATTACKER_SPELL_AND_WEAPON_CRIT_CHANCE);
return std::max(chance, 0.0f);
}
float Unit::GetUnitCriticalChanceAgainst(WeaponAttackType attackType, Unit const* victim) const
{
float chance = GetUnitCriticalChanceDone(attackType);
return victim->GetUnitCriticalChanceTaken(this, attackType, chance);
}
void Unit::_DeleteRemovedAuras()
{
while (!m_removedAuras.empty())
{
delete m_removedAuras.front();
m_removedAuras.pop_front();
}
m_removedAurasCount = 0;
}
void Unit::_UpdateSpells(uint32 time)
{
if (!_spellHistory->IsPaused())
_spellHistory->Update();
if (m_currentSpells[CURRENT_AUTOREPEAT_SPELL])
_UpdateAutoRepeatSpell();
// remove finished spells from current pointers
for (Spell*& spell : m_currentSpells)
{
if (spell && spell->getState() == SPELL_STATE_FINISHED)
{
spell->SetReferencedFromCurrent(false);
spell = nullptr; // remove pointer
}
}
// m_auraUpdateIterator can be updated in indirect called code at aura remove to skip next planned to update but removed auras
for (m_auraUpdateIterator = m_ownedAuras.begin(); m_auraUpdateIterator != m_ownedAuras.end();)
{
Aura* i_aura = m_auraUpdateIterator->second;
++m_auraUpdateIterator; // need shift to next for allow update if need into aura update
i_aura->UpdateOwner(time, this);
}
// remove expired auras - do that after updates(used in scripts?)
for (AuraMap::iterator i = m_ownedAuras.begin(); i != m_ownedAuras.end();)
{
if (i->second->IsExpired())
RemoveOwnedAura(i, AURA_REMOVE_BY_EXPIRE);
else if (i->second->GetSpellInfo()->IsChanneled() && i->second->GetCasterGUID() != GetGUID() && !ObjectAccessor::GetWorldObject(*this, i->second->GetCasterGUID()))
RemoveOwnedAura(i, AURA_REMOVE_BY_CANCEL); // remove channeled auras when caster is not on the same map
else
++i;
}
for (AuraApplication* visibleAura : m_visibleAurasToUpdate)
visibleAura->ClientUpdate();
m_visibleAurasToUpdate.clear();
_DeleteRemovedAuras();
if (!m_gameObj.empty())
{
GameObjectList::iterator itr;
for (itr = m_gameObj.begin(); itr != m_gameObj.end();)
{
if (!(*itr)->isSpawned())
{
(*itr)->SetOwnerGUID(ObjectGuid::Empty);
(*itr)->SetRespawnTime(0);
(*itr)->Delete();
m_gameObj.erase(itr++);
}
else
++itr;
}
}
}
void Unit::_UpdateAutoRepeatSpell()
{
SpellInfo const* autoRepeatSpellInfo = m_currentSpells[CURRENT_AUTOREPEAT_SPELL]->m_spellInfo;
// check "realtime" interrupts
// don't cancel spells which are affected by a SPELL_AURA_CAST_WHILE_WALKING effect
if ((isMoving() && m_currentSpells[CURRENT_AUTOREPEAT_SPELL]->CheckMovement() != SPELL_CAST_OK) || IsNonMeleeSpellCast(false, false, true, autoRepeatSpellInfo->Id == 75))
{
// cancel wand shoot
if (autoRepeatSpellInfo->Id != 75)
InterruptSpell(CURRENT_AUTOREPEAT_SPELL);
return;
}
// castroutine
if (isAttackReady(RANGED_ATTACK) && m_currentSpells[CURRENT_AUTOREPEAT_SPELL]->getState() != SPELL_STATE_PREPARING)
{
// Check if able to cast
SpellCastResult result = m_currentSpells[CURRENT_AUTOREPEAT_SPELL]->CheckCast(true);
if (result != SPELL_CAST_OK)
{
if (autoRepeatSpellInfo->Id != 75)
InterruptSpell(CURRENT_AUTOREPEAT_SPELL);
else if (GetTypeId() == TYPEID_PLAYER)
Spell::SendCastResult(ToPlayer(), autoRepeatSpellInfo, m_currentSpells[CURRENT_AUTOREPEAT_SPELL]->m_SpellVisual, m_currentSpells[CURRENT_AUTOREPEAT_SPELL]->m_castId, result);
return;
}
// we want to shoot
Spell* spell = new Spell(this, autoRepeatSpellInfo, TRIGGERED_IGNORE_GCD);
spell->prepare(m_currentSpells[CURRENT_AUTOREPEAT_SPELL]->m_targets);
}
}
void Unit::AddChannelObject(ObjectGuid guid)
{
AddDynamicUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::ChannelObjects)) = guid;
}
void Unit::SetChannelObject(uint32 slot, ObjectGuid guid)
{
SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::ChannelObjects, slot), guid);
}
void Unit::RemoveChannelObject(ObjectGuid guid)
{
int32 index = m_unitData->ChannelObjects.FindIndex(guid);
if (index >= 0)
RemoveDynamicUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::ChannelObjects), index);
}
void Unit::ClearChannelObjects()
{
ClearDynamicUpdateFieldValues(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::ChannelObjects));
}
void Unit::SetCurrentCastSpell(Spell* pSpell)
{
ASSERT(pSpell); // NULL may be never passed here, use InterruptSpell or InterruptNonMeleeSpells
CurrentSpellTypes CSpellType = pSpell->GetCurrentContainer();
if (pSpell == m_currentSpells[CSpellType]) // avoid breaking self
return;
// special breakage effects:
switch (CSpellType)
{
case CURRENT_GENERIC_SPELL:
{
InterruptSpell(CURRENT_GENERIC_SPELL, false);
// generic spells always break channeled not delayed spells
if (m_currentSpells[CURRENT_CHANNELED_SPELL]
&& !m_currentSpells[CURRENT_CHANNELED_SPELL]->GetSpellInfo()->HasAttribute(SPELL_ATTR5_ALLOW_ACTIONS_DURING_CHANNEL)
&& !pSpell->GetSpellInfo()->HasAttribute(SPELL_ATTR9_ALLOW_CAST_WHILE_CHANNELING))
InterruptSpell(CURRENT_CHANNELED_SPELL, false);
// autorepeat breaking
if (m_currentSpells[CURRENT_AUTOREPEAT_SPELL])
{
// break autorepeat if not Auto Shot
if (m_currentSpells[CURRENT_AUTOREPEAT_SPELL]->GetSpellInfo()->Id != 75)
InterruptSpell(CURRENT_AUTOREPEAT_SPELL);
}
if (pSpell->GetCastTime() > 0)
AddUnitState(UNIT_STATE_CASTING);
break;
}
case CURRENT_CHANNELED_SPELL:
{
// channel spells always break generic non-delayed and any channeled spells
InterruptSpell(CURRENT_GENERIC_SPELL, false);
InterruptSpell(CURRENT_CHANNELED_SPELL);
// it also does break autorepeat if not Auto Shot
if (m_currentSpells[CURRENT_AUTOREPEAT_SPELL] &&
m_currentSpells[CURRENT_AUTOREPEAT_SPELL]->GetSpellInfo()->Id != 75)
InterruptSpell(CURRENT_AUTOREPEAT_SPELL);
AddUnitState(UNIT_STATE_CASTING);
break;
}
case CURRENT_AUTOREPEAT_SPELL:
{
if (m_currentSpells[CSpellType] && m_currentSpells[CSpellType]->getState() == SPELL_STATE_IDLE)
m_currentSpells[CSpellType]->setState(SPELL_STATE_FINISHED);
// only Auto Shoot does not break anything
if (pSpell->GetSpellInfo()->Id != 75)
{
// generic autorepeats break generic non-delayed and channeled non-delayed spells
InterruptSpell(CURRENT_GENERIC_SPELL, false);
InterruptSpell(CURRENT_CHANNELED_SPELL, false);
}
break;
}
default:
break; // other spell types don't break anything now
}
// current spell (if it is still here) may be safely deleted now
if (m_currentSpells[CSpellType])
m_currentSpells[CSpellType]->SetReferencedFromCurrent(false);
// set new current spell
m_currentSpells[CSpellType] = pSpell;
pSpell->SetReferencedFromCurrent(true);
pSpell->m_selfContainer = &(m_currentSpells[pSpell->GetCurrentContainer()]);
}
void Unit::InterruptSpell(CurrentSpellTypes spellType, bool withDelayed, bool withInstant)
{
//TC_LOG_DEBUG("entities.unit", "Interrupt spell for unit {}.", GetEntry());
Spell* spell = m_currentSpells[spellType];
if (spell
&& (withDelayed || spell->getState() != SPELL_STATE_LAUNCHED)
&& (withInstant || spell->GetCastTime() > 0 || spell->getState() == SPELL_STATE_CHANNELING))
{
// for example, do not let self-stun aura interrupt itself
if (!spell->IsInterruptable())
return;
// send autorepeat cancel message for autorepeat spells
if (spellType == CURRENT_AUTOREPEAT_SPELL)
if (GetTypeId() == TYPEID_PLAYER)
ToPlayer()->SendAutoRepeatCancel(this);
if (spell->getState() != SPELL_STATE_FINISHED)
spell->cancel();
else
{
m_currentSpells[spellType] = nullptr;
spell->SetReferencedFromCurrent(false);
}
if (GetTypeId() == TYPEID_UNIT && IsAIEnabled())
ToCreature()->AI()->OnSpellFailed(spell->GetSpellInfo());
}
}
void Unit::FinishSpell(CurrentSpellTypes spellType, SpellCastResult result /*= SPELL_CAST_OK*/)
{
Spell* spell = m_currentSpells[spellType];
if (!spell)
return;
if (spellType == CURRENT_CHANNELED_SPELL)
spell->SendChannelUpdate(0, result);
spell->finish(result);
}
bool Unit::IsNonMeleeSpellCast(bool withDelayed, bool skipChanneled, bool skipAutorepeat, bool isAutoshoot, bool skipInstant) const
{
// We don't do loop here to explicitly show that melee spell is excluded.
// Maybe later some special spells will be excluded too.
// generic spells are cast when they are not finished and not delayed
if (m_currentSpells[CURRENT_GENERIC_SPELL] &&
(m_currentSpells[CURRENT_GENERIC_SPELL]->getState() != SPELL_STATE_FINISHED) &&
(withDelayed || m_currentSpells[CURRENT_GENERIC_SPELL]->getState() != SPELL_STATE_LAUNCHED))
{
if (!skipInstant || m_currentSpells[CURRENT_GENERIC_SPELL]->GetCastTime())
{
if (!isAutoshoot || !(m_currentSpells[CURRENT_GENERIC_SPELL]->m_spellInfo->HasAttribute(SPELL_ATTR2_DO_NOT_RESET_COMBAT_TIMERS)))
return true;
}
}
// channeled spells may be delayed, but they are still considered cast
if (!skipChanneled && m_currentSpells[CURRENT_CHANNELED_SPELL] &&
(m_currentSpells[CURRENT_CHANNELED_SPELL]->getState() != SPELL_STATE_FINISHED))
{
if (!isAutoshoot || !(m_currentSpells[CURRENT_CHANNELED_SPELL]->m_spellInfo->HasAttribute(SPELL_ATTR2_DO_NOT_RESET_COMBAT_TIMERS)))
return true;
}
// autorepeat spells may be finished or delayed, but they are still considered cast
if (!skipAutorepeat && m_currentSpells[CURRENT_AUTOREPEAT_SPELL])
return true;
return false;
}
void Unit::InterruptNonMeleeSpells(bool withDelayed, uint32 spell_id, bool withInstant)
{
// generic spells are interrupted if they are not finished or delayed
if (m_currentSpells[CURRENT_GENERIC_SPELL] && (!spell_id || m_currentSpells[CURRENT_GENERIC_SPELL]->m_spellInfo->Id == spell_id))
InterruptSpell(CURRENT_GENERIC_SPELL, withDelayed, withInstant);
// autorepeat spells are interrupted if they are not finished or delayed
if (m_currentSpells[CURRENT_AUTOREPEAT_SPELL] && (!spell_id || m_currentSpells[CURRENT_AUTOREPEAT_SPELL]->m_spellInfo->Id == spell_id))
InterruptSpell(CURRENT_AUTOREPEAT_SPELL, withDelayed, withInstant);
// channeled spells are interrupted if they are not finished, even if they are delayed
if (m_currentSpells[CURRENT_CHANNELED_SPELL] && (!spell_id || m_currentSpells[CURRENT_CHANNELED_SPELL]->m_spellInfo->Id == spell_id))
InterruptSpell(CURRENT_CHANNELED_SPELL, true, true);
}
Spell* Unit::FindCurrentSpellBySpellId(uint32 spell_id) const
{
for (Spell* spell : m_currentSpells)
if (spell && spell->m_spellInfo->Id == spell_id)
return spell;
return nullptr;
}
int32 Unit::GetCurrentSpellCastTime(uint32 spell_id) const
{
if (Spell const* spell = FindCurrentSpellBySpellId(spell_id))
return spell->GetCastTime();
return 0;
}
bool Unit::IsMovementPreventedByCasting() const
{
// can always move when not casting
if (!HasUnitState(UNIT_STATE_CASTING))
return false;
if (Spell* spell = m_currentSpells[CURRENT_GENERIC_SPELL])
if (CanCastSpellWhileMoving(spell->GetSpellInfo()) || spell->getState() == SPELL_STATE_FINISHED ||
!spell->m_spellInfo->InterruptFlags.HasFlag(SpellInterruptFlags::Movement))
return false;
// channeled spells during channel stage (after the initial cast timer) allow movement with a specific spell attribute
if (Spell* spell = m_currentSpells[CURRENT_CHANNELED_SPELL])
if (spell->getState() != SPELL_STATE_FINISHED && spell->IsChannelActive())
if (spell->GetSpellInfo()->IsMoveAllowedChannel() || CanCastSpellWhileMoving(spell->GetSpellInfo()))
return false;
// prohibit movement for all other spell casts
return true;
}
bool Unit::CanCastSpellWhileMoving(SpellInfo const* spellInfo) const
{
if (spellInfo->HasAttribute(SPELL_ATTR13_DO_NOT_ALLOW_DISABLE_MOVEMENT_INTERRUPT))
return false;
if (HasAuraTypeWithAffectMask(SPELL_AURA_CAST_WHILE_WALKING, spellInfo))
return true;
if (HasAuraType(SPELL_AURA_CAST_WHILE_WALKING_ALL))
return true;
for (uint32 label : spellInfo->Labels)
if (HasAuraTypeWithMiscvalue(SPELL_AURA_CAST_WHILE_WALKING_BY_SPELL_LABEL, label))
return true;
return false;
}
bool Unit::isInFrontInMap(Unit const* target, float distance, float arc) const
{
return IsWithinDistInMap(target, distance) && HasInArc(arc, target);
}
bool Unit::isInBackInMap(Unit const* target, float distance, float arc) const
{
return IsWithinDistInMap(target, distance) && !HasInArc(2 * float(M_PI) - arc, target);
}
bool Unit::isInAccessiblePlaceFor(Creature const* c) const
{
// Aquatic creatures are not allowed to leave liquids
if (!IsInWater() && c->IsAquatic())
return false;
// Underwater special case. Some creatures may not go below liquid surfaces
if (IsUnderWater() && c->CannotPenetrateWater())
return false;
// Water checks
if (IsInWater() && !c->CanEnterWater())
return false;
// Some creatures are tied to the ocean floor and cannot chase swimming targets.
if (!IsOnOceanFloor() && c->IsUnderWater() && c->HasUnitFlag(UNIT_FLAG_CANT_SWIM))
return false;
return true;
}
bool Unit::IsInWater() const
{
return GetLiquidStatus() & (LIQUID_MAP_IN_WATER | LIQUID_MAP_UNDER_WATER);
}
bool Unit::IsUnderWater() const
{
return GetLiquidStatus() & LIQUID_MAP_UNDER_WATER;
}
bool Unit::IsOnOceanFloor() const
{
return GetLiquidStatus() & LIQUID_MAP_OCEAN_FLOOR;
}
void Unit::ProcessPositionDataChanged(PositionFullTerrainStatus const& data)
{
ZLiquidStatus oldLiquidStatus = GetLiquidStatus();
WorldObject::ProcessPositionDataChanged(data);
ProcessTerrainStatusUpdate(oldLiquidStatus, data.liquidInfo);
SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::CurrentAreaID), data.areaId);
}
void Unit::ProcessTerrainStatusUpdate(ZLiquidStatus oldLiquidStatus, Optional const& newLiquidData)
{
if (!IsControlledByPlayer())
return;
// remove appropriate auras if we are swimming/not swimming respectively
if (IsInWater())
RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::UnderWater);
else
RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::AboveWater);
// liquid aura handling
LiquidTypeEntry const* curLiquid = nullptr;
if (IsInWater() && newLiquidData)
curLiquid = sLiquidTypeStore.LookupEntry(newLiquidData->entry);
if (curLiquid != _lastLiquid)
{
if (_lastLiquid && _lastLiquid->SpellID)
RemoveAurasDueToSpell(_lastLiquid->SpellID);
Player* player = GetCharmerOrOwnerPlayerOrPlayerItself();
// Set _lastLiquid before casting liquid spell to avoid infinite loops
_lastLiquid = curLiquid;
if (curLiquid && curLiquid->SpellID && (!player || !player->IsGameMaster()))
CastSpell(this, curLiquid->SpellID, true);
}
// mount capability depends on liquid state change
if (oldLiquidStatus != GetLiquidStatus())
UpdateMountCapability();
}
void Unit::DeMorph()
{
SetDisplayId(GetNativeDisplayId());
}
Aura* Unit::_TryStackingOrRefreshingExistingAura(AuraCreateInfo& createInfo)
{
ASSERT(!createInfo.CasterGUID.IsEmpty() || createInfo.Caster);
// Check if these can stack anyway
if (!createInfo.CasterGUID && !createInfo.GetSpellInfo()->IsStackableOnOneSlotWithDifferentCasters())
createInfo.CasterGUID = createInfo.Caster->GetGUID();
// passive and Incanter's Absorption and auras with different type can stack with themselves any number of times
if (!createInfo.GetSpellInfo()->IsMultiSlotAura())
{
// check if cast item changed
ObjectGuid castItemGUID = createInfo.CastItemGUID;
// find current aura from spell and change it's stackamount, or refresh it's duration
if (Aura* foundAura = GetOwnedAura(createInfo.GetSpellInfo()->Id, createInfo.GetSpellInfo()->IsStackableOnOneSlotWithDifferentCasters() ? ObjectGuid::Empty : createInfo.CasterGUID, createInfo.GetSpellInfo()->HasAttribute(SPELL_ATTR0_CU_ENCHANT_PROC) ? castItemGUID : ObjectGuid::Empty, 0))
{
// effect masks do not match
// extremely rare case
// let's just recreate aura
if (createInfo.GetAuraEffectMask() != foundAura->GetEffectMask())
return nullptr;
// update basepoints with new values - effect amount will be recalculated in ModStackAmount
for (SpellEffectInfo const& spellEffectInfo : createInfo.GetSpellInfo()->GetEffects())
{
AuraEffect const* auraEff = foundAura->GetEffect(spellEffectInfo.EffectIndex);
if (!auraEff)
continue;
int32 bp;
if (createInfo.BaseAmount)
bp = *(createInfo.BaseAmount + spellEffectInfo.EffectIndex);
else
bp = int32(spellEffectInfo.BasePoints);
int32* oldBP = const_cast(&(auraEff->m_baseAmount));
if (spellEffectInfo.EffectAttributes.HasFlag(SpellEffectAttributes::AuraPointsStack))
*oldBP += bp;
else
*oldBP = bp;
}
// correct cast item guid if needed
if (castItemGUID != foundAura->GetCastItemGUID())
{
ObjectGuid* oldGUID = const_cast(&foundAura->m_castItemGuid);
*oldGUID = castItemGUID;
uint32* oldItemId = const_cast(&foundAura->m_castItemId);
*oldItemId = createInfo.CastItemId;
int32* oldItemLevel = const_cast(&foundAura->m_castItemLevel);
*oldItemLevel = createInfo.CastItemLevel;
}
// try to increase stack amount
foundAura->ModStackAmount(createInfo.StackAmount, AURA_REMOVE_BY_DEFAULT, createInfo.ResetPeriodicTimer);
return foundAura;
}
}
return nullptr;
}
void Unit::_AddAura(UnitAura* aura, Unit* caster)
{
ASSERT(!m_cleanupDone);
m_ownedAuras.emplace(aura->GetId(), aura);
_RemoveNoStackAurasDueToAura(aura, true);
if (aura->IsRemoved())
return;
aura->SetIsSingleTarget(caster && aura->GetSpellInfo()->IsSingleTarget());
if (aura->IsSingleTarget())
{
ASSERT((IsInWorld() && !IsDuringRemoveFromWorld()) || aura->GetCasterGUID() == GetGUID());
/* @HACK: Player is not in world during loading auras.
* Single target auras are not saved or loaded from database
* but may be created as a result of aura links.
*/
std::vector aurasSharingLimit;
// remove other single target auras
for (Aura* scAura : caster->GetSingleCastAuras())
if (scAura->IsSingleTargetWith(aura))
aurasSharingLimit.push_back(scAura);
// register single target aura
caster->GetSingleCastAuras().push_front(aura);
uint32 maxOtherAuras = aura->GetSpellInfo()->MaxAffectedTargets - 1;
while (aurasSharingLimit.size() > maxOtherAuras)
{
aurasSharingLimit.back()->Remove();
aurasSharingLimit.pop_back();
}
}
}
// creates aura application instance and registers it in lists
// aura application effects are handled separately to prevent aura list corruption
AuraApplication* Unit::_CreateAuraApplication(Aura* aura, uint32 effMask)
{
// can't apply aura on unit which is going to be deleted - to not create a memory leak
ASSERT(!m_cleanupDone);
// just return if the aura has been already removed
// this can happen if OnEffectHitTarget() script hook killed the unit or the aura owner (which can be different)
if (aura->IsRemoved())
{
TC_LOG_ERROR("spells", "Unit::_CreateAuraApplication() called with a removed aura. Check if OnEffectHitTarget() is triggering any spell with apply aura effect (that's not allowed!)\nUnit: {}\nAura: {}", GetDebugInfo(), aura->GetDebugInfo());
return nullptr;
}
// aura mustn't be already applied on target
ASSERT (!aura->IsAppliedOnTarget(GetGUID()) && "Unit::_CreateAuraApplication: aura musn't be applied on target");
SpellInfo const* aurSpellInfo = aura->GetSpellInfo();
uint32 aurId = aurSpellInfo->Id;
// ghost spell check, allow apply any auras at player loading in ghost mode (will be cleanup after load)
if (!IsAlive() && !aurSpellInfo->IsDeathPersistent() &&
(GetTypeId() != TYPEID_PLAYER || !ToPlayer()->GetSession()->PlayerLoading()))
return nullptr;
Unit* caster = aura->GetCaster();
AuraApplication * aurApp = new AuraApplication(this, caster, aura, effMask);
m_appliedAuras.insert(AuraApplicationMap::value_type(aurId, aurApp));
if (aurSpellInfo->HasAnyAuraInterruptFlag())
{
m_interruptableAuras.push_front(aurApp);
AddInterruptMask(aurSpellInfo->AuraInterruptFlags, aurSpellInfo->AuraInterruptFlags2);
}
if (AuraStateType aState = aura->GetSpellInfo()->GetAuraState())
m_auraStateAuras.insert(AuraStateAurasMap::value_type(aState, aurApp));
aura->_ApplyForTarget(this, caster, aurApp);
return aurApp;
}
void Unit::_ApplyAuraEffect(Aura* aura, uint8 effIndex)
{
ASSERT(aura);
ASSERT(aura->HasEffect(effIndex));
AuraApplication * aurApp = aura->GetApplicationOfTarget(GetGUID());
ASSERT(aurApp);
if (!aurApp->GetEffectMask())
_ApplyAura(aurApp, 1 << effIndex);
else
aurApp->_HandleEffect(effIndex, true);
}
// handles effects of aura application
// should be done after registering aura in lists
void Unit::_ApplyAura(AuraApplication* aurApp, uint32 effMask)
{
Aura* aura = aurApp->GetBase();
_RemoveNoStackAurasDueToAura(aura, false);
if (aurApp->GetRemoveMode())
return;
// Update target aura state flag
if (AuraStateType aState = aura->GetSpellInfo()->GetAuraState())
{
uint32 aStateMask = (1 << (aState - 1));
// force update so the new caster registers it
if ((aStateMask & PER_CASTER_AURA_STATE_MASK) && *m_unitData->AuraState & aStateMask)
ForceUpdateFieldChange(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::AuraState));
else
ModifyAuraState(aState, true);
}
if (aurApp->GetRemoveMode())
return;
// Sitdown on apply aura req seated
if (aura->GetSpellInfo()->HasAuraInterruptFlag(SpellAuraInterruptFlags::Standing) && !IsSitState())
SetStandState(UNIT_STAND_STATE_SIT);
Unit* caster = aura->GetCaster();
if (aurApp->GetRemoveMode())
return;
aura->HandleAuraSpecificMods(aurApp, caster, true, false);
// apply effects of the aura
for (AuraEffect const* aurEff : aura->GetAuraEffects())
{
if (effMask & 1 << aurEff->GetEffIndex())
{
aurApp->_HandleEffect(aurEff->GetEffIndex(), true);
if (aurApp->GetRemoveMode())
break;
}
}
if (Player* player = ToPlayer())
{
if (sConditionMgr->IsSpellUsedInSpellClickConditions(aurApp->GetBase()->GetId()))
player->UpdateVisibleObjectInteractions(false, true, false, false);
player->FailCriteria(CriteriaFailEvent::GainAura, aurApp->GetBase()->GetId());
player->StartCriteria(CriteriaStartEvent::GainAura, aurApp->GetBase()->GetId());
player->UpdateCriteria(CriteriaType::GainAura, aurApp->GetBase()->GetId());
}
}
// removes aura application from lists and unapplies effects
void Unit::_UnapplyAura(AuraApplicationMap::iterator& i, AuraRemoveMode removeMode)
{
AuraApplication * aurApp = i->second;
ASSERT(aurApp);
ASSERT(!aurApp->GetRemoveMode());
ASSERT(aurApp->GetTarget() == this);
aurApp->SetRemoveMode(removeMode);
Aura* aura = aurApp->GetBase();
TC_LOG_DEBUG("spells", "Aura {} now is remove mode {}", aura->GetId(), removeMode);
// dead loop is killing the server probably
ASSERT(m_removedAurasCount < 0xFFFFFFFF);
++m_removedAurasCount;
Unit* caster = aura->GetCaster();
// Remove all pointers from lists here to prevent possible pointer invalidation on spellcast/auraapply/auraremove
m_appliedAuras.erase(i);
if (aura->GetSpellInfo()->HasAnyAuraInterruptFlag())
{
Trinity::Containers::Lists::RemoveUnique(m_interruptableAuras, aurApp);
UpdateInterruptMask();
}
bool auraStateFound = false;
AuraStateType auraState = aura->GetSpellInfo()->GetAuraState();
if (auraState)
{
bool canBreak = false;
// Get mask of all aurastates from remaining auras
for (AuraStateAurasMap::iterator itr = m_auraStateAuras.lower_bound(auraState); itr != m_auraStateAuras.upper_bound(auraState) && !(auraStateFound && canBreak);)
{
if (itr->second == aurApp)
{
m_auraStateAuras.erase(itr);
itr = m_auraStateAuras.lower_bound(auraState);
canBreak = true;
continue;
}
auraStateFound = true;
++itr;
}
}
aurApp->_Remove();
aura->_UnapplyForTarget(this, caster, aurApp);
// remove effects of the spell - needs to be done after removing aura from lists
for (AuraEffect const* aurEff : aura->GetAuraEffects())
if (aurApp->HasEffect(aurEff->GetEffIndex()))
aurApp->_HandleEffect(aurEff->GetEffIndex(), false);
// all effect mustn't be applied
ASSERT(!aurApp->GetEffectMask());
// Remove totem at next update if totem loses its aura
if (aurApp->GetRemoveMode() == AURA_REMOVE_BY_EXPIRE && GetTypeId() == TYPEID_UNIT && IsTotem())
{
if (ToTotem()->GetSpell() == aura->GetId() && ToTotem()->GetTotemType() == TOTEM_PASSIVE)
ToTotem()->setDeathState(JUST_DIED);
}
// Remove aurastates only if needed and were not found
if (auraState)
{
if (!auraStateFound)
ModifyAuraState(auraState, false);
else
{
// update for casters, some shouldn't 'see' the aura state
uint32 aStateMask = (1 << (auraState - 1));
if ((aStateMask & PER_CASTER_AURA_STATE_MASK) != 0)
ForceUpdateFieldChange(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::AuraState));
}
}
aura->HandleAuraSpecificMods(aurApp, caster, false, false);
if (Player* player = ToPlayer())
{
if (sConditionMgr->IsSpellUsedInSpellClickConditions(aurApp->GetBase()->GetId()))
player->UpdateVisibleObjectInteractions(false, true, false, false);
player->FailCriteria(CriteriaFailEvent::LoseAura, aurApp->GetBase()->GetId());
}
i = m_appliedAuras.begin();
}
void Unit::_UnapplyAura(AuraApplication* aurApp, AuraRemoveMode removeMode)
{
// aura can be removed from unit only if it's applied on it, shouldn't happen
ASSERT(aurApp->GetBase()->GetApplicationOfTarget(GetGUID()) == aurApp);
uint32 spellId = aurApp->GetBase()->GetId();
AuraApplicationMapBoundsNonConst range = m_appliedAuras.equal_range(spellId);
for (AuraApplicationMap::iterator iter = range.first; iter != range.second;)
{
if (iter->second == aurApp)
{
_UnapplyAura(iter, removeMode);
return;
}
else
++iter;
}
ABORT();
}
void Unit::_RemoveNoStackAurasDueToAura(Aura* aura, bool owned)
{
SpellInfo const* spellProto = aura->GetSpellInfo();
// passive spell special case (only non stackable with ranks)
if (spellProto->IsPassiveStackableWithRanks())
return;
if (!IsHighestExclusiveAura(aura))
{
aura->Remove();
return;
}
if (owned)
RemoveOwnedAuras([aura](Aura const* ownedAura) { return !aura->CanStackWith(ownedAura); }, AURA_REMOVE_BY_DEFAULT);
else
RemoveAppliedAuras([aura](AuraApplication const* appliedAura) { return !aura->CanStackWith(appliedAura->GetBase()); }, AURA_REMOVE_BY_DEFAULT);
}
void Unit::_RegisterAuraEffect(AuraEffect* aurEff, bool apply)
{
if (apply)
{
m_modAuras[aurEff->GetAuraType()].push_front(aurEff);
if (Player* player = ToPlayer())
{
player->StartCriteria(CriteriaStartEvent::GainAuraEffect, aurEff->GetAuraType());
player->FailCriteria(CriteriaFailEvent::GainAuraEffect, aurEff->GetAuraType());
}
}
else
Trinity::Containers::Lists::RemoveUnique(m_modAuras[aurEff->GetAuraType()], aurEff);
}
// All aura base removes should go through this function!
void Unit::RemoveOwnedAura(AuraMap::iterator& i, AuraRemoveMode removeMode)
{
Aura* aura = i->second;
ASSERT(!aura->IsRemoved());
// if unit currently update aura list then make safe update iterator shift to next
if (m_auraUpdateIterator == i)
++m_auraUpdateIterator;
m_ownedAuras.erase(i);
m_removedAuras.push_front(aura);
// Unregister single target aura
if (aura->IsSingleTarget())
aura->UnregisterSingleTarget();
aura->_Remove(removeMode);
i = m_ownedAuras.begin();
}
void Unit::RemoveOwnedAura(uint32 spellId, ObjectGuid casterGUID, uint32 reqEffMask, AuraRemoveMode removeMode)
{
for (AuraMap::iterator itr = m_ownedAuras.lower_bound(spellId); itr != m_ownedAuras.upper_bound(spellId);)
if (((itr->second->GetEffectMask() & reqEffMask) == reqEffMask) && (!casterGUID || itr->second->GetCasterGUID() == casterGUID))
{
RemoveOwnedAura(itr, removeMode);
itr = m_ownedAuras.lower_bound(spellId);
}
else
++itr;
}
void Unit::RemoveOwnedAura(Aura* aura, AuraRemoveMode removeMode)
{
if (aura->IsRemoved())
return;
ASSERT(aura->GetOwner() == this);
if (removeMode == AURA_REMOVE_NONE)
{
TC_LOG_ERROR("spells", "Unit::RemoveOwnedAura() called with unallowed removeMode AURA_REMOVE_NONE, spellId {}", aura->GetId());
return;
}
uint32 spellId = aura->GetId();
AuraMapBoundsNonConst range = m_ownedAuras.equal_range(spellId);
for (AuraMap::iterator itr = range.first; itr != range.second; ++itr)
{
if (itr->second == aura)
{
RemoveOwnedAura(itr, removeMode);
return;
}
}
ABORT();
}
Aura* Unit::GetOwnedAura(uint32 spellId, ObjectGuid casterGUID, ObjectGuid itemCasterGUID, uint32 reqEffMask, Aura* except) const
{
AuraMapBounds range = m_ownedAuras.equal_range(spellId);
for (AuraMap::const_iterator itr = range.first; itr != range.second; ++itr)
{
if (((itr->second->GetEffectMask() & reqEffMask) == reqEffMask)
&& (!casterGUID || itr->second->GetCasterGUID() == casterGUID)
&& (!itemCasterGUID || itr->second->GetCastItemGUID() == itemCasterGUID)
&& (!except || except != itr->second))
{
return itr->second;
}
}
return nullptr;
}
void Unit::RemoveAura(AuraApplicationMap::iterator &i, AuraRemoveMode mode)
{
AuraApplication * aurApp = i->second;
// Do not remove aura which is already being removed
if (aurApp->GetRemoveMode())
return;
Aura* aura = aurApp->GetBase();
_UnapplyAura(i, mode);
// Remove aura - for Area and Target auras
if (aura->GetOwner() == this)
aura->Remove(mode);
}
void Unit::RemoveAura(uint32 spellId, ObjectGuid caster, uint32 reqEffMask, AuraRemoveMode removeMode)
{
AuraApplicationMapBoundsNonConst range = m_appliedAuras.equal_range(spellId);
for (AuraApplicationMap::iterator iter = range.first; iter != range.second;)
{
Aura const* aura = iter->second->GetBase();
if (((aura->GetEffectMask() & reqEffMask) == reqEffMask)
&& (!caster || aura->GetCasterGUID() == caster))
{
RemoveAura(iter, removeMode);
return;
}
else
++iter;
}
}
void Unit::RemoveAura(AuraApplication * aurApp, AuraRemoveMode mode)
{
// we've special situation here, RemoveAura called while during aura removal
// this kind of call is needed only when aura effect removal handler
// or event triggered by it expects to remove
// not yet removed effects of an aura
if (aurApp->GetRemoveMode())
{
// remove remaining effects of an aura
for (AuraEffect const* aurEff : aurApp->GetBase()->GetAuraEffects())
{
if (aurApp->HasEffect(aurEff->GetEffIndex()))
aurApp->_HandleEffect(aurEff->GetEffIndex(), false);
}
return;
}
// no need to remove
if (aurApp->GetBase()->GetApplicationOfTarget(GetGUID()) != aurApp || aurApp->GetBase()->IsRemoved())
return;
uint32 spellId = aurApp->GetBase()->GetId();
AuraApplicationMapBoundsNonConst range = m_appliedAuras.equal_range(spellId);
for (AuraApplicationMap::iterator iter = range.first; iter != range.second;)
{
if (aurApp == iter->second)
{
RemoveAura(iter, mode);
return;
}
else
++iter;
}
}
void Unit::RemoveAura(Aura* aura, AuraRemoveMode mode)
{
if (aura->IsRemoved())
return;
if (AuraApplication * aurApp = aura->GetApplicationOfTarget(GetGUID()))
RemoveAura(aurApp, mode);
}
void Unit::RemoveAppliedAuras(std::function const& check, AuraRemoveMode removeMode /*= AURA_REMOVE_BY_DEFAULT*/)
{
for (AuraApplicationMap::iterator iter = m_appliedAuras.begin(); iter != m_appliedAuras.end();)
{
if (check(iter->second))
{
RemoveAura(iter, removeMode);
continue;
}
++iter;
}
}
void Unit::RemoveOwnedAuras(std::function const& check, AuraRemoveMode removeMode /*= AURA_REMOVE_BY_DEFAULT*/)
{
for (AuraMap::iterator iter = m_ownedAuras.begin(); iter != m_ownedAuras.end();)
{
if (check(iter->second))
{
RemoveOwnedAura(iter, removeMode);
continue;
}
++iter;
}
}
void Unit::RemoveAppliedAuras(uint32 spellId, std::function const& check, AuraRemoveMode removeMode /*= AURA_REMOVE_BY_DEFAULT*/)
{
for (AuraApplicationMap::iterator iter = m_appliedAuras.lower_bound(spellId); iter != m_appliedAuras.upper_bound(spellId);)
{
if (check(iter->second))
{
RemoveAura(iter, removeMode);
iter = m_appliedAuras.lower_bound(spellId);
continue;
}
++iter;
}
}
void Unit::RemoveOwnedAuras(uint32 spellId, std::function const& check, AuraRemoveMode removeMode /*= AURA_REMOVE_BY_DEFAULT*/)
{
for (AuraMap::iterator iter = m_ownedAuras.lower_bound(spellId); iter != m_ownedAuras.upper_bound(spellId);)
{
if (check(iter->second))
{
RemoveOwnedAura(iter, removeMode);
iter = m_ownedAuras.lower_bound(spellId);
continue;
}
++iter;
}
}
void Unit::RemoveAurasByType(AuraType auraType, std::function const& check, AuraRemoveMode removeMode /*= AURA_REMOVE_BY_DEFAULT*/)
{
for (AuraEffectList::iterator iter = m_modAuras[auraType].begin(); iter != m_modAuras[auraType].end();)
{
Aura* aura = (*iter)->GetBase();
AuraApplication * aurApp = aura->GetApplicationOfTarget(GetGUID());
ASSERT(aurApp);
++iter;
if (check(aurApp))
{
uint32 removedAuras = m_removedAurasCount;
RemoveAura(aurApp, removeMode);
if (m_removedAurasCount > removedAuras + 1)
iter = m_modAuras[auraType].begin();
}
}
}
void Unit::RemoveAurasDueToSpell(uint32 spellId, ObjectGuid casterGUID, uint32 reqEffMask, AuraRemoveMode removeMode)
{
for (AuraApplicationMap::iterator iter = m_appliedAuras.lower_bound(spellId); iter != m_appliedAuras.upper_bound(spellId);)
{
Aura const* aura = iter->second->GetBase();
if (((aura->GetEffectMask() & reqEffMask) == reqEffMask)
&& (!casterGUID || aura->GetCasterGUID() == casterGUID))
{
RemoveAura(iter, removeMode);
iter = m_appliedAuras.lower_bound(spellId);
}
else
++iter;
}
}
void Unit::RemoveAuraFromStack(uint32 spellId, ObjectGuid casterGUID, AuraRemoveMode removeMode, uint16 num)
{
AuraMapBoundsNonConst range = m_ownedAuras.equal_range(spellId);
for (AuraMap::iterator iter = range.first; iter != range.second;)
{
Aura* aura = iter->second;
if ((aura->GetType() == UNIT_AURA_TYPE)
&& (!casterGUID || aura->GetCasterGUID() == casterGUID))
{
aura->ModStackAmount(-num, removeMode);
return;
}
else
++iter;
}
}
void Unit::RemoveAurasDueToSpellByDispel(uint32 spellId, uint32 dispellerSpellId, ObjectGuid casterGUID, WorldObject* dispeller, uint8 chargesRemoved /*= 1*/)
{
AuraMapBoundsNonConst range = m_ownedAuras.equal_range(spellId);
for (AuraMap::iterator iter = range.first; iter != range.second;)
{
Aura* aura = iter->second;
if (aura->GetCasterGUID() == casterGUID)
{
DispelInfo dispelInfo(dispeller, dispellerSpellId, chargesRemoved);
// Call OnDispel hook on AuraScript
aura->CallScriptDispel(&dispelInfo);
if (aura->GetSpellInfo()->HasAttribute(SPELL_ATTR7_DISPEL_REMOVES_CHARGES))
aura->ModCharges(-dispelInfo.GetRemovedCharges(), AURA_REMOVE_BY_ENEMY_SPELL);
else
aura->ModStackAmount(-dispelInfo.GetRemovedCharges(), AURA_REMOVE_BY_ENEMY_SPELL);
// Call AfterDispel hook on AuraScript
aura->CallScriptAfterDispel(&dispelInfo);
return;
}
else
++iter;
}
}
void Unit::RemoveAurasDueToSpellBySteal(uint32 spellId, ObjectGuid casterGUID, WorldObject* stealer, int32 stolenCharges /*= 1*/)
{
AuraMapBoundsNonConst range = m_ownedAuras.equal_range(spellId);
for (AuraMap::iterator iter = range.first; iter != range.second;)
{
Aura* aura = iter->second;
if (aura->GetCasterGUID() == casterGUID)
{
std::array damage = { };
std::array baseDamage = { };
std::bitset effMask;
std::bitset recalculateMask;
Unit* caster = aura->GetCaster();
for (AuraEffect const* aurEff : aura->GetAuraEffects())
{
SpellEffIndex i = aurEff->GetEffIndex();
baseDamage[i] = aurEff->GetBaseAmount();
damage[i] = aurEff->GetAmount();
effMask[i] = true;
if (aurEff->CanBeRecalculated())
recalculateMask[i] = true;
}
bool stealCharge = aura->GetSpellInfo()->HasAttribute(SPELL_ATTR7_DISPEL_REMOVES_CHARGES);
// Cast duration to unsigned to prevent permanent aura's such as Righteous Fury being permanently added to caster
uint32 dur = std::min(2u * MINUTE * IN_MILLISECONDS, uint32(aura->GetDuration()));
if (Unit* unitStealer = stealer->ToUnit())
{
if (Aura* oldAura = unitStealer->GetAura(aura->GetId(), aura->GetCasterGUID()))
{
if (stealCharge)
oldAura->ModCharges(stolenCharges);
else
oldAura->ModStackAmount(stolenCharges);
oldAura->SetDuration(int32(dur));
}
else
{
// single target state must be removed before aura creation to preserve existing single target aura
if (aura->IsSingleTarget())
aura->UnregisterSingleTarget();
AuraCreateInfo createInfo(aura->GetCastId(), aura->GetSpellInfo(), aura->GetCastDifficulty(), effMask.to_ulong(), unitStealer);
createInfo
.SetCasterGUID(aura->GetCasterGUID())
.SetBaseAmount(baseDamage.data())
.SetStackAmount(stolenCharges);
if (Aura* newAura = Aura::TryRefreshStackOrCreate(createInfo))
{
// created aura must not be single target aura,, so stealer won't loose it on recast
if (newAura->IsSingleTarget())
{
newAura->UnregisterSingleTarget();
// bring back single target aura status to the old aura
aura->SetIsSingleTarget(true);
caster->GetSingleCastAuras().push_front(aura);
}
// FIXME: using aura->GetMaxDuration() maybe not blizzlike but it fixes stealing of spells like Innervate
newAura->SetLoadedState(aura->GetMaxDuration(), int32(dur), stealCharge ? stolenCharges : aura->GetCharges(), recalculateMask.to_ulong(), damage.data());
newAura->ApplyForTargets();
}
}
}
if (stealCharge)
aura->ModCharges(-stolenCharges, AURA_REMOVE_BY_ENEMY_SPELL);
else
aura->ModStackAmount(-stolenCharges, AURA_REMOVE_BY_ENEMY_SPELL);
return;
}
else
++iter;
}
}
void Unit::RemoveAurasDueToItemSpell(uint32 spellId, ObjectGuid castItemGuid)
{
for (AuraApplicationMap::iterator iter = m_appliedAuras.lower_bound(spellId); iter != m_appliedAuras.upper_bound(spellId);)
{
if (iter->second->GetBase()->GetCastItemGUID() == castItemGuid)
{
RemoveAura(iter);
iter = m_appliedAuras.lower_bound(spellId);
}
else
++iter;
}
}
void Unit::RemoveAurasByType(AuraType auraType, ObjectGuid casterGUID, Aura* except, bool negative, bool positive)
{
for (AuraEffectList::iterator iter = m_modAuras[auraType].begin(); iter != m_modAuras[auraType].end();)
{
Aura* aura = (*iter)->GetBase();
AuraApplication * aurApp = aura->GetApplicationOfTarget(GetGUID());
ASSERT(aurApp);
++iter;
if (aura != except && (!casterGUID || aura->GetCasterGUID() == casterGUID)
&& ((negative && !aurApp->IsPositive()) || (positive && aurApp->IsPositive())))
{
uint32 removedAuras = m_removedAurasCount;
RemoveAura(aurApp);
if (m_removedAurasCount > removedAuras + 1)
iter = m_modAuras[auraType].begin();
}
}
}
void Unit::RemoveAurasWithAttribute(uint32 flags)
{
for (AuraApplicationMap::iterator iter = m_appliedAuras.begin(); iter != m_appliedAuras.end();)
{
SpellInfo const* spell = iter->second->GetBase()->GetSpellInfo();
if (spell->Attributes & flags)
RemoveAura(iter);
else
++iter;
}
}
void Unit::RemoveNotOwnSingleTargetAuras(bool onPhaseChange /*= false*/)
{
// single target auras from other casters
// Iterate m_ownedAuras - aura is marked as single target in Unit::AddAura (and pushed to m_ownedAuras).
// m_appliedAuras will NOT contain the aura before first Unit::Update after adding it to m_ownedAuras.
// Quickly removing such an aura will lead to it not being unregistered from caster's single cast auras container
// leading to assertion failures if the aura was cast on a player that can
// (and is changing map at the point where this function is called).
// Such situation occurs when player is logging in inside an instance and fails the entry check for any reason.
// The aura that was loaded from db (indirectly, via linked casts) gets removed before it has a chance
// to register in m_appliedAuras
for (AuraMap::iterator iter = m_ownedAuras.begin(); iter != m_ownedAuras.end();)
{
Aura const* aura = iter->second;
if (aura->GetCasterGUID() != GetGUID() && aura->IsSingleTarget())
{
if (!onPhaseChange)
RemoveOwnedAura(iter);
else
{
Unit* caster = aura->GetCaster();
if (!caster || !caster->InSamePhase(this))
RemoveOwnedAura(iter);
else
++iter;
}
}
else
++iter;
}
// single target auras at other targets
AuraList& scAuras = GetSingleCastAuras();
for (AuraList::iterator iter = scAuras.begin(); iter != scAuras.end();)
{
Aura* aura = *iter;
if (aura->GetUnitOwner() != this && (!onPhaseChange || !aura->GetUnitOwner()->InSamePhase(this)))
{
aura->Remove();
iter = scAuras.begin();
}
else
++iter;
}
}
template
bool IsInterruptFlagIgnoredForSpell(InterruptFlag /*flag*/, Unit const* /*unit*/, SpellInfo const* /*auraSpellInfo*/, bool /*isChannel*/, SpellInfo const* /*interruptSource*/)
{
return false;
}
template<>
bool IsInterruptFlagIgnoredForSpell(SpellAuraInterruptFlags flag, Unit const* unit, SpellInfo const* auraSpellInfo, bool isChannel, SpellInfo const* interruptSource)
{
switch (flag)
{
case SpellAuraInterruptFlags::Moving:
return unit->CanCastSpellWhileMoving(auraSpellInfo);
case SpellAuraInterruptFlags::Action:
case SpellAuraInterruptFlags::ActionDelayed:
if (interruptSource)
{
if (interruptSource->HasAttribute(SPELL_ATTR1_ALLOW_WHILE_STEALTHED) && auraSpellInfo->Dispel == DISPEL_STEALTH)
return true;
if (interruptSource->HasAttribute(SPELL_ATTR2_ALLOW_WHILE_INVISIBLE) && auraSpellInfo->Dispel == DISPEL_INVISIBILITY)
return true;
if (interruptSource->HasAttribute(SPELL_ATTR9_ALLOW_CAST_WHILE_CHANNELING) && isChannel)
return true;
}
break;
default:
break;
}
return false;
}
template
void Unit::RemoveAurasWithInterruptFlags(InterruptFlags flag, SpellInfo const* source)
{
if (!HasInterruptFlag(flag))
return;
// interrupt auras
for (AuraApplicationList::iterator iter = m_interruptableAuras.begin(); iter != m_interruptableAuras.end();)
{
Aura* aura = (*iter)->GetBase();
++iter;
if (aura->GetSpellInfo()->HasAuraInterruptFlag(flag)
&& (!source || aura->GetId() != source->Id)
&& !IsInterruptFlagIgnoredForSpell(flag, this, aura->GetSpellInfo(), false, source))
{
uint32 removedAuras = m_removedAurasCount;
RemoveAura(aura, AURA_REMOVE_BY_INTERRUPT);
if (m_removedAurasCount > removedAuras + 1)
iter = m_interruptableAuras.begin();
}
}
// interrupt channeled spell
if (Spell* spell = m_currentSpells[CURRENT_CHANNELED_SPELL])
if (spell->getState() == SPELL_STATE_CHANNELING
&& spell->GetSpellInfo()->HasChannelInterruptFlag(flag)
&& (!source || spell->GetSpellInfo()->Id != source->Id)
&& !IsInterruptFlagIgnoredForSpell(flag, this, spell->GetSpellInfo(), true, source))
InterruptNonMeleeSpells(false);
UpdateInterruptMask();
}
template TC_GAME_API void Unit::RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags flag, SpellInfo const* source);
template TC_GAME_API void Unit::RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags2 flag, SpellInfo const* source);
void Unit::RemoveAurasWithFamily(SpellFamilyNames family, flag128 const& familyFlag, ObjectGuid casterGUID)
{
for (AuraApplicationMap::iterator iter = m_appliedAuras.begin(); iter != m_appliedAuras.end();)
{
Aura const* aura = iter->second->GetBase();
if (!casterGUID || aura->GetCasterGUID() == casterGUID)
{
SpellInfo const* spell = aura->GetSpellInfo();
if (spell->SpellFamilyName == uint32(family) && spell->SpellFamilyFlags & familyFlag)
{
RemoveAura(iter);
continue;
}
}
++iter;
}
}
void Unit::RemoveMovementImpairingAuras(bool withRoot)
{
if (withRoot)
RemoveAurasWithMechanic(1 << MECHANIC_ROOT, AURA_REMOVE_BY_DEFAULT, 0, true);
RemoveAurasWithMechanic(1 << MECHANIC_SNARE, AURA_REMOVE_BY_DEFAULT, 0, false);
}
void Unit::RemoveAurasWithMechanic(uint64 mechanicMaskToRemove, AuraRemoveMode removeMode, uint32 exceptSpellId, bool withEffectMechanics)
{
std::vector aurasToUpdateTargets;
RemoveAppliedAuras([=, &aurasToUpdateTargets](AuraApplication const* aurApp)
{
Aura* aura = aurApp->GetBase();
if (exceptSpellId && aura->GetId() == exceptSpellId)
return false;
uint64 appliedMechanicMask = aura->GetSpellInfo()->GetSpellMechanicMaskByEffectMask(aurApp->GetEffectMask());
if (!(appliedMechanicMask & mechanicMaskToRemove))
return false;
// spell mechanic matches required mask for removal
if ((UI64LIT(1) << aura->GetSpellInfo()->Mechanic) & mechanicMaskToRemove || withEffectMechanics)
return true;
// effect mechanic matches required mask for removal - don't remove, only update targets
aurasToUpdateTargets.push_back(aura);
return false;
}, removeMode);
for (Aura* aura : aurasToUpdateTargets)
{
aura->UpdateTargetMap(aura->GetCaster());
// Fully remove the aura if all effects were removed
if (!aura->IsPassive() && aura->GetOwner() == this && !aura->GetApplicationOfTarget(GetGUID()))
aura->Remove(removeMode);
}
}
void Unit::RemoveAurasByShapeShift()
{
uint64 mechanic_mask = (1 << MECHANIC_SNARE) | (1 << MECHANIC_ROOT);
for (AuraApplicationMap::iterator iter = m_appliedAuras.begin(); iter != m_appliedAuras.end();)
{
Aura const* aura = iter->second->GetBase();
if ((aura->GetSpellInfo()->GetAllEffectsMechanicMask() & mechanic_mask) && !aura->GetSpellInfo()->HasAttribute(SPELL_ATTR0_CU_AURA_CC))
{
RemoveAura(iter);
continue;
}
++iter;
}
}
void Unit::RemoveAreaAurasDueToLeaveWorld()
{
// make sure that all area auras not applied on self are removed - prevent access to deleted pointer later
for (AuraMap::iterator iter = m_ownedAuras.begin(); iter != m_ownedAuras.end();)
{
Aura* aura = iter->second;
++iter;
Aura::ApplicationMap const& appMap = aura->GetApplicationMap();
for (Aura::ApplicationMap::const_iterator itr = appMap.begin(); itr!= appMap.end();)
{
AuraApplication * aurApp = itr->second;
++itr;
Unit* target = aurApp->GetTarget();
if (target == this)
continue;
target->RemoveAura(aurApp);
// things linked on aura remove may apply new area aura - so start from the beginning
iter = m_ownedAuras.begin();
}
}
// remove area auras owned by others
for (AuraApplicationMap::iterator iter = m_appliedAuras.begin(); iter != m_appliedAuras.end();)
{
if (iter->second->GetBase()->GetOwner() != this)
{
RemoveAura(iter);
}
else
++iter;
}
}
void Unit::RemoveAllAuras()
{
// this may be a dead loop if some events on aura remove will continiously apply aura on remove
// we want to have all auras removed, so use your brain when linking events
for (int counter = 0; !m_appliedAuras.empty() || !m_ownedAuras.empty(); counter++)
{
AuraApplicationMap::iterator aurAppIter;
for (aurAppIter = m_appliedAuras.begin(); aurAppIter != m_appliedAuras.end();)
_UnapplyAura(aurAppIter, AURA_REMOVE_BY_DEFAULT);
AuraMap::iterator aurIter;
for (aurIter = m_ownedAuras.begin(); aurIter != m_ownedAuras.end();)
RemoveOwnedAura(aurIter);
const int maxIteration = 50;
// give this loop a few tries, if there are still auras then log as much information as possible
if (counter >= maxIteration)
{
std::stringstream sstr;
sstr << "Unit::RemoveAllAuras() iterated " << maxIteration << " times already but there are still "
<< m_appliedAuras.size() << " m_appliedAuras and " << m_ownedAuras.size() << " m_ownedAuras. Details:" << "\n";
sstr << GetDebugInfo() << "\n";
if (!m_appliedAuras.empty())
{
sstr << "m_appliedAuras:" << "\n";
for (std::pair& auraAppPair : m_appliedAuras)
sstr << auraAppPair.second->GetDebugInfo() << "\n";
}
if (!m_ownedAuras.empty())
{
sstr << "m_ownedAuras:" << "\n";
for (auto const& [spellId, aura] : m_ownedAuras)
sstr << aura->GetDebugInfo() << "\n";
}
TC_LOG_ERROR("entities.unit", "{}", sstr.str());
ABORT_MSG("%s", sstr.str().c_str());
break;
}
}
}
void Unit::RemoveArenaAuras()
{
// in join, remove positive buffs, on end, remove negative
// used to remove positive visible auras in arenas
RemoveAppliedAuras([](AuraApplication const* aurApp)
{
Aura const* aura = aurApp->GetBase();
return (!aura->GetSpellInfo()->HasAttribute(SPELL_ATTR4_ALLOW_ENTERING_ARENA) // don't remove stances, shadowform, pally/hunter auras
&& !aura->IsPassive() // don't remove passive auras
&& (aurApp->IsPositive() || !aura->GetSpellInfo()->HasAttribute(SPELL_ATTR3_ALLOW_AURA_WHILE_DEAD))) || // not negative death persistent auras
aura->GetSpellInfo()->HasAttribute(SPELL_ATTR5_REMOVE_ENTERING_ARENA); // special marker, always remove
});
}
void Unit::RemoveAurasOnEvade()
{
if (IsCharmedOwnedByPlayerOrPlayer()) // if it is a player owned creature it should not remove the aura
return;
// don't remove vehicle auras, passengers aren't supposed to drop off the vehicle
// don't remove clone caster on evade (to be verified)
auto evadeAuraCheck = [](Aura const* aura)
{
if (aura->HasEffectType(SPELL_AURA_CONTROL_VEHICLE))
return false;
if (aura->HasEffectType(SPELL_AURA_CLONE_CASTER))
return false;
if (aura->GetSpellInfo()->HasAttribute(SPELL_ATTR1_AURA_STAYS_AFTER_COMBAT))
return false;
return true;
};
auto evadeAuraApplicationCheck = [&evadeAuraCheck](AuraApplication const* aurApp)
{
return evadeAuraCheck(aurApp->GetBase());
};
RemoveAppliedAuras(evadeAuraApplicationCheck);
RemoveOwnedAuras(evadeAuraCheck);
}
void Unit::RemoveAllAurasOnDeath()
{
// used just after dieing to remove all visible auras
// and disable the mods for the passive ones
for (AuraApplicationMap::iterator iter = m_appliedAuras.begin(); iter != m_appliedAuras.end();)
{
Aura const* aura = iter->second->GetBase();
if (!aura->IsPassive() && !aura->IsDeathPersistent())
_UnapplyAura(iter, AURA_REMOVE_BY_DEATH);
else
++iter;
}
for (AuraMap::iterator iter = m_ownedAuras.begin(); iter != m_ownedAuras.end();)
{
Aura* aura = iter->second;
if (!aura->IsPassive() && !aura->IsDeathPersistent())
RemoveOwnedAura(iter, AURA_REMOVE_BY_DEATH);
else
++iter;
}
}
void Unit::RemoveAllAurasRequiringDeadTarget()
{
for (AuraApplicationMap::iterator iter = m_appliedAuras.begin(); iter != m_appliedAuras.end();)
{
Aura const* aura = iter->second->GetBase();
if (!aura->IsPassive() && aura->GetSpellInfo()->IsRequiringDeadTarget())
_UnapplyAura(iter, AURA_REMOVE_BY_DEFAULT);
else
++iter;
}
for (AuraMap::iterator iter = m_ownedAuras.begin(); iter != m_ownedAuras.end();)
{
Aura* aura = iter->second;
if (!aura->IsPassive() && aura->GetSpellInfo()->IsRequiringDeadTarget())
RemoveOwnedAura(iter, AURA_REMOVE_BY_DEFAULT);
else
++iter;
}
}
void Unit::RemoveAllAurasExceptType(AuraType type)
{
for (AuraApplicationMap::iterator iter = m_appliedAuras.begin(); iter != m_appliedAuras.end();)
{
Aura const* aura = iter->second->GetBase();
if (aura->GetSpellInfo()->HasAura(type))
++iter;
else
_UnapplyAura(iter, AURA_REMOVE_BY_DEFAULT);
}
for (AuraMap::iterator iter = m_ownedAuras.begin(); iter != m_ownedAuras.end();)
{
Aura* aura = iter->second;
if (aura->GetSpellInfo()->HasAura(type))
++iter;
else
RemoveOwnedAura(iter, AURA_REMOVE_BY_DEFAULT);
}
}
void Unit::RemoveAllAurasExceptType(AuraType type1, AuraType type2)
{
for (AuraApplicationMap::iterator iter = m_appliedAuras.begin(); iter != m_appliedAuras.end();)
{
Aura const* aura = iter->second->GetBase();
if (aura->GetSpellInfo()->HasAura(type1) || aura->GetSpellInfo()->HasAura(type2))
++iter;
else
_UnapplyAura(iter, AURA_REMOVE_BY_DEFAULT);
}
for (AuraMap::iterator iter = m_ownedAuras.begin(); iter != m_ownedAuras.end();)
{
Aura* aura = iter->second;
if (aura->GetSpellInfo()->HasAura(type1) || aura->GetSpellInfo()->HasAura(type2))
++iter;
else
RemoveOwnedAura(iter, AURA_REMOVE_BY_DEFAULT);
}
}
void Unit::RemoveAllGroupBuffsFromCaster(ObjectGuid casterGUID)
{
for (AuraMap::iterator iter = m_ownedAuras.begin(); iter != m_ownedAuras.end();)
{
Aura* aura = iter->second;
if (aura->GetCasterGUID() == casterGUID && aura->GetSpellInfo()->IsGroupBuff())
{
RemoveOwnedAura(iter);
continue;
}
++iter;
}
}
void Unit::DelayOwnedAuras(uint32 spellId, ObjectGuid caster, int32 delaytime)
{
AuraMapBoundsNonConst range = m_ownedAuras.equal_range(spellId);
for (; range.first != range.second; ++range.first)
{
Aura* aura = range.first->second;
if (!caster || aura->GetCasterGUID() == caster)
{
if (aura->GetDuration() < delaytime)
aura->SetDuration(0);
else
aura->SetDuration(aura->GetDuration() - delaytime);
// update for out of range group members (on 1 slot use)
aura->SetNeedClientUpdateForTargets();
}
}
}
void Unit::_RemoveAllAuraStatMods()
{
for (AuraApplicationMap::iterator i = m_appliedAuras.begin(); i != m_appliedAuras.end(); ++i)
(*i).second->GetBase()->HandleAllEffects(i->second, AURA_EFFECT_HANDLE_STAT, false);
}
void Unit::_ApplyAllAuraStatMods()
{
for (AuraApplicationMap::iterator i = m_appliedAuras.begin(); i != m_appliedAuras.end(); ++i)
(*i).second->GetBase()->HandleAllEffects(i->second, AURA_EFFECT_HANDLE_STAT, true);
}
AuraEffect* Unit::GetAuraEffect(uint32 spellId, uint8 effIndex, ObjectGuid caster) const
{
AuraApplicationMapBounds range = m_appliedAuras.equal_range(spellId);
for (AuraApplicationMap::const_iterator itr = range.first; itr != range.second; ++itr)
{
if (itr->second->HasEffect(effIndex)
&& (!caster || itr->second->GetBase()->GetCasterGUID() == caster))
{
return itr->second->GetBase()->GetEffect(effIndex);
}
}
return nullptr;
}
AuraEffect* Unit::GetAuraEffectOfRankedSpell(uint32 spellId, uint8 effIndex, ObjectGuid caster) const
{
uint32 rankSpell = sSpellMgr->GetFirstSpellInChain(spellId);
while (rankSpell)
{
if (AuraEffect* aurEff = GetAuraEffect(rankSpell, effIndex, caster))
return aurEff;
rankSpell = sSpellMgr->GetNextSpellInChain(rankSpell);
}
return nullptr;
}
AuraEffect* Unit::GetAuraEffect(AuraType type, SpellFamilyNames family, flag128 const& familyFlag, ObjectGuid casterGUID) const
{
AuraEffectList const& auras = GetAuraEffectsByType(type);
for (AuraEffectList::const_iterator i = auras.begin(); i != auras.end(); ++i)
{
SpellInfo const* spell = (*i)->GetSpellInfo();
if (spell->SpellFamilyName == uint32(family) && spell->SpellFamilyFlags & familyFlag)
{
if (!casterGUID.IsEmpty() && (*i)->GetCasterGUID() != casterGUID)
continue;
return (*i);
}
}
return nullptr;
}
AuraApplication * Unit::GetAuraApplication(uint32 spellId, ObjectGuid casterGUID, ObjectGuid itemCasterGUID, uint32 reqEffMask, AuraApplication * except) const
{
return GetAuraApplication(spellId, [&](AuraApplication const* app)
{
Aura const* aura = app->GetBase();
if (((aura->GetEffectMask() & reqEffMask) == reqEffMask)
&& (!casterGUID || aura->GetCasterGUID() == casterGUID)
&& (!itemCasterGUID || aura->GetCastItemGUID() == itemCasterGUID)
&& (!except || except != app))
{
return true;
}
return false;
});
}
AuraApplication* Unit::GetAuraApplication(uint32 spellId, std::function const& predicate) const
{
for (AuraApplicationMap::value_type const& pair : Trinity::Containers::MapEqualRange(m_appliedAuras, spellId))
if (predicate(pair.second))
return pair.second;
return nullptr;
}
AuraApplication* Unit::GetAuraApplication(uint32 spellId, std::function const& predicate) const
{
for (AuraApplicationMap::value_type const& pair : Trinity::Containers::MapEqualRange(m_appliedAuras, spellId))
if (predicate(pair.second->GetBase()))
return pair.second;
return nullptr;
}
AuraApplication* Unit::GetAuraApplication(std::function const& predicate) const
{
for (AuraApplicationMap::value_type const& pair : m_appliedAuras)
if (predicate(pair.second))
return pair.second;
return nullptr;
}
AuraApplication* Unit::GetAuraApplication(std::function const& predicate) const
{
for (AuraApplicationMap::value_type const& pair : m_appliedAuras)
if (predicate(pair.second->GetBase()))
return pair.second;
return nullptr;
}
Aura* Unit::GetAura(uint32 spellId, ObjectGuid casterGUID, ObjectGuid itemCasterGUID, uint32 reqEffMask) const
{
AuraApplication* aurApp = GetAuraApplication(spellId, casterGUID, itemCasterGUID, reqEffMask);
return aurApp ? aurApp->GetBase() : nullptr;
}
Aura* Unit::GetAura(uint32 spellId, std::function const& predicate) const
{
AuraApplication* aurApp = GetAuraApplication(spellId, predicate);
return aurApp ? aurApp->GetBase() : nullptr;
}
Aura* Unit::GetAura(std::function const& predicate) const
{
AuraApplication* aurApp = GetAuraApplication(predicate);
return aurApp ? aurApp->GetBase() : nullptr;
}
AuraApplication * Unit::GetAuraApplicationOfRankedSpell(uint32 spellId, ObjectGuid casterGUID, ObjectGuid itemCasterGUID, uint32 reqEffMask, AuraApplication* except) const
{
uint32 rankSpell = sSpellMgr->GetFirstSpellInChain(spellId);
while (rankSpell)
{
if (AuraApplication * aurApp = GetAuraApplication(rankSpell, casterGUID, itemCasterGUID, reqEffMask, except))
return aurApp;
rankSpell = sSpellMgr->GetNextSpellInChain(rankSpell);
}
return nullptr;
}
Aura* Unit::GetAuraOfRankedSpell(uint32 spellId, ObjectGuid casterGUID, ObjectGuid itemCasterGUID, uint32 reqEffMask) const
{
AuraApplication * aurApp = GetAuraApplicationOfRankedSpell(spellId, casterGUID, itemCasterGUID, reqEffMask);
return aurApp ? aurApp->GetBase() : nullptr;
}
void Unit::GetDispellableAuraList(WorldObject const* caster, uint32 dispelMask, DispelChargesList& dispelList, bool isReflect /*= false*/) const
{
AuraMap const& auras = GetOwnedAuras();
for (auto itr = auras.begin(); itr != auras.end(); ++itr)
{
Aura* aura = itr->second;
AuraApplication const* aurApp = aura->GetApplicationOfTarget(GetGUID());
if (!aurApp)
continue;
// don't try to remove passive auras
if (aura->IsPassive())
continue;
if (aura->GetSpellInfo()->GetDispelMask() & dispelMask)
{
// do not remove positive auras if friendly target
// negative auras if non-friendly
// unless we're reflecting (dispeller eliminates one of it's benefitial buffs)
if (isReflect != (aurApp->IsPositive() == IsFriendlyTo(caster)))
continue;
// 2.4.3 Patch Notes: "Dispel effects will no longer attempt to remove effects that have 100% dispel resistance."
int32 chance = aura->CalcDispelChance(this, !IsFriendlyTo(caster));
if (!chance)
continue;
// The charges / stack amounts don't count towards the total number of auras that can be dispelled.
// Ie: A dispel on a target with 5 stacks of Winters Chill and a Polymorph has 1 / (1 + 1) -> 50% chance to dispell
// Polymorph instead of 1 / (5 + 1) -> 16%.
bool const dispelCharges = aura->GetSpellInfo()->HasAttribute(SPELL_ATTR7_DISPEL_REMOVES_CHARGES);
uint8 charges = dispelCharges ? aura->GetCharges() : aura->GetStackAmount();
if (charges > 0)
dispelList.emplace_back(aura, chance, charges);
}
}
}
bool Unit::HasAuraEffect(uint32 spellId, uint8 effIndex, ObjectGuid caster) const
{
AuraApplicationMapBounds range = m_appliedAuras.equal_range(spellId);
for (AuraApplicationMap::const_iterator itr = range.first; itr != range.second; ++itr)
{
if (itr->second->HasEffect(effIndex)
&& (!caster || itr->second->GetBase()->GetCasterGUID() == caster))
{
return true;
}
}
return false;
}
uint32 Unit::GetAuraCount(uint32 spellId) const
{
uint32 count = 0;
AuraApplicationMapBounds range = m_appliedAuras.equal_range(spellId);
for (AuraApplicationMap::const_iterator itr = range.first; itr != range.second; ++itr)
{
if (itr->second->GetBase()->GetStackAmount() == 0)
++count;
else
count += (uint32)itr->second->GetBase()->GetStackAmount();
}
return count;
}
bool Unit::HasAura(uint32 spellId, ObjectGuid casterGUID, ObjectGuid itemCasterGUID, uint32 reqEffMask) const
{
return GetAuraApplication(spellId, casterGUID, itemCasterGUID, reqEffMask) != nullptr;
}
bool Unit::HasAura(std::function const& predicate) const
{
return GetAuraApplication(predicate) != nullptr;
}
bool Unit::HasAuraType(AuraType auraType) const
{
return (!m_modAuras[auraType].empty());
}
bool Unit::HasAuraTypeWithCaster(AuraType auraType, ObjectGuid caster) const
{
for (AuraEffect const* eff : GetAuraEffectsByType(auraType))
if (caster == eff->GetCasterGUID())
return true;
return false;
}
bool Unit::HasAuraTypeWithMiscvalue(AuraType auraType, int32 miscvalue) const
{
AuraEffectList const& mTotalAuraList = GetAuraEffectsByType(auraType);
for (AuraEffectList::const_iterator i = mTotalAuraList.begin(); i != mTotalAuraList.end(); ++i)
if (miscvalue == (*i)->GetMiscValue())
return true;
return false;
}
bool Unit::HasAuraTypeWithAffectMask(AuraType auraType, SpellInfo const* affectedSpell) const
{
AuraEffectList const& mTotalAuraList = GetAuraEffectsByType(auraType);
for (AuraEffectList::const_iterator i = mTotalAuraList.begin(); i != mTotalAuraList.end(); ++i)
if ((*i)->IsAffectingSpell(affectedSpell))
return true;
return false;
}
bool Unit::HasAuraTypeWithValue(AuraType auraType, int32 value) const
{
AuraEffectList const& mTotalAuraList = GetAuraEffectsByType(auraType);
for (AuraEffectList::const_iterator i = mTotalAuraList.begin(); i != mTotalAuraList.end(); ++i)
if (value == (*i)->GetAmount())
return true;
return false;
}
bool Unit::HasAuraTypeWithTriggerSpell(AuraType auratype, uint32 triggerSpell) const
{
for (AuraEffect const* aura : GetAuraEffectsByType(auratype))
if (aura->GetSpellEffectInfo().TriggerSpell == triggerSpell)
return true;
return false;
}
template
bool Unit::HasNegativeAuraWithInterruptFlag(InterruptFlags flag, ObjectGuid guid) const
{
if (!HasInterruptFlag(flag))
return false;
for (AuraApplicationList::const_iterator iter = m_interruptableAuras.begin(); iter != m_interruptableAuras.end(); ++iter)
if (!(*iter)->IsPositive() && (*iter)->GetBase()->GetSpellInfo()->HasAuraInterruptFlag(flag) &&
(!guid || (*iter)->GetBase()->GetCasterGUID() == guid))
return true;
return false;
}
template TC_GAME_API bool Unit::HasNegativeAuraWithInterruptFlag(SpellAuraInterruptFlags flag, ObjectGuid guid) const;
template TC_GAME_API bool Unit::HasNegativeAuraWithInterruptFlag(SpellAuraInterruptFlags2 flag, ObjectGuid guid) const;
bool Unit::HasAuraWithMechanic(uint64 mechanicMask) const
{
for (AuraApplicationMap::const_iterator iter = m_appliedAuras.begin(); iter != m_appliedAuras.end(); ++iter)
{
SpellInfo const* spellInfo = iter->second->GetBase()->GetSpellInfo();
if (spellInfo->Mechanic && (mechanicMask & (UI64LIT(1) << spellInfo->Mechanic)))
return true;
for (SpellEffectInfo const& spellEffectInfo : spellInfo->GetEffects())
if (iter->second->HasEffect(spellEffectInfo.EffectIndex) && spellEffectInfo.IsEffect() && spellEffectInfo.Mechanic)
if (mechanicMask & (UI64LIT(1) << spellEffectInfo.Mechanic))
return true;
}
return false;
}
bool Unit::HasStrongerAuraWithDR(SpellInfo const* auraSpellInfo, Unit* caster) const
{
DiminishingGroup diminishGroup = auraSpellInfo->GetDiminishingReturnsGroupForSpell();
DiminishingLevels level = GetDiminishing(diminishGroup);
for (auto itr = m_appliedAuras.begin(); itr != m_appliedAuras.end(); ++itr)
{
SpellInfo const* spellInfo = itr->second->GetBase()->GetSpellInfo();
if (spellInfo->GetDiminishingReturnsGroupForSpell() != diminishGroup)
continue;
int32 existingDuration = itr->second->GetBase()->GetDuration();
int32 newDuration = auraSpellInfo->GetMaxDuration();
ApplyDiminishingToDuration(auraSpellInfo, newDuration, caster, level);
if (newDuration > 0 && newDuration < existingDuration)
return true;
}
return false;
}
AuraEffect* Unit::IsScriptOverriden(SpellInfo const* spell, int32 script) const
{
AuraEffectList const& auras = GetAuraEffectsByType(SPELL_AURA_OVERRIDE_CLASS_SCRIPTS);
for (AuraEffectList::const_iterator i = auras.begin(); i != auras.end(); ++i)
{
if ((*i)->GetMiscValue() == script)
if ((*i)->IsAffectingSpell(spell))
return (*i);
}
return nullptr;
}
uint32 Unit::GetDiseasesByCaster(ObjectGuid casterGUID, bool remove)
{
static const AuraType diseaseAuraTypes[] =
{
SPELL_AURA_PERIODIC_DAMAGE, // Frost Fever and Blood Plague
SPELL_AURA_LINKED // Crypt Fever and Ebon Plague
};
uint32 diseases = 0;
for (AuraType aType : diseaseAuraTypes)
{
for (auto itr = m_modAuras[aType].begin(); itr != m_modAuras[aType].end();)
{
// Get auras with disease dispel type by caster
if ((*itr)->GetSpellInfo()->Dispel == DISPEL_DISEASE
&& (*itr)->GetCasterGUID() == casterGUID)
{
++diseases;
if (remove)
{
RemoveAura((*itr)->GetId(), (*itr)->GetCasterGUID());
itr = m_modAuras[aType].begin();
continue;
}
}
++itr;
}
}
return diseases;
}
uint32 Unit::GetDoTsByCaster(ObjectGuid casterGUID) const
{
static const AuraType diseaseAuraTypes[] =
{
SPELL_AURA_PERIODIC_DAMAGE,
SPELL_AURA_PERIODIC_DAMAGE_PERCENT,
SPELL_AURA_NONE
};
uint32 dots = 0;
for (AuraType const* itr = &diseaseAuraTypes[0]; itr && itr[0] != SPELL_AURA_NONE; ++itr)
{
Unit::AuraEffectList const& auras = GetAuraEffectsByType(*itr);
for (AuraEffectList::const_iterator i = auras.begin(); i != auras.end(); ++i)
{
// Get auras by caster
if ((*i)->GetCasterGUID() == casterGUID)
++dots;
}
}
return dots;
}
int32 Unit::GetTotalAuraModifier(AuraType auraType, std::function const& predicate) const
{
AuraEffectList const& mTotalAuraList = GetAuraEffectsByType(auraType);
if (mTotalAuraList.empty())
return 0;
std::map sameEffectSpellGroup;
int32 modifier = 0;
for (AuraEffect const* aurEff : mTotalAuraList)
{
if (predicate(aurEff))
{
// Check if the Aura Effect has a the Same Effect Stack Rule and if so, use the highest amount of that SpellGroup
// If the Aura Effect does not have this Stack Rule, it returns false so we can add to the multiplier as usual
if (!sSpellMgr->AddSameEffectStackRuleSpellGroups(aurEff->GetSpellInfo(), static_cast(auraType), aurEff->GetAmount(), sameEffectSpellGroup))
modifier += aurEff->GetAmount();
}
}
// Add the highest of the Same Effect Stack Rule SpellGroups to the accumulator
for (auto itr = sameEffectSpellGroup.begin(); itr != sameEffectSpellGroup.end(); ++itr)
modifier += itr->second;
return modifier;
}
float Unit::GetTotalAuraMultiplier(AuraType auraType, std::function const& predicate) const
{
AuraEffectList const& mTotalAuraList = GetAuraEffectsByType(auraType);
if (mTotalAuraList.empty())
return 1.0f;
std::map sameEffectSpellGroup;
float multiplier = 1.0f;
for (AuraEffect const* aurEff : mTotalAuraList)
{
if (predicate(aurEff))
{
// Check if the Aura Effect has a the Same Effect Stack Rule and if so, use the highest amount of that SpellGroup
// If the Aura Effect does not have this Stack Rule, it returns false so we can add to the multiplier as usual
if (!sSpellMgr->AddSameEffectStackRuleSpellGroups(aurEff->GetSpellInfo(), static_cast(auraType), aurEff->GetAmount(), sameEffectSpellGroup))
AddPct(multiplier, aurEff->GetAmount());
}
}
// Add the highest of the Same Effect Stack Rule SpellGroups to the multiplier
for (auto itr = sameEffectSpellGroup.begin(); itr != sameEffectSpellGroup.end(); ++itr)
AddPct(multiplier, itr->second);
return multiplier;
}
int32 Unit::GetMaxPositiveAuraModifier(AuraType auraType, std::function const& predicate) const
{
AuraEffectList const& mTotalAuraList = GetAuraEffectsByType(auraType);
if (mTotalAuraList.empty())
return 0;
int32 modifier = 0;
for (AuraEffect const* aurEff : mTotalAuraList)
{
if (predicate(aurEff))
modifier = std::max(modifier, aurEff->GetAmount());
}
return modifier;
}
int32 Unit::GetMaxNegativeAuraModifier(AuraType auraType, std::function const& predicate) const
{
AuraEffectList const& mTotalAuraList = GetAuraEffectsByType(auraType);
if (mTotalAuraList.empty())
return 0;
int32 modifier = 0;
for (AuraEffect const* aurEff : mTotalAuraList)
{
if (predicate(aurEff))
modifier = std::min(modifier, aurEff->GetAmount());
}
return modifier;
}
int32 Unit::GetTotalAuraModifier(AuraType auraType) const
{
return GetTotalAuraModifier(auraType, [](AuraEffect const* /*aurEff*/) { return true; });
}
float Unit::GetTotalAuraMultiplier(AuraType auraType) const
{
return GetTotalAuraMultiplier(auraType, [](AuraEffect const* /*aurEff*/) { return true; });
}
int32 Unit::GetMaxPositiveAuraModifier(AuraType auraType) const
{
return GetMaxPositiveAuraModifier(auraType, [](AuraEffect const* /*aurEff*/) { return true; });
}
int32 Unit::GetMaxNegativeAuraModifier(AuraType auraType) const
{
return GetMaxNegativeAuraModifier(auraType, [](AuraEffect const* /*aurEff*/) { return true; });
}
int32 Unit::GetTotalAuraModifierByMiscMask(AuraType auraType, uint32 miscMask) const
{
return GetTotalAuraModifier(auraType, [miscMask](AuraEffect const* aurEff) -> bool
{
if ((aurEff->GetMiscValue() & miscMask) != 0)
return true;
return false;
});
}
float Unit::GetTotalAuraMultiplierByMiscMask(AuraType auraType, uint32 miscMask) const
{
return GetTotalAuraMultiplier(auraType, [miscMask](AuraEffect const* aurEff) -> bool
{
if ((aurEff->GetMiscValue() & miscMask) != 0)
return true;
return false;
});
}
int32 Unit::GetMaxPositiveAuraModifierByMiscMask(AuraType auraType, uint32 miscMask, AuraEffect const* except /*= nullptr*/) const
{
return GetMaxPositiveAuraModifier(auraType, [miscMask, except](AuraEffect const* aurEff) -> bool
{
if (except != aurEff && (aurEff->GetMiscValue() & miscMask) != 0)
return true;
return false;
});
}
int32 Unit::GetMaxNegativeAuraModifierByMiscMask(AuraType auraType, uint32 miscMask) const
{
return GetMaxNegativeAuraModifier(auraType, [miscMask](AuraEffect const* aurEff) -> bool
{
if ((aurEff->GetMiscValue() & miscMask) != 0)
return true;
return false;
});
}
int32 Unit::GetTotalAuraModifierByMiscValue(AuraType auraType, int32 miscValue) const
{
return GetTotalAuraModifier(auraType, [miscValue](AuraEffect const* aurEff) -> bool
{
if (aurEff->GetMiscValue() == miscValue)
return true;
return false;
});
}
float Unit::GetTotalAuraMultiplierByMiscValue(AuraType auraType, int32 miscValue) const
{
return GetTotalAuraMultiplier(auraType, [miscValue](AuraEffect const* aurEff) -> bool
{
if (aurEff->GetMiscValue() == miscValue)
return true;
return false;
});
}
int32 Unit::GetMaxPositiveAuraModifierByMiscValue(AuraType auraType, int32 miscValue) const
{
return GetMaxPositiveAuraModifier(auraType, [miscValue](AuraEffect const* aurEff) -> bool
{
if (aurEff->GetMiscValue() == miscValue)
return true;
return false;
});
}
int32 Unit::GetMaxNegativeAuraModifierByMiscValue(AuraType auraType, int32 miscValue) const
{
return GetMaxNegativeAuraModifier(auraType, [miscValue](AuraEffect const* aurEff) -> bool
{
if (aurEff->GetMiscValue() == miscValue)
return true;
return false;
});
}
int32 Unit::GetTotalAuraModifierByAffectMask(AuraType auraType, SpellInfo const* affectedSpell) const
{
return GetTotalAuraModifier(auraType, [affectedSpell](AuraEffect const* aurEff) -> bool
{
if (aurEff->IsAffectingSpell(affectedSpell))
return true;
return false;
});
}
float Unit::GetTotalAuraMultiplierByAffectMask(AuraType auraType, SpellInfo const* affectedSpell) const
{
return GetTotalAuraMultiplier(auraType, [affectedSpell](AuraEffect const* aurEff) -> bool
{
if (aurEff->IsAffectingSpell(affectedSpell))
return true;
return false;
});
}
int32 Unit::GetMaxPositiveAuraModifierByAffectMask(AuraType auraType, SpellInfo const* affectedSpell) const
{
return GetMaxPositiveAuraModifier(auraType, [affectedSpell](AuraEffect const* aurEff) -> bool
{
if (aurEff->IsAffectingSpell(affectedSpell))
return true;
return false;
});
}
int32 Unit::GetMaxNegativeAuraModifierByAffectMask(AuraType auraType, SpellInfo const* affectedSpell) const
{
return GetMaxNegativeAuraModifier(auraType, [affectedSpell](AuraEffect const* aurEff) -> bool
{
if (aurEff->IsAffectingSpell(affectedSpell))
return true;
return false;
});
}
void Unit::InitStatBuffMods()
{
for (uint8 i = STAT_STRENGTH; i < MAX_STATS; ++i)
{
m_floatStatPosBuff[i] = 0.0f;
m_floatStatNegBuff[i] = 0.0f;
UpdateStatBuffModForClient(Stats(i));
}
}
void Unit::UpdateStatBuffMod(Stats stat)
{
float modPos = 0.0f;
float modNeg = 0.0f;
float factor = 0.0f;
UnitMods const unitMod = static_cast(UNIT_MOD_STAT_START + AsUnderlyingType(stat));
// includes value from items and enchantments
float modValue = GetFlatModifierValue(unitMod, BASE_VALUE);
if (modValue > 0.f)
modPos += modValue;
else
modNeg += modValue;
if (IsGuardian())
{
modValue = static_cast(this)->GetBonusStatFromOwner(stat);
if (modValue > 0.f)
modPos += modValue;
else
modNeg += modValue;
}
// SPELL_AURA_MOD_STAT_BONUS_PCT only affects BASE_VALUE
modPos = CalculatePct(modPos, std::max(GetFlatModifierValue(unitMod, BASE_PCT_EXCLUDE_CREATE), -100.0f));
modNeg = CalculatePct(modNeg, std::max(GetFlatModifierValue(unitMod, BASE_PCT_EXCLUDE_CREATE), -100.0f));
modPos += GetTotalAuraModifier(SPELL_AURA_MOD_STAT, [stat](AuraEffect const* aurEff) -> bool
{
if ((aurEff->GetMiscValue() < 0 || aurEff->GetMiscValue() == stat) && aurEff->GetAmount() > 0)
return true;
return false;
});
modNeg += GetTotalAuraModifier(SPELL_AURA_MOD_STAT, [stat](AuraEffect const* aurEff) -> bool
{
if ((aurEff->GetMiscValue() < 0 || aurEff->GetMiscValue() == stat) && aurEff->GetAmount() < 0)
return true;
return false;
});
factor = GetTotalAuraMultiplier(SPELL_AURA_MOD_PERCENT_STAT, [stat](AuraEffect const* aurEff) -> bool
{
if (aurEff->GetMiscValue() == -1 || aurEff->GetMiscValue() == stat)
return true;
return false;
});
factor *= GetTotalAuraMultiplier(SPELL_AURA_MOD_TOTAL_STAT_PERCENTAGE, [stat](AuraEffect const* aurEff) -> bool
{
if (aurEff->GetMiscValue() == -1 || aurEff->GetMiscValue() == stat)
return true;
return false;
});
modPos *= factor;
modNeg *= factor;
m_floatStatPosBuff[stat] = modPos;
m_floatStatNegBuff[stat] = modNeg;
UpdateStatBuffModForClient(stat);
}
void Unit::UpdateStatBuffModForClient(Stats stat)
{
SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::StatPosBuff, stat), int32(m_floatStatPosBuff[stat]));
SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::StatNegBuff, stat), int32(m_floatStatNegBuff[stat]));
}
void Unit::_RegisterDynObject(DynamicObject* dynObj)
{
m_dynObj.push_back(dynObj);
if (GetTypeId() == TYPEID_UNIT && IsAIEnabled())
ToCreature()->AI()->JustRegisteredDynObject(dynObj);
}
void Unit::_UnregisterDynObject(DynamicObject* dynObj)
{
std::erase(m_dynObj, dynObj);
if (GetTypeId() == TYPEID_UNIT && IsAIEnabled())
ToCreature()->AI()->JustUnregisteredDynObject(dynObj);
}
DynamicObject* Unit::GetDynObject(uint32 spellId) const
{
std::vector dynamicobjects = GetDynObjects(spellId);
return dynamicobjects.empty() ? nullptr : dynamicobjects.front();
}
std::vector Unit::GetDynObjects(uint32 spellId) const
{
std::vector dynamicobjects;
for (DynObjectList::const_iterator i = m_dynObj.begin(); i != m_dynObj.end(); ++i)
if ((*i)->GetSpellId() == spellId)
dynamicobjects.push_back(*i);
return dynamicobjects;
}
void Unit::RemoveDynObject(uint32 spellId)
{
for (DynObjectList::iterator i = m_dynObj.begin(); i != m_dynObj.end();)
{
DynamicObject* dynObj = *i;
if (dynObj->GetSpellId() == spellId)
{
dynObj->Remove();
i = m_dynObj.begin();
}
else
++i;
}
}
void Unit::RemoveAllDynObjects()
{
while (!m_dynObj.empty())
m_dynObj.back()->Remove();
}
GameObject* Unit::GetGameObject(uint32 spellId) const
{
std::vector gameobjects = GetGameObjects(spellId);
return gameobjects.empty() ? nullptr : gameobjects.front();
}
std::vector Unit::GetGameObjects(uint32 spellId) const
{
std::vector gameobjects;
for (GameObjectList::const_iterator i = m_gameObj.begin(); i != m_gameObj.end(); ++i)
if ((*i)->GetSpellId() == spellId)
gameobjects.push_back(*i);
return gameobjects;
}
void Unit::AddGameObject(GameObject* gameObj)
{
if (!gameObj || !gameObj->GetOwnerGUID().IsEmpty())
return;
m_gameObj.push_back(gameObj);
gameObj->SetOwnerGUID(GetGUID());
if (gameObj->GetSpellId())
{
SpellInfo const* createBySpell = sSpellMgr->GetSpellInfo(gameObj->GetSpellId(), GetMap()->GetDifficultyID());
// Need disable spell use for owner
if (createBySpell && createBySpell->IsCooldownStartedOnEvent())
// note: item based cooldowns and cooldown spell mods with charges ignored (unknown existing cases)
GetSpellHistory()->StartCooldown(createBySpell, 0, nullptr, true);
}
if (GetTypeId() == TYPEID_UNIT && ToCreature()->IsAIEnabled())
ToCreature()->AI()->JustSummonedGameobject(gameObj);
}
void Unit::RemoveGameObject(GameObject* gameObj, bool del)
{
if (!gameObj || gameObj->GetOwnerGUID() != GetGUID())
return;
gameObj->SetOwnerGUID(ObjectGuid::Empty);
for (uint8 i = 0; i < MAX_GAMEOBJECT_SLOT; ++i)
{
if (m_ObjectSlot[i] == gameObj->GetGUID())
{
m_ObjectSlot[i].Clear();
break;
}
}
// GO created by some spell
if (uint32 spellid = gameObj->GetSpellId())
{
RemoveAurasDueToSpell(spellid);
SpellInfo const* createBySpell = sSpellMgr->GetSpellInfo(spellid, GetMap()->GetDifficultyID());
// Need activate spell use for owner
if (createBySpell && createBySpell->IsCooldownStartedOnEvent())
// note: item based cooldowns and cooldown spell mods with charges ignored (unknown existing cases)
GetSpellHistory()->SendCooldownEvent(createBySpell);
}
m_gameObj.remove(gameObj);
if (GetTypeId() == TYPEID_UNIT && ToCreature()->IsAIEnabled())
ToCreature()->AI()->SummonedGameobjectDespawn(gameObj);
if (del)
{
gameObj->SetRespawnTime(0);
gameObj->Delete();
}
}
void Unit::RemoveGameObject(uint32 spellid, bool del)
{
if (m_gameObj.empty())
return;
GameObjectList::iterator i, next;
for (i = m_gameObj.begin(); i != m_gameObj.end(); i = next)
{
next = i;
if (spellid == 0 || (*i)->GetSpellId() == spellid)
{
(*i)->SetOwnerGUID(ObjectGuid::Empty);
if (del)
{
(*i)->SetRespawnTime(0);
(*i)->Delete();
}
next = m_gameObj.erase(i);
}
else
++next;
}
}
void Unit::RemoveAllGameObjects()
{
// remove references to unit
while (!m_gameObj.empty())
{
GameObjectList::iterator i = m_gameObj.begin();
(*i)->SetOwnerGUID(ObjectGuid::Empty);
(*i)->SetRespawnTime(0);
(*i)->Delete();
m_gameObj.erase(i);
}
}
void Unit::_RegisterAreaTrigger(AreaTrigger* areaTrigger)
{
m_areaTrigger.push_back(areaTrigger);
if (GetTypeId() == TYPEID_UNIT && IsAIEnabled())
ToCreature()->AI()->JustRegisteredAreaTrigger(areaTrigger);
}
void Unit::_UnregisterAreaTrigger(AreaTrigger* areaTrigger)
{
std::erase(m_areaTrigger, areaTrigger);
if (GetTypeId() == TYPEID_UNIT && IsAIEnabled())
ToCreature()->AI()->JustUnregisteredAreaTrigger(areaTrigger);
}
AreaTrigger* Unit::GetAreaTrigger(uint32 spellId) const
{
std::vector areaTriggers = GetAreaTriggers(spellId);
return areaTriggers.empty() ? nullptr : areaTriggers.front();
}
std::vector Unit::GetAreaTriggers(uint32 spellId) const
{
std::vector areaTriggers;
for (AreaTriggerList::const_iterator i = m_areaTrigger.begin(); i != m_areaTrigger.end(); ++i)
if ((*i)->GetSpellId() == spellId)
areaTriggers.push_back(*i);
return areaTriggers;
}
void Unit::RemoveAreaTrigger(uint32 spellId)
{
for (AreaTriggerList::iterator i = m_areaTrigger.begin(); i != m_areaTrigger.end();)
{
AreaTrigger* areaTrigger = *i;
if (areaTrigger->GetSpellId() == spellId)
{
areaTrigger->Remove();
i = m_areaTrigger.begin();
}
else
++i;
}
}
void Unit::RemoveAreaTrigger(AuraEffect const* aurEff)
{
for (AreaTrigger* areaTrigger : m_areaTrigger)
{
if (areaTrigger->GetAuraEffect() == aurEff)
{
areaTrigger->Remove();
break; // There can only be one AreaTrigger per AuraEffect
}
}
}
void Unit::RemoveAllAreaTriggers(AreaTriggerRemoveReason reason /*= AreaTriggerRemoveReason::Default*/)
{
for (AreaTrigger* at : AreaTriggerList(std::move(m_areaTrigger)))
{
if (reason == AreaTriggerRemoveReason::UnitDespawn && at->GetTemplate()->ActionSetFlags.HasFlag(AreaTriggerActionSetFlag::DontDespawnWithCreator))
continue;
at->Remove();
}
}
void Unit::EnterAreaTrigger(AreaTrigger* areaTrigger)
{
m_insideAreaTriggers.push_back(areaTrigger);
}
void Unit::ExitAreaTrigger(AreaTrigger* areaTrigger)
{
std::erase(m_insideAreaTriggers, areaTrigger);
}
void Unit::ExitAllAreaTriggers()
{
AreaTriggerList atList = std::move(m_insideAreaTriggers);
for (AreaTrigger* at : atList)
at->HandleUnitExit(this);
}
void Unit::SendSpellNonMeleeDamageLog(SpellNonMeleeDamage const* log)
{
WorldPackets::CombatLog::SpellNonMeleeDamageLog packet;
packet.Me = log->target->GetGUID();
packet.CasterGUID = log->attacker ? log->attacker->GetGUID() : ObjectGuid::Empty;
packet.CastID = log->castId;
packet.SpellID = log->Spell ? log->Spell->Id : 0;
packet.Visual = log->SpellVisual;
packet.Damage = log->damage;
packet.OriginalDamage = log->originalDamage;
if (log->damage > log->preHitHealth)
packet.Overkill = log->damage - log->preHitHealth;
else
packet.Overkill = -1;
packet.SchoolMask = log->schoolMask;
packet.Absorbed = log->absorb;
packet.Resisted = log->resist;
packet.ShieldBlock = log->blocked;
packet.Periodic = log->periodicLog;
packet.Flags = log->HitInfo;
WorldPackets::Spells::ContentTuningParams contentTuningParams;
if (contentTuningParams.GenerateDataForUnits(log->attacker, log->target))
packet.ContentTuning = contentTuningParams;
SendCombatLogMessage(&packet);
}
/*static*/ void Unit::ProcSkillsAndAuras(Unit* actor, Unit* actionTarget, ProcFlagsInit const& typeMaskActor, ProcFlagsInit const& typeMaskActionTarget,
ProcFlagsSpellType spellTypeMask, ProcFlagsSpellPhase spellPhaseMask, ProcFlagsHit hitMask,
Spell* spell, DamageInfo* damageInfo, HealInfo* healInfo)
{
static constexpr int32 ProcChainHardLimit = 10;
if (spell && spell->GetProcChainLength() >= ProcChainHardLimit)
{
TC_LOG_ERROR("spells.aura.effect", "Unit::ProcSkillsAndAuras: Possible infinite proc loop detected, current triggering spell {}", spell->GetDebugInfo().c_str());
return;
}
WeaponAttackType attType = damageInfo ? damageInfo->GetAttackType() : BASE_ATTACK;
SpellInfo const* spellInfo = [&]() -> SpellInfo const*
{
if (spell)
return spell->GetSpellInfo();
if (damageInfo)
return damageInfo->GetSpellInfo();
if (healInfo)
return healInfo->GetSpellInfo();
return nullptr;
}();
if (typeMaskActor && actor && !(spellInfo && spellInfo->HasAttribute(SPELL_ATTR3_SUPPRESS_CASTER_PROCS)))
actor->ProcSkillsAndReactives(false, actionTarget, typeMaskActor, hitMask, attType);
if (typeMaskActionTarget && actionTarget && !(spellInfo && spellInfo->HasAttribute(SPELL_ATTR3_SUPPRESS_TARGET_PROCS)))
actionTarget->ProcSkillsAndReactives(true, actor, typeMaskActionTarget, hitMask, attType);
if (actor)
actor->TriggerAurasProcOnEvent(nullptr, nullptr, actionTarget, typeMaskActor, typeMaskActionTarget, spellTypeMask, spellPhaseMask, hitMask, spell, damageInfo, healInfo);
}
void Unit::SendPeriodicAuraLog(SpellPeriodicAuraLogInfo* info)
{
AuraEffect const* aura = info->auraEff;
WorldPackets::CombatLog::SpellPeriodicAuraLog data;
data.TargetGUID = GetGUID();
data.CasterGUID = aura->GetCasterGUID();
data.SpellID = aura->GetId();
data.LogData.Initialize(this);
WorldPackets::CombatLog::PeriodicAuraLogEffect& spellLogEffect = data.Effects.emplace_back();
spellLogEffect.Effect = aura->GetAuraType();
spellLogEffect.Amount = info->damage;
spellLogEffect.OriginalDamage = info->originalDamage;
spellLogEffect.OverHealOrKill = info->overDamage;
spellLogEffect.SchoolMaskOrPower = aura->GetSpellInfo()->GetSchoolMask();
spellLogEffect.AbsorbedOrAmplitude = info->absorb;
spellLogEffect.Resisted = info->resist;
spellLogEffect.Crit = info->critical;
/// @todo: implement debug info
WorldPackets::Spells::ContentTuningParams contentTuningParams;
if (Unit* caster = ObjectAccessor::GetUnit(*this, aura->GetCasterGUID()))
if (contentTuningParams.GenerateDataForUnits(caster, this))
spellLogEffect.ContentTuning = contentTuningParams;
SendCombatLogMessage(&data);
}
void Unit::SendSpellDamageResist(Unit* target, uint32 spellId)
{
WorldPackets::CombatLog::ProcResist procResist;
procResist.Caster = GetGUID();
procResist.SpellID = spellId;
procResist.Target = target->GetGUID();
SendMessageToSet(procResist.Write(), true);
}
void Unit::SendSpellDamageImmune(Unit* target, uint32 spellId, bool isPeriodic)
{
WorldPackets::CombatLog::SpellOrDamageImmune spellOrDamageImmune;
spellOrDamageImmune.CasterGUID = GetGUID();
spellOrDamageImmune.VictimGUID = target->GetGUID();
spellOrDamageImmune.SpellID = spellId;
spellOrDamageImmune.IsPeriodic = isPeriodic;
SendMessageToSet(spellOrDamageImmune.Write(), true);
}
void Unit::SendAttackStateUpdate(CalcDamageInfo* damageInfo)
{
WorldPackets::CombatLog::AttackerStateUpdate packet;
packet.HitInfo = damageInfo->HitInfo;
packet.AttackerGUID = damageInfo->Attacker->GetGUID();
packet.VictimGUID = damageInfo->Target->GetGUID();
packet.Damage = damageInfo->Damage;
packet.OriginalDamage = damageInfo->OriginalDamage;
int32 overkill = damageInfo->Damage - damageInfo->Target->GetHealth();
packet.OverDamage = (overkill < 0 ? -1 : overkill);
packet.SubDmg.emplace();
packet.SubDmg->SchoolMask = damageInfo->DamageSchoolMask; // School of sub damage
packet.SubDmg->FDamage = damageInfo->Damage; // sub damage
packet.SubDmg->Damage = damageInfo->Damage; // Sub Damage
packet.SubDmg->Absorbed = damageInfo->Absorb;
packet.SubDmg->Resisted = damageInfo->Resist;
packet.VictimState = damageInfo->TargetState;
packet.BlockAmount = damageInfo->Blocked;
packet.RageGained = damageInfo->RageGained;
packet.LogData.Initialize(damageInfo->Attacker);
WorldPackets::Spells::ContentTuningParams contentTuningParams;
if (contentTuningParams.GenerateDataForUnits(damageInfo->Attacker, damageInfo->Target))
packet.ContentTuning = contentTuningParams;
SendCombatLogMessage(&packet);
}
void Unit::SendAttackStateUpdate(uint32 HitInfo, Unit* target, uint8 /*SwingType*/, SpellSchoolMask damageSchoolMask, uint32 Damage, uint32 AbsorbDamage, uint32 Resist, VictimState TargetState, uint32 BlockedAmount, uint32 RageGained)
{
CalcDamageInfo dmgInfo;
dmgInfo.HitInfo = HitInfo;
dmgInfo.Attacker = this;
dmgInfo.Target = target;
dmgInfo.Damage = Damage - AbsorbDamage - Resist - BlockedAmount;
dmgInfo.OriginalDamage = Damage;
dmgInfo.DamageSchoolMask = damageSchoolMask;
dmgInfo.Absorb = AbsorbDamage;
dmgInfo.Resist = Resist;
dmgInfo.TargetState = TargetState;
dmgInfo.Blocked = BlockedAmount;
dmgInfo.RageGained = RageGained;
SendAttackStateUpdate(&dmgInfo);
}
void Unit::SetPowerType(Powers power, bool sendUpdate/* = true*/, bool onInit /*= false*/)
{
if (!onInit && GetPowerType() == power)
return;
PowerTypeEntry const* powerTypeEntry = sDB2Manager.GetPowerTypeEntry(power);
if (!powerTypeEntry)
return;
if (IsCreature() && !powerTypeEntry->GetFlags().HasFlag(PowerTypeFlags::IsUsedByNPCs))
return;
SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::DisplayPower), power);
// Update max power
UpdateMaxPower(power);
// Update current power
if (!onInit)
{
switch (power)
{
case POWER_MANA: // Keep the same (druid form switching...)
case POWER_ENERGY:
break;
case POWER_RAGE: // Reset to zero
SetPower(POWER_RAGE, 0);
break;
case POWER_FOCUS: // Make it full
SetFullPower(power);
break;
default:
break;
}
}
else
SetInitialPowerValue(power);
if (!sendUpdate)
return;
if (Player* thisPlayer = ToPlayer())
{
if (thisPlayer->GetGroup())
thisPlayer->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_POWER_TYPE);
}
/*else if (Pet* pet = ToCreature()->ToPet()) TODO 6.x
{
if (pet->isControlled())
pet->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_PET_POWER_TYPE);
}*/
}
void Unit::SetInitialPowerValue(Powers powerType)
{
PowerTypeEntry const* powerTypeEntry = sDB2Manager.GetPowerTypeEntry(powerType);
if (!powerTypeEntry)
return;
if (powerTypeEntry->GetFlags().HasFlag(PowerTypeFlags::UnitsUseDefaultPowerOnInit))
SetPower(powerType, powerTypeEntry->DefaultPower);
else
SetFullPower(powerType);
}
Powers Unit::CalculateDisplayPowerType() const
{
Powers displayPower = POWER_MANA;
switch (GetShapeshiftForm())
{
case FORM_GHOUL:
case FORM_CAT_FORM:
displayPower = POWER_ENERGY;
break;
case FORM_BEAR_FORM:
case FORM_DIRE_BEAR_FORM:
displayPower = POWER_RAGE;
break;
case FORM_TRAVEL_FORM:
case FORM_GHOST_WOLF:
displayPower = POWER_MANA;
break;
default:
{
Unit::AuraEffectList const& powerTypeAuras = GetAuraEffectsByType(SPELL_AURA_MOD_POWER_DISPLAY);
if (!powerTypeAuras.empty())
{
AuraEffect const* powerTypeAura = powerTypeAuras.front();
displayPower = Powers(powerTypeAura->GetMiscValue());
}
else
{
ChrClassesEntry const* cEntry = sChrClassesStore.LookupEntry(GetClass());
if (cEntry && cEntry->DisplayPower < MAX_POWERS)
displayPower = Powers(cEntry->DisplayPower);
if (Vehicle* vehicle = GetVehicleKit())
{
if (PowerDisplayEntry const* powerDisplay = sPowerDisplayStore.LookupEntry(vehicle->GetVehicleInfo()->PowerDisplayID[0]))
displayPower = Powers(powerDisplay->ActualType);
}
else if (IsHunterPet())
displayPower = POWER_FOCUS;
}
break;
}
}
return displayPower;
}
void Unit::UpdateDisplayPower()
{
SetPowerType(CalculateDisplayPowerType());
}
void Unit::SetSheath(SheathState sheathed)
{
SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::SheatheState), sheathed);
if (sheathed == SHEATH_STATE_UNARMED)
RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Sheathing);
}
void Unit::_addAttacker(Unit* pAttacker)
{
m_attackers.insert(pAttacker);
}
void Unit::_removeAttacker(Unit* pAttacker)
{
m_attackers.erase(pAttacker);
}
Unit* Unit::getAttackerForHelper() const // If someone wants to help, who to give them
{
if (!IsEngaged())
return nullptr;
if (Unit* victim = GetVictim())
if ((!IsPet() && !GetPlayerMovingMe()) || IsInCombatWith(victim))
return victim;
CombatManager const& mgr = GetCombatManager();
// pick arbitrary targets; our pvp combat > owner's pvp combat > our pve combat > owner's pve combat
Unit* owner = GetCharmerOrOwner();
if (mgr.HasPvPCombat())
return mgr.GetPvPCombatRefs().begin()->second->GetOther(this);
if (owner && (owner->GetCombatManager().HasPvPCombat()))
return owner->GetCombatManager().GetPvPCombatRefs().begin()->second->GetOther(owner);
if (mgr.HasPvECombat())
return mgr.GetPvECombatRefs().begin()->second->GetOther(this);
if (owner && (owner->GetCombatManager().HasPvECombat()))
return owner->GetCombatManager().GetPvECombatRefs().begin()->second->GetOther(owner);
return nullptr;
}
bool Unit::Attack(Unit* victim, bool meleeAttack)
{
if (!victim || victim == this)
return false;
// dead units can neither attack nor be attacked
if (!IsAlive() || !victim->IsInWorld() || !victim->IsAlive())
return false;
// player cannot attack in mount state
if (GetTypeId() == TYPEID_PLAYER && IsMounted())
return false;
Creature* creature = ToCreature();
// creatures cannot attack while evading
if (creature && creature->IsInEvadeMode())
return false;
// nobody can attack GM in GM-mode
if (victim->GetTypeId() == TYPEID_PLAYER)
{
if (victim->ToPlayer()->IsGameMaster())
return false;
}
else
{
if (victim->ToCreature()->IsEvadingAttacks())
return false;
}
// remove SPELL_AURA_MOD_UNATTACKABLE at attack (in case non-interruptible spells stun aura applied also that not let attack)
if (HasAuraType(SPELL_AURA_MOD_UNATTACKABLE))
RemoveAurasByType(SPELL_AURA_MOD_UNATTACKABLE);
if (m_attacking)
{
if (m_attacking == victim)
{
// switch to melee attack from ranged/magic
if (meleeAttack)
{
if (!HasUnitState(UNIT_STATE_MELEE_ATTACKING))
{
AddUnitState(UNIT_STATE_MELEE_ATTACKING);
SendMeleeAttackStart(victim);
return true;
}
}
else if (HasUnitState(UNIT_STATE_MELEE_ATTACKING))
{
ClearUnitState(UNIT_STATE_MELEE_ATTACKING);
SendMeleeAttackStop(victim);
return true;
}
return false;
}
// switch target
InterruptSpell(CURRENT_MELEE_SPELL);
if (!meleeAttack)
ClearUnitState(UNIT_STATE_MELEE_ATTACKING);
}
if (m_attacking)
m_attacking->_removeAttacker(this);
m_attacking = victim;
m_attacking->_addAttacker(this);
// Set our target
SetTarget(victim->GetGUID());
if (meleeAttack)
AddUnitState(UNIT_STATE_MELEE_ATTACKING);
// set position before any AI calls/assistance
//if (GetTypeId() == TYPEID_UNIT)
// ToCreature()->SetCombatStartPosition(GetPositionX(), GetPositionY(), GetPositionZ());
if (creature && !IsControlledByPlayer())
{
EngageWithTarget(victim); // ensure that anything we're attacking has threat
creature->SendAIReaction(AI_REACTION_HOSTILE);
creature->CallAssistance();
// Remove emote and stand state - will be restored on creature reset
SetEmoteState(EMOTE_ONESHOT_NONE);
SetStandState(UNIT_STAND_STATE_STAND);
}
// delay offhand weapon attack by 50% of the base attack time
if (haveOffhandWeapon() && GetTypeId() != TYPEID_PLAYER)
setAttackTimer(OFF_ATTACK, std::max(getAttackTimer(OFF_ATTACK), getAttackTimer(BASE_ATTACK) + uint32(CalculatePct(GetBaseAttackTime(BASE_ATTACK), 50))));
if (meleeAttack)
SendMeleeAttackStart(victim);
// Let the pet know we've started attacking someting. Handles melee attacks only
// Spells such as auto-shot and others handled in WorldSession::HandleCastSpellOpcode
if (GetTypeId() == TYPEID_PLAYER)
{
for (Unit* controlled : m_Controlled)
if (Creature* cControlled = controlled->ToCreature())
if (CreatureAI* controlledAI = cControlled->AI())
controlledAI->OwnerAttacked(victim);
}
return true;
}
bool Unit::AttackStop()
{
if (!m_attacking)
return false;
Unit* victim = m_attacking;
m_attacking->_removeAttacker(this);
m_attacking = nullptr;
// Clear our target
SetTarget(ObjectGuid::Empty);
ClearUnitState(UNIT_STATE_MELEE_ATTACKING);
InterruptSpell(CURRENT_MELEE_SPELL);
// reset only at real combat stop
if (Creature* creature = ToCreature())
{
creature->SetNoCallAssistance(false);
}
SendMeleeAttackStop(victim);
return true;
}
void Unit::ValidateAttackersAndOwnTarget()
{
// iterate attackers
UnitVector toRemove;
AttackerSet const& attackers = getAttackers();
for (Unit* attacker : attackers)
if (!attacker->IsValidAttackTarget(this))
toRemove.push_back(attacker);
for (Unit* attacker : toRemove)
attacker->AttackStop();
// remove our own victim
if (Unit* victim = GetVictim())
if (!IsValidAttackTarget(victim))
AttackStop();
}
void Unit::CombatStop(bool includingCast, bool mutualPvP, bool (*unitFilter)(Unit const* otherUnit))
{
if (includingCast && IsNonMeleeSpellCast(false))
InterruptNonMeleeSpells(false);
AttackStop();
if (!unitFilter)
RemoveAllAttackers();
else
{
std::vector attackersToRemove;
attackersToRemove.reserve(m_attackers.size());
std::copy_if(m_attackers.begin(), m_attackers.end(), std::back_inserter(attackersToRemove), unitFilter);
for (Unit* attacker : attackersToRemove)
attacker->AttackStop();
}
if (GetTypeId() == TYPEID_PLAYER)
ToPlayer()->SendAttackSwingCancelAttack(); // melee and ranged forced attack cancel
m_combatManager.EndAllPvECombat(unitFilter);
if (mutualPvP)
m_combatManager.EndAllPvPCombat(unitFilter);
else // vanish and brethren are weird
m_combatManager.SuppressPvPCombat(unitFilter);
}
void Unit::CombatStopWithPets(bool includingCast)
{
CombatStop(includingCast);
for (Unit* minion : m_Controlled)
minion->CombatStop(includingCast);
}
bool Unit::isAttackingPlayer() const
{
if (HasUnitState(UNIT_STATE_ATTACK_PLAYER))
return true;
for (ControlList::const_iterator itr = m_Controlled.begin(); itr != m_Controlled.end(); ++itr)
if ((*itr)->isAttackingPlayer())
return true;
for (uint8 i = 0; i < MAX_SUMMON_SLOT; ++i)
if (!m_SummonSlot[i].IsEmpty())
if (Creature* summon = GetMap()->GetCreature(m_SummonSlot[i]))
if (summon->isAttackingPlayer())
return true;
return false;
}
void Unit::RemoveAllAttackers()
{
while (!m_attackers.empty())
{
AttackerSet::iterator iter = m_attackers.begin();
if (!(*iter)->AttackStop())
{
TC_LOG_ERROR("entities.unit", "WORLD: Unit has an attacker that isn't attacking it!");
m_attackers.erase(iter);
}
}
}
void Unit::ModifyAuraState(AuraStateType flag, bool apply)
{
uint32 mask = 1 << (flag - 1);
if (apply)
{
if (!(*m_unitData->AuraState & mask))
{
SetUpdateFieldFlagValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::AuraState), mask);
if (GetTypeId() == TYPEID_PLAYER)
{
PlayerSpellMap const& sp_list = ToPlayer()->GetSpellMap();
for (PlayerSpellMap::const_iterator itr = sp_list.begin(); itr != sp_list.end(); ++itr)
{
if (itr->second.state == PLAYERSPELL_REMOVED || itr->second.disabled)
continue;
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(itr->first, DIFFICULTY_NONE);
if (!spellInfo || !spellInfo->IsPassive())
continue;
if (spellInfo->CasterAuraState == uint32(flag))
CastSpell(this, itr->first, true);
}
}
else if (Pet* pet = ToCreature()->ToPet())
{
for (PetSpellMap::const_iterator itr = pet->m_spells.begin(); itr != pet->m_spells.end(); ++itr)
{
if (itr->second.state == PETSPELL_REMOVED)
continue;
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(itr->first, DIFFICULTY_NONE);
if (!spellInfo || !spellInfo->IsPassive())
continue;
if (spellInfo->CasterAuraState == uint32(flag))
CastSpell(this, itr->first, true);
}
}
}
}
else
{
if (*m_unitData->AuraState & mask)
{
RemoveUpdateFieldFlagValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::AuraState), mask);
Unit::AuraApplicationMap& tAuras = GetAppliedAuras();
for (Unit::AuraApplicationMap::iterator itr = tAuras.begin(); itr != tAuras.end();)
{
SpellInfo const* spellProto = itr->second->GetBase()->GetSpellInfo();
if (itr->second->GetBase()->GetCasterGUID() == GetGUID() && spellProto->CasterAuraState == uint32(flag) && (spellProto->IsPassive() || flag != AURA_STATE_ENRAGED))
RemoveAura(itr);
else
++itr;
}
}
}
}
uint32 Unit::BuildAuraStateUpdateForTarget(Unit const* target) const
{
uint32 auraStates = *m_unitData->AuraState & ~(PER_CASTER_AURA_STATE_MASK);
for (AuraStateAurasMap::const_iterator itr = m_auraStateAuras.begin(); itr != m_auraStateAuras.end(); ++itr)
if ((1 << (itr->first - 1)) & PER_CASTER_AURA_STATE_MASK)
if (itr->second->GetBase()->GetCasterGUID() == target->GetGUID())
auraStates |= (1 << (itr->first - 1));
return auraStates;
}
bool Unit::HasAuraState(AuraStateType flag, SpellInfo const* spellProto, Unit const* Caster) const
{
if (Caster)
{
if (spellProto)
{
if (Caster->HasAuraTypeWithAffectMask(SPELL_AURA_ABILITY_IGNORE_AURASTATE, spellProto))
return true;
}
// Check per caster aura state
// If aura with aurastate by caster not found return false
if ((1 << (flag - 1)) & PER_CASTER_AURA_STATE_MASK)
{
AuraStateAurasMapBounds range = m_auraStateAuras.equal_range(flag);
for (AuraStateAurasMap::const_iterator itr = range.first; itr != range.second; ++itr)
if (itr->second->GetBase()->GetCasterGUID() == Caster->GetGUID())
return true;
return false;
}
}
return (*m_unitData->AuraState & (1 << (flag - 1))) != 0;
}
void Unit::SetOwnerGUID(ObjectGuid owner)
{
if (GetOwnerGUID() == owner)
return;
SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::SummonedBy), owner);
if (!owner)
return;
// Update owner dependent fields
Player* player = ObjectAccessor::GetPlayer(*this, owner);
if (!player || !player->HaveAtClient(this)) // if player cannot see this unit yet, he will receive needed data with create object
return;
UpdateData udata(GetMapId());
WorldPacket packet;
BuildValuesUpdateBlockForPlayerWithFlag(&udata, UF::UpdateFieldFlag::Owner, player);
udata.BuildPacket(&packet);
player->SendDirectMessage(&packet);
}
Unit* Unit::GetDemonCreator() const
{
return ObjectAccessor::GetUnit(*this, GetDemonCreatorGUID());
}
Player* Unit::GetDemonCreatorPlayer() const
{
return ObjectAccessor::GetPlayer(*this, GetDemonCreatorGUID());
}
Player* Unit::GetControllingPlayer() const
{
ObjectGuid guid = GetCharmerOrOwnerGUID();
if (!guid.IsEmpty())
{
if (Unit* master = ObjectAccessor::GetUnit(*this, guid))
return master->GetControllingPlayer();
return nullptr;
}
else
return const_cast(ToPlayer());
}
Minion* Unit::GetFirstMinion() const
{
ObjectGuid pet_guid = GetMinionGUID();
if (!pet_guid.IsEmpty())
{
if (Creature* pet = ObjectAccessor::GetCreatureOrPetOrVehicle(*this, pet_guid))
if (pet->HasUnitTypeMask(UNIT_MASK_MINION))
return (Minion*)pet;
TC_LOG_ERROR("entities.unit", "Unit::GetFirstMinion: Minion {} not exist.", pet_guid.ToString());
const_cast(this)->SetMinionGUID(ObjectGuid::Empty);
}
return nullptr;
}
Guardian* Unit::GetGuardianPet() const
{
ObjectGuid pet_guid = GetPetGUID();
if (!pet_guid.IsEmpty())
{
if (Creature* pet = ObjectAccessor::GetCreatureOrPetOrVehicle(*this, pet_guid))
if (pet->HasUnitTypeMask(UNIT_MASK_GUARDIAN))
return (Guardian*)pet;
TC_LOG_FATAL("entities.unit", "Unit::GetGuardianPet: Guardian {} not exist.", pet_guid.ToString());
const_cast(this)->SetPetGUID(ObjectGuid::Empty);
}
return nullptr;
}
void Unit::SetMinion(Minion *minion, bool apply)
{
TC_LOG_DEBUG("entities.unit", "SetMinion {} for {}, apply {}", minion->GetEntry(), GetEntry(), apply);
if (apply)
{
if (!minion->GetOwnerGUID().IsEmpty())
{
TC_LOG_FATAL("entities.unit", "SetMinion: Minion {} is not the minion of owner {}", minion->GetEntry(), GetEntry());
return;
}
if (!IsInWorld())
{
TC_LOG_FATAL("entities.unit", "SetMinion: Minion being added to owner not in world. Minion: {}, Owner: {}", minion->GetGUID().ToString(), GetDebugInfo());
return;
}
minion->SetOwnerGUID(GetGUID());
m_Controlled.insert(minion);
if (GetTypeId() == TYPEID_PLAYER)
{
minion->m_ControlledByPlayer = true;
minion->SetUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED);
}
// Can only have one pet. If a new one is summoned, dismiss the old one.
if (minion->IsGuardianPet())
{
if (Guardian* oldPet = GetGuardianPet())
{
if (oldPet != minion && (oldPet->IsPet() || minion->IsPet() || oldPet->GetEntry() != minion->GetEntry()))
{
// remove existing minion pet
if (Pet* oldPetAsPet = oldPet->ToPet())
oldPetAsPet->Remove(PET_SAVE_NOT_IN_SLOT);
else
oldPet->UnSummon();
SetPetGUID(minion->GetGUID());
SetMinionGUID(ObjectGuid::Empty);
}
}
else
{
SetPetGUID(minion->GetGUID());
SetMinionGUID(ObjectGuid::Empty);
}
}
if (minion->HasUnitTypeMask(UNIT_MASK_CONTROLABLE_GUARDIAN))
{
if (GetMinionGUID().IsEmpty())
SetMinionGUID(minion->GetGUID());
}
SummonPropertiesEntry const* properties = minion->m_Properties;
if (properties && SummonTitle(properties->Title) == SummonTitle::Companion)
{
SetCritterGUID(minion->GetGUID());
if (Player const* thisPlayer = ToPlayer())
{
if (properties->GetFlags().HasFlag(SummonPropertiesFlags::SummonFromBattlePetJournal))
{
if (BattlePets::BattlePet const* pet = thisPlayer->GetSession()->GetBattlePetMgr()->GetPet(thisPlayer->GetSummonedBattlePetGUID()))
{
minion->SetBattlePetCompanionGUID(thisPlayer->GetSummonedBattlePetGUID());
minion->SetBattlePetCompanionNameTimestamp(pet->NameTimestamp);
minion->SetWildBattlePetLevel(pet->PacketInfo.Level);
if (uint32 display = pet->PacketInfo.DisplayID)
minion->SetDisplayId(display, true);
}
}
}
}
// PvP, FFAPvP
minion->ReplaceAllPvpFlags(GetPvpFlags());
// FIXME: hack, speed must be set only at follow
if (GetTypeId() == TYPEID_PLAYER && minion->IsPet())
for (uint8 i = 0; i < MAX_MOVE_TYPE; ++i)
minion->SetSpeedRate(UnitMoveType(i), m_speed_rate[i]);
// Send infinity cooldown - client does that automatically but after relog cooldown needs to be set again
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(minion->m_unitData->CreatedBySpell, DIFFICULTY_NONE);
if (spellInfo && (spellInfo->IsCooldownStartedOnEvent()))
GetSpellHistory()->StartCooldown(spellInfo, 0, nullptr, true);
}
else
{
if (minion->GetOwnerGUID() != GetGUID())
{
TC_LOG_FATAL("entities.unit", "SetMinion: Minion {} is not the minion of owner {}", minion->GetEntry(), GetEntry());
return;
}
m_Controlled.erase(minion);
if (minion->m_Properties && SummonTitle(minion->m_Properties->Title) == SummonTitle::Companion)
if (GetCritterGUID() == minion->GetGUID())
SetCritterGUID(ObjectGuid::Empty);
if (minion->IsGuardianPet())
{
if (GetPetGUID() == minion->GetGUID())
SetPetGUID(ObjectGuid::Empty);
}
else if (minion->IsTotem())
{
// All summoned by totem minions must disappear when it is removed.
if (SpellInfo const* spInfo = sSpellMgr->GetSpellInfo(minion->ToTotem()->GetSpell(), DIFFICULTY_NONE))
{
for (SpellEffectInfo const& spellEffectInfo : spInfo->GetEffects())
{
if (!spellEffectInfo.IsEffect(SPELL_EFFECT_SUMMON))
continue;
RemoveAllMinionsByEntry(spellEffectInfo.MiscValue);
}
}
}
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(minion->m_unitData->CreatedBySpell, DIFFICULTY_NONE);
// Remove infinity cooldown
if (spellInfo && (spellInfo->IsCooldownStartedOnEvent()))
GetSpellHistory()->SendCooldownEvent(spellInfo);
//if (minion->HasUnitTypeMask(UNIT_MASK_GUARDIAN))
{
if (GetMinionGUID() == minion->GetGUID())
{
SetMinionGUID(ObjectGuid::Empty);
// Check if there is another minion
for (ControlList::iterator itr = m_Controlled.begin(); itr != m_Controlled.end(); ++itr)
{
// do not use this check, creature do not have charm guid
//if (GetCharmedGUID() == (*itr)->GetGUID())
if (GetGUID() == (*itr)->GetCharmerGUID())
continue;
//ASSERT((*itr)->GetOwnerGUID() == GetGUID());
if ((*itr)->GetOwnerGUID() != GetGUID())
{
OutDebugInfo();
(*itr)->OutDebugInfo();
ABORT();
}
ASSERT((*itr)->GetTypeId() == TYPEID_UNIT);
if (!(*itr)->HasUnitTypeMask(UNIT_MASK_CONTROLABLE_GUARDIAN))
continue;
SetMinionGUID((*itr)->GetGUID());
// show another pet bar if there is no charm bar
if (GetTypeId() == TYPEID_PLAYER && !GetCharmedGUID())
{
if ((*itr)->IsPet())
ToPlayer()->PetSpellInitialize();
else
ToPlayer()->CharmSpellInitialize();
}
break;
}
}
}
}
UpdatePetCombatState();
}
void Unit::GetAllMinionsByEntry(std::list& Minions, uint32 entry)
{
for (Unit::ControlList::iterator itr = m_Controlled.begin(); itr != m_Controlled.end(); ++itr)
{
Unit* unit = *itr;
if (unit->GetEntry() == entry && unit->IsSummon()) // minion, actually
Minions.push_back(unit->ToTempSummon());
}
}
void Unit::RemoveAllMinionsByEntry(uint32 entry)
{
for (Unit::ControlList::iterator itr = m_Controlled.begin(); itr != m_Controlled.end();)
{
Unit* unit = *itr;
++itr;
if (unit->GetEntry() == entry && unit->GetTypeId() == TYPEID_UNIT
&& unit->ToCreature()->IsSummon()) // minion, actually
unit->ToTempSummon()->UnSummon();
// i think this is safe because i have never heard that a despawned minion will trigger a same minion
}
}
void Unit::SetCharm(Unit* charm, bool apply)
{
if (apply)
{
if (GetTypeId() == TYPEID_PLAYER)
{
ASSERT(GetCharmedGUID().IsEmpty(),
"Player %s is trying to charm unit %u, but it already has a charmed unit %s", GetName().c_str(), charm->GetEntry(), GetCharmedGUID().ToString().c_str());
SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::Charm), charm->GetGUID());
m_charmed = charm;
charm->m_ControlledByPlayer = true;
/// @todo maybe we can use this flag to check if controlled by player
charm->SetUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED);
}
else
charm->m_ControlledByPlayer = false;
// PvP, FFAPvP
charm->ReplaceAllPvpFlags(GetPvpFlags());
ASSERT(charm->GetCharmerGUID().IsEmpty(),
"Unit %u is being charmed, but it already has a charmer %s", charm->GetEntry(), charm->GetCharmerGUID().ToString().c_str());
charm->SetUpdateFieldValue(charm->m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::CharmedBy), GetGUID());
charm->m_charmer = this;
_isWalkingBeforeCharm = charm->IsWalking();
if (_isWalkingBeforeCharm)
charm->SetWalk(false);
m_Controlled.insert(charm);
}
else
{
charm->ClearUnitState(UNIT_STATE_CHARMED);
if (GetTypeId() == TYPEID_PLAYER)
{
ASSERT(GetCharmedGUID() == charm->GetGUID(),
"Player %s is trying to uncharm unit %u, but it has another charmed unit %s", GetName().c_str(), charm->GetEntry(), GetCharmedGUID().ToString().c_str());
SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::Charm), ObjectGuid::Empty);
m_charmed = nullptr;
}
ASSERT(charm->GetCharmerGUID() == GetGUID(),
"Unit %u is being uncharmed, but it has another charmer %s", charm->GetEntry(), charm->GetCharmerGUID().ToString().c_str());
charm->SetUpdateFieldValue(charm->m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::CharmedBy), ObjectGuid::Empty);
charm->m_charmer = nullptr;
if (charm->GetTypeId() == TYPEID_PLAYER)
{
charm->m_ControlledByPlayer = true;
charm->SetUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED);
charm->ToPlayer()->UpdatePvPState();
}
else if (Player* player = charm->GetCharmerOrOwnerPlayerOrPlayerItself())
{
charm->m_ControlledByPlayer = true;
charm->SetUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED);
charm->ReplaceAllPvpFlags(player->GetPvpFlags());
}
else
{
charm->m_ControlledByPlayer = false;
charm->RemoveUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED);
charm->ReplaceAllPvpFlags(UNIT_BYTE2_FLAG_NONE);
}
if (charm->IsWalking() != _isWalkingBeforeCharm)
charm->SetWalk(_isWalkingBeforeCharm);
if (charm->GetTypeId() == TYPEID_PLAYER
|| !charm->ToCreature()->HasUnitTypeMask(UNIT_MASK_MINION)
|| charm->GetOwnerGUID() != GetGUID())
{
m_Controlled.erase(charm);
}
}
UpdatePetCombatState();
}
/*static*/ void Unit::DealHeal(HealInfo& healInfo)
{
int32 gain = 0;
Unit* healer = healInfo.GetHealer();
Unit* victim = healInfo.GetTarget();
uint32 addhealth = healInfo.GetHeal();
if (UnitAI* victimAI = victim->GetAI())
victimAI->HealReceived(healer, addhealth);
if (UnitAI* healerAI = healer ? healer->GetAI() : nullptr)
healerAI->HealDone(victim, addhealth);
if (addhealth)
gain = victim->ModifyHealth(int32(addhealth));
// Hook for OnHeal Event
sScriptMgr->OnHeal(healer, victim, (uint32&)gain);
Unit* unit = healer;
if (healer && healer->GetTypeId() == TYPEID_UNIT && healer->IsTotem())
unit = healer->GetOwner();
if (unit)
{
if (Player* player = unit->ToPlayer())
{
if (!healInfo.GetSpellInfo() || !healInfo.GetSpellInfo()->HasAttribute(SPELL_ATTR7_DO_NOT_COUNT_FOR_PVP_SCOREBOARD))
if (Battleground* bg = player->GetBattleground())
bg->UpdatePlayerScore(player, SCORE_HEALING_DONE, gain);
// use the actual gain, as the overheal shall not be counted, skip gain 0 (it ignored anyway in to criteria)
if (gain)
player->UpdateCriteria(CriteriaType::HealingDone, gain, 0, 0, victim);
player->UpdateCriteria(CriteriaType::HighestHealCast, addhealth);
}
}
if (Player* player = victim->ToPlayer())
{
player->UpdateCriteria(CriteriaType::TotalHealReceived, gain);
player->UpdateCriteria(CriteriaType::HighestHealReceived, addhealth);
}
if (gain)
healInfo.SetEffectiveHeal(gain > 0 ? static_cast(gain) : 0UL);
}
bool Unit::IsMagnet() const
{
// Grounding Totem
if (*m_unitData->CreatedBySpell == 8177) /// @todo: find a more generic solution
return true;
return false;
}
Unit* Unit::GetMeleeHitRedirectTarget(Unit* victim, SpellInfo const* spellInfo /*= nullptr*/)
{
AuraEffectList const& interceptAuras = victim->GetAuraEffectsByType(SPELL_AURA_INTERCEPT_MELEE_RANGED_ATTACKS);
for (AuraEffectList::const_iterator i = interceptAuras.begin(); i != interceptAuras.end(); ++i)
{
if (Unit* magnet = (*i)->GetBase()->GetCaster())
if (IsValidAttackTarget(magnet, spellInfo) && magnet->IsWithinLOSInMap(this)
&& (!spellInfo || (spellInfo->CheckExplicitTarget(this, magnet) == SPELL_CAST_OK
&& spellInfo->CheckTarget(this, magnet, false) == SPELL_CAST_OK)))
{
(*i)->GetBase()->DropCharge(AURA_REMOVE_BY_EXPIRE);
return magnet;
}
}
return victim;
}
Unit* Unit::GetFirstControlled() const
{
// Sequence: charmed, pet, other guardians
Unit* unit = GetCharmed();
if (!unit)
{
ObjectGuid guid = GetMinionGUID();
if (!guid.IsEmpty())
unit = ObjectAccessor::GetUnit(*this, guid);
}
return unit;
}
void Unit::RemoveAllControlled()
{
// possessed pet and vehicle
if (GetTypeId() == TYPEID_PLAYER)
ToPlayer()->StopCastingCharm();
while (!m_Controlled.empty())
{
Unit* target = *m_Controlled.begin();
m_Controlled.erase(m_Controlled.begin());
if (target->GetCharmerGUID() == GetGUID())
target->RemoveCharmAuras();
else if (target->GetOwnerGUID() == GetGUID() && target->IsSummon())
target->ToTempSummon()->UnSummon();
else
TC_LOG_ERROR("entities.unit", "Unit {} is trying to release unit {} which is neither charmed nor owned by it", GetEntry(), target->GetEntry());
}
if (!GetPetGUID().IsEmpty())
TC_LOG_FATAL("entities.unit", "Unit {} is not able to release its pet {}", GetEntry(), GetPetGUID().ToString());
if (!GetMinionGUID().IsEmpty())
TC_LOG_FATAL("entities.unit", "Unit {} is not able to release its minion {}", GetEntry(), GetMinionGUID().ToString());
if (!GetCharmedGUID().IsEmpty())
TC_LOG_FATAL("entities.unit", "Unit {} is not able to release its charm {}", GetEntry(), GetCharmedGUID().ToString());
if (!IsPet()) // pets don't use the flag for this
RemoveUnitFlag(UNIT_FLAG_PET_IN_COMBAT); // m_controlled is now empty, so we know none of our minions are in combat
}
bool Unit::isPossessedByPlayer() const
{
return HasUnitState(UNIT_STATE_POSSESSED) && GetCharmerGUID().IsPlayer();
}
bool Unit::isPossessing(Unit* u) const
{
return u->isPossessed() && GetCharmedGUID() == u->GetGUID();
}
bool Unit::isPossessing() const
{
if (Unit* u = GetCharmed())
return u->isPossessed();
else
return false;
}
Unit* Unit::GetNextRandomRaidMemberOrPet(float radius)
{
Player* player = nullptr;
if (GetTypeId() == TYPEID_PLAYER)
player = ToPlayer();
// Should we enable this also for charmed units?
else if (GetTypeId() == TYPEID_UNIT && IsPet())
player = GetOwner()->ToPlayer();
if (!player)
return nullptr;
Group* group = player->GetGroup();
// When there is no group check pet presence
if (!group)
{
// We are pet now, return owner
if (player != this)
return IsWithinDistInMap(player, radius) ? player : nullptr;
Unit* pet = GetGuardianPet();
// No pet, no group, nothing to return
if (!pet)
return nullptr;
// We are owner now, return pet
return IsWithinDistInMap(pet, radius) ? pet : nullptr;
}
std::vector nearMembers;
// reserve place for players and pets because resizing vector every unit push is unefficient (vector is reallocated then)
nearMembers.reserve(group->GetMembersCount() * 2);
for (GroupReference const& itr : group->GetMembers())
{
Player* Target = itr.GetSource();
// IsHostileTo check duel and controlled by enemy
if (Target != this && IsWithinDistInMap(Target, radius) && Target->IsAlive() && !IsHostileTo(Target))
nearMembers.push_back(Target);
// Push player's pet to vector
if (Unit* pet = Target->GetGuardianPet())
if (pet != this && IsWithinDistInMap(pet, radius) && pet->IsAlive() && !IsHostileTo(pet))
nearMembers.push_back(pet);
}
if (nearMembers.empty())
return nullptr;
uint32 randTarget = urand(0, nearMembers.size()-1);
return nearMembers[randTarget];
}
// only called in Player::SetSeer
// so move it to Player?
void Unit::AddPlayerToVision(Player* player)
{
if (m_sharedVision.empty())
{
setActive(true);
SetIsStoredInWorldObjectGridContainer(true);
}
m_sharedVision.push_back(player);
}
// only called in Player::SetSeer
void Unit::RemovePlayerFromVision(Player* player)
{
m_sharedVision.remove(player);
if (m_sharedVision.empty())
{
setActive(false);
SetIsStoredInWorldObjectGridContainer(false);
}
}
void Unit::RemoveBindSightAuras()
{
RemoveAurasByType(SPELL_AURA_BIND_SIGHT);
}
void Unit::RemoveCharmAuras()
{
RemoveAurasByType(SPELL_AURA_MOD_CHARM);
RemoveAurasByType(SPELL_AURA_MOD_POSSESS_PET);
RemoveAurasByType(SPELL_AURA_MOD_POSSESS);
RemoveAurasByType(SPELL_AURA_AOE_CHARM);
}
void Unit::UnsummonAllTotems()
{
for (uint8 i = 0; i < MAX_SUMMON_SLOT; ++i)
{
if (!m_SummonSlot[i])
continue;
if (Creature* OldTotem = GetMap()->GetCreature(m_SummonSlot[i]))
if (OldTotem->IsSummon())
OldTotem->ToTempSummon()->UnSummon();
}
}
void Unit::SendHealSpellLog(HealInfo& healInfo, bool critical /*= false*/)
{
WorldPackets::CombatLog::SpellHealLog spellHealLog;
TC_LOG_DEBUG("spells", "HealSpellLog -- SpellId: {} Caster: {} Target: {} (Health: {} OverHeal: {} Absorbed: {} Crit: {})", healInfo.GetSpellInfo()->Id, healInfo.GetHealer()->GetGUID().ToString(), healInfo.GetTarget()->GetGUID().ToString(),
healInfo.GetHeal(), healInfo.GetHeal() - healInfo.GetEffectiveHeal(), healInfo.GetAbsorb(), critical);
spellHealLog.TargetGUID = healInfo.GetTarget()->GetGUID();
spellHealLog.CasterGUID = healInfo.GetHealer()->GetGUID();
spellHealLog.SpellID = healInfo.GetSpellInfo()->Id;
spellHealLog.Health = healInfo.GetHeal();
spellHealLog.OriginalHeal = healInfo.GetOriginalHeal();
spellHealLog.OverHeal = int32(healInfo.GetHeal()) - healInfo.GetEffectiveHeal();
spellHealLog.Absorbed = healInfo.GetAbsorb();
spellHealLog.Crit = critical;
spellHealLog.LogData.Initialize(healInfo.GetTarget());
SendCombatLogMessage(&spellHealLog);
}
int32 Unit::HealBySpell(HealInfo& healInfo, bool critical /*= false*/)
{
// calculate heal absorb and reduce healing
Unit::CalcHealAbsorb(healInfo);
Unit::DealHeal(healInfo);
SendHealSpellLog(healInfo, critical);
return healInfo.GetEffectiveHeal();
}
void Unit::SendEnergizeSpellLog(Unit* victim, uint32 spellID, int32 damage, int32 overEnergize, Powers powertype)
{
WorldPackets::CombatLog::SpellEnergizeLog data;
data.CasterGUID = GetGUID();
data.TargetGUID = victim->GetGUID();
data.SpellID = spellID;
data.Type = powertype;
data.Amount = damage;
data.OverEnergize = overEnergize;
data.LogData.Initialize(victim);
SendCombatLogMessage(&data);
}
void Unit::EnergizeBySpell(Unit* victim, SpellInfo const* spellInfo, int32 damage, Powers powerType)
{
if (Player* player = victim->ToPlayer())
if (PowerTypeEntry const* powerTypeEntry = sDB2Manager.GetPowerTypeEntry(powerType))
if (powerTypeEntry->GetFlags().HasFlag(PowerTypeFlags::UseRegenInterrupt))
player->InterruptPowerRegen(powerType);
int32 gain = victim->ModifyPower(powerType, damage, false);
int32 overEnergize = damage - gain;
victim->GetThreatManager().ForwardThreatForAssistingMe(this, float(damage) / 2, spellInfo, true);
SendEnergizeSpellLog(victim, spellInfo->Id, gain, overEnergize, powerType);
}
int32 Unit::SpellDamageBonusDone(Unit* victim, SpellInfo const* spellProto, int32 pdamage, DamageEffectType damagetype, SpellEffectInfo const& spellEffectInfo, uint32 stack /*= 1*/, Spell* spell /*= nullptr*/, AuraEffect const* aurEff /*= nullptr*/) const
{
if (!spellProto || !victim)
return pdamage;
int32 DoneTotal = 0;
float DoneTotalMod = 1.0f;
auto callDamageScript = [&](int32& dmg, int32& flatMod, float& pctMod)
{
if (spell)
spell->CallScriptCalcDamageHandlers(spellEffectInfo, victim, dmg, flatMod, pctMod);
else if (aurEff)
aurEff->GetBase()->CallScriptCalcDamageAndHealingHandlers(aurEff, aurEff->GetBase()->GetApplicationOfTarget(victim->GetGUID()), victim, dmg, flatMod, pctMod);
};
// Some spells don't benefit from done mods
if (damagetype == DIRECT_DAMAGE || spellProto->HasAttribute(SPELL_ATTR3_IGNORE_CASTER_MODIFIERS))
{
callDamageScript(pdamage, DoneTotal, DoneTotalMod);
return int32(std::max(float(pdamage + DoneTotal) * DoneTotalMod, 0.0f));
}
// For totems get damage bonus from owner
if (GetTypeId() == TYPEID_UNIT && IsTotem())
if (Unit* owner = GetOwner())
return owner->SpellDamageBonusDone(victim, spellProto, pdamage, damagetype, spellEffectInfo, stack, spell, aurEff);
DoneTotalMod = SpellDamagePctDone(victim, spellProto, damagetype, spellEffectInfo);
// Done fixed damage bonus auras
int32 DoneAdvertisedBenefit = SpellBaseDamageBonusDone(spellProto->GetSchoolMask());
// modify spell power by victim's SPELL_AURA_MOD_DAMAGE_TAKEN auras (eg Amplify/Dampen Magic)
DoneAdvertisedBenefit += victim->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_DAMAGE_TAKEN, spellProto->GetSchoolMask());
// Pets just add their bonus damage to their spell damage
// note that their spell damage is just gain of their own auras
if (HasUnitTypeMask(UNIT_MASK_GUARDIAN))
DoneAdvertisedBenefit += static_cast(this)->GetBonusDamage();
// Check for table values
if (spellEffectInfo.BonusCoefficientFromAP > 0.0f)
{
float ApCoeffMod = spellEffectInfo.BonusCoefficientFromAP;
if (Player* modOwner = GetSpellModOwner())
{
ApCoeffMod *= 100.0f;
modOwner->ApplySpellMod(spellProto, SpellModOp::BonusCoefficient, ApCoeffMod);
ApCoeffMod /= 100.0f;
}
WeaponAttackType const attType = [&]()
{
if ((spellProto->IsRangedWeaponSpell() && spellProto->DmgClass != SPELL_DAMAGE_CLASS_MELEE))
return RANGED_ATTACK;
if (spellProto->HasAttribute(SPELL_ATTR3_REQUIRES_OFF_HAND_WEAPON) && !spellProto->HasAttribute(SPELL_ATTR3_REQUIRES_MAIN_HAND_WEAPON))
return OFF_ATTACK;
return BASE_ATTACK;
}();
float APbonus = float(victim->GetTotalAuraModifier(attType != RANGED_ATTACK ? SPELL_AURA_MELEE_ATTACK_POWER_ATTACKER_BONUS : SPELL_AURA_RANGED_ATTACK_POWER_ATTACKER_BONUS));
APbonus += GetTotalAttackPowerValue(attType);
DoneTotal += int32(stack * ApCoeffMod * APbonus);
}
// Default calculation
if (DoneAdvertisedBenefit)
{
float coeff = spellEffectInfo.BonusCoefficient;
if (Player* modOwner = GetSpellModOwner())
{
coeff *= 100.0f;
modOwner->ApplySpellMod(spellProto, SpellModOp::BonusCoefficient, coeff);
coeff /= 100.0f;
}
DoneTotal += int32(DoneAdvertisedBenefit * coeff * stack);
}
callDamageScript(pdamage, DoneTotal, DoneTotalMod);
float tmpDamage = float(pdamage + DoneTotal) * DoneTotalMod;
// apply spellmod to Done damage (flat and pct)
if (Player* modOwner = GetSpellModOwner())
modOwner->ApplySpellMod(spellProto, damagetype == DOT ? SpellModOp::PeriodicHealingAndDamage : SpellModOp::HealingAndDamage, tmpDamage);
return int32(std::max(tmpDamage, 0.0f));
}
float Unit::SpellDamagePctDone(Unit* victim, SpellInfo const* spellProto, DamageEffectType damagetype, SpellEffectInfo const& spellEffectInfo) const
{
if (!spellProto || !victim || damagetype == DIRECT_DAMAGE)
return 1.0f;
// Some spells don't benefit from done mods
if (spellProto->HasAttribute(SPELL_ATTR3_IGNORE_CASTER_MODIFIERS))
return 1.0f;
// Some spells don't benefit from pct done mods
if (spellProto->HasAttribute(SPELL_ATTR6_IGNORE_CASTER_DAMAGE_MODIFIERS))
return 1.0f;
// For totems get damage bonus from owner
if (GetTypeId() == TYPEID_UNIT && IsTotem())
if (Unit* owner = GetOwner())
return owner->SpellDamagePctDone(victim, spellProto, damagetype, spellEffectInfo);
// Done total percent damage auras
float DoneTotalMod = 1.0f;
// Pet damage?
if (GetTypeId() == TYPEID_UNIT && !IsPet())
DoneTotalMod *= ToCreature()->GetSpellDamageMod(ToCreature()->GetCreatureTemplate()->Classification);
// Versatility
if (Player* modOwner = GetSpellModOwner())
AddPct(DoneTotalMod, modOwner->GetRatingBonusValue(CR_VERSATILITY_DAMAGE_DONE) + modOwner->GetTotalAuraModifier(SPELL_AURA_MOD_VERSATILITY));
float maxModDamagePercentSchool = 0.0f;
if (Player const* thisPlayer = ToPlayer())
{
for (uint32 i = 0; i < MAX_SPELL_SCHOOL; ++i)
if (spellProto->GetSchoolMask() & (1 << i))
maxModDamagePercentSchool = std::max(maxModDamagePercentSchool, thisPlayer->m_activePlayerData->ModDamageDonePercent[i]);
}
else
maxModDamagePercentSchool = GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_DAMAGE_PERCENT_DONE, spellProto->GetSchoolMask());
DoneTotalMod *= maxModDamagePercentSchool;
uint32 creatureTypeMask = victim->GetCreatureTypeMask();
DoneTotalMod *= GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_DAMAGE_DONE_VERSUS, creatureTypeMask);
// bonus against aurastate
DoneTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_DAMAGE_DONE_VERSUS_AURASTATE, [victim](AuraEffect const* aurEff) -> bool
{
if (victim->HasAuraState(static_cast(aurEff->GetMiscValue())))
return true;
return false;
});
// bonus against target aura mechanic
DoneTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_DAMAGE_PERCENT_DONE_BY_TARGET_AURA_MECHANIC, [victim](AuraEffect const* aurEff) -> bool
{
if (victim->HasAuraWithMechanic(UI64LIT(1) << aurEff->GetMiscValue()))
return true;
return false;
});
// Add SPELL_AURA_MOD_DAMAGE_DONE_FOR_MECHANIC percent bonus
if (spellEffectInfo.Mechanic)
AddPct(DoneTotalMod, GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_DAMAGE_DONE_FOR_MECHANIC, spellEffectInfo.Mechanic));
else if (spellProto->Mechanic)
AddPct(DoneTotalMod, GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_DAMAGE_DONE_FOR_MECHANIC, spellProto->Mechanic));
// Custom scripted damage
switch (spellProto->SpellFamilyName)
{
case SPELLFAMILY_MAGE:
// Ice Lance (no unique family flag)
if (spellProto->Id == 228598)
if (victim->HasAuraState(AURA_STATE_FROZEN, spellProto, this))
DoneTotalMod *= 3.0f;
break;
case SPELLFAMILY_WARLOCK:
// Shadow Bite (30% increase from each dot)
if (spellProto->SpellFamilyFlags[1] & 0x00400000 && IsPet())
if (uint8 count = victim->GetDoTsByCaster(GetOwnerGUID()))
AddPct(DoneTotalMod, 30 * count);
// Drain Soul - increased damage for targets under 20% HP
if (spellProto->Id == 198590)
if (HasAuraState(AURA_STATE_WOUNDED_20_PERCENT))
DoneTotalMod *= 2;
break;
}
return DoneTotalMod;
}
int32 Unit::SpellDamageBonusTaken(Unit* caster, SpellInfo const* spellProto, int32 pdamage, DamageEffectType damagetype) const
{
if (!spellProto || damagetype == DIRECT_DAMAGE)
return pdamage;
float TakenTotalMod = 1.0f;
// Mod damage from spell mechanic
if (uint64 mechanicMask = spellProto->GetAllEffectsMechanicMask())
{
TakenTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_MECHANIC_DAMAGE_TAKEN_PERCENT, [mechanicMask](AuraEffect const* aurEff) -> bool
{
if (mechanicMask & uint64(UI64LIT(1) << aurEff->GetMiscValue()))
return true;
return false;
});
}
if (AuraEffect const* cheatDeath = GetAuraEffect(45182, EFFECT_0))
if (cheatDeath->GetMiscValue() & SPELL_SCHOOL_MASK_NORMAL)
AddPct(TakenTotalMod, cheatDeath->GetAmount());
// Spells with SPELL_ATTR4_IGNORE_DAMAGE_TAKEN_MODIFIERS should only benefit from mechanic damage mod auras.
if (!spellProto->HasAttribute(SPELL_ATTR4_IGNORE_DAMAGE_TAKEN_MODIFIERS))
{
// Versatility
if (Player* modOwner = GetSpellModOwner())
{
// only 50% of SPELL_AURA_MOD_VERSATILITY for damage reduction
float versaBonus = modOwner->GetTotalAuraModifier(SPELL_AURA_MOD_VERSATILITY) / 2.0f;
AddPct(TakenTotalMod, -(modOwner->GetRatingBonusValue(CR_VERSATILITY_DAMAGE_TAKEN) + versaBonus));
}
// from positive and negative SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN
// multiplicative bonus, for example Dispersion + Shadowform (0.10*0.85=0.085)
TakenTotalMod *= GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN, spellProto->GetSchoolMask());
TakenTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_DAMAGE_TAKEN_BY_LABEL, [spellProto](AuraEffect const* aurEff) -> bool
{
return spellProto->HasLabel(aurEff->GetMiscValue());
});
// From caster spells
if (caster)
{
TakenTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_SCHOOL_MASK_DAMAGE_FROM_CASTER, [caster, spellProto](AuraEffect const* aurEff) -> bool
{
return aurEff->GetCasterGUID() == caster->GetGUID() && (aurEff->GetMiscValue() & spellProto->GetSchoolMask());
});
TakenTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_SPELL_DAMAGE_FROM_CASTER, [caster, spellProto](AuraEffect const* aurEff) -> bool
{
return aurEff->GetCasterGUID() == caster->GetGUID() && aurEff->IsAffectingSpell(spellProto);
});
TakenTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_SPELL_DAMAGE_FROM_CASTER_BY_LABEL, [caster, spellProto](AuraEffect const* aurEff) -> bool
{
return aurEff->GetCasterGUID() == caster->GetGUID() && spellProto->HasLabel(aurEff->GetMiscValue());
});
}
if (damagetype == DOT)
{
TakenTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_PERIODIC_DAMAGE_TAKEN, [spellProto](AuraEffect const* aurEff) -> bool
{
return aurEff->GetMiscValue() & spellProto->GetSchoolMask();
});
}
}
// Sanctified Wrath (bypass damage reduction)
if (caster && TakenTotalMod < 1.0f)
{
float damageReduction = 1.0f - TakenTotalMod;
Unit::AuraEffectList const& casterIgnoreResist = caster->GetAuraEffectsByType(SPELL_AURA_MOD_IGNORE_TARGET_RESIST);
for (AuraEffect const* aurEff : casterIgnoreResist)
{
if (!(aurEff->GetMiscValue() & spellProto->GetSchoolMask()))
continue;
AddPct(damageReduction, -aurEff->GetAmount());
}
TakenTotalMod = 1.0f - damageReduction;
}
float tmpDamage = pdamage * TakenTotalMod;
return int32(std::max(tmpDamage, 0.0f));
}
int32 Unit::SpellBaseDamageBonusDone(SpellSchoolMask schoolMask) const
{
if (Player const* thisPlayer = ToPlayer())
{
float overrideSP = thisPlayer->m_activePlayerData->OverrideSpellPowerByAPPercent;
if (overrideSP > 0.0f)
return int32(CalculatePct(GetTotalAttackPowerValue(BASE_ATTACK), overrideSP) + 0.5f);
}
int32 DoneAdvertisedBenefit = GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_DAMAGE_DONE, schoolMask);
if (GetTypeId() == TYPEID_PLAYER)
{
// Base value
DoneAdvertisedBenefit += ToPlayer()->GetBaseSpellPowerBonus();
// Check if we are ever using mana - PaperDollFrame.lua
if (GetPowerIndex(POWER_MANA) != MAX_POWERS)
DoneAdvertisedBenefit += std::max(0, int32(GetStat(STAT_INTELLECT))); // spellpower from intellect
// Damage bonus from stats
AuraEffectList const& mDamageDoneOfStatPercent = GetAuraEffectsByType(SPELL_AURA_MOD_SPELL_DAMAGE_OF_STAT_PERCENT);
for (AuraEffect const* aurEff : mDamageDoneOfStatPercent)
{
if ((aurEff->GetMiscValue() & schoolMask) != 0)
{
// stat used stored in miscValueB for this aura
Stats const usedStat = static_cast(aurEff->GetMiscValueB());
DoneAdvertisedBenefit += static_cast(CalculatePct(GetStat(usedStat), aurEff->GetAmount()));
}
}
}
return DoneAdvertisedBenefit;
}
float Unit::SpellCritChanceDone(Spell* spell, AuraEffect const* aurEff, SpellSchoolMask schoolMask, WeaponAttackType attackType /*= BASE_ATTACK*/) const
{
SpellInfo const* spellInfo = spell ? spell->GetSpellInfo() : aurEff->GetSpellInfo();
//! Mobs can't crit with spells. (Except player controlled)
if (GetTypeId() == TYPEID_UNIT && !GetSpellModOwner())
return 0.0f;
// not critting spell
if (spell && !spellInfo->HasAttribute(SPELL_ATTR0_CU_CAN_CRIT))
return 0.0f;
float crit_chance = 0.0f;
switch (spellInfo->DmgClass)
{
case SPELL_DAMAGE_CLASS_NONE:
case SPELL_DAMAGE_CLASS_MAGIC:
{
auto getPhysicalCritChance = [&]
{
return GetUnitCriticalChanceDone(attackType);
};
auto getMagicCritChance = [&]
{
if (Player const* thisPlayer = ToPlayer())
return *thisPlayer->m_activePlayerData->SpellCritPercentage;
return m_baseSpellCritChance;
};
if (schoolMask & SPELL_SCHOOL_MASK_NORMAL)
crit_chance = std::max(crit_chance, getPhysicalCritChance());
if (schoolMask & ~SPELL_SCHOOL_MASK_NORMAL)
crit_chance = std::max(crit_chance, getMagicCritChance());
break;
}
case SPELL_DAMAGE_CLASS_MELEE:
case SPELL_DAMAGE_CLASS_RANGED:
{
crit_chance += GetUnitCriticalChanceDone(attackType);
break;
}
default:
return 0.0f;
}
// percent done
// only players use intelligence for critical chance computations
if (Player* modOwner = GetSpellModOwner())
modOwner->ApplySpellMod(spellInfo, SpellModOp::CritChance, crit_chance);
return std::max(crit_chance, 0.0f);
}
float Unit::SpellCritChanceTaken(Unit const* caster, Spell* spell, AuraEffect const* aurEff, SpellSchoolMask /*schoolMask*/, float doneChance, WeaponAttackType attackType /*= BASE_ATTACK*/) const
{
SpellInfo const* spellInfo = spell ? spell->GetSpellInfo() : aurEff->GetSpellInfo();
// not critting spell
if (spell && !spellInfo->HasAttribute(SPELL_ATTR0_CU_CAN_CRIT))
return 0.0f;
float crit_chance = doneChance;
switch (spellInfo->DmgClass)
{
case SPELL_DAMAGE_CLASS_MAGIC:
{
// taken
if (!spellInfo->IsPositive())
{
// Modify critical chance by victim SPELL_AURA_MOD_ATTACKER_SPELL_AND_WEAPON_CRIT_CHANCE
crit_chance += GetTotalAuraModifier(SPELL_AURA_MOD_ATTACKER_SPELL_AND_WEAPON_CRIT_CHANCE);
}
if (caster)
{
// scripted (increase crit chance ... against ... target by x%
AuraEffectList const& mOverrideClassScript = caster->GetAuraEffectsByType(SPELL_AURA_OVERRIDE_CLASS_SCRIPTS);
for (AuraEffect const* aurEff : mOverrideClassScript)
{
if (!aurEff->IsAffectingSpell(spellInfo))
continue;
switch (aurEff->GetMiscValue())
{
case 911: // Shatter
if (HasAuraState(AURA_STATE_FROZEN, spellInfo, caster))
{
crit_chance *= 1.5f;
if (AuraEffect const* eff = aurEff->GetBase()->GetEffect(EFFECT_1))
crit_chance += eff->GetAmount();
}
break;
default:
break;
}
}
// Custom crit by class
switch (spellInfo->SpellFamilyName)
{
case SPELLFAMILY_ROGUE:
// Shiv-applied poisons can't crit
if (caster->FindCurrentSpellBySpellId(5938))
crit_chance = 0.0f;
break;
}
// Spell crit suppression
if (GetTypeId() == TYPEID_UNIT)
{
int32 const levelDiff = static_cast(GetLevelForTarget(caster)) - caster->GetLevel();
crit_chance -= levelDiff * 1.0f;
}
}
break;
}
case SPELL_DAMAGE_CLASS_MELEE:
case SPELL_DAMAGE_CLASS_RANGED:
if (caster)
crit_chance = GetUnitCriticalChanceTaken(caster, attackType, crit_chance);
break;
case SPELL_DAMAGE_CLASS_NONE:
default:
return 0.f;
}
// for this types the bonus was already added in GetUnitCriticalChance, do not add twice
if (caster && spellInfo->DmgClass != SPELL_DAMAGE_CLASS_MELEE && spellInfo->DmgClass != SPELL_DAMAGE_CLASS_RANGED)
{
crit_chance += GetTotalAuraModifier(SPELL_AURA_MOD_CRIT_CHANCE_FOR_CASTER_WITH_ABILITIES, [caster, spellInfo](AuraEffect const* aurEff) -> bool
{
return aurEff->GetCasterGUID() == caster->GetGUID() && aurEff->IsAffectingSpell(spellInfo);
});
crit_chance += GetTotalAuraModifier(SPELL_AURA_MOD_CRIT_CHANCE_FOR_CASTER, [caster](AuraEffect const* aurEff) -> bool
{
return aurEff->GetCasterGUID() == caster->GetGUID();
});
crit_chance += caster->GetTotalAuraModifier(SPELL_AURA_MOD_CRIT_CHANCE_VERSUS_TARGET_HEALTH, [this](AuraEffect const* aurEff)
{
return !HealthBelowPct(aurEff->GetMiscValueB());
});
if (TempSummon const* tempSummon = caster->ToTempSummon())
{
crit_chance += GetTotalAuraModifier(SPELL_AURA_MOD_CRIT_CHANCE_FOR_CASTER_PET, [tempSummon](AuraEffect const* aurEff) -> bool
{
return aurEff->GetCasterGUID() == tempSummon->GetSummonerGUID();
});
}
}
// call script handlers
if (spell)
spell->CallScriptCalcCritChanceHandlers(this, crit_chance);
else
aurEff->GetBase()->CallScriptEffectCalcCritChanceHandlers(aurEff, aurEff->GetBase()->GetApplicationOfTarget(GetGUID()), this, crit_chance);
return std::max(crit_chance, 0.0f);
}
/*static*/ uint32 Unit::SpellCriticalDamageBonus(Unit const* caster, SpellInfo const* spellProto, uint32 damage, Unit* victim)
{
// Calculate critical bonus
int32 crit_bonus = damage * 2;
float crit_mod = 0.0f;
if (caster)
{
crit_mod += (caster->GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_CRIT_DAMAGE_BONUS, spellProto->GetSchoolMask()) - 1.0f) * 100;
if (crit_bonus != 0)
AddPct(crit_bonus, crit_mod);
AddPct(crit_bonus, victim->GetTotalAuraModifier(SPELL_AURA_MOD_CRITICAL_DAMAGE_TAKEN_FROM_CASTER, [&](AuraEffect const* aurEff)
{
return aurEff->GetCasterGUID() == caster->GetGUID();
}));
crit_bonus -= damage;
// adds additional damage to critBonus (from talents)
if (Player* modOwner = caster->GetSpellModOwner())
modOwner->ApplySpellMod(spellProto, SpellModOp::CritDamageAndHealing, crit_bonus);
crit_bonus += damage;
}
return crit_bonus;
}
/*static*/ uint32 Unit::SpellCriticalHealingBonus(Unit const* caster, SpellInfo const* spellProto, uint32 damage, Unit* /*victim*/)
{
// Calculate critical bonus
int32 crit_bonus = damage;
// adds additional damage to critBonus (from talents)
if (caster)
if (Player* modOwner = caster->GetSpellModOwner())
modOwner->ApplySpellMod(spellProto, SpellModOp::CritDamageAndHealing, crit_bonus);
damage += crit_bonus;
if (caster)
damage = int32(float(damage) * caster->GetTotalAuraMultiplier(SPELL_AURA_MOD_CRITICAL_HEALING_AMOUNT));
return damage;
}
int32 Unit::SpellHealingBonusDone(Unit* victim, SpellInfo const* spellProto, int32 healamount, DamageEffectType damagetype, SpellEffectInfo const& spellEffectInfo, uint32 stack /*= 1*/, Spell* spell /*= nullptr*/, AuraEffect const* aurEff /*= nullptr*/) const
{
// For totems get healing bonus from owner (statue isn't totem in fact)
if (GetTypeId() == TYPEID_UNIT && IsTotem())
if (Unit* owner = GetOwner())
return owner->SpellHealingBonusDone(victim, spellProto, healamount, damagetype, spellEffectInfo, stack, spell, aurEff);
// No bonus healing for potion spells
if (spellProto->SpellFamilyName == SPELLFAMILY_POTION)
return healamount;
int32 DoneTotal = 0;
float DoneTotalMod = SpellHealingPctDone(victim, spellProto);
// done scripted mod (take it from owner)
Unit const* owner = GetOwner() ? GetOwner() : this;
AuraEffectList const& mOverrideClassScript= owner->GetAuraEffectsByType(SPELL_AURA_OVERRIDE_CLASS_SCRIPTS);
for (AuraEffect const* aurEff : mOverrideClassScript)
{
if (!aurEff->IsAffectingSpell(spellProto))
continue;
switch (aurEff->GetMiscValue())
{
case 3736: // Hateful Totem of the Third Wind / Increased Lesser Healing Wave / LK Arena (4/5/6) Totem of the Third Wind / Savage Totem of the Third Wind
DoneTotal += aurEff->GetAmount();
break;
default:
break;
}
}
// Done fixed damage bonus auras
int32 DoneAdvertisedBenefit = SpellBaseHealingBonusDone(spellProto->GetSchoolMask());
// modify spell power by victim's SPELL_AURA_MOD_HEALING auras (eg Amplify/Dampen Magic)
DoneAdvertisedBenefit += victim->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_HEALING, spellProto->GetSchoolMask());
// Pets just add their bonus damage to their spell damage
// note that their spell damage is just gain of their own auras
if (HasUnitTypeMask(UNIT_MASK_GUARDIAN))
DoneAdvertisedBenefit += static_cast(this)->GetBonusDamage();
// Check for table values
if (spellEffectInfo.BonusCoefficientFromAP > 0.0f)
{
WeaponAttackType const attType = (spellProto->IsRangedWeaponSpell() && spellProto->DmgClass != SPELL_DAMAGE_CLASS_MELEE) ? RANGED_ATTACK : BASE_ATTACK;
float APbonus = float(victim->GetTotalAuraModifier(attType == BASE_ATTACK ? SPELL_AURA_MELEE_ATTACK_POWER_ATTACKER_BONUS : SPELL_AURA_RANGED_ATTACK_POWER_ATTACKER_BONUS));
APbonus += GetTotalAttackPowerValue(attType);
DoneTotal += int32(spellEffectInfo.BonusCoefficientFromAP * stack * APbonus);
}
// Default calculation
if (DoneAdvertisedBenefit)
{
float coeff = spellEffectInfo.BonusCoefficient;
if (Player* modOwner = GetSpellModOwner())
{
coeff *= 100.0f;
modOwner->ApplySpellMod(spellProto, SpellModOp::BonusCoefficient, coeff);
coeff /= 100.0f;
}
DoneTotal += int32(DoneAdvertisedBenefit * coeff * stack);
}
for (SpellEffectInfo const& otherSpellEffect : spellProto->GetEffects())
{
switch (otherSpellEffect.ApplyAuraName)
{
// Bonus healing does not apply to these spells
case SPELL_AURA_PERIODIC_LEECH:
case SPELL_AURA_PERIODIC_HEALTH_FUNNEL:
DoneTotal = 0;
break;
default:
break;
}
if (otherSpellEffect.IsEffect(SPELL_EFFECT_HEALTH_LEECH))
DoneTotal = 0;
}
if (spell)
spell->CallScriptCalcHealingHandlers(spellEffectInfo, victim, healamount, DoneTotal, DoneTotalMod);
else if (aurEff)
aurEff->GetBase()->CallScriptCalcDamageAndHealingHandlers(aurEff, aurEff->GetBase()->GetApplicationOfTarget(victim->GetGUID()), victim, healamount, DoneTotal, DoneTotalMod);
float heal = float(healamount + DoneTotal) * DoneTotalMod;
// apply spellmod to Done amount
if (Player* modOwner = GetSpellModOwner())
modOwner->ApplySpellMod(spellProto, damagetype == DOT ? SpellModOp::PeriodicHealingAndDamage : SpellModOp::HealingAndDamage, heal);
return int32(std::max(heal, 0.0f));
}
float Unit::SpellHealingPctDone(Unit* victim, SpellInfo const* spellProto) const
{
// For totems get healing bonus from owner
if (GetTypeId() == TYPEID_UNIT && IsTotem())
if (Unit* owner = GetOwner())
return owner->SpellHealingPctDone(victim, spellProto);
// Some spells don't benefit from done mods
if (spellProto->HasAttribute(SPELL_ATTR3_IGNORE_CASTER_MODIFIERS))
return 1.0f;
// Some spells don't benefit from done mods
if (spellProto->HasAttribute(SPELL_ATTR6_IGNORE_HEALING_MODIFIERS))
return 1.0f;
// Some spells don't benefit from done mods
if (spellProto->HasAttribute(SPELL_ATTR9_IGNORE_CASTER_HEALING_MODIFIERS))
return 1.0f;
// No bonus healing for potion spells
if (spellProto->SpellFamilyName == SPELLFAMILY_POTION)
return 1.0f;
float DoneTotalMod = 1.0f;
// Healing done percent
if (Player const* thisPlayer = ToPlayer())
{
float maxModDamagePercentSchool = 0.0f;
for (uint32 i = 0; i < MAX_SPELL_SCHOOL; ++i)
if (spellProto->GetSchoolMask() & (1 << i))
maxModDamagePercentSchool = std::max(maxModDamagePercentSchool, thisPlayer->m_activePlayerData->ModHealingDonePercent[i]);
DoneTotalMod *= maxModDamagePercentSchool;
}
else // SPELL_AURA_MOD_HEALING_DONE_PERCENT is included in m_activePlayerData->ModHealingDonePercent for players
DoneTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_HEALING_DONE_PERCENT);
// bonus against aurastate
DoneTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_DAMAGE_DONE_VERSUS_AURASTATE, [victim](AuraEffect const* aurEff) -> bool
{
if (victim->HasAuraState(static_cast(aurEff->GetMiscValue())))
return true;
return false;
});
// bonus from missing health of target
float healthPctDiff = 100.0f - victim->GetHealthPct();
for (AuraEffect const* healingDonePctVsTargetHealth : GetAuraEffectsByType(SPELL_AURA_MOD_HEALING_DONE_PCT_VERSUS_TARGET_HEALTH))
if (healingDonePctVsTargetHealth->IsAffectingSpell(spellProto))
AddPct(DoneTotalMod, CalculatePct(float(healingDonePctVsTargetHealth->GetAmount()), healthPctDiff));
return DoneTotalMod;
}
int32 Unit::SpellHealingBonusTaken(Unit* caster, SpellInfo const* spellProto, int32 healamount, DamageEffectType damagetype) const
{
bool allowPositive = !spellProto->HasAttribute(SPELL_ATTR6_IGNORE_HEALING_MODIFIERS);
bool allowNegative = !spellProto->HasAttribute(SPELL_ATTR6_IGNORE_HEALING_MODIFIERS) || spellProto->HasAttribute(SPELL_ATTR13_ALWAYS_ALLOW_NEGATIVE_HEALING_PERCENT_MODIFIERS);
if (!allowPositive && !allowNegative)
return healamount;
float TakenTotalMod = 1.0f;
// Healing taken percent
if (allowNegative)
{
float minval = float(GetMaxNegativeAuraModifier(SPELL_AURA_MOD_HEALING_PCT));
if (minval)
AddPct(TakenTotalMod, minval);
if (damagetype == DOT)
{
// Healing over time taken percent
float minval_hot = float(GetMaxNegativeAuraModifier(SPELL_AURA_MOD_HOT_PCT));
if (minval_hot)
AddPct(TakenTotalMod, minval_hot);
}
}
if (allowPositive)
{
float maxval = float(GetMaxPositiveAuraModifier(SPELL_AURA_MOD_HEALING_PCT));
if (maxval)
AddPct(TakenTotalMod, maxval);
if (damagetype == DOT)
{
// Healing over time taken percent
float maxval_hot = float(GetMaxPositiveAuraModifier(SPELL_AURA_MOD_HOT_PCT));
if (maxval_hot)
AddPct(TakenTotalMod, maxval_hot);
}
// Nourish cast
if (spellProto->SpellFamilyName == SPELLFAMILY_DRUID && spellProto->SpellFamilyFlags[1] & 0x2000000)
{
// Rejuvenation, Regrowth, Lifebloom, or Wild Growth
if (GetAuraEffect(SPELL_AURA_PERIODIC_HEAL, SPELLFAMILY_DRUID, flag128(0x50, 0x4000010, 0)))
// increase healing by 20%
TakenTotalMod *= 1.2f;
}
}
if (caster)
{
TakenTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_HEALING_RECEIVED, [caster, spellProto, allowPositive, allowNegative](AuraEffect const* aurEff) -> bool
{
if (caster->GetGUID() != aurEff->GetCasterGUID() || !aurEff->IsAffectingSpell(spellProto))
return false;
if (aurEff->GetAmount() > 0)
{
if (!allowPositive)
return false;
}
else if (!allowNegative)
return false;
return true;
});
TakenTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_HEALING_TAKEN_FROM_CASTER, [caster, allowPositive, allowNegative](AuraEffect const* aurEff) -> bool
{
if (aurEff->GetCasterGUID() != caster->GetGUID())
return false;
if (aurEff->GetAmount() > 0)
{
if (!allowPositive)
return false;
}
else if (!allowNegative)
return false;
return true;
});
}
float heal = healamount * TakenTotalMod;
return int32(std::max(heal, 0.0f));
}
int32 Unit::SpellBaseHealingBonusDone(SpellSchoolMask schoolMask) const
{
if (Player const* thisPlayer = ToPlayer())
{
float overrideSP = thisPlayer->m_activePlayerData->OverrideSpellPowerByAPPercent;
if (overrideSP > 0.0f)
return int32(CalculatePct(GetTotalAttackPowerValue(BASE_ATTACK), overrideSP) + 0.5f);
}
int32 advertisedBenefit = GetTotalAuraModifier(SPELL_AURA_MOD_HEALING_DONE, [schoolMask](AuraEffect const* aurEff) -> bool
{
if (!aurEff->GetMiscValue() || (aurEff->GetMiscValue() & schoolMask) != 0)
return true;
return false;
});
// Healing bonus of spirit, intellect and strength
if (GetTypeId() == TYPEID_PLAYER)
{
// Base value
advertisedBenefit += ToPlayer()->GetBaseSpellPowerBonus();
// Check if we are ever using mana - PaperDollFrame.lua
if (GetPowerIndex(POWER_MANA) != MAX_POWERS)
advertisedBenefit += std::max(0, int32(GetStat(STAT_INTELLECT))); // spellpower from intellect
// Healing bonus from stats
AuraEffectList const& mHealingDoneOfStatPercent = GetAuraEffectsByType(SPELL_AURA_MOD_SPELL_HEALING_OF_STAT_PERCENT);
for (AuraEffectList::const_iterator i = mHealingDoneOfStatPercent.begin(); i != mHealingDoneOfStatPercent.end(); ++i)
{
// stat used dependent from misc value (stat index)
Stats usedStat = Stats((*i)->GetSpellEffectInfo().MiscValue);
advertisedBenefit += int32(CalculatePct(GetStat(usedStat), (*i)->GetAmount()));
}
}
return advertisedBenefit;
}
bool Unit::IsImmunedToSpell(SpellInfo const* spellInfo, WorldObject const* caster, bool requireImmunityPurgesEffectAttribute /*= false*/) const
{
if (!spellInfo)
return false;
auto hasImmunity = [requireImmunityPurgesEffectAttribute](SpellImmuneContainer const& container, uint32 key)
{
Trinity::IteratorPair range = Trinity::Containers::MapEqualRange(container, key);
if (!requireImmunityPurgesEffectAttribute)
return range.begin() != range.end();
return std::any_of(range.begin(), range.end(), [](SpellImmuneContainer::value_type const& entry)
{
if (SpellInfo const* immunitySourceSpell = sSpellMgr->GetSpellInfo(entry.second, DIFFICULTY_NONE))
if (immunitySourceSpell->HasAttribute(SPELL_ATTR1_IMMUNITY_PURGES_EFFECT))
return true;
return false;
});
};
// Single spell immunity.
SpellImmuneContainer const& idList = m_spellImmune[IMMUNITY_ID];
if (hasImmunity(idList, spellInfo->Id))
return true;
if (spellInfo->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES))
return false;
if (uint32 dispel = spellInfo->Dispel)
{
SpellImmuneContainer const& dispelList = m_spellImmune[IMMUNITY_DISPEL];
if (hasImmunity(dispelList, dispel))
return true;
}
// Spells that don't have effectMechanics.
if (uint32 mechanic = spellInfo->Mechanic)
{
SpellImmuneContainer const& mechanicList = m_spellImmune[IMMUNITY_MECHANIC];
if (hasImmunity(mechanicList, mechanic))
return true;
}
bool immuneToAllEffects = true;
for (SpellEffectInfo const& spellEffectInfo : spellInfo->GetEffects())
{
// State/effect immunities applied by aura expect full spell immunity
// Ignore effects with mechanic, they are supposed to be checked separately
if (!spellEffectInfo.IsEffect())
continue;
if (!IsImmunedToSpellEffect(spellInfo, spellEffectInfo, caster, requireImmunityPurgesEffectAttribute))
{
immuneToAllEffects = false;
break;
}
if (spellInfo->HasAttribute(SPELL_ATTR4_NO_PARTIAL_IMMUNITY))
return true;
}
if (immuneToAllEffects) //Return immune only if the target is immune to all spell effects.
return true;
if (uint32 schoolMask = spellInfo->GetSchoolMask())
{
uint32 schoolImmunityMask = 0;
SpellImmuneContainer const& schoolList = m_spellImmune[IMMUNITY_SCHOOL];
for (auto itr = schoolList.begin(); itr != schoolList.end(); ++itr)
{
if ((itr->first & schoolMask) == 0)
continue;
SpellInfo const* immuneSpellInfo = sSpellMgr->GetSpellInfo(itr->second, GetMap()->GetDifficultyID());
if (requireImmunityPurgesEffectAttribute)
if (!immuneSpellInfo || !immuneSpellInfo->HasAttribute(SPELL_ATTR1_IMMUNITY_PURGES_EFFECT))
continue;
if (!(immuneSpellInfo && immuneSpellInfo->HasAttribute(SPELL_ATTR1_IMMUNITY_TO_HOSTILE_AND_FRIENDLY_EFFECTS)) && caster && caster->IsFriendlyTo(this))
continue;
if (spellInfo->CanPierceImmuneAura(immuneSpellInfo))
continue;
schoolImmunityMask |= itr->first;
}
if ((schoolImmunityMask & schoolMask) == schoolMask)
return true;
}
return false;
}
uint32 Unit::GetSchoolImmunityMask() const
{
uint32 mask = 0;
SpellImmuneContainer const& schoolList = m_spellImmune[IMMUNITY_SCHOOL];
for (auto itr = schoolList.begin(); itr != schoolList.end(); ++itr)
mask |= itr->first;
return mask;
}
uint32 Unit::GetDamageImmunityMask() const
{
uint32 mask = 0;
SpellImmuneContainer const& damageList = m_spellImmune[IMMUNITY_DAMAGE];
for (auto itr = damageList.begin(); itr != damageList.end(); ++itr)
mask |= itr->first;
return mask;
}
uint64 Unit::GetMechanicImmunityMask() const
{
uint64 mask = 0;
SpellImmuneContainer const& mechanicList = m_spellImmune[IMMUNITY_MECHANIC];
for (auto itr = mechanicList.begin(); itr != mechanicList.end(); ++itr)
mask |= (UI64LIT(1) << itr->first);
return mask;
}
EnumFlag Unit::GetSpellOtherImmunityMask() const
{
SpellOtherImmunity mask = { };
SpellImmuneContainer const& damageList = m_spellImmune[IMMUNITY_OTHER];
for (auto itr = damageList.begin(); itr != damageList.end(); ++itr)
mask |= SpellOtherImmunity(itr->first);
return mask;
}
bool Unit::IsImmunedToDamage(SpellSchoolMask schoolMask) const
{
if (schoolMask == SPELL_SCHOOL_MASK_NONE)
return false;
// If m_immuneToSchool type contain this school type, IMMUNE damage.
uint32 schoolImmunityMask = GetSchoolImmunityMask();
if ((schoolImmunityMask & schoolMask) == schoolMask) // We need to be immune to all types
return true;
// If m_immuneToDamage type contain magic, IMMUNE damage.
uint32 damageImmunityMask = GetDamageImmunityMask();
if ((damageImmunityMask & schoolMask) == schoolMask) // We need to be immune to all types
return true;
return false;
}
bool Unit::IsImmunedToDamage(WorldObject const* caster, SpellInfo const* spellInfo, SpellEffectInfo const* spellEffectInfo /*= nullptr*/) const
{
if (!spellInfo)
return false;
if (spellInfo->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES) || spellInfo->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES))
return false;
if (spellEffectInfo && spellEffectInfo->EffectAttributes.HasFlag(SpellEffectAttributes::NoImmunity))
return false;
if (uint32 schoolMask = spellInfo->GetSchoolMask())
{
auto hasImmunity = [&](SpellImmuneContainer const& container)
{
uint32 schoolImmunityMask = 0;
for (auto&& [immunitySchoolMask, immunityAuraId] : container)
{
SpellInfo const* immuneAuraInfo = sSpellMgr->GetSpellInfo(immunityAuraId, GetMap()->GetDifficultyID());
if (immuneAuraInfo && !immuneAuraInfo->HasAttribute(SPELL_ATTR1_IMMUNITY_TO_HOSTILE_AND_FRIENDLY_EFFECTS) && caster && caster->IsFriendlyTo(this))
continue;
if (immuneAuraInfo && spellInfo->CanPierceImmuneAura(immuneAuraInfo))
continue;
schoolImmunityMask |= immunitySchoolMask;
}
// // We need to be immune to all types
return (schoolImmunityMask & schoolMask) == schoolMask;
};
// If m_immuneToSchool type contain this school type, IMMUNE damage.
if (hasImmunity(m_spellImmune[IMMUNITY_SCHOOL]))
return true;
// If m_immuneToDamage type contain magic, IMMUNE damage.
if (hasImmunity(m_spellImmune[IMMUNITY_DAMAGE]))
return true;
}
return false;
}
bool Unit::IsImmunedToSpellEffect(SpellInfo const* spellInfo, SpellEffectInfo const& spellEffectInfo, WorldObject const* caster,
bool requireImmunityPurgesEffectAttribute /*= false*/) const
{
if (!spellInfo)
return false;
if (spellInfo->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES))
return false;
if (spellEffectInfo.EffectAttributes.HasFlag(SpellEffectAttributes::NoImmunity))
return false;
auto hasImmunity = [requireImmunityPurgesEffectAttribute](SpellImmuneContainer const& container, uint32 key)
{
Trinity::IteratorPair range = Trinity::Containers::MapEqualRange(container, key);
if (!requireImmunityPurgesEffectAttribute)
return range.begin() != range.end();
return std::any_of(range.begin(), range.end(), [](SpellImmuneContainer::value_type const& entry)
{
if (SpellInfo const* immunitySourceSpell = sSpellMgr->GetSpellInfo(entry.second, DIFFICULTY_NONE))
if (immunitySourceSpell->HasAttribute(SPELL_ATTR1_IMMUNITY_PURGES_EFFECT))
return true;
return false;
});
};
// If m_immuneToEffect type contain this effect type, IMMUNE effect.
auto const& effectList = m_spellImmune[IMMUNITY_EFFECT];
if (hasImmunity(effectList, spellEffectInfo.Effect))
return true;
if (uint32 mechanic = spellEffectInfo.Mechanic)
{
SpellImmuneContainer const& mechanicList = m_spellImmune[IMMUNITY_MECHANIC];
if (hasImmunity(mechanicList, mechanic))
return true;
}
if (AuraType aura = spellEffectInfo.ApplyAuraName)
{
if (!spellInfo->HasAttribute(SPELL_ATTR3_ALWAYS_HIT))
{
SpellImmuneContainer const& list = m_spellImmune[IMMUNITY_STATE];
if (hasImmunity(list, aura))
return true;
}
if (!spellInfo->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES))
{
// Check for immune to application of harmful magical effects
for (AuraEffect const* immuneAuraApply : GetAuraEffectsByType(SPELL_AURA_MOD_IMMUNE_AURA_APPLY_SCHOOL))
{
if (!(immuneAuraApply->GetMiscValue() & spellInfo->GetSchoolMask())) // Check school
continue;
if (immuneAuraApply->GetSpellInfo()->HasAttribute(SPELL_ATTR1_IMMUNITY_TO_HOSTILE_AND_FRIENDLY_EFFECTS) || (caster && !IsFriendlyTo(caster))) // Harmful
return true;
}
}
}
return false;
}
bool Unit::IsImmunedToAuraPeriodicTick(WorldObject const* caster, SpellInfo const* spellInfo, SpellEffectInfo const* spellEffectInfo) const
{
if (!spellInfo)
return false;
if (spellInfo->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES) || spellInfo->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES) /*only school immunities are checked in this function*/)
return false;
if (spellEffectInfo && spellEffectInfo->EffectAttributes.HasFlag(SpellEffectAttributes::NoImmunity))
return false;
if (uint32 schoolMask = spellInfo->GetSchoolMask())
{
auto hasImmunity = [&](SpellImmuneContainer const& container)
{
uint32 schoolImmunityMask = 0;
for (auto&& [immunitySchoolMask, immunityAuraId] : container)
{
SpellInfo const* immuneAuraInfo = sSpellMgr->GetSpellInfo(immunityAuraId, GetMap()->GetDifficultyID());
if (immuneAuraInfo && !immuneAuraInfo->HasAttribute(SPELL_ATTR1_IMMUNITY_TO_HOSTILE_AND_FRIENDLY_EFFECTS) && caster && caster->IsFriendlyTo(this))
continue;
schoolImmunityMask |= immunitySchoolMask;
}
// // We need to be immune to all types
return (schoolImmunityMask & schoolMask) == schoolMask;
};
// If m_immuneToSchool type contain this school type, IMMUNE damage.
if (hasImmunity(m_spellImmune[IMMUNITY_SCHOOL]))
return true;
}
return false;
}
int32 Unit::MeleeDamageBonusDone(Unit* pVictim, int32 damage, WeaponAttackType attType, DamageEffectType damagetype, SpellInfo const* spellProto /*= nullptr*/, SpellEffectInfo const* spellEffectInfo /*= nullptr*/, Mechanics mechanic /*= nullptr*/, SpellSchoolMask damageSchoolMask /*= SPELL_SCHOOL_MASK_NORMAL*/, Spell* spell /*= nullptr*/, AuraEffect const* aurEff /*= nullptr*/)
{
if (!pVictim || damage == 0)
return 0;
uint32 creatureTypeMask = pVictim->GetCreatureTypeMask();
// Done fixed damage bonus auras
int32 DoneFlatBenefit = 0;
// ..done
DoneFlatBenefit += GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_DAMAGE_DONE_CREATURE, creatureTypeMask);
// ..done
// SPELL_AURA_MOD_DAMAGE_DONE included in weapon damage
// ..done (base at attack power for marked target and base at attack power for creature type)
int32 APbonus = 0;
if (attType == RANGED_ATTACK)
{
APbonus += pVictim->GetTotalAuraModifier(SPELL_AURA_RANGED_ATTACK_POWER_ATTACKER_BONUS);
// ..done (base at attack power and creature type)
APbonus += GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_RANGED_ATTACK_POWER_VERSUS, creatureTypeMask);
}
else
{
APbonus += pVictim->GetTotalAuraModifier(SPELL_AURA_MELEE_ATTACK_POWER_ATTACKER_BONUS);
// ..done (base at attack power and creature type)
APbonus += GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_MELEE_ATTACK_POWER_VERSUS, creatureTypeMask);
}
if (APbonus != 0) // Can be negative
{
bool const normalized = spellProto && spellProto->HasEffect(SPELL_EFFECT_NORMALIZED_WEAPON_DMG);
DoneFlatBenefit += int32(APbonus / 3.5f * GetAPMultiplier(attType, normalized));
}
// Done total percent damage auras
float DoneTotalMod = 1.0f;
SpellSchoolMask schoolMask = spellProto ? spellProto->GetSchoolMask() : damageSchoolMask;
if (!(schoolMask & SPELL_SCHOOL_MASK_NORMAL))
{
// Some spells don't benefit from pct done mods
// mods for SPELL_SCHOOL_MASK_NORMAL are already factored in base melee damage calculation
if (!spellProto || !spellProto->HasAttribute(SPELL_ATTR6_IGNORE_CASTER_DAMAGE_MODIFIERS))
{
float maxModDamagePercentSchool = 0.0f;
if (Player const* thisPlayer = ToPlayer())
{
for (uint32 i = SPELL_SCHOOL_HOLY; i < MAX_SPELL_SCHOOL; ++i)
if (schoolMask & (1 << i))
maxModDamagePercentSchool = std::max(maxModDamagePercentSchool, thisPlayer->m_activePlayerData->ModDamageDonePercent[i]);
}
else
maxModDamagePercentSchool = GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_DAMAGE_PERCENT_DONE, schoolMask);
DoneTotalMod *= maxModDamagePercentSchool;
}
}
if (!spellProto)
{
// melee attack
for (AuraEffect const* autoAttackDamage : GetAuraEffectsByType(SPELL_AURA_MOD_AUTOATTACK_DAMAGE))
AddPct(DoneTotalMod, autoAttackDamage->GetAmount());
}
DoneTotalMod *= GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_DAMAGE_DONE_VERSUS, creatureTypeMask);
// bonus against aurastate
DoneTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_DAMAGE_DONE_VERSUS_AURASTATE, [pVictim](AuraEffect const* aurEff) -> bool
{
if (pVictim->HasAuraState(AuraStateType(aurEff->GetMiscValue())))
return true;
return false;
});
// bonus against target aura mechanic
DoneTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_DAMAGE_PERCENT_DONE_BY_TARGET_AURA_MECHANIC, [pVictim](AuraEffect const* aurEff) -> bool
{
if (pVictim->HasAuraWithMechanic(UI64LIT(1) << aurEff->GetMiscValue()))
return true;
return false;
});
// Add SPELL_AURA_MOD_DAMAGE_DONE_FOR_MECHANIC percent bonus
if (mechanic != MECHANIC_NONE)
AddPct(DoneTotalMod, GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_DAMAGE_DONE_FOR_MECHANIC, mechanic));
else if (spellProto && spellProto->Mechanic)
AddPct(DoneTotalMod, GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_DAMAGE_DONE_FOR_MECHANIC, spellProto->Mechanic));
if (spell)
spell->CallScriptCalcDamageHandlers(*spellEffectInfo, pVictim, damage, DoneFlatBenefit, DoneTotalMod);
else if (aurEff)
aurEff->GetBase()->CallScriptCalcDamageAndHealingHandlers(aurEff, aurEff->GetBase()->GetApplicationOfTarget(pVictim->GetGUID()), pVictim, damage, DoneFlatBenefit, DoneTotalMod);
float damageF = float(damage + DoneFlatBenefit) * DoneTotalMod;
// apply spellmod to Done damage
if (spellProto)
if (Player* modOwner = GetSpellModOwner())
modOwner->ApplySpellMod(spellProto, damagetype == DOT ? SpellModOp::PeriodicHealingAndDamage : SpellModOp::HealingAndDamage, damageF);
// bonus result can be negative
return int32(std::max(damageF, 0.0f));
}
int32 Unit::MeleeDamageBonusTaken(Unit* attacker, int32 pdamage, WeaponAttackType attType, DamageEffectType damagetype, SpellInfo const* spellProto /*= nullptr*/, SpellSchoolMask damageSchoolMask /*= SPELL_SCHOOL_MASK_NORMAL*/)
{
if (pdamage == 0)
return 0;
int32 TakenFlatBenefit = 0;
// ..taken
TakenFlatBenefit += GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_DAMAGE_TAKEN, attacker->GetMeleeDamageSchoolMask());
if (attType != RANGED_ATTACK)
TakenFlatBenefit += GetTotalAuraModifier(SPELL_AURA_MOD_MELEE_DAMAGE_TAKEN);
else
TakenFlatBenefit += GetTotalAuraModifier(SPELL_AURA_MOD_RANGED_DAMAGE_TAKEN);
if ((TakenFlatBenefit < 0) && (pdamage < -TakenFlatBenefit))
return 0;
// Taken total percent damage auras
float TakenTotalMod = 1.0f;
// ..taken
TakenTotalMod *= GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN, attacker->GetMeleeDamageSchoolMask());
// .. taken pct (special attacks)
if (spellProto)
{
// From caster spells
TakenTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_SCHOOL_MASK_DAMAGE_FROM_CASTER, [attacker, spellProto](AuraEffect const* aurEff) -> bool
{
return aurEff->GetCasterGUID() == attacker->GetGUID() && (aurEff->GetMiscValue() & spellProto->GetSchoolMask());
});
TakenTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_SPELL_DAMAGE_FROM_CASTER, [attacker, spellProto](AuraEffect const* aurEff) -> bool
{
return aurEff->GetCasterGUID() == attacker->GetGUID() && aurEff->IsAffectingSpell(spellProto);
});
// Mod damage from spell mechanic
uint64 mechanicMask = spellProto->GetAllEffectsMechanicMask();
// Shred, Maul - "Effects which increase Bleed damage also increase Shred damage"
if (spellProto->SpellFamilyName == SPELLFAMILY_DRUID && spellProto->SpellFamilyFlags[0] & 0x00008800)
mechanicMask |= (1 << MECHANIC_BLEED);
if (mechanicMask)
{
TakenTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_MECHANIC_DAMAGE_TAKEN_PERCENT, [mechanicMask](AuraEffect const* aurEff) -> bool
{
if (mechanicMask & uint64(UI64LIT(1) << (aurEff->GetMiscValue())))
return true;
return false;
});
}
if (damagetype == DOT)
{
TakenTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_PERIODIC_DAMAGE_TAKEN, [spellProto](AuraEffect const* aurEff) -> bool
{
return aurEff->GetMiscValue() & spellProto->GetSchoolMask();
});
}
}
else // melee attack
{
TakenTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_MELEE_DAMAGE_FROM_CASTER, [attacker](AuraEffect const* aurEff) -> bool
{
return aurEff->GetCasterGUID() == attacker->GetGUID();
});
}
if (AuraEffect const* cheatDeath = GetAuraEffect(45182, EFFECT_0))
AddPct(TakenTotalMod, cheatDeath->GetAmount());
if (attType != RANGED_ATTACK)
TakenTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_MELEE_DAMAGE_TAKEN_PCT);
else
TakenTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_RANGED_DAMAGE_TAKEN_PCT);
// Versatility
if (Player* modOwner = GetSpellModOwner())
{
// only 50% of SPELL_AURA_MOD_VERSATILITY for damage reduction
float versaBonus = modOwner->GetTotalAuraModifier(SPELL_AURA_MOD_VERSATILITY) / 2.0f;
AddPct(TakenTotalMod, -(modOwner->GetRatingBonusValue(CR_VERSATILITY_DAMAGE_TAKEN) + versaBonus));
}
// Sanctified Wrath (bypass damage reduction)
if (TakenTotalMod < 1.0f)
{
SpellSchoolMask const attackSchoolMask = spellProto ? spellProto->GetSchoolMask() : damageSchoolMask;
float damageReduction = 1.0f - TakenTotalMod;
Unit::AuraEffectList const& casterIgnoreResist = attacker->GetAuraEffectsByType(SPELL_AURA_MOD_IGNORE_TARGET_RESIST);
for (AuraEffect const* aurEff : casterIgnoreResist)
{
if (!(aurEff->GetMiscValue() & attackSchoolMask))
continue;
AddPct(damageReduction, -aurEff->GetAmount());
}
TakenTotalMod = 1.0f - damageReduction;
}
float tmpDamage = float(pdamage + TakenFlatBenefit) * TakenTotalMod;
return int32(std::max(tmpDamage, 0.0f));
}
void Unit::ApplySpellImmune(uint32 spellId, SpellImmunity op, uint32 type, bool apply)
{
if (apply)
m_spellImmune[op].emplace(type, spellId);
else
{
auto bounds = m_spellImmune[op].equal_range(type);
for (auto itr = bounds.first; itr != bounds.second;)
{
if (itr->second == spellId)
itr = m_spellImmune[op].erase(itr);
else
++itr;
}
}
}
float Unit::GetWeaponProcChance() const
{
// normalized proc chance for weapon attack speed
// (odd formula...)
if (isAttackReady(BASE_ATTACK))
return (GetBaseAttackTime(BASE_ATTACK) * 1.8f / 1000.0f);
else if (haveOffhandWeapon() && isAttackReady(OFF_ATTACK))
return (GetBaseAttackTime(OFF_ATTACK) * 1.6f / 1000.0f);
return 0;
}
float Unit::GetPPMProcChance(uint32 WeaponSpeed, float PPM, SpellInfo const* spellProto) const
{
// proc per minute chance calculation
if (PPM <= 0)
return 0.0f;
// Apply chance modifer aura
if (spellProto)
if (Player* modOwner = GetSpellModOwner())
modOwner->ApplySpellMod(spellProto, SpellModOp::ProcFrequency, PPM);
return std::floor((WeaponSpeed * PPM) / 600.0f); // result is chance in percents (probability = Speed_in_sec * (PPM / 60))
}
void Unit::Mount(uint32 mount, uint32 VehicleId, uint32 creatureEntry)
{
RemoveAurasByType(SPELL_AURA_COSMETIC_MOUNTED);
if (mount)
SetMountDisplayId(mount);
SetUnitFlag(UNIT_FLAG_MOUNT);
CalculateHoverHeight();
if (Player* player = ToPlayer())
{
// mount as a vehicle
if (VehicleId)
{
if (CreateVehicleKit(VehicleId, creatureEntry))
{
player->SendOnCancelExpectedVehicleRideAura();
// mounts can also have accessories
GetVehicleKit()->InstallAllAccessories(false);
}
}
// disable pet controls
player->DisablePetControlsOnMount(REACT_PASSIVE, COMMAND_FOLLOW);
player->SendMovementSetCollisionHeight(player->GetCollisionHeight(), WorldPackets::Movement::UpdateCollisionHeightReason::Mount);
}
RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Mount);
}
void Unit::Dismount()
{
if (!IsMounted())
return;
SetMountDisplayId(0);
RemoveUnitFlag(UNIT_FLAG_MOUNT);
if (Player* thisPlayer = ToPlayer())
thisPlayer->SendMovementSetCollisionHeight(thisPlayer->GetCollisionHeight(), WorldPackets::Movement::UpdateCollisionHeightReason::Mount);
CalculateHoverHeight();
// dismount as a vehicle
if (GetTypeId() == TYPEID_PLAYER && GetVehicleKit())
{
// Remove vehicle from player
RemoveVehicleKit();
}
RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Dismount);
// only resummon old pet if the player is already added to a map
// this prevents adding a pet to a not created map which would otherwise cause a crash
// (it could probably happen when logging in after a previous crash)
if (Player* player = ToPlayer())
{
player->EnablePetControlsOnDismount();
player->ResummonPetTemporaryUnSummonedIfAny();
player->ResummonBattlePetTemporaryUnSummonedIfAny();
}
}
void Unit::CancelMountAura(bool force)
{
if (!HasAuraType(SPELL_AURA_MOUNTED))
return;
RemoveAurasByType(SPELL_AURA_MOUNTED, [force](AuraApplication const* aurApp)
{
SpellInfo const* spellInfo = aurApp->GetBase()->GetSpellInfo();
return force || (!spellInfo->HasAttribute(SPELL_ATTR0_NO_AURA_CANCEL) && spellInfo->IsPositive() && !spellInfo->IsPassive());
});
}
MountCapabilityEntry const* Unit::GetMountCapability(uint32 mountType) const
{
if (!mountType)
return nullptr;
DB2Manager::MountTypeXCapabilitySet const* capabilities = sDB2Manager.GetMountCapabilities(mountType);
if (!capabilities)
return nullptr;
uint32 areaId = GetAreaId();
uint32 ridingSkill = 5000;
EnumFlag mountFlags = AreaMountFlags::None;
bool isSubmerged = false;
bool isInWater = false;
if (GetTypeId() == TYPEID_PLAYER)
ridingSkill = ToPlayer()->GetSkillValue(SKILL_RIDING);
if (HasAuraType(SPELL_AURA_MOUNT_RESTRICTIONS))
{
for (AuraEffect const* auraEffect : GetAuraEffectsByType(SPELL_AURA_MOUNT_RESTRICTIONS))
mountFlags |= AreaMountFlags(auraEffect->GetMiscValue());
}
else if (AreaTableEntry const* areaTable = sAreaTableStore.LookupEntry(areaId))
mountFlags = areaTable->GetMountFlags();
LiquidData liquid;
ZLiquidStatus liquidStatus = GetMap()->GetLiquidStatus(GetPhaseShift(), GetPositionX(), GetPositionY(), GetPositionZ(), {}, &liquid);
isSubmerged = (liquidStatus & LIQUID_MAP_UNDER_WATER) != 0 || HasUnitMovementFlag(MOVEMENTFLAG_SWIMMING);
isInWater = (liquidStatus & (LIQUID_MAP_IN_WATER | LIQUID_MAP_UNDER_WATER)) != 0;
for (MountTypeXCapabilityEntry const* mountTypeXCapability : *capabilities)
{
MountCapabilityEntry const* mountCapability = sMountCapabilityStore.LookupEntry(mountTypeXCapability->MountCapabilityID);
if (!mountCapability)
continue;
if (ridingSkill < mountCapability->ReqRidingSkill)
continue;
if (!(mountCapability->Flags & MOUNT_CAPABIILTY_FLAG_IGNORE_RESTRICTIONS))
{
if (mountCapability->Flags & MOUNT_CAPABILITY_FLAG_GROUND && !(mountFlags.HasFlag(AreaMountFlags::AllowGroundMounts)))
continue;
if (mountCapability->Flags & MOUNT_CAPABILITY_FLAG_FLYING && !(mountFlags.HasFlag(AreaMountFlags::AllowFlyingMounts)))
continue;
if (mountCapability->Flags & MOUNT_CAPABILITY_FLAG_FLOAT && !(mountFlags.HasFlag(AreaMountFlags::AllowSurfaceSwimmingMounts)))
continue;
if (mountCapability->Flags & MOUNT_CAPABILITY_FLAG_UNDERWATER && !(mountFlags.HasFlag(AreaMountFlags::AllowUnderwaterSwimmingMounts)))
continue;
}
if (!isSubmerged)
{
if (!isInWater)
{
// player is completely out of water
if (!(mountCapability->Flags & MOUNT_CAPABILITY_FLAG_GROUND))
continue;
}
// player is on water surface
else if (!(mountCapability->Flags & MOUNT_CAPABILITY_FLAG_FLOAT))
continue;
}
else if (isInWater)
{
if (!(mountCapability->Flags & MOUNT_CAPABILITY_FLAG_UNDERWATER))
continue;
}
else if (!(mountCapability->Flags & MOUNT_CAPABILITY_FLAG_FLOAT))
continue;
if (mountCapability->ReqMapID != -1 &&
int32(GetMapId()) != mountCapability->ReqMapID &&
GetMap()->GetEntry()->CosmeticParentMapID != mountCapability->ReqMapID &&
GetMap()->GetEntry()->ParentMapID != mountCapability->ReqMapID)
continue;
if (mountCapability->ReqAreaID && !DB2Manager::IsInArea(areaId, mountCapability->ReqAreaID))
continue;
if (mountCapability->ReqSpellAuraID && !HasAura(mountCapability->ReqSpellAuraID))
continue;
if (mountCapability->ReqSpellKnownID && !HasSpell(mountCapability->ReqSpellKnownID))
continue;
if (Player const* thisPlayer = ToPlayer())
if (!ConditionMgr::IsPlayerMeetingCondition(thisPlayer, mountCapability->PlayerConditionID))
continue;
return mountCapability;
}
return nullptr;
}
void Unit::UpdateMountCapability()
{
if (IsLoading())
return;
if (SpellShapeshiftFormEntry const* spellShapeshiftForm = sSpellShapeshiftFormStore.LookupEntry(GetShapeshiftForm()))
if (uint32 mountType = spellShapeshiftForm->MountTypeID)
if (!GetMountCapability(mountType))
CancelTravelShapeshiftForm(AURA_REMOVE_BY_INTERRUPT);
AuraEffectVector mounts = CopyAuraEffectList(GetAuraEffectsByType(SPELL_AURA_MOUNTED));
for (AuraEffect* aurEff : mounts)
{
aurEff->RecalculateAmount();
if (!aurEff->GetAmount())
aurEff->GetBase()->Remove();
else if (MountCapabilityEntry const* capability = sMountCapabilityStore.LookupEntry(aurEff->GetAmount())) // aura may get removed by interrupt flag, reapply
{
SetFlightCapabilityID(capability->FlightCapabilityID, true);
if (!HasAura(capability->ModSpellAuraID))
CastSpell(this, capability->ModSpellAuraID, aurEff);
}
}
}
bool Unit::IsServiceProvider() const
{
return HasNpcFlag(
UNIT_NPC_FLAG_VENDOR | UNIT_NPC_FLAG_TRAINER | UNIT_NPC_FLAG_FLIGHTMASTER |
UNIT_NPC_FLAG_PETITIONER | UNIT_NPC_FLAG_BATTLEMASTER | UNIT_NPC_FLAG_BANKER |
UNIT_NPC_FLAG_INNKEEPER | UNIT_NPC_FLAG_SPIRIT_HEALER |
UNIT_NPC_FLAG_AREA_SPIRIT_HEALER | UNIT_NPC_FLAG_TABARDDESIGNER | UNIT_NPC_FLAG_AUCTIONEER);
}
void Unit::EngageWithTarget(Unit* enemy)
{
if (!enemy)
return;
if (CanHaveThreatList())
m_threatManager.AddThreat(enemy, 0.0f, nullptr, true, true);
else
SetInCombatWith(enemy);
}
void Unit::SetImmuneToAll(bool apply, bool keepCombat)
{
if (apply)
{
SetUnitFlag(UNIT_FLAG_IMMUNE_TO_PC | UNIT_FLAG_IMMUNE_TO_NPC);
ValidateAttackersAndOwnTarget();
if (!keepCombat)
m_combatManager.EndAllCombat();
}
else
RemoveUnitFlag(UNIT_FLAG_IMMUNE_TO_PC | UNIT_FLAG_IMMUNE_TO_NPC);
}
void Unit::SetImmuneToPC(bool apply, bool keepCombat)
{
if (apply)
{
SetUnitFlag(UNIT_FLAG_IMMUNE_TO_PC);
ValidateAttackersAndOwnTarget();
if (!keepCombat)
{
std::list toEnd;
for (auto const& pair : m_combatManager.GetPvECombatRefs())
if (pair.second->GetOther(this)->HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED))
toEnd.push_back(pair.second);
for (auto const& pair : m_combatManager.GetPvPCombatRefs())
if (pair.second->GetOther(this)->HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED))
toEnd.push_back(pair.second);
for (CombatReference* ref : toEnd)
ref->EndCombat();
}
}
else
RemoveUnitFlag(UNIT_FLAG_IMMUNE_TO_PC);
}
void Unit::SetImmuneToNPC(bool apply, bool keepCombat)
{
if (apply)
{
SetUnitFlag(UNIT_FLAG_IMMUNE_TO_NPC);
ValidateAttackersAndOwnTarget();
if (!keepCombat)
{
std::list toEnd;
for (auto const& pair : m_combatManager.GetPvECombatRefs())
if (!pair.second->GetOther(this)->HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED))
toEnd.push_back(pair.second);
for (auto const& pair : m_combatManager.GetPvPCombatRefs())
if (!pair.second->GetOther(this)->HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED))
toEnd.push_back(pair.second);
for (CombatReference* ref : toEnd)
ref->EndCombat();
}
}
else
RemoveUnitFlag(UNIT_FLAG_IMMUNE_TO_NPC);
}
void Unit::SetUninteractible(bool apply)
{
if (apply)
SetUnitFlag(UNIT_FLAG_UNINTERACTIBLE);
else
RemoveUnitFlag(UNIT_FLAG_UNINTERACTIBLE);
}
void Unit::SetCannotTurn(bool apply)
{
if (apply)
SetUnitFlag2(UNIT_FLAG2_CANNOT_TURN);
else
RemoveUnitFlag2(UNIT_FLAG2_CANNOT_TURN);
}
bool Unit::IsThreatened() const
{
return !m_threatManager.IsThreatListEmpty();
}
bool Unit::isTargetableForAttack(bool checkFakeDeath) const
{
if (!IsAlive())
return false;
if (HasUnitFlag(UNIT_FLAG_NON_ATTACKABLE) || IsUninteractible())
return false;
if (GetTypeId() == TYPEID_PLAYER && ToPlayer()->IsGameMaster())
return false;
return !HasUnitState(UNIT_STATE_UNATTACKABLE) && (!checkFakeDeath || !HasUnitState(UNIT_STATE_DIED));
}
int64 Unit::ModifyHealth(int64 dVal)
{
int64 gain = 0;
if (dVal == 0)
return 0;
int64 curHealth = (int64)GetHealth();
int64 val = dVal + curHealth;
if (val <= 0)
{
SetHealth(0);
return -curHealth;
}
int64 maxHealth = (int64)GetMaxHealth();
if (val < maxHealth)
{
SetHealth(val);
gain = val - curHealth;
}
else if (curHealth != maxHealth)
{
SetHealth(maxHealth);
gain = maxHealth - curHealth;
}
if (dVal < 0)
{
WorldPackets::Combat::HealthUpdate packet;
packet.Guid = GetGUID();
packet.Health = GetHealth();
if (Player* player = GetCharmerOrOwnerPlayerOrPlayerItself())
player->GetSession()->SendPacket(packet.Write());
}
return gain;
}
int64 Unit::GetHealthGain(int64 dVal)
{
int64 gain = 0;
if (dVal == 0)
return 0;
int64 curHealth = (int64)GetHealth();
int64 val = dVal + curHealth;
if (val <= 0)
{
return -curHealth;
}
int64 maxHealth = (int64)GetMaxHealth();
if (val < maxHealth)
gain = dVal;
else if (curHealth != maxHealth)
gain = maxHealth - curHealth;
return gain;
}
void Unit::TriggerOnHealthChangeAuras(uint64 oldVal, uint64 newVal)
{
if (!HasAuraType(SPELL_AURA_TRIGGER_SPELL_ON_HEALTH_PCT))
return;
AuraEffectVector effects = CopyAuraEffectList(GetAuraEffectsByType(SPELL_AURA_TRIGGER_SPELL_ON_HEALTH_PCT));
for (AuraEffect const* effect : effects)
{
uint32 triggerHealthPct = effect->GetAmount();
uint32 triggerSpell = effect->GetSpellEffectInfo().TriggerSpell;
uint64 threshold = CountPctFromMaxHealth(triggerHealthPct);
switch (AuraTriggerOnHealthChangeDirection(effect->GetMiscValue()))
{
case AuraTriggerOnHealthChangeDirection::Above:
if (newVal < threshold || oldVal > threshold)
continue;
break;
case AuraTriggerOnHealthChangeDirection::Below:
if (newVal > threshold || oldVal < threshold)
continue;
break;
default:
break;
}
CastSpell(this, triggerSpell, effect);
}
}
// returns negative amount on power reduction
int32 Unit::ModifyPower(Powers power, int32 dVal, bool withPowerUpdate /*= true*/)
{
int32 gain = 0;
if (dVal == 0)
return 0;
if (dVal > 0)
dVal *= GetTotalAuraMultiplierByMiscValue(SPELL_AURA_MOD_POWER_GAIN_PCT, power);
int32 curPower = GetPower(power);
int32 val = dVal + curPower;
if (val <= GetMinPower(power))
{
SetPower(power, GetMinPower(power), withPowerUpdate);
return -curPower;
}
int32 maxPower = GetMaxPower(power);
if (val < maxPower)
{
SetPower(power, val, withPowerUpdate);
gain = val - curPower;
}
else if (curPower != maxPower)
{
SetPower(power, maxPower, withPowerUpdate);
gain = maxPower - curPower;
}
return gain;
}
bool Unit::IsAlwaysVisibleFor(WorldObject const* seer) const
{
if (WorldObject::IsAlwaysVisibleFor(seer))
return true;
// Always seen by owner
ObjectGuid guid = GetCharmerOrOwnerGUID();
if (!guid.IsEmpty())
if (seer->GetGUID() == guid)
return true;
if (Player const* seerPlayer = seer->ToPlayer())
if (Unit* owner = GetOwner())
if (Player* ownerPlayer = owner->ToPlayer())
if (ownerPlayer->IsGroupVisibleFor(seerPlayer))
return true;
return false;
}
bool Unit::IsAlwaysDetectableFor(WorldObject const* seer) const
{
if (WorldObject::IsAlwaysDetectableFor(seer))
return true;
if (HasAuraTypeWithCaster(SPELL_AURA_MOD_STALKED, seer->GetGUID()))
return true;
return false;
}
bool Unit::IsVisible() const
{
return (m_serverSideVisibility.GetValue(SERVERSIDE_VISIBILITY_GM) > SEC_PLAYER) ? false : true;
}
void Unit::SetVisible(bool x)
{
if (!x)
m_serverSideVisibility.SetValue(SERVERSIDE_VISIBILITY_GM, SEC_GAMEMASTER);
else
m_serverSideVisibility.SetValue(SERVERSIDE_VISIBILITY_GM, SEC_PLAYER);
UpdateObjectVisibility();
}
void Unit::UpdateSpeed(UnitMoveType mtype)
{
int32 main_speed_mod = 0;
float stack_bonus = 1.0f;
float non_stack_bonus = 1.0f;
switch (mtype)
{
// Only apply debuffs
case MOVE_FLIGHT_BACK:
case MOVE_RUN_BACK:
case MOVE_SWIM_BACK:
break;
case MOVE_WALK:
return;
case MOVE_RUN:
{
if (IsMounted()) // Use on mount auras
{
main_speed_mod = GetMaxPositiveAuraModifier(SPELL_AURA_MOD_INCREASE_MOUNTED_SPEED);
stack_bonus = GetTotalAuraMultiplier(SPELL_AURA_MOD_MOUNTED_SPEED_ALWAYS);
non_stack_bonus += GetMaxPositiveAuraModifier(SPELL_AURA_MOD_MOUNTED_SPEED_NOT_STACK) / 100.0f;
}
else
{
main_speed_mod = GetMaxPositiveAuraModifier(SPELL_AURA_MOD_INCREASE_SPEED);
stack_bonus = GetTotalAuraMultiplier(SPELL_AURA_MOD_SPEED_ALWAYS);
non_stack_bonus += GetMaxPositiveAuraModifier(SPELL_AURA_MOD_SPEED_NOT_STACK) / 100.0f;
}
break;
}
case MOVE_SWIM:
{
main_speed_mod = GetMaxPositiveAuraModifier(SPELL_AURA_MOD_INCREASE_SWIM_SPEED);
break;
}
case MOVE_FLIGHT:
{
if (GetTypeId() == TYPEID_UNIT && IsControlledByPlayer()) // not sure if good for pet
{
main_speed_mod = GetMaxPositiveAuraModifier(SPELL_AURA_MOD_INCREASE_VEHICLE_FLIGHT_SPEED);
stack_bonus = GetTotalAuraMultiplier(SPELL_AURA_MOD_VEHICLE_SPEED_ALWAYS);
// for some spells this mod is applied on vehicle owner
int32 owner_speed_mod = 0;
if (Unit* owner = GetCharmer())
owner_speed_mod = owner->GetMaxPositiveAuraModifier(SPELL_AURA_MOD_INCREASE_VEHICLE_FLIGHT_SPEED);
main_speed_mod = std::max(main_speed_mod, owner_speed_mod);
}
else if (IsMounted())
{
main_speed_mod = GetMaxPositiveAuraModifier(SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED);
stack_bonus = GetTotalAuraMultiplier(SPELL_AURA_MOD_MOUNTED_FLIGHT_SPEED_ALWAYS);
}
else // Use not mount (shapeshift for example) auras (should stack)
main_speed_mod = GetTotalAuraModifier(SPELL_AURA_MOD_INCREASE_FLIGHT_SPEED) + GetTotalAuraModifier(SPELL_AURA_MOD_INCREASE_VEHICLE_FLIGHT_SPEED);
non_stack_bonus += GetMaxPositiveAuraModifier(SPELL_AURA_MOD_FLIGHT_SPEED_NOT_STACK) / 100.0f;
// Update speed for vehicle if available
if (GetTypeId() == TYPEID_PLAYER && GetVehicle())
GetVehicleBase()->UpdateSpeed(MOVE_FLIGHT);
break;
}
default:
TC_LOG_ERROR("entities.unit", "Unit::UpdateSpeed: Unsupported move type ({})", mtype);
return;
}
// now we ready for speed calculation
float speed = std::max(non_stack_bonus, stack_bonus);
if (main_speed_mod)
AddPct(speed, main_speed_mod);
switch (mtype)
{
case MOVE_RUN:
case MOVE_SWIM:
case MOVE_FLIGHT:
{
// Set creature speed rate
if (GetTypeId() == TYPEID_UNIT)
speed *= ToCreature()->GetCreatureTemplate()->speed_run; // at this point, MOVE_WALK is never reached
// Normalize speed by 191 aura SPELL_AURA_USE_NORMAL_MOVEMENT_SPEED if need
/// @todo possible affect only on MOVE_RUN
if (int32 normalization = GetMaxPositiveAuraModifier(SPELL_AURA_USE_NORMAL_MOVEMENT_SPEED))
{
if (Creature* creature = ToCreature())
if (CreatureImmunities const* immunities = SpellMgr::GetCreatureImmunities(creature->GetCreatureTemplate()->CreatureImmunitiesId))
if (immunities->Mechanic[MECHANIC_SNARE] || immunities->Mechanic[MECHANIC_DAZE])
break;
// Use speed from aura
float max_speed = normalization / (IsControlledByPlayer() ? playerBaseMoveSpeed[mtype] : baseMoveSpeed[mtype]);
if (speed > max_speed)
speed = max_speed;
}
if (mtype == MOVE_RUN)
{
// force minimum speed rate @ aura 437 SPELL_AURA_MOD_MINIMUM_SPEED_RATE
if (int32 minSpeedMod = GetMaxPositiveAuraModifier(SPELL_AURA_MOD_MINIMUM_SPEED_RATE))
{
float minSpeed = minSpeedMod / (IsControlledByPlayer() ? playerBaseMoveSpeed[mtype] : baseMoveSpeed[mtype]);
if (speed < minSpeed)
speed = minSpeed;
}
}
break;
}
default:
break;
}
if (Creature* creature = ToCreature())
{
if (creature->HasUnitTypeMask(UNIT_MASK_MINION) && !creature->IsInCombat())
{
if (GetMotionMaster()->GetCurrentMovementGeneratorType() == FOLLOW_MOTION_TYPE)
{
Unit* followed = ASSERT_NOTNULL(dynamic_cast(GetMotionMaster()->GetCurrentMovementGenerator()))->GetTarget();
if (followed && followed->GetGUID() == GetOwnerGUID() && !followed->IsInCombat())
{
float ownerSpeed = followed->GetSpeedRate(mtype);
if (speed < ownerSpeed || creature->IsWithinDist3d(followed, 10.0f))
speed = ownerSpeed;
speed *= std::min(std::max(1.0f, 0.75f + (GetDistance(followed) - PET_FOLLOW_DIST) * 0.05f), 1.3f);
}
}
}
}
// Apply strongest slow aura mod to speed
int32 slow = GetMaxNegativeAuraModifier(SPELL_AURA_MOD_DECREASE_SPEED);
if (slow)
AddPct(speed, slow);
if (float minSpeedMod = (float)GetMaxPositiveAuraModifier(SPELL_AURA_MOD_MINIMUM_SPEED))
{
float baseMinSpeed = 1.0f;
if (!GetOwnerGUID().IsPlayer() && !IsHunterPet() && GetTypeId() == TYPEID_UNIT)
baseMinSpeed = ToCreature()->GetCreatureTemplate()->speed_run;
float min_speed = CalculatePct(baseMinSpeed, minSpeedMod);
if (speed < min_speed)
speed = min_speed;
}
SetSpeedRate(mtype, speed);
}
float Unit::GetSpeed(UnitMoveType mtype) const
{
return m_speed_rate[mtype]*(IsControlledByPlayer() ? playerBaseMoveSpeed[mtype] : baseMoveSpeed[mtype]);
}
void Unit::SetSpeed(UnitMoveType mtype, float newValue)
{
SetSpeedRate(mtype, newValue / (IsControlledByPlayer() ? playerBaseMoveSpeed[mtype] : baseMoveSpeed[mtype]));
}
void Unit::SetSpeedRate(UnitMoveType mtype, float rate)
{
rate = std::max(rate, 0.01f);
// Update speed only on change
if (m_speed_rate[mtype] == rate)
return;
m_speed_rate[mtype] = rate;
PropagateSpeedChange();
// Spline packets are for creatures and move_update are for players
static OpcodeServer const moveTypeToOpcode[MAX_MOVE_TYPE][3] =
{
{SMSG_MOVE_SPLINE_SET_WALK_SPEED, SMSG_MOVE_SET_WALK_SPEED, SMSG_MOVE_UPDATE_WALK_SPEED },
{SMSG_MOVE_SPLINE_SET_RUN_SPEED, SMSG_MOVE_SET_RUN_SPEED, SMSG_MOVE_UPDATE_RUN_SPEED },
{SMSG_MOVE_SPLINE_SET_RUN_BACK_SPEED, SMSG_MOVE_SET_RUN_BACK_SPEED, SMSG_MOVE_UPDATE_RUN_BACK_SPEED },
{SMSG_MOVE_SPLINE_SET_SWIM_SPEED, SMSG_MOVE_SET_SWIM_SPEED, SMSG_MOVE_UPDATE_SWIM_SPEED },
{SMSG_MOVE_SPLINE_SET_SWIM_BACK_SPEED, SMSG_MOVE_SET_SWIM_BACK_SPEED, SMSG_MOVE_UPDATE_SWIM_BACK_SPEED },
{SMSG_MOVE_SPLINE_SET_TURN_RATE, SMSG_MOVE_SET_TURN_RATE, SMSG_MOVE_UPDATE_TURN_RATE },
{SMSG_MOVE_SPLINE_SET_FLIGHT_SPEED, SMSG_MOVE_SET_FLIGHT_SPEED, SMSG_MOVE_UPDATE_FLIGHT_SPEED },
{SMSG_MOVE_SPLINE_SET_FLIGHT_BACK_SPEED, SMSG_MOVE_SET_FLIGHT_BACK_SPEED, SMSG_MOVE_UPDATE_FLIGHT_BACK_SPEED},
{SMSG_MOVE_SPLINE_SET_PITCH_RATE, SMSG_MOVE_SET_PITCH_RATE, SMSG_MOVE_UPDATE_PITCH_RATE },
};
if (GetTypeId() == TYPEID_PLAYER)
{
// register forced speed changes for WorldSession::HandleForceSpeedChangeAck
// and do it only for real sent packets and use run for run/mounted as client expected
++ToPlayer()->m_forced_speed_changes[mtype];
if (!IsInCombat())
if (Pet* pet = ToPlayer()->GetPet())
pet->SetSpeedRate(mtype, m_speed_rate[mtype]);
}
if (Player* playerMover = Unit::ToPlayer(GetUnitBeingMoved())) // unit controlled by a player.
{
// Send notification to self
WorldPackets::Movement::MoveSetSpeed selfpacket(moveTypeToOpcode[mtype][1]);
selfpacket.MoverGUID = GetGUID();
selfpacket.SequenceIndex = m_movementCounter++;
selfpacket.Speed = GetSpeed(mtype);
playerMover->GetSession()->SendPacket(selfpacket.Write());
// Send notification to other players
WorldPackets::Movement::MoveUpdateSpeed packet(moveTypeToOpcode[mtype][2]);
packet.Status = &m_movementInfo;
packet.Speed = GetSpeed(mtype);
playerMover->SendMessageToSet(packet.Write(), false);
}
else
{
WorldPackets::Movement::MoveSplineSetSpeed packet(moveTypeToOpcode[mtype][0]);
packet.MoverGUID = GetGUID();
packet.Speed = GetSpeed(mtype);
SendMessageToSet(packet.Write(), true);
}
}
void Unit::SetFlightCapabilityID(int32 flightCapabilityId, bool clientUpdate)
{
if (flightCapabilityId && !sFlightCapabilityStore.HasRecord(flightCapabilityId))
return;
SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::FlightCapabilityID), flightCapabilityId);
UpdateAdvFlyingSpeed(ADV_FLYING_AIR_FRICTION, clientUpdate);
UpdateAdvFlyingSpeed(ADV_FLYING_MAX_VEL, clientUpdate);
UpdateAdvFlyingSpeed(ADV_FLYING_LIFT_COEFFICIENT, clientUpdate);
UpdateAdvFlyingSpeed(ADV_FLYING_DOUBLE_JUMP_VEL_MOD, clientUpdate);
UpdateAdvFlyingSpeed(ADV_FLYING_GLIDE_START_MIN_HEIGHT, clientUpdate);
UpdateAdvFlyingSpeed(ADV_FLYING_ADD_IMPULSE_MAX_SPEED, clientUpdate);
UpdateAdvFlyingSpeed(ADV_FLYING_BANKING_RATE, clientUpdate);
UpdateAdvFlyingSpeed(ADV_FLYING_PITCHING_RATE_DOWN, clientUpdate);
UpdateAdvFlyingSpeed(ADV_FLYING_PITCHING_RATE_UP, clientUpdate);
UpdateAdvFlyingSpeed(ADV_FLYING_TURN_VELOCITY_THRESHOLD, clientUpdate);
UpdateAdvFlyingSpeed(ADV_FLYING_SURFACE_FRICTION, clientUpdate);
UpdateAdvFlyingSpeed(ADV_FLYING_OVER_MAX_DECELERATION, clientUpdate);
UpdateAdvFlyingSpeed(ADV_FLYING_LAUNCH_SPEED_COEFFICIENT, clientUpdate);
}
void Unit::UpdateAdvFlyingSpeed(AdvFlyingRateTypeSingle speedType, bool clientUpdate)
{
FlightCapabilityEntry const* flightCapabilityEntry = sFlightCapabilityStore.LookupEntry(GetFlightCapabilityID());
if (!flightCapabilityEntry)
flightCapabilityEntry = sFlightCapabilityStore.AssertEntry(1);
auto [opcode, newValue, rateAura] = [&]
{
switch (speedType)
{
case ADV_FLYING_AIR_FRICTION:
return std::tuple(SMSG_MOVE_SET_ADV_FLYING_AIR_FRICTION, flightCapabilityEntry->AirFriction, SPELL_AURA_MOD_ADV_FLYING_AIR_FRICTION);
case ADV_FLYING_MAX_VEL:
return std::tuple(SMSG_MOVE_SET_ADV_FLYING_MAX_VEL, flightCapabilityEntry->MaxVel, SPELL_AURA_MOD_ADV_FLYING_MAX_VEL);
case ADV_FLYING_LIFT_COEFFICIENT:
return std::tuple(SMSG_MOVE_SET_ADV_FLYING_LIFT_COEFFICIENT, flightCapabilityEntry->LiftCoefficient, SPELL_AURA_MOD_ADV_FLYING_LIFT_COEF);
case ADV_FLYING_DOUBLE_JUMP_VEL_MOD:
return std::tuple(SMSG_MOVE_SET_ADV_FLYING_DOUBLE_JUMP_VEL_MOD, flightCapabilityEntry->DoubleJumpVelMod, SPELL_AURA_NONE);
case ADV_FLYING_GLIDE_START_MIN_HEIGHT:
return std::tuple(SMSG_MOVE_SET_ADV_FLYING_GLIDE_START_MIN_HEIGHT, flightCapabilityEntry->GlideStartMinHeight, SPELL_AURA_NONE);
case ADV_FLYING_ADD_IMPULSE_MAX_SPEED:
return std::tuple(SMSG_MOVE_SET_ADV_FLYING_ADD_IMPULSE_MAX_SPEED, flightCapabilityEntry->AddImpulseMaxSpeed, SPELL_AURA_MOD_ADV_FLYING_ADD_IMPULSE_MAX_SPEED);
case ADV_FLYING_SURFACE_FRICTION:
return std::tuple(SMSG_MOVE_SET_ADV_FLYING_SURFACE_FRICTION, flightCapabilityEntry->SurfaceFriction, SPELL_AURA_NONE);
case ADV_FLYING_OVER_MAX_DECELERATION:
return std::tuple(SMSG_MOVE_SET_ADV_FLYING_OVER_MAX_DECELERATION, flightCapabilityEntry->OverMaxDeceleration, SPELL_AURA_MOD_ADV_FLYING_OVER_MAX_DECELERATION);
case ADV_FLYING_LAUNCH_SPEED_COEFFICIENT:
return std::tuple(SMSG_MOVE_SET_ADV_FLYING_LAUNCH_SPEED_COEFFICIENT, flightCapabilityEntry->LaunchSpeedCoefficient, SPELL_AURA_NONE);
default:
return std::tuple();
}
}();
if (rateAura != SPELL_AURA_NONE)
{
// take only lowest negative and highest positive auras - these effects do not stack
if (int32 neg = GetMaxNegativeAuraModifier(rateAura, [](AuraEffect const* mod) { return mod->GetAmount() > 0 && mod->GetAmount() < 100; }))
ApplyPct(newValue, neg);
if (int32 pos = GetMaxPositiveAuraModifier(rateAura, [](AuraEffect const* mod) { return mod->GetAmount() > 100; }))
ApplyPct(newValue, pos);
}
if (m_advFlyingSpeed[speedType] == newValue)
return;
m_advFlyingSpeed[speedType] = newValue;
if (!clientUpdate)
return;
if (Player* playerMover = Unit::ToPlayer(GetUnitBeingMoved()))
{
WorldPackets::Movement::SetAdvFlyingSpeed selfpacket(opcode);
selfpacket.MoverGUID = GetGUID();
selfpacket.SequenceIndex = m_movementCounter++;
selfpacket.Speed = newValue;
playerMover->GetSession()->SendPacket(selfpacket.Write());
}
}
void Unit::UpdateAdvFlyingSpeed(AdvFlyingRateTypeRange speedType, bool clientUpdate)
{
FlightCapabilityEntry const* flightCapabilityEntry = sFlightCapabilityStore.LookupEntry(GetFlightCapabilityID());
if (!flightCapabilityEntry)
flightCapabilityEntry = sFlightCapabilityStore.AssertEntry(1);
auto [opcode, min, max, rateAura] = [&]
{
switch (speedType)
{
case ADV_FLYING_BANKING_RATE:
return std::tuple(SMSG_MOVE_SET_ADV_FLYING_BANKING_RATE, flightCapabilityEntry->BankingRateMin, flightCapabilityEntry->BankingRateMax, SPELL_AURA_MOD_ADV_FLYING_BANKING_RATE);
case ADV_FLYING_PITCHING_RATE_DOWN:
return std::tuple(SMSG_MOVE_SET_ADV_FLYING_PITCHING_RATE_DOWN, flightCapabilityEntry->PitchingRateDownMin, flightCapabilityEntry->PitchingRateDownMax, SPELL_AURA_MOD_ADV_FLYING_PITCHING_RATE_DOWN);
case ADV_FLYING_PITCHING_RATE_UP:
return std::tuple(SMSG_MOVE_SET_ADV_FLYING_PITCHING_RATE_UP, flightCapabilityEntry->PitchingRateUpMin, flightCapabilityEntry->PitchingRateUpMax, SPELL_AURA_MOD_ADV_FLYING_PITCHING_RATE_UP);
case ADV_FLYING_TURN_VELOCITY_THRESHOLD:
return std::tuple(SMSG_MOVE_SET_ADV_FLYING_TURN_VELOCITY_THRESHOLD, flightCapabilityEntry->TurnVelocityThresholdMin, flightCapabilityEntry->TurnVelocityThresholdMax, SPELL_AURA_NONE);
default:
return std::tuple();
}
}();
if (rateAura != SPELL_AURA_NONE)
{
// take only lowest negative and highest positive auras - these effects do not stack
if (int32 neg = GetMaxNegativeAuraModifier(rateAura, [](AuraEffect const* mod) { return mod->GetAmount() > 0 && mod->GetAmount() < 100; }))
{
ApplyPct(min, neg);
ApplyPct(max, neg);
}
if (int32 pos = GetMaxPositiveAuraModifier(rateAura, [](AuraEffect const* mod) { return mod->GetAmount() > 100; }))
{
ApplyPct(min, pos);
ApplyPct(max, pos);
}
}
if (m_advFlyingSpeed[speedType] == min && m_advFlyingSpeed[speedType + 1] == max)
return;
m_advFlyingSpeed[speedType] = min;
m_advFlyingSpeed[speedType + 1] = max;
if (!clientUpdate)
return;
if (Player* playerMover = Unit::ToPlayer(GetUnitBeingMoved()))
{
WorldPackets::Movement::SetAdvFlyingSpeedRange selfpacket(opcode);
selfpacket.MoverGUID = GetGUID();
selfpacket.SequenceIndex = m_movementCounter++;
selfpacket.SpeedMin = min;
selfpacket.SpeedMax = max;
playerMover->GetSession()->SendPacket(selfpacket.Write());
}
}
void Unit::FollowerAdded(AbstractFollower* f)
{
m_followingMe.insert(f);
}
void Unit::FollowerRemoved(AbstractFollower* f)
{
m_followingMe.erase(f);
}
void Unit::RemoveAllFollowers()
{
while (!m_followingMe.empty())
(*m_followingMe.begin())->SetTarget(nullptr);
}
void Unit::setDeathState(DeathState s)
{
// Death state needs to be updated before RemoveAllAurasOnDeath() is called, to prevent entering combat
m_deathState = s;
bool isOnVehicle = GetVehicle() != nullptr;
if (s != ALIVE && s != JUST_RESPAWNED)
{
CombatStop();
if (IsNonMeleeSpellCast(false))
InterruptNonMeleeSpells(false);
ExitVehicle(); // Exit vehicle before calling RemoveAllControlled
// vehicles use special type of charm that is not removed by the next function
// triggering an assert
UnsummonAllTotems();
RemoveAllControlled();
RemoveAllAurasOnDeath();
}
if (s == JUST_DIED)
{
// remove aurastates allowing special moves
ClearAllReactives();
ClearDiminishings();
// Don't clear the movement if the Unit was on a vehicle as we are exiting now
if (!isOnVehicle)
{
if (GetMotionMaster()->StopOnDeath())
DisableSpline();
}
// without this when removing IncreaseMaxHealth aura player may stuck with 1 hp
// do not why since in IncreaseMaxHealth currenthealth is checked
SetHealth(0);
SetPower(GetPowerType(), 0);
SetEmoteState(EMOTE_ONESHOT_NONE);
SetStandState(UNIT_STAND_STATE_STAND);
if (m_vignette && !m_vignette->Data->GetFlags().HasFlag(VignetteFlags::PersistsThroughDeath))
SetVignette(0);
// players in instance don't have ZoneScript, but they have InstanceScript
if (ZoneScript* zoneScript = GetZoneScript() ? GetZoneScript() : GetInstanceScript())
zoneScript->OnUnitDeath(this);
}
else if (s == JUST_RESPAWNED)
RemoveUnitFlag(UNIT_FLAG_SKINNABLE); // clear skinnable for creature and player (at battleground)
}
//======================================================================
void Unit::AtEnterCombat()
{
for (auto itr = m_appliedAuras.begin(); itr != m_appliedAuras.end();)
{
AuraApplication* aurApp = itr->second;
++itr;
aurApp->GetBase()->CallScriptEnterLeaveCombatHandlers(aurApp, true);
}
if (Spell* spell = m_currentSpells[CURRENT_GENERIC_SPELL])
if (spell->getState() == SPELL_STATE_PREPARING
&& spell->m_spellInfo->HasAttribute(SPELL_ATTR0_NOT_IN_COMBAT_ONLY_PEACEFUL)
&& spell->m_spellInfo->InterruptFlags.HasFlag(SpellInterruptFlags::Combat))
InterruptNonMeleeSpells(false);
RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::EnteringCombat);
Unit::ProcSkillsAndAuras(this, nullptr, PROC_FLAG_ENTER_COMBAT, PROC_FLAG_NONE, PROC_SPELL_TYPE_MASK_ALL, PROC_SPELL_PHASE_NONE, PROC_HIT_NONE, nullptr, nullptr, nullptr);
if (!IsInteractionAllowedInCombat())
UpdateNearbyPlayersInteractions();
}
void Unit::AtExitCombat()
{
for (auto itr = m_appliedAuras.begin(); itr != m_appliedAuras.end();)
{
AuraApplication* aurApp = itr->second;
++itr;
aurApp->GetBase()->CallScriptEnterLeaveCombatHandlers(aurApp, false);
}
RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::LeavingCombat);
if (!IsInteractionAllowedInCombat())
UpdateNearbyPlayersInteractions();
}
void Unit::AtTargetAttacked(Unit* target, bool canInitialAggro)
{
if (!target->IsEngaged() && !canInitialAggro)
return;
target->EngageWithTarget(this);
if (Unit* targetOwner = target->GetCharmerOrOwner())
targetOwner->EngageWithTarget(this);
Player* myPlayerOwner = GetCharmerOrOwnerPlayerOrPlayerItself();
Player* targetPlayerOwner = target->GetCharmerOrOwnerPlayerOrPlayerItself();
if (myPlayerOwner && targetPlayerOwner && !(myPlayerOwner->duel && myPlayerOwner->duel->Opponent == targetPlayerOwner))
{
myPlayerOwner->UpdatePvP(true);
myPlayerOwner->SetContestedPvP(targetPlayerOwner);
myPlayerOwner->RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::PvPActive);
}
}
void Unit::UpdatePetCombatState()
{
ASSERT(!IsPet()); // player pets do not use UNIT_FLAG_PET_IN_COMBAT for this purpose - but player pets should also never have minions of their own to call this
bool state = false;
for (Unit* minion : m_Controlled)
if (minion->IsInCombat())
{
state = true;
break;
}
if (state)
SetUnitFlag(UNIT_FLAG_PET_IN_COMBAT);
else
RemoveUnitFlag(UNIT_FLAG_PET_IN_COMBAT);
}
void Unit::SetInteractionAllowedWhileHostile(bool interactionAllowed)
{
if (interactionAllowed)
SetUnitFlag2(UNIT_FLAG2_INTERACT_WHILE_HOSTILE);
else
RemoveUnitFlag2(UNIT_FLAG2_INTERACT_WHILE_HOSTILE);
UpdateNearbyPlayersInteractions();
}
void Unit::SetInteractionAllowedInCombat(bool interactionAllowed)
{
if (interactionAllowed)
SetUnitFlag3(UNIT_FLAG3_ALLOW_INTERACTION_WHILE_IN_COMBAT);
else
RemoveUnitFlag3(UNIT_FLAG3_ALLOW_INTERACTION_WHILE_IN_COMBAT);
if (IsInCombat())
UpdateNearbyPlayersInteractions();
}
void Unit::UpdateNearbyPlayersInteractions()
{
if (m_unitData->NpcFlags)
ForceUpdateFieldChange(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::NpcFlags));
if (m_unitData->NpcFlags2)
ForceUpdateFieldChange(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::NpcFlags2));
}
//======================================================================
DiminishingLevels Unit::GetDiminishing(DiminishingGroup group) const
{
DiminishingReturn const& diminish = m_Diminishing[group];
if (!diminish.hitCount)
return DIMINISHING_LEVEL_1;
// If last spell was cast more than 18 seconds ago - reset level.
if (!diminish.stack && GetMSTimeDiffToNow(diminish.hitTime) > 18 * IN_MILLISECONDS)
return DIMINISHING_LEVEL_1;
return DiminishingLevels(diminish.hitCount);
}
void Unit::IncrDiminishing(SpellInfo const* auraSpellInfo)
{
DiminishingGroup group = auraSpellInfo->GetDiminishingReturnsGroupForSpell();
uint32 currentLevel = GetDiminishing(group);
uint32 const maxLevel = auraSpellInfo->GetDiminishingReturnsMaxLevel();
DiminishingReturn& diminish = m_Diminishing[group];
if (currentLevel < maxLevel)
diminish.hitCount = currentLevel + 1;
}
bool Unit::ApplyDiminishingToDuration(SpellInfo const* auraSpellInfo, int32& duration, WorldObject* caster, DiminishingLevels previousLevel) const
{
DiminishingGroup const group = auraSpellInfo->GetDiminishingReturnsGroupForSpell();
if (duration == -1 || group == DIMINISHING_NONE)
return true;
int32 const limitDuration = auraSpellInfo->GetDiminishingReturnsLimitDuration();
// test pet/charm masters instead pets/charmeds
Unit const* targetOwner = GetCharmerOrOwner();
Unit const* casterOwner = caster->GetCharmerOrOwner();
if (limitDuration > 0 && duration > limitDuration)
{
Unit const* target = targetOwner ? targetOwner : this;
WorldObject const* source = casterOwner ? casterOwner : caster;
if (target->IsAffectedByDiminishingReturns() && source->GetTypeId() == TYPEID_PLAYER)
duration = limitDuration;
}
float mod = 1.0f;
switch (group)
{
case DIMINISHING_TAUNT:
{
if (GetTypeId() == TYPEID_UNIT && (ToCreature()->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_OBEYS_TAUNT_DIMINISHING_RETURNS))
{
DiminishingLevels diminish = previousLevel;
switch (diminish)
{
case DIMINISHING_LEVEL_1: break;
case DIMINISHING_LEVEL_2: mod = 0.65f; break;
case DIMINISHING_LEVEL_3: mod = 0.4225f; break;
case DIMINISHING_LEVEL_4: mod = 0.274625f; break;
case DIMINISHING_LEVEL_TAUNT_IMMUNE: mod = 0.0f; break;
default: break;
}
}
break;
}
case DIMINISHING_AOE_KNOCKBACK:
{
if (auraSpellInfo->GetDiminishingReturnsGroupType() == DRTYPE_ALL ||
(auraSpellInfo->GetDiminishingReturnsGroupType() == DRTYPE_PLAYER &&
(targetOwner ? targetOwner->IsAffectedByDiminishingReturns() : IsAffectedByDiminishingReturns())))
{
DiminishingLevels diminish = previousLevel;
switch (diminish)
{
case DIMINISHING_LEVEL_1: break;
case DIMINISHING_LEVEL_2: mod = 0.0f; break;
default: break;
}
}
break;
}
default:
{
if (auraSpellInfo->GetDiminishingReturnsGroupType() == DRTYPE_ALL ||
(auraSpellInfo->GetDiminishingReturnsGroupType() == DRTYPE_PLAYER &&
(targetOwner ? targetOwner->IsAffectedByDiminishingReturns() : IsAffectedByDiminishingReturns())))
{
DiminishingLevels diminish = previousLevel;
switch (diminish)
{
case DIMINISHING_LEVEL_1: break;
case DIMINISHING_LEVEL_2: mod = 0.5f; break;
case DIMINISHING_LEVEL_3: mod = 0.25f; break;
case DIMINISHING_LEVEL_IMMUNE: mod = 0.0f; break;
default: break;
}
}
break;
}
}
duration = int32(duration * mod);
return (duration != 0);
}
void Unit::ApplyDiminishingAura(DiminishingGroup group, bool apply)
{
// Checking for existing in the table
DiminishingReturn& diminish = m_Diminishing[group];
if (apply)
++diminish.stack;
else if (diminish.stack)
{
--diminish.stack;
// Remember time after last aura from group removed
if (!diminish.stack)
diminish.hitTime = GameTime::GetGameTimeMS();
}
}
void Unit::ClearDiminishings()
{
for (DiminishingReturn& dim : m_Diminishing)
dim.Clear();
}
uint32 Unit::GetCreatureType() const
{
if (GetTypeId() == TYPEID_PLAYER)
{
ShapeshiftForm form = GetShapeshiftForm();
SpellShapeshiftFormEntry const* ssEntry = sSpellShapeshiftFormStore.LookupEntry(form);
if (ssEntry && ssEntry->CreatureType > 0)
return ssEntry->CreatureType;
else
{
ChrRacesEntry const* raceEntry = sChrRacesStore.AssertEntry(GetRace());
return raceEntry->CreatureType;
}
}
else
return ToCreature()->GetCreatureTemplate()->type;
}
uint32 Unit::GetCreatureTypeMask() const
{
uint32 creatureType = GetCreatureType();
return (creatureType >= 1) ? (1 << (creatureType - 1)) : 0;
}
void Unit::SetShapeshiftForm(ShapeshiftForm form)
{
SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::ShapeshiftForm), form);
}
void Unit::CancelShapeshiftForm(bool onlyTravelShapeshiftForm /*= false*/, AuraRemoveMode removeMode /*= AURA_REMOVE_BY_DEFAULT*/, bool force /*= false*/)
{
ShapeshiftForm form = GetShapeshiftForm();
if (form == FORM_NONE)
return;
bool isTravelShapeshiftForm = [form]()
{
if (SpellShapeshiftFormEntry const* shapeInfo = sSpellShapeshiftFormStore.LookupEntry(form))
{
if (shapeInfo->MountTypeID)
return true;
if (shapeInfo->ID == FORM_TRAVEL_FORM || shapeInfo->ID == FORM_AQUATIC_FORM)
return true;
}
return false;
}();
if (onlyTravelShapeshiftForm && !isTravelShapeshiftForm)
return;
AuraEffectVector shapeshifts = CopyAuraEffectList(GetAuraEffectsByType(SPELL_AURA_MOD_SHAPESHIFT));
for (AuraEffect* aurEff : shapeshifts)
{
SpellInfo const* spellInfo = aurEff->GetBase()->GetSpellInfo();
if (force || (!spellInfo->HasAttribute(SPELL_ATTR0_NO_AURA_CANCEL) && spellInfo->IsPositive() && !spellInfo->IsPassive()))
aurEff->GetBase()->Remove(removeMode);
}
}
bool Unit::IsInFeralForm() const
{
ShapeshiftForm form = GetShapeshiftForm();
return form == FORM_CAT_FORM || form == FORM_BEAR_FORM || form == FORM_DIRE_BEAR_FORM || form == FORM_GHOST_WOLF;
}
bool Unit::IsInDisallowedMountForm() const
{
return IsDisallowedMountForm(GetTransformSpell(), GetShapeshiftForm(), GetDisplayId());
}
bool Unit::IsDisallowedMountForm(uint32 spellId, ShapeshiftForm form, uint32 displayId) const
{
if (SpellInfo const* transformSpellInfo = sSpellMgr->GetSpellInfo(spellId, GetMap()->GetDifficultyID()))
if (transformSpellInfo->HasAttribute(SPELL_ATTR0_ALLOW_WHILE_MOUNTED))
return false;
if (form)
{
SpellShapeshiftFormEntry const* shapeshift = sSpellShapeshiftFormStore.LookupEntry(form);
if (!shapeshift)
return true;
if (!shapeshift->GetFlags().HasFlag(SpellShapeshiftFormFlags::Stance))
return true;
}
if (displayId == GetNativeDisplayId())
return false;
CreatureDisplayInfoEntry const* display = sCreatureDisplayInfoStore.LookupEntry(displayId);
if (!display)
return true;
CreatureDisplayInfoExtraEntry const* displayExtra = sCreatureDisplayInfoExtraStore.LookupEntry(display->ExtendedDisplayInfoID);
if (!displayExtra)
return true;
CreatureModelDataEntry const* model = sCreatureModelDataStore.LookupEntry(display->ModelID);
ChrRacesEntry const* race = sChrRacesStore.LookupEntry(displayExtra->DisplayRaceID);
if (model && !model->GetFlags().HasFlag(CreatureModelDataFlags::CanMountWhileTransformedAsThis))
if (race && !race->GetFlags().HasFlag(ChrRacesFlag::CanMount))
return true;
return false;
}
/*#######################################
######## ########
######## STAT SYSTEM ########
######## ########
#######################################*/
void Unit::HandleStatFlatModifier(UnitMods unitMod, UnitModifierFlatType modifierType, float amount, bool apply)
{
if (unitMod >= UNIT_MOD_END || modifierType >= MODIFIER_TYPE_FLAT_END)
{
TC_LOG_ERROR("entities.unit", "ERROR in HandleStatFlatModifier(): non-existing UnitMods or wrong UnitModifierType!");
return;
}
if (!amount)
return;
switch (modifierType)
{
case BASE_VALUE:
case BASE_PCT_EXCLUDE_CREATE:
case TOTAL_VALUE:
m_auraFlatModifiersGroup[unitMod][modifierType] += apply ? amount : -amount;
break;
default:
break;
}
UpdateUnitMod(unitMod);
}
void Unit::ApplyStatPctModifier(UnitMods unitMod, UnitModifierPctType modifierType, float pct)
{
if (unitMod >= UNIT_MOD_END || modifierType >= MODIFIER_TYPE_PCT_END)
{
TC_LOG_ERROR("entities.unit", "ERROR in ApplyStatPctModifier(): non-existing UnitMods or wrong UnitModifierType!");
return;
}
if (!pct)
return;
switch (modifierType)
{
case BASE_PCT:
case TOTAL_PCT:
AddPct(m_auraPctModifiersGroup[unitMod][modifierType], pct);
break;
default:
break;
}
UpdateUnitMod(unitMod);
}
void Unit::SetStatFlatModifier(UnitMods unitMod, UnitModifierFlatType modifierType, float val)
{
if (m_auraFlatModifiersGroup[unitMod][modifierType] == val)
return;
m_auraFlatModifiersGroup[unitMod][modifierType] = val;
UpdateUnitMod(unitMod);
}
void Unit::SetStatPctModifier(UnitMods unitMod, UnitModifierPctType modifierType, float val)
{
if (m_auraPctModifiersGroup[unitMod][modifierType] == val)
return;
m_auraPctModifiersGroup[unitMod][modifierType] = val;
UpdateUnitMod(unitMod);
}
float Unit::GetFlatModifierValue(UnitMods unitMod, UnitModifierFlatType modifierType) const
{
if (unitMod >= UNIT_MOD_END || modifierType >= MODIFIER_TYPE_FLAT_END)
{
TC_LOG_ERROR("entities.unit", "attempt to access non-existing modifier value from UnitMods!");
return 0.0f;
}
return m_auraFlatModifiersGroup[unitMod][modifierType];
}
float Unit::GetPctModifierValue(UnitMods unitMod, UnitModifierPctType modifierType) const
{
if (unitMod >= UNIT_MOD_END || modifierType >= MODIFIER_TYPE_PCT_END)
{
TC_LOG_ERROR("entities.unit", "attempt to access non-existing modifier value from UnitMods!");
return 0.0f;
}
return m_auraPctModifiersGroup[unitMod][modifierType];
}
void Unit::UpdateUnitMod(UnitMods unitMod)
{
if (!CanModifyStats())
return;
switch (unitMod)
{
case UNIT_MOD_STAT_STRENGTH:
case UNIT_MOD_STAT_AGILITY:
case UNIT_MOD_STAT_STAMINA:
case UNIT_MOD_STAT_INTELLECT: UpdateStats(GetStatByAuraGroup(unitMod)); break;
case UNIT_MOD_ARMOR: UpdateArmor(); break;
case UNIT_MOD_HEALTH: UpdateMaxHealth(); break;
case UNIT_MOD_MANA:
case UNIT_MOD_RAGE:
case UNIT_MOD_FOCUS:
case UNIT_MOD_ENERGY:
case UNIT_MOD_COMBO_POINTS:
case UNIT_MOD_RUNES:
case UNIT_MOD_RUNIC_POWER:
case UNIT_MOD_SOUL_SHARDS:
case UNIT_MOD_LUNAR_POWER:
case UNIT_MOD_HOLY_POWER:
case UNIT_MOD_ALTERNATE:
case UNIT_MOD_MAELSTROM:
case UNIT_MOD_CHI:
case UNIT_MOD_INSANITY:
case UNIT_MOD_BURNING_EMBERS:
case UNIT_MOD_DEMONIC_FURY:
case UNIT_MOD_ARCANE_CHARGES:
case UNIT_MOD_FURY:
case UNIT_MOD_PAIN:
case UNIT_MOD_ESSENCE:
case UNIT_MOD_RUNE_BLOOD:
case UNIT_MOD_RUNE_FROST:
case UNIT_MOD_RUNE_UNHOLY:
case UNIT_MOD_ALTERNATE_QUEST:
case UNIT_MOD_ALTERNATE_ENCOUNTER:
case UNIT_MOD_ALTERNATE_MOUNT: UpdateMaxPower(Powers(unitMod - UNIT_MOD_POWER_START)); break;
case UNIT_MOD_RESISTANCE_HOLY:
case UNIT_MOD_RESISTANCE_FIRE:
case UNIT_MOD_RESISTANCE_NATURE:
case UNIT_MOD_RESISTANCE_FROST:
case UNIT_MOD_RESISTANCE_SHADOW:
case UNIT_MOD_RESISTANCE_ARCANE: UpdateResistances(GetSpellSchoolByAuraGroup(unitMod)); break;
case UNIT_MOD_ATTACK_POWER: UpdateAttackPowerAndDamage(); break;
case UNIT_MOD_ATTACK_POWER_RANGED: UpdateAttackPowerAndDamage(true); break;
case UNIT_MOD_DAMAGE_MAINHAND: UpdateDamagePhysical(BASE_ATTACK); break;
case UNIT_MOD_DAMAGE_OFFHAND: UpdateDamagePhysical(OFF_ATTACK); break;
case UNIT_MOD_DAMAGE_RANGED: UpdateDamagePhysical(RANGED_ATTACK); break;
default:
ABORT_MSG("Not implemented UnitMod %u", unitMod);
break;
}
}
void Unit::UpdateDamageDoneMods(WeaponAttackType attackType, int32 /*skipEnchantSlot = -1*/)
{
UnitMods unitMod;
switch (attackType)
{
case BASE_ATTACK:
unitMod = UNIT_MOD_DAMAGE_MAINHAND;
break;
case OFF_ATTACK:
unitMod = UNIT_MOD_DAMAGE_OFFHAND;
break;
case RANGED_ATTACK:
unitMod = UNIT_MOD_DAMAGE_RANGED;
break;
default:
ABORT();
break;
}
float amount = GetTotalAuraModifier(SPELL_AURA_MOD_DAMAGE_DONE, [&](AuraEffect const* aurEff) -> bool
{
if (!(aurEff->GetMiscValue() & SPELL_SCHOOL_MASK_NORMAL))
return false;
return CheckAttackFitToAuraRequirement(attackType, aurEff);
});
SetStatFlatModifier(unitMod, TOTAL_VALUE, amount);
}
void Unit::UpdateAllDamageDoneMods()
{
for (uint8 i = BASE_ATTACK; i < MAX_ATTACK; ++i)
UpdateDamageDoneMods(WeaponAttackType(i));
}
void Unit::UpdateDamagePctDoneMods(WeaponAttackType attackType)
{
float factor;
UnitMods unitMod;
switch (attackType)
{
case BASE_ATTACK:
factor = 1.0f;
unitMod = UNIT_MOD_DAMAGE_MAINHAND;
break;
case OFF_ATTACK:
// off hand has 50% penalty
factor = 0.5f;
unitMod = UNIT_MOD_DAMAGE_OFFHAND;
break;
case RANGED_ATTACK:
factor = 1.0f;
unitMod = UNIT_MOD_DAMAGE_RANGED;
break;
default:
ABORT();
break;
}
factor *= GetTotalAuraMultiplier(SPELL_AURA_MOD_DAMAGE_PERCENT_DONE, [attackType, this](AuraEffect const* aurEff) -> bool
{
if (!(aurEff->GetMiscValue() & SPELL_SCHOOL_MASK_NORMAL))
return false;
return CheckAttackFitToAuraRequirement(attackType, aurEff);
});
if (attackType == OFF_ATTACK)
factor *= GetTotalAuraMultiplier(SPELL_AURA_MOD_OFFHAND_DAMAGE_PCT, [this, attackType](AuraEffect const* aurEff)
{
return CheckAttackFitToAuraRequirement(attackType, aurEff);
});
SetStatPctModifier(unitMod, TOTAL_PCT, factor);
}
void Unit::UpdateAllDamagePctDoneMods()
{
for (uint8 i = BASE_ATTACK; i < MAX_ATTACK; ++i)
UpdateDamagePctDoneMods(WeaponAttackType(i));
}
float Unit::GetTotalStatValue(Stats stat) const
{
float createStat = GetCreateStat(stat); // retrieved early to workaround a GCC false positive warning about out of bounds array access (conversion to UnitMods confuses it)
UnitMods unitMod = UnitMods(UNIT_MOD_STAT_START + AsUnderlyingType(stat));
// value = ((base_value * base_pct) + total_value) * total_pct
float value = CalculatePct(GetFlatModifierValue(unitMod, BASE_VALUE), std::max(GetFlatModifierValue(unitMod, BASE_PCT_EXCLUDE_CREATE), -100.0f));
value += createStat;
value *= GetPctModifierValue(unitMod, BASE_PCT);
value += GetFlatModifierValue(unitMod, TOTAL_VALUE);
value *= GetPctModifierValue(unitMod, TOTAL_PCT);
return value;
}
float Unit::GetTotalAuraModValue(UnitMods unitMod) const
{
if (unitMod >= UNIT_MOD_END)
{
TC_LOG_ERROR("entities.unit", "attempt to access non-existing UnitMods in GetTotalAuraModValue()!");
return 0.0f;
}
float value = CalculatePct(GetFlatModifierValue(unitMod, BASE_VALUE), std::max(GetFlatModifierValue(unitMod, BASE_PCT_EXCLUDE_CREATE), -100.0f));
value *= GetPctModifierValue(unitMod, BASE_PCT);
value += GetFlatModifierValue(unitMod, TOTAL_VALUE);
value *= GetPctModifierValue(unitMod, TOTAL_PCT);
return value;
}
SpellSchools Unit::GetSpellSchoolByAuraGroup(UnitMods unitMod) const
{
SpellSchools school = SPELL_SCHOOL_NORMAL;
switch (unitMod)
{
case UNIT_MOD_RESISTANCE_HOLY: school = SPELL_SCHOOL_HOLY; break;
case UNIT_MOD_RESISTANCE_FIRE: school = SPELL_SCHOOL_FIRE; break;
case UNIT_MOD_RESISTANCE_NATURE: school = SPELL_SCHOOL_NATURE; break;
case UNIT_MOD_RESISTANCE_FROST: school = SPELL_SCHOOL_FROST; break;
case UNIT_MOD_RESISTANCE_SHADOW: school = SPELL_SCHOOL_SHADOW; break;
case UNIT_MOD_RESISTANCE_ARCANE: school = SPELL_SCHOOL_ARCANE; break;
default:
break;
}
return school;
}
Stats Unit::GetStatByAuraGroup(UnitMods unitMod) const
{
Stats stat = STAT_STRENGTH;
switch (unitMod)
{
case UNIT_MOD_STAT_STRENGTH: stat = STAT_STRENGTH; break;
case UNIT_MOD_STAT_AGILITY: stat = STAT_AGILITY; break;
case UNIT_MOD_STAT_STAMINA: stat = STAT_STAMINA; break;
case UNIT_MOD_STAT_INTELLECT: stat = STAT_INTELLECT; break;
default:
break;
}
return stat;
}
void Unit::UpdateResistances(uint32 school)
{
if (school > SPELL_SCHOOL_NORMAL)
{
UnitMods unitMod = UnitMods(UNIT_MOD_RESISTANCE_START + school);
float value = CalculatePct(GetFlatModifierValue(unitMod, BASE_VALUE), std::max(GetFlatModifierValue(unitMod, BASE_PCT_EXCLUDE_CREATE), -100.0f));
value *= GetPctModifierValue(unitMod, BASE_PCT);
float baseValue = value;
value += GetFlatModifierValue(unitMod, TOTAL_VALUE);
value *= GetPctModifierValue(unitMod, TOTAL_PCT);
SetResistance(SpellSchools(school), int32(value));
SetBonusResistanceMod(SpellSchools(school), int32(value - baseValue));
}
else
UpdateArmor();
}
float Unit::GetTotalAttackPowerValue(WeaponAttackType attType, bool includeWeapon /*= true*/) const
{
if (attType == RANGED_ATTACK)
{
float ap = m_unitData->RangedAttackPower + m_unitData->RangedAttackPowerModPos + m_unitData->RangedAttackPowerModNeg;
if (includeWeapon)
ap += std::max(m_unitData->MainHandWeaponAttackPower, m_unitData->RangedWeaponAttackPower);
if (ap < 0)
return 0.0f;
return ap * (1.0f + m_unitData->RangedAttackPowerMultiplier);
}
else
{
float ap = m_unitData->AttackPower + m_unitData->AttackPowerModPos + m_unitData->AttackPowerModNeg;
if (includeWeapon)
{
if (attType == BASE_ATTACK)
ap += std::max(m_unitData->MainHandWeaponAttackPower, m_unitData->RangedWeaponAttackPower);
else
{
ap += m_unitData->OffHandWeaponAttackPower;
ap /= 2;
}
}
if (ap < 0)
return 0.0f;
return ap * (1.0f + m_unitData->AttackPowerMultiplier);
}
}
float Unit::GetWeaponDamageRange(WeaponAttackType attType, WeaponDamageRange type) const
{
if (attType == OFF_ATTACK && !haveOffhandWeapon())
return 0.0f;
return m_weaponDamage[attType][type];
}
bool Unit::CanFreeMove() const
{
return !HasUnitState(UNIT_STATE_CONFUSED | UNIT_STATE_FLEEING | UNIT_STATE_IN_FLIGHT |
UNIT_STATE_ROOT | UNIT_STATE_STUNNED | UNIT_STATE_DISTRACTED) && GetOwnerGUID().IsEmpty();
}
void Unit::SetLevel(uint8 lvl, bool sendUpdate/* = true*/)
{
SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::Level), lvl);
if (!sendUpdate)
return;
if (Player* player = ToPlayer())
{
// group update
if (player->GetGroup())
player->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_LEVEL);
sCharacterCache->UpdateCharacterLevel(GetGUID(), lvl);
}
}
void Unit::SetHealth(uint64 val)
{
if (getDeathState() == JUST_DIED || getDeathState() == CORPSE)
val = 0;
else if (GetTypeId() == TYPEID_PLAYER && getDeathState() == DEAD)
val = 1;
else
{
uint64 maxHealth = GetMaxHealth();
if (maxHealth < val)
val = maxHealth;
}
uint64 oldVal = GetHealth();
SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::Health), val);
TriggerOnHealthChangeAuras(oldVal, val);
// group update
if (Player* player = ToPlayer())
{
if (player->GetGroup())
player->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_CUR_HP);
}
else if (Pet* pet = ToCreature()->ToPet())
{
if (pet->isControlled())
pet->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_PET_CUR_HP);
}
}
void Unit::SetMaxHealth(uint64 val)
{
if (!val)
val = 1;
uint64 health = GetHealth();
SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::MaxHealth), val);
// group update
if (GetTypeId() == TYPEID_PLAYER)
{
if (ToPlayer()->GetGroup())
ToPlayer()->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_MAX_HP);
}
else if (Pet* pet = ToCreature()->ToPet())
{
if (pet->isControlled())
pet->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_PET_MAX_HP);
}
if (val < health)
SetHealth(val);
}
int32 Unit::GetPower(Powers power) const
{
uint32 powerIndex = GetPowerIndex(power);
if (powerIndex == MAX_POWERS || powerIndex >= MAX_POWERS_PER_CLASS)
return 0;
return m_unitData->Power[powerIndex];
}
int32 Unit::GetMaxPower(Powers power) const
{
uint32 powerIndex = GetPowerIndex(power);
if (powerIndex == MAX_POWERS || powerIndex >= MAX_POWERS_PER_CLASS)
return 0;
return m_unitData->MaxPower[powerIndex];
}
void Unit::SetPower(Powers power, int32 val, bool withPowerUpdate /*= true*/)
{
uint32 powerIndex = GetPowerIndex(power);
if (powerIndex == MAX_POWERS || powerIndex >= MAX_POWERS_PER_CLASS)
return;
int32 maxPower = GetMaxPower(power);
if (maxPower < val)
val = maxPower;
int32 oldPower = m_unitData->Power[powerIndex];
SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::Power, powerIndex), val);
if (IsInWorld() && withPowerUpdate)
{
WorldPackets::Combat::PowerUpdate packet;
packet.Guid = GetGUID();
/// @todo: Support multiple counts ?
packet.Powers.emplace_back(val, power);
SendMessageToSet(packet.Write(), GetTypeId() == TYPEID_PLAYER);
}
TriggerOnPowerChangeAuras(power, oldPower, val);
// group update
if (Player* player = ToPlayer())
{
if (player->GetGroup())
player->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_CUR_POWER);
}
/*else if (Pet* pet = ToCreature()->ToPet()) TODO 6.x
{
if (pet->isControlled())
pet->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_PET_CUR_POWER);
}*/
}
void Unit::SetMaxPower(Powers power, int32 val)
{
uint32 powerIndex = GetPowerIndex(power);
if (powerIndex == MAX_POWERS || powerIndex >= MAX_POWERS_PER_CLASS)
return;
int32 cur_power = GetPower(power);
SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::MaxPower, powerIndex), val);
// group update
if (GetTypeId() == TYPEID_PLAYER)
{
if (ToPlayer()->GetGroup())
ToPlayer()->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_MAX_POWER);
}
/*else if (Pet* pet = ToCreature()->ToPet()) TODO 6.x
{
if (pet->isControlled())
pet->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_PET_MAX_POWER);
}*/
if (val < cur_power)
SetPower(power, val);
}
void Unit::TriggerOnPowerChangeAuras(Powers power, int32 oldVal, int32 newVal)
{
auto processAuras = [&](AuraEffectVector const& effects)
{
for (AuraEffect const* effect : effects)
{
if (effect->GetMiscValue() == power)
{
int32 effectAmount = effect->GetAmount();
uint32 triggerSpell = effect->GetSpellEffectInfo().TriggerSpell;
float oldValueCheck = oldVal;
float newValueCheck = newVal;
if (effect->GetAuraType() == SPELL_AURA_TRIGGER_SPELL_ON_POWER_PCT)
{
int32 maxPower = GetMaxPower(power);
if (!maxPower)
continue;
oldValueCheck = GetPctOf(oldVal, maxPower);
newValueCheck = GetPctOf(newVal, maxPower);
}
switch (AuraTriggerOnPowerChangeDirection(effect->GetMiscValueB()))
{
case AuraTriggerOnPowerChangeDirection::Gain:
if (oldValueCheck >= effect->GetAmount() || newValueCheck < effectAmount)
continue;
break;
case AuraTriggerOnPowerChangeDirection::Loss:
if (oldValueCheck <= effect->GetAmount() || newValueCheck > effectAmount)
continue;
break;
default:
break;
}
CastSpell(this, triggerSpell, effect);
}
}
};
processAuras(CopyAuraEffectList(GetAuraEffectsByType(SPELL_AURA_TRIGGER_SPELL_ON_POWER_PCT)));
processAuras(CopyAuraEffectList(GetAuraEffectsByType(SPELL_AURA_TRIGGER_SPELL_ON_POWER_AMOUNT)));
}
void Unit::AIUpdateTick(uint32 diff)
{
if (UnitAI* ai = GetAI())
{
m_aiLocked = true;
ai->UpdateAI(diff);
m_aiLocked = false;
}
}
void Unit::PushAI(UnitAI* newAI)
{
i_AIs.emplace(newAI);
}
void Unit::SetAI(UnitAI* newAI)
{
PushAI(newAI);
RefreshAI();
}
bool Unit::PopAI()
{
if (!i_AIs.empty())
{
i_AIs.pop();
return true;
}
else
return false;
}
void Unit::RefreshAI()
{
ASSERT(!m_aiLocked, "Tried to change current AI during UpdateAI()");
if (i_AIs.empty())
i_AI = nullptr;
else
i_AI = i_AIs.top();
}
void Unit::ScheduleAIChange()
{
bool const charmed = IsCharmed();
if (charmed)
PushAI(GetScheduledChangeAI());
else
{
RestoreDisabledAI();
PushAI(GetScheduledChangeAI()); //This could actually be PopAI() to get the previous AI but it's required atm to trigger UpdateCharmAI()
}
}
void Unit::RestoreDisabledAI()
{
// Keep popping the stack until we either reach the bottom or find a valid AI
while (PopAI())
if (GetTopAI() && dynamic_cast(GetTopAI()) == nullptr)
return;
}
UnitAI* Unit::GetScheduledChangeAI()
{
if (Creature* creature = ToCreature())
return sCreatureAIRegistry->GetRegistryItem("ScheduledChangeAI")->Create(creature);
else
return nullptr;
}
bool Unit::HasScheduledAIChange() const
{
if (UnitAI* ai = GetAI())
return dynamic_cast(ai) != nullptr;
else
return true;
}
void Unit::AddToWorld()
{
WorldObject::AddToWorld();
i_motionMaster->AddToWorld();
RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::EnterWorld);
}
void Unit::RemoveFromWorld()
{
// cleanup
ASSERT(GetGUID());
if (IsInWorld())
{
if (IsAreaSpiritHealer())
{
if (Creature* creature = ToCreature())
creature->SummonGraveyardTeleporter();
}
m_duringRemoveFromWorld = true;
if (UnitAI* ai = GetAI())
ai->OnDespawn();
if (IsVehicle())
RemoveVehicleKit(true);
RemoveCharmAuras();
RemoveBindSightAuras();
RemoveNotOwnSingleTargetAuras();
RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::LeaveWorld);
RemoveAllGameObjects();
RemoveAllDynObjects();
RemoveAllAreaTriggers(AreaTriggerRemoveReason::UnitDespawn);
ExitAllAreaTriggers(); // exit all areatriggers the unit is in
ExitVehicle(); // Remove applied auras with SPELL_AURA_CONTROL_VEHICLE
UnsummonAllTotems();
RemoveAllControlled();
RemoveAreaAurasDueToLeaveWorld();
RemoveAllFollowers();
if (IsCharmed())
RemoveCharmedBy(nullptr);
ASSERT(!GetCharmedGUID(), "Unit %u has charmed guid when removed from world", GetEntry());
ASSERT(!GetCharmerGUID(), "Unit %u has charmer guid when removed from world", GetEntry());
if (Unit* owner = GetOwner())
{
if (owner->m_Controlled.find(this) != owner->m_Controlled.end())
{
TC_LOG_FATAL("entities.unit", "Unit {} is in controlled list of {} when removed from world", GetEntry(), owner->GetEntry());
ABORT();
}
}
WorldObject::RemoveFromWorld();
m_duringRemoveFromWorld = false;
}
}
void Unit::CleanupBeforeRemoveFromMap(bool finalCleanup)
{
// This needs to be before RemoveFromWorld to make GetCaster() return a valid pointer on aura removal
InterruptNonMeleeSpells(true);
SetVignette(0);
if (IsInWorld())
RemoveFromWorld();
else
{
// cleanup that must happen even if not in world
if (IsVehicle())
RemoveVehicleKit(true);
}
ASSERT(GetGUID());
// A unit may be in removelist and not in world, but it is still in grid
// and may have some references during delete
RemoveAllAuras();
RemoveAllGameObjects();
if (finalCleanup)
m_cleanupDone = true;
CombatStop();
}
void Unit::CleanupsBeforeDelete(bool finalCleanup)
{
CleanupBeforeRemoveFromMap(finalCleanup);
WorldObject::CleanupsBeforeDelete(finalCleanup);
}
void Unit::UpdateCharmAI()
{
if (IsCharmed())
{
UnitAI* newAI = nullptr;
if (GetTypeId() == TYPEID_PLAYER)
{
if (Unit* charmer = GetCharmer())
{
// first, we check if the creature's own AI specifies an override playerai for its owned players
if (Creature* creatureCharmer = charmer->ToCreature())
{
if (CreatureAI* charmerAI = creatureCharmer->AI())
newAI = charmerAI->GetAIForCharmedPlayer(ToPlayer());
}
else
TC_LOG_ERROR("entities.unit.ai", "Attempt to assign charm AI to player {} who is charmed by non-creature {}.", GetGUID().ToString(), GetCharmerGUID().ToString());
}
if (!newAI) // otherwise, we default to the generic one
newAI = new SimpleCharmedPlayerAI(ToPlayer());
}
else
{
ASSERT(GetTypeId() == TYPEID_UNIT);
if (isPossessed() || IsVehicle())
newAI = ASSERT_NOTNULL(sCreatureAIRegistry->GetRegistryItem("PossessedAI"))->Create(ToCreature());
else
newAI = ASSERT_NOTNULL(sCreatureAIRegistry->GetRegistryItem("PetAI"))->Create(ToCreature());
}
ASSERT(newAI);
SetAI(newAI);
newAI->OnCharmed(true);
}
else
{
RestoreDisabledAI();
// Hack: this is required because we want to call OnCharmed(true) on the restored AI
RefreshAI();
if (UnitAI* ai = GetAI())
ai->OnCharmed(true);
}
}
CharmInfo* Unit::InitCharmInfo()
{
if (!m_charmInfo)
m_charmInfo = std::make_unique(this);
return m_charmInfo.get();
}
void Unit::DeleteCharmInfo()
{
if (!m_charmInfo)
return;
m_charmInfo->RestoreState();
m_charmInfo = nullptr;
}
void Unit::SetMovedUnit(Unit* target)
{
m_unitMovedByMe->m_playerMovingMe = nullptr;
m_unitMovedByMe = ASSERT_NOTNULL(target);
m_unitMovedByMe->m_playerMovingMe = ASSERT_NOTNULL(ToPlayer());
WorldPackets::Movement::MoveSetActiveMover packet;
packet.MoverGUID = target->GetGUID();
ToPlayer()->SendDirectMessage(packet.Write());
}
ProcFlagsHit createProcHitMask(SpellNonMeleeDamage* damageInfo, SpellMissInfo missCondition)
{
ProcFlagsHit hitMask = PROC_HIT_NONE;
// Check victim state
if (missCondition != SPELL_MISS_NONE)
{
switch (missCondition)
{
case SPELL_MISS_MISS:
hitMask |= PROC_HIT_MISS;
break;
case SPELL_MISS_DODGE:
hitMask |= PROC_HIT_DODGE;
break;
case SPELL_MISS_PARRY:
hitMask |= PROC_HIT_PARRY;
break;
case SPELL_MISS_BLOCK:
// spells can't be partially blocked (it's damage can though)
hitMask |= PROC_HIT_BLOCK | PROC_HIT_FULL_BLOCK;
break;
case SPELL_MISS_EVADE:
hitMask |= PROC_HIT_EVADE;
break;
case SPELL_MISS_IMMUNE:
case SPELL_MISS_IMMUNE2:
hitMask |= PROC_HIT_IMMUNE;
break;
case SPELL_MISS_DEFLECT:
hitMask |= PROC_HIT_DEFLECT;
break;
case SPELL_MISS_ABSORB:
hitMask |= PROC_HIT_ABSORB;
break;
case SPELL_MISS_REFLECT:
hitMask |= PROC_HIT_REFLECT;
break;
case SPELL_MISS_RESIST:
hitMask |= PROC_HIT_FULL_RESIST;
break;
default:
break;
}
}
else
{
// On block
if (damageInfo->blocked)
{
hitMask |= PROC_HIT_BLOCK;
if (damageInfo->fullBlock)
hitMask |= PROC_HIT_FULL_BLOCK;
}
// On absorb
if (damageInfo->absorb)
hitMask |= PROC_HIT_ABSORB;
// Don't set hit/crit hitMask if damage is nullified
bool const damageNullified = (damageInfo->HitInfo & (HITINFO_FULL_ABSORB | HITINFO_FULL_RESIST)) != 0 || (hitMask & PROC_HIT_FULL_BLOCK) != 0;
if (!damageNullified)
{
// On crit
if (damageInfo->HitInfo & SPELL_HIT_TYPE_CRIT)
hitMask |= PROC_HIT_CRITICAL;
else
hitMask |= PROC_HIT_NORMAL;
}
else if ((damageInfo->HitInfo & HITINFO_FULL_RESIST) != 0)
hitMask |= PROC_HIT_FULL_RESIST;
}
return hitMask;
}
void Unit::ProcSkillsAndReactives(bool isVictim, Unit* procTarget, ProcFlagsInit const& typeMask, ProcFlagsHit hitMask, WeaponAttackType /*attType*/)
{
// Player is loaded now - do not allow passive spell casts to proc
if (GetTypeId() == TYPEID_PLAYER && ToPlayer()->GetSession()->PlayerLoading())
return;
// For melee/ranged based attack need update skills and set some Aura states if victim present
if (typeMask & MELEE_BASED_TRIGGER_MASK && procTarget)
{
// If exist crit/parry/dodge/block need update aura state (for victim and attacker)
if (hitMask & (PROC_HIT_CRITICAL | PROC_HIT_PARRY | PROC_HIT_DODGE | PROC_HIT_BLOCK))
{
// for victim
if (isVictim)
{
// if victim and dodge attack
if (hitMask & PROC_HIT_DODGE)
{
// Update AURA_STATE on dodge
if (GetClass() != CLASS_ROGUE) // skip Rogue Riposte
{
ModifyAuraState(AURA_STATE_DEFENSIVE, true);
StartReactiveTimer(REACTIVE_DEFENSE);
}
}
// if victim and parry attack
if (hitMask & PROC_HIT_PARRY)
{
ModifyAuraState(AURA_STATE_DEFENSIVE, true);
StartReactiveTimer(REACTIVE_DEFENSE);
}
// if and victim block attack
if (hitMask & PROC_HIT_BLOCK)
{
ModifyAuraState(AURA_STATE_DEFENSIVE, true);
StartReactiveTimer(REACTIVE_DEFENSE);
}
}
}
}
}
void Unit::GetProcAurasTriggeredOnEvent(AuraApplicationProcContainer& aurasTriggeringProc, AuraApplicationList* procAuras, ProcEventInfo& eventInfo)
{
TimePoint now = GameTime::Now();
auto processAuraApplication = [&](AuraApplication* aurApp)
{
if (uint32 procEffectMask = aurApp->GetBase()->GetProcEffectMask(aurApp, eventInfo, now))
{
aurApp->GetBase()->PrepareProcToTrigger(aurApp, eventInfo, now);
aurasTriggeringProc.emplace_back(procEffectMask, aurApp);
}
else
{
if (aurApp->GetBase()->GetSpellInfo()->HasAttribute(SPELL_ATTR0_PROC_FAILURE_BURNS_CHARGE))
{
if (SpellProcEntry const* procEntry = sSpellMgr->GetSpellProcEntry(aurApp->GetBase()->GetSpellInfo()))
{
aurApp->GetBase()->PrepareProcChargeDrop(procEntry, eventInfo);
aurasTriggeringProc.emplace_back(0, aurApp);
}
}
if (aurApp->GetBase()->GetSpellInfo()->HasAttribute(SPELL_ATTR2_PROC_COOLDOWN_ON_FAILURE))
if (SpellProcEntry const* procEntry = sSpellMgr->GetSpellProcEntry(aurApp->GetBase()->GetSpellInfo()))
aurApp->GetBase()->AddProcCooldown(procEntry, now);
}
};
// use provided list of auras which can proc
if (procAuras)
{
for (AuraApplication* aurApp : *procAuras)
{
ASSERT(aurApp->GetTarget() == this);
processAuraApplication(aurApp);
}
}
// or generate one on our own
else
{
for (AuraApplicationMap::iterator itr = GetAppliedAuras().begin(); itr != GetAppliedAuras().end(); ++itr)
processAuraApplication(itr->second);
}
}
void Unit::TriggerAurasProcOnEvent(AuraApplicationList* myProcAuras, AuraApplicationList* targetProcAuras, Unit* actionTarget,
ProcFlagsInit const& typeMaskActor, ProcFlagsInit const& typeMaskActionTarget, ProcFlagsSpellType spellTypeMask,
ProcFlagsSpellPhase spellPhaseMask, ProcFlagsHit hitMask, Spell* spell, DamageInfo* damageInfo, HealInfo* healInfo)
{
// prepare data for self trigger
ProcEventInfo myProcEventInfo(this, actionTarget, actionTarget, typeMaskActor, spellTypeMask, spellPhaseMask, hitMask, spell, damageInfo, healInfo);
AuraApplicationProcContainer myAurasTriggeringProc;
if (typeMaskActor)
{
GetProcAurasTriggeredOnEvent(myAurasTriggeringProc, myProcAuras, myProcEventInfo);
// needed for example for Cobra Strikes, pet does the attack, but aura is on owner
if (Player* modOwner = GetSpellModOwner())
{
if (modOwner != this && spell)
{
AuraApplicationList modAuras;
for (auto itr = modOwner->GetAppliedAuras().begin(); itr != modOwner->GetAppliedAuras().end(); ++itr)
{
if (spell->m_appliedMods.count(itr->second->GetBase()) != 0)
modAuras.push_front(itr->second);
}
modOwner->GetProcAurasTriggeredOnEvent(myAurasTriggeringProc, &modAuras, myProcEventInfo);
}
}
}
// prepare data for target trigger
ProcEventInfo targetProcEventInfo(this, actionTarget, this, typeMaskActionTarget, spellTypeMask, spellPhaseMask, hitMask, spell, damageInfo, healInfo);
AuraApplicationProcContainer targetAurasTriggeringProc;
if (typeMaskActionTarget && actionTarget)
actionTarget->GetProcAurasTriggeredOnEvent(targetAurasTriggeringProc, targetProcAuras, targetProcEventInfo);
TriggerAurasProcOnEvent(myProcEventInfo, myAurasTriggeringProc);
if (typeMaskActionTarget && actionTarget)
actionTarget->TriggerAurasProcOnEvent(targetProcEventInfo, targetAurasTriggeringProc);
}
void Unit::TriggerAurasProcOnEvent(ProcEventInfo& eventInfo, AuraApplicationProcContainer& aurasTriggeringProc)
{
Spell const* triggeringSpell = eventInfo.GetProcSpell();
bool const disableProcs = triggeringSpell && triggeringSpell->IsProcDisabled();
int32 oldProcChainLength = std::exchange(m_procChainLength, std::max(m_procChainLength + 1, triggeringSpell ? triggeringSpell->GetProcChainLength() : 0));
if (disableProcs)
SetCantProc(true);
for (auto const& [procEffectMask, aurApp] : aurasTriggeringProc)
{
if (aurApp->GetRemoveMode())
continue;
aurApp->GetBase()->TriggerProcOnEvent(procEffectMask, aurApp, eventInfo);
}
if (disableProcs)
SetCantProc(false);
m_procChainLength = oldProcChainLength;
}
///----------Pet responses methods-----------------
void Unit::SendPetActionFeedback(PetActionFeedback msg, uint32 spellId)
{
Unit* owner = GetOwner();
if (!owner || owner->GetTypeId() != TYPEID_PLAYER)
return;
WorldPackets::Pet::PetActionFeedback petActionFeedback;
petActionFeedback.SpellID = spellId;
petActionFeedback.Response = msg;
owner->ToPlayer()->SendDirectMessage(petActionFeedback.Write());
}
void Unit::SendPetTalk(uint32 pettalk)
{
Unit* owner = GetOwner();
if (!owner || owner->GetTypeId() != TYPEID_PLAYER)
return;
WorldPackets::Pet::PetActionSound petActionSound;
petActionSound.UnitGUID = GetGUID();
petActionSound.Action = pettalk;
owner->ToPlayer()->SendDirectMessage(petActionSound.Write());
}
void Unit::SendPetAIReaction(ObjectGuid guid)
{
Unit* owner = GetOwner();
if (!owner || owner->GetTypeId() != TYPEID_PLAYER)
return;
WorldPackets::Combat::AIReaction packet;
packet.UnitGUID = guid;
packet.Reaction = AI_REACTION_HOSTILE;
owner->ToPlayer()->SendDirectMessage(packet.Write());
}
///----------End of Pet responses methods----------
void Unit::PropagateSpeedChange()
{
GetMotionMaster()->PropagateSpeedChange();
}
MovementGeneratorType Unit::GetDefaultMovementType() const
{
return IDLE_MOTION_TYPE;
}
void Unit::StopMoving()
{
ClearUnitState(UNIT_STATE_MOVING);
// not need send any packets if not in world or not moving
if (!IsInWorld() || movespline->Finalized())
return;
// Update position now since Stop does not start a new movement that can be updated later
if (movespline->HasStarted())
UpdateSplinePosition();
Movement::MoveSplineInit init(this);
init.Stop();
}
void Unit::PauseMovement(uint32 timer/* = 0*/, uint8 slot/* = 0*/, bool forced/* = true*/)
{
if (IsInvalidMovementSlot(slot))
return;
if (MovementGenerator* movementGenerator = GetMotionMaster()->GetCurrentMovementGenerator(MovementSlot(slot)))
movementGenerator->Pause(timer);
if (forced && GetMotionMaster()->GetCurrentSlot() == MovementSlot(slot))
StopMoving();
}
void Unit::ResumeMovement(uint32 timer/* = 0*/, uint8 slot/* = 0*/)
{
if (IsInvalidMovementSlot(slot))
return;
if (MovementGenerator* movementGenerator = GetMotionMaster()->GetCurrentMovementGenerator(MovementSlot(slot)))
movementGenerator->Resume(timer);
}
bool Unit::IsSitState() const
{
UnitStandStateType s = GetStandState();
return
s == UNIT_STAND_STATE_SIT_CHAIR || s == UNIT_STAND_STATE_SIT_LOW_CHAIR ||
s == UNIT_STAND_STATE_SIT_MEDIUM_CHAIR || s == UNIT_STAND_STATE_SIT_HIGH_CHAIR ||
s == UNIT_STAND_STATE_SIT;
}
bool Unit::IsStandState() const
{
UnitStandStateType s = GetStandState();
return !IsSitState() && s != UNIT_STAND_STATE_SLEEP && s != UNIT_STAND_STATE_KNEEL;
}
void Unit::SetStandState(UnitStandStateType state, uint32 animKitID /* = 0*/)
{
SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::StandState), state);
if (IsStandState())
RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Standing);
if (GetTypeId() == TYPEID_PLAYER)
{
WorldPackets::Misc::StandStateUpdate packet(state, animKitID);
ToPlayer()->SendDirectMessage(packet.Write());
}
}
void Unit::SetAnimTier(AnimTier animTier, bool notifyClient /*= true*/)
{
SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::AnimTier), AsUnderlyingType(animTier));
if (notifyClient)
{
WorldPackets::Misc::SetAnimTier setAnimTier;
setAnimTier.Unit = GetGUID();
setAnimTier.Tier = AsUnderlyingType(animTier);
SendMessageToSet(setAnimTier.Write(), true);
}
}
bool Unit::IsPolymorphed() const
{
uint32 transformId = GetTransformSpell();
if (!transformId)
return false;
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(transformId, GetMap()->GetDifficultyID());
if (!spellInfo)
return false;
return spellInfo->GetSpellSpecific() == SPELL_SPECIFIC_MAGE_POLYMORPH;
}
void Unit::RecalculateObjectScale()
{
int32 scaleAuras = GetTotalAuraModifier(SPELL_AURA_MOD_SCALE) + GetTotalAuraModifier(SPELL_AURA_MOD_SCALE_2);
float scale = GetNativeObjectScale() + CalculatePct(1.0f, scaleAuras);
float scaleMin = GetTypeId() == TYPEID_PLAYER ? 0.1 : 0.01;
SetObjectScale(std::max(scale, scaleMin));
}
void Unit::SetDisplayId(uint32 displayId, bool setNative /*= false*/)
{
float displayScale = DEFAULT_PLAYER_DISPLAY_SCALE;
if (IsCreature() && !IsPet())
if (CreatureModel const* model = ToCreature()->GetCreatureTemplate()->GetModelWithDisplayId(displayId))
displayScale = model->DisplayScale;
SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::DisplayID), displayId);
SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::DisplayScale), displayScale);
if (setNative)
{
SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::NativeDisplayID), displayId);
SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::NativeXDisplayScale), displayScale);
}
// Set Gender by ModelInfo
if (CreatureModelInfo const* modelInfo = sObjectMgr->GetCreatureModelInfo(displayId))
SetGender(Gender(modelInfo->gender));
CalculateHoverHeight();
}
void Unit::RestoreDisplayId(bool ignorePositiveAurasPreventingMounting /*= false*/)
{
AuraEffect* handledAura = nullptr;
// try to receive model from transform auras
AuraEffectList const& transforms = GetAuraEffectsByType(SPELL_AURA_TRANSFORM);
if (!transforms.empty())
{
// iterate over already applied transform auras - from newest to oldest
for (auto i = transforms.begin(); i != transforms.end(); ++i)
{
if (AuraApplication const* aurApp = (*i)->GetBase()->GetApplicationOfTarget(GetGUID()))
{
if (!handledAura)
{
if (!ignorePositiveAurasPreventingMounting)
handledAura = (*i);
else if (CreatureTemplate const* ci = sObjectMgr->GetCreatureTemplate((*i)->GetMiscValue()))
if (!IsDisallowedMountForm((*i)->GetId(), FORM_NONE, ObjectMgr::ChooseDisplayId(ci)->CreatureDisplayID))
handledAura = (*i);
}
// prefer negative auras
if (!aurApp->IsPositive())
{
handledAura = (*i);
break;
}
}
}
}
AuraEffectList const& shapeshiftAura = GetAuraEffectsByType(SPELL_AURA_MOD_SHAPESHIFT);
// transform aura was found
if (handledAura)
{
handledAura->HandleEffect(this, AURA_EFFECT_HANDLE_SEND_FOR_CLIENT, true);
return;
}
// we've found shapeshift
else if (!shapeshiftAura.empty()) // we've found shapeshift
{
// only one such aura possible at a time
if (uint32 modelId = GetModelForForm(GetShapeshiftForm(), shapeshiftAura.front()->GetId()))
{
if (!ignorePositiveAurasPreventingMounting || !IsDisallowedMountForm(0, GetShapeshiftForm(), modelId))
SetDisplayId(modelId);
else
SetDisplayId(GetNativeDisplayId());
return;
}
}
// no auras found - set modelid to default
SetDisplayId(GetNativeDisplayId());
}
void Unit::ClearAllReactives()
{
for (uint8 i = 0; i < MAX_REACTIVE; ++i)
m_reactiveTimer[i] = 0;
if (HasAuraState(AURA_STATE_DEFENSIVE))
ModifyAuraState(AURA_STATE_DEFENSIVE, false);
if (HasAuraState(AURA_STATE_DEFENSIVE_2))
ModifyAuraState(AURA_STATE_DEFENSIVE_2, false);
}
void Unit::UpdateReactives(uint32 p_time)
{
for (uint8 i = 0; i < MAX_REACTIVE; ++i)
{
ReactiveType reactive = ReactiveType(i);
if (!m_reactiveTimer[reactive])
continue;
if (m_reactiveTimer[reactive] <= p_time)
{
m_reactiveTimer[reactive] = 0;
switch (reactive)
{
case REACTIVE_DEFENSE:
if (HasAuraState(AURA_STATE_DEFENSIVE))
ModifyAuraState(AURA_STATE_DEFENSIVE, false);
break;
case REACTIVE_DEFENSE_2:
if (HasAuraState(AURA_STATE_DEFENSIVE_2))
ModifyAuraState(AURA_STATE_DEFENSIVE_2, false);
break;
default:
break;
}
}
else
{
m_reactiveTimer[reactive] -= p_time;
}
}
}
Unit* Unit::SelectNearbyTarget(Unit* exclude, float dist) const
{
std::list targets;
Trinity::AnyUnfriendlyUnitInObjectRangeCheck u_check(this, this, dist);
Trinity::UnitListSearcher searcher(this, targets, u_check);
Cell::VisitAllObjects(this, searcher, dist);
// remove current target
if (GetVictim())
targets.remove(GetVictim());
if (exclude)
targets.remove(exclude);
// remove not LoS targets
for (std::list::iterator tIter = targets.begin(); tIter != targets.end();)
{
if (!IsWithinLOSInMap(*tIter) || (*tIter)->IsTotem() || (*tIter)->IsSpiritService() || (*tIter)->IsCritter())
targets.erase(tIter++);
else
++tIter;
}
// no appropriate targets
if (targets.empty())
return nullptr;
// select random
return Trinity::Containers::SelectRandomContainerElement(targets);
}
uint32 Unit::GetBaseAttackTime(WeaponAttackType att) const
{
return m_baseAttackSpeed[att];
}
void Unit::SetBaseAttackTime(WeaponAttackType att, uint32 val)
{
m_baseAttackSpeed[att] = val;
UpdateAttackTimeField(att);
}
void Unit::UpdateAttackTimeField(WeaponAttackType att)
{
switch (att)
{
case BASE_ATTACK:
case OFF_ATTACK:
SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::AttackRoundBaseTime, att), uint32(m_baseAttackSpeed[att] * m_modAttackSpeedPct[att]));
break;
case RANGED_ATTACK:
SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::RangedAttackRoundBaseTime), uint32(m_baseAttackSpeed[RANGED_ATTACK] * m_modAttackSpeedPct[RANGED_ATTACK]));
break;
default:
break;;
}
}
void ApplyPercentModFloatVar(float& var, float val, bool apply)
{
var *= (apply ? (100.0f + val) / 100.0f : 100.0f / (100.0f + val));
}
void Unit::ApplyAttackTimePercentMod(WeaponAttackType att, float val, bool apply)
{
float remainingTimePct = float(m_attackTimer[att]) / (m_baseAttackSpeed[att] * m_modAttackSpeedPct[att]);
if (val > 0.f)
{
ApplyPercentModFloatVar(m_modAttackSpeedPct[att], val, !apply);
if (att == BASE_ATTACK)
ApplyPercentModUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::ModHaste), val, !apply);
else if (att == RANGED_ATTACK)
ApplyPercentModUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::ModRangedHaste), val, !apply);
}
else
{
ApplyPercentModFloatVar(m_modAttackSpeedPct[att], -val, apply);
if (att == BASE_ATTACK)
ApplyPercentModUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::ModHaste), -val, apply);
else if (att == RANGED_ATTACK)
ApplyPercentModUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::ModRangedHaste), -val, apply);
}
UpdateAttackTimeField(att);
m_attackTimer[att] = uint32(m_baseAttackSpeed[att] * m_modAttackSpeedPct[att] * remainingTimePct);
}
void Unit::ApplyCastTimePercentMod(float val, bool apply)
{
if (val > 0.f)
{
ApplyPercentModUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::ModCastingSpeed), val, !apply);
ApplyPercentModUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::ModSpellHaste), val, !apply);
ApplyPercentModUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::ModHasteRegen), val, !apply);
}
else
{
ApplyPercentModUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::ModCastingSpeed), -val, apply);
ApplyPercentModUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::ModSpellHaste), -val, apply);
ApplyPercentModUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::ModHasteRegen), -val, apply);
}
}
void Unit::UpdateAuraForGroup()
{
if (Player* player = ToPlayer())
{
if (player->GetGroup())
player->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_AURAS);
}
else if (GetTypeId() == TYPEID_UNIT && IsPet())
{
Pet* pet = ((Pet*)this);
if (pet->isControlled())
pet->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_PET_AURAS);
}
}
void Unit::SetCantProc(bool apply)
{
if (apply)
++m_procDeep;
else
{
ASSERT(m_procDeep);
--m_procDeep;
}
}
float Unit::GetAPMultiplier(WeaponAttackType attType, bool normalized) const
{
if (GetTypeId() != TYPEID_PLAYER || (IsInFeralForm() && !normalized))
return GetBaseAttackTime(attType) / 1000.0f;
Item* weapon = ToPlayer()->GetWeaponForAttack(attType, true);
if (!weapon)
return 2.0f;
if (!normalized)
return weapon->GetTemplate()->GetDelay() / 1000.0f;
switch (weapon->GetTemplate()->GetSubClass())
{
case ITEM_SUBCLASS_WEAPON_AXE2:
case ITEM_SUBCLASS_WEAPON_MACE2:
case ITEM_SUBCLASS_WEAPON_POLEARM:
case ITEM_SUBCLASS_WEAPON_SWORD2:
case ITEM_SUBCLASS_WEAPON_STAFF:
case ITEM_SUBCLASS_WEAPON_FISHING_POLE:
return 3.3f;
case ITEM_SUBCLASS_WEAPON_AXE:
case ITEM_SUBCLASS_WEAPON_MACE:
case ITEM_SUBCLASS_WEAPON_SWORD:
case ITEM_SUBCLASS_WEAPON_WARGLAIVES:
case ITEM_SUBCLASS_WEAPON_EXOTIC:
case ITEM_SUBCLASS_WEAPON_EXOTIC2:
case ITEM_SUBCLASS_WEAPON_FIST_WEAPON:
return 2.4f;
case ITEM_SUBCLASS_WEAPON_DAGGER:
return 1.7f;
case ITEM_SUBCLASS_WEAPON_THROWN:
return 2.0f;
default:
return weapon->GetTemplate()->GetDelay() / 1000.0f;
}
}
Pet* Unit::CreateTamedPetFrom(Creature* creatureTarget, uint32 spell_id)
{
if (GetTypeId() != TYPEID_PLAYER)
return nullptr;
Pet* pet = new Pet(ToPlayer(), HUNTER_PET);
if (!pet->CreateBaseAtCreature(creatureTarget))
{
delete pet;
return nullptr;
}
uint8 level = creatureTarget->GetLevelForTarget(this) + 5 < GetLevel() ? (GetLevel() - 5) : creatureTarget->GetLevelForTarget(this);
if (!InitTamedPet(pet, level, spell_id))
{
delete pet;
return nullptr;
}
return pet;
}
Pet* Unit::CreateTamedPetFrom(uint32 creatureEntry, uint32 spell_id)
{
if (GetTypeId() != TYPEID_PLAYER)
return nullptr;
CreatureTemplate const* creatureInfo = sObjectMgr->GetCreatureTemplate(creatureEntry);
if (!creatureInfo)
return nullptr;
Pet* pet = new Pet(ToPlayer(), HUNTER_PET);
if (!pet->CreateBaseAtCreatureInfo(creatureInfo, this) || !InitTamedPet(pet, GetLevel(), spell_id))
{
delete pet;
return nullptr;
}
return pet;
}
bool Unit::InitTamedPet(Pet* pet, uint8 level, uint32 spell_id)
{
Player* player = ToPlayer();
PetStable& petStable = player->GetOrInitPetStable();
auto freeActiveSlotItr = std::find_if(petStable.ActivePets.begin(), petStable.ActivePets.end(), [](Optional const& petInfo)
{
return !petInfo.has_value();
});
if (freeActiveSlotItr == petStable.ActivePets.end())
return false;
pet->SetCreatorGUID(GetGUID());
pet->SetFaction(GetFaction());
pet->SetCreatedBySpell(spell_id);
pet->SetUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED);
if (!pet->InitStatsForLevel(level))
{
TC_LOG_ERROR("entities.unit", "Pet::InitStatsForLevel() failed for creature (Entry: {})!", pet->GetEntry());
return false;
}
PhasingHandler::InheritPhaseShift(pet, this);
pet->GetCharmInfo()->SetPetNumber(sObjectMgr->GeneratePetNumber(), true);
// this enables pet details window (Shift+P)
pet->InitPetCreateSpells();
//pet->InitLevelupSpellsForLevel();
pet->SetFullHealth();
petStable.SetCurrentActivePetIndex(std::distance(petStable.ActivePets.begin(), freeActiveSlotItr));
pet->FillPetInfo(&freeActiveSlotItr->emplace());
player->AddPetToUpdateFields(**freeActiveSlotItr, PetSaveMode(*petStable.GetCurrentActivePetIndex()), PET_STABLE_ACTIVE);
return true;
}
void Unit::SendDurabilityLoss(Player* receiver, uint32 percent)
{
WorldPackets::Misc::DurabilityDamageDeath packet;
packet.Percent = percent;
receiver->GetSession()->SendPacket(packet.Write());
}
void Unit::PlayOneShotAnimKitId(uint16 animKitId)
{
if (!sAnimKitStore.LookupEntry(animKitId))
{
TC_LOG_ERROR("entities.unit", "Unit::PlayOneShotAnimKitId using invalid AnimKit ID: {}", animKitId);
return;
}
WorldPackets::Misc::PlayOneShotAnimKit data;
data.Unit = GetGUID();
data.AnimKitID = animKitId;
SendMessageToSet(data.Write(), true);
}
void Unit::SetAIAnimKitId(uint16 animKitId)
{
if (_aiAnimKitId == animKitId)
return;
if (animKitId && !sAnimKitStore.LookupEntry(animKitId))
return;
_aiAnimKitId = animKitId;
WorldPackets::Misc::SetAIAnimKit data;
data.Unit = GetGUID();
data.AnimKitID = animKitId;
SendMessageToSet(data.Write(), true);
}
void Unit::SetMovementAnimKitId(uint16 animKitId)
{
if (_movementAnimKitId == animKitId)
return;
if (animKitId && !sAnimKitStore.LookupEntry(animKitId))
return;
_movementAnimKitId = animKitId;
WorldPackets::Misc::SetMovementAnimKit data;
data.Unit = GetGUID();
data.AnimKitID = animKitId;
SendMessageToSet(data.Write(), true);
}
void Unit::SetMeleeAnimKitId(uint16 animKitId)
{
if (_meleeAnimKitId == animKitId)
return;
if (animKitId && !sAnimKitStore.LookupEntry(animKitId))
return;
_meleeAnimKitId = animKitId;
WorldPackets::Misc::SetMeleeAnimKit data;
data.Unit = GetGUID();
data.AnimKitID = animKitId;
SendMessageToSet(data.Write(), true);
}
/*static*/ void Unit::Kill(Unit* attacker, Unit* victim, bool durabilityLoss /*= true*/, bool skipSettingDeathState /*= false*/)
{
// Prevent killing unit twice (and giving reward from kill twice)
if (!victim->GetHealth())
return;
if (attacker && !attacker->IsInMap(victim))
attacker = nullptr;
// find player: owner of controlled `this` or `this` itself maybe
Player* player = nullptr;
if (attacker)
player = attacker->GetCharmerOrOwnerPlayerOrPlayerItself();
Creature* creature = victim->ToCreature();
bool isRewardAllowed = attacker != victim;
if (creature)
isRewardAllowed = isRewardAllowed && !creature->GetTapList().empty();
std::vector tappers;
if (isRewardAllowed && creature)
{
for (ObjectGuid tapperGuid : creature->GetTapList())
if (Player* tapper = ObjectAccessor::GetPlayer(*creature, tapperGuid))
tappers.push_back(tapper);
if (!creature->CanHaveLoot())
isRewardAllowed = false;
}
// Exploit fix
if (creature && creature->IsPet() && creature->GetOwnerGUID().IsPlayer())
isRewardAllowed = false;
// Reward player, his pets, and group/raid members
// call kill spell proc event (before real die and combat stop to triggering auras removed at death/combat stop)
if (isRewardAllowed)
{
std::unordered_set groups;
for (Player* tapper : tappers)
{
if (Group* tapperGroup = tapper->GetGroup())
{
if (groups.insert(tapperGroup).second)
{
WorldPackets::Party::PartyKillLog partyKillLog;
partyKillLog.Player = player && tapperGroup->IsMember(player->GetGUID()) ? player->GetGUID() : tapper->GetGUID();
partyKillLog.Victim = victim->GetGUID();
partyKillLog.Write();
tapperGroup->BroadcastPacket(partyKillLog.GetRawPacket(), tapperGroup->GetMemberGroup(tapper->GetGUID()) != 0);
if (creature)
tapperGroup->UpdateLooterGuid(creature, true);
}
}
else
{
WorldPackets::Party::PartyKillLog partyKillLog;
partyKillLog.Player = tapper->GetGUID();
partyKillLog.Victim = victim->GetGUID();
tapper->SendDirectMessage(partyKillLog.Write());
}
}
// Generate loot before updating looter
if (creature)
{
DungeonEncounterEntry const* dungeonEncounter = nullptr;
if (InstanceScript const* instance = creature->GetInstanceScript())
dungeonEncounter = instance->GetBossDungeonEncounter(creature);
if (creature->GetMap()->IsDungeon())
{
if (dungeonEncounter)
{
creature->m_personalLoot = GenerateDungeonEncounterPersonalLoot(dungeonEncounter->ID, creature->GetLootId(),
LootTemplates_Creature, LOOT_CORPSE, creature, creature->GetCreatureDifficulty()->GoldMin, creature->GetCreatureDifficulty()->GoldMax,
creature->GetLootMode(), creature->GetMap()->GetMapDifficulty(), tappers);
}
else if (!tappers.empty())
{
Group* group = !groups.empty() ? *groups.begin() : nullptr;
Player* looter = group ? ASSERT_NOTNULL(ObjectAccessor::GetPlayer(*creature, group->GetLooterGuid())) : tappers[0];
Loot* loot = new Loot(creature->GetMap(), creature->GetGUID(), LOOT_CORPSE, dungeonEncounter ? group : nullptr);
if (uint32 lootid = creature->GetLootId())
loot->FillLoot(lootid, LootTemplates_Creature, looter, dungeonEncounter != nullptr, false, creature->GetLootMode(), ItemBonusMgr::GetContextForPlayer(creature->GetMap()->GetMapDifficulty(), looter));
if (creature->GetLootMode() > 0)
loot->generateMoneyLoot(creature->GetCreatureDifficulty()->GoldMin, creature->GetCreatureDifficulty()->GoldMax);
if (group)
loot->NotifyLootList(creature->GetMap());
creature->m_personalLoot[looter->GetGUID()].reset(loot); // trash mob loot is personal, generated with round robin rules
// Update round robin looter only if the creature had loot
if (!loot->isLooted())
for (Group* tapperGroup : groups)
tapperGroup->UpdateLooterGuid(creature);
}
}
else
{
for (Player* tapper : tappers)
{
Loot* loot = new Loot(creature->GetMap(), creature->GetGUID(), LOOT_CORPSE, nullptr);
if (dungeonEncounter)
loot->SetDungeonEncounterId(dungeonEncounter->ID);
if (uint32 lootid = creature->GetLootId())
loot->FillLoot(lootid, LootTemplates_Creature, tapper, true, false, creature->GetLootMode(), ItemBonusMgr::GetContextForPlayer(creature->GetMap()->GetMapDifficulty(), tapper));
if (creature->GetLootMode() > 0)
loot->generateMoneyLoot(creature->GetCreatureDifficulty()->GoldMin, creature->GetCreatureDifficulty()->GoldMax);
creature->m_personalLoot[tapper->GetGUID()].reset(loot);
}
}
}
if (Vignettes::VignetteData const* vignette = victim->GetVignette())
{
for (Player* tapper : tappers)
{
if (Quest const* reward = sObjectMgr->GetQuestTemplate(vignette->Data->RewardQuestID))
tapper->RewardQuest(reward, LootItemType::Item, 0, victim, false);
if (vignette->Data->VisibleTrackingQuestID)
tapper->SetRewardedQuest(vignette->Data->VisibleTrackingQuestID);
}
}
KillRewarder(Trinity::IteratorPair(tappers.data(), tappers.data() + tappers.size()), victim, false).Reward();
}
// Do KILL and KILLED procs. KILL proc is called only for the unit who landed the killing blow (and its owner - for pets and totems) regardless of who tapped the victim
if (attacker && (attacker->IsPet() || attacker->IsTotem()))
{
// proc only once for victim
if (Unit* owner = attacker->GetOwner())
Unit::ProcSkillsAndAuras(owner, victim, PROC_FLAG_KILL, PROC_FLAG_NONE, PROC_SPELL_TYPE_MASK_ALL, PROC_SPELL_PHASE_NONE, PROC_HIT_NONE, nullptr, nullptr, nullptr);
}
if (!victim->IsCritter())
{
Unit::ProcSkillsAndAuras(attacker, victim, PROC_FLAG_KILL, PROC_FLAG_NONE, PROC_SPELL_TYPE_MASK_ALL, PROC_SPELL_PHASE_NONE, PROC_HIT_NONE, nullptr, nullptr, nullptr);
for (Player* tapper : tappers)
if (tapper->IsAtGroupRewardDistance(victim))
Unit::ProcSkillsAndAuras(tapper, victim, { PROC_FLAG_NONE, PROC_FLAG_2_TARGET_DIES }, PROC_FLAG_NONE, PROC_SPELL_TYPE_MASK_ALL, PROC_SPELL_PHASE_NONE, PROC_HIT_NONE, nullptr, nullptr, nullptr);
}
// Proc auras on death - must be before aura/combat remove
Unit::ProcSkillsAndAuras(victim, victim, PROC_FLAG_NONE, PROC_FLAG_DEATH, PROC_SPELL_TYPE_MASK_ALL, PROC_SPELL_PHASE_NONE, PROC_HIT_NONE, nullptr, nullptr, nullptr);
// update get killing blow achievements, must be done before setDeathState to be able to require auras on target
// and before Spirit of Redemption as it also removes auras
if (attacker)
if (Player* killerPlayer = attacker->GetCharmerOrOwnerPlayerOrPlayerItself())
killerPlayer->UpdateCriteria(CriteriaType::DeliveredKillingBlow, 1, 0, 0, victim);
if (!skipSettingDeathState)
{
TC_LOG_DEBUG("entities.unit", "SET JUST_DIED");
victim->setDeathState(JUST_DIED);
}
// Inform pets (if any) when player kills target)
// MUST come after victim->setDeathState(JUST_DIED); or pet next target
// selection will get stuck on same target and break pet react state
for (Player* tapper : tappers)
{
Pet* pet = tapper->GetPet();
if (pet && pet->IsAlive() && pet->isControlled())
{
if (pet->IsAIEnabled())
pet->AI()->KilledUnit(victim);
else
TC_LOG_ERROR("entities.unit", "Pet doesn't have any AI in Unit::Kill(). {}", pet->GetDebugInfo());
}
}
// 10% durability loss on death
if (Player* plrVictim = victim->ToPlayer())
{
// remember victim PvP death for corpse type and corpse reclaim delay
// at original death (not at SpiritOfRedemtionTalent timeout)
plrVictim->SetPvPDeath(player != nullptr);
// only if not player and not controlled by player pet. And not at BG
if ((durabilityLoss && !player && !victim->ToPlayer()->InBattleground()) || (player && sWorld->getBoolConfig(CONFIG_DURABILITY_LOSS_IN_PVP)))
{
double baseLoss = sWorld->getRate(RATE_DURABILITY_LOSS_ON_DEATH);
uint32 loss = uint32(baseLoss - (baseLoss * plrVictim->GetTotalAuraMultiplier(SPELL_AURA_MOD_DURABILITY_LOSS)));
TC_LOG_DEBUG("entities.unit", "We are dead, losing {} percent durability", loss);
// Durability loss is calculated more accurately again for each item in Player::DurabilityLoss
plrVictim->DurabilityLossAll(baseLoss, false);
// durability lost message
plrVictim->SendDurabilityLoss(plrVictim, loss);
}
// Call KilledUnit for creatures
if (attacker && attacker->GetTypeId() == TYPEID_UNIT && attacker->IsAIEnabled())
attacker->ToCreature()->AI()->KilledUnit(victim);
// last damage from non duel opponent or opponent controlled creature
if (plrVictim->duel)
{
plrVictim->duel->Opponent->CombatStopWithPets(true);
plrVictim->CombatStopWithPets(true);
plrVictim->DuelComplete(DUEL_INTERRUPTED);
}
}
else // creature died
{
TC_LOG_DEBUG("entities.unit", "DealDamageNotPlayer");
ASSERT_NODEBUGINFO(creature);
if (!creature->IsPet())
{
// must be after setDeathState which resets dynamic flags
if (!creature->IsFullyLooted())
creature->SetDynamicFlag(UNIT_DYNFLAG_LOOTABLE);
else
creature->AllLootRemovedFromCorpse();
if (creature->CanHaveLoot() && LootTemplates_Skinning.HaveLootFor(creature->GetCreatureDifficulty()->SkinLootID))
{
creature->SetDynamicFlag(UNIT_DYNFLAG_CAN_SKIN);
creature->SetUnitFlag(UNIT_FLAG_SKINNABLE);
}
}
// Call KilledUnit for creatures, this needs to be called after the lootable flag is set
if (attacker && attacker->GetTypeId() == TYPEID_UNIT && attacker->IsAIEnabled())
attacker->ToCreature()->AI()->KilledUnit(victim);
// Call creature just died function
if (CreatureAI* ai = creature->AI())
{
ai->OnHealthDepleted(attacker, true);
ai->JustDied(attacker);
}
if (TempSummon * summon = creature->ToTempSummon())
{
if (WorldObject * summoner = summon->GetSummoner())
{
if (summoner->ToCreature() && summoner->ToCreature()->IsAIEnabled())
summoner->ToCreature()->AI()->SummonedCreatureDies(creature, attacker);
else if (summoner->ToGameObject() && summoner->ToGameObject()->AI())
summoner->ToGameObject()->AI()->SummonedCreatureDies(creature, attacker);
}
}
}
// outdoor pvp things, do these after setting the death state, else the player activity notify won't work... doh...
// handle player kill only if not suicide (spirit of redemption for example)
if (player && attacker != victim)
{
if (OutdoorPvP* pvp = player->GetOutdoorPvP())
pvp->HandleKill(player, victim);
if (Battlefield* bf = sBattlefieldMgr->GetBattlefieldToZoneId(player->GetMap(), player->GetZoneId()))
bf->HandleKill(player, victim);
}
//if (victim->GetTypeId() == TYPEID_PLAYER)
// if (OutdoorPvP* pvp = victim->ToPlayer()->GetOutdoorPvP())
// pvp->HandlePlayerActivityChangedpVictim->ToPlayer();
// battleground things (do this at the end, so the death state flag will be properly set to handle in the bg->handlekill)
if (attacker)
{
if (BattlegroundMap* bgMap = victim->GetMap()->ToBattlegroundMap())
{
if (Battleground* bg = bgMap->GetBG())
{
if (Player* playerVictim = victim->ToPlayer())
{
if (player)
bg->HandleKillPlayer(playerVictim, player);
}
else
bg->HandleKillUnit(victim->ToCreature(), attacker);
}
}
}
// achievement stuff
if (attacker && victim->GetTypeId() == TYPEID_PLAYER)
{
if (attacker->GetTypeId() == TYPEID_UNIT)
victim->ToPlayer()->UpdateCriteria(CriteriaType::KilledByCreature, attacker->GetEntry());
else if (attacker->GetTypeId() == TYPEID_PLAYER && victim != attacker)
victim->ToPlayer()->UpdateCriteria(CriteriaType::KilledByPlayer, 1, attacker->ToPlayer()->GetEffectiveTeam());
}
// Hook for OnPVPKill Event
if (attacker)
{
if (Player* killerPlr = attacker->ToPlayer())
{
if (Player* killedPlr = victim->ToPlayer())
sScriptMgr->OnPVPKill(killerPlr, killedPlr);
else if (Creature* killedCre = victim->ToCreature())
sScriptMgr->OnCreatureKill(killerPlr, killedCre);
}
else if (Creature* killerCre = attacker->ToCreature())
{
if (Player* killed = victim->ToPlayer())
sScriptMgr->OnPlayerKilledByCreature(killerCre, killed);
}
}
}
void Unit::SetControlled(bool apply, UnitState state)
{
if (apply)
{
if (HasUnitState(state))
return;
if (state & UNIT_STATE_CONTROLLED)
CastStop();
AddUnitState(state);
switch (state)
{
case UNIT_STATE_STUNNED:
SetStunned(true);
break;
case UNIT_STATE_ROOT:
if (!HasUnitState(UNIT_STATE_STUNNED))
SetRooted(true);
break;
case UNIT_STATE_CONFUSED:
if (!HasUnitState(UNIT_STATE_STUNNED))
{
SendMeleeAttackStop();
// SendAutoRepeatCancel ?
SetConfused(true);
}
break;
case UNIT_STATE_FLEEING:
if (!HasUnitState(UNIT_STATE_STUNNED | UNIT_STATE_CONFUSED))
{
SendMeleeAttackStop();
// SendAutoRepeatCancel ?
SetFeared(true);
}
break;
default:
break;
}
}
else
{
switch (state)
{
case UNIT_STATE_STUNNED:
if (HasAuraType(SPELL_AURA_MOD_STUN) || HasAuraType(SPELL_AURA_MOD_STUN_DISABLE_GRAVITY))
return;
ClearUnitState(state);
SetStunned(false);
break;
case UNIT_STATE_ROOT:
if (HasAuraType(SPELL_AURA_MOD_ROOT) || HasAuraType(SPELL_AURA_MOD_ROOT_2) || HasAuraType(SPELL_AURA_MOD_ROOT_DISABLE_GRAVITY) || GetVehicle() || (ToCreature() && ToCreature()->IsSessile()))
return;
ClearUnitState(state);
if (!HasUnitState(UNIT_STATE_STUNNED))
SetRooted(false);
break;
case UNIT_STATE_CONFUSED:
if (HasAuraType(SPELL_AURA_MOD_CONFUSE))
return;
ClearUnitState(state);
SetConfused(false);
break;
case UNIT_STATE_FLEEING:
if (HasAuraType(SPELL_AURA_MOD_FEAR))
return;
ClearUnitState(state);
SetFeared(false);
break;
default:
return;
}
ApplyControlStatesIfNeeded();
}
}
void Unit::ApplyControlStatesIfNeeded()
{
// Unit States might have been already cleared but auras still present. I need to check with HasAuraType
if (HasUnitState(UNIT_STATE_STUNNED) || HasAuraType(SPELL_AURA_MOD_STUN) || HasAuraType(SPELL_AURA_MOD_STUN_DISABLE_GRAVITY))
SetStunned(true);
if (HasUnitState(UNIT_STATE_ROOT) || HasAuraType(SPELL_AURA_MOD_ROOT) || HasAuraType(SPELL_AURA_MOD_ROOT_2) || HasAuraType(SPELL_AURA_MOD_ROOT_DISABLE_GRAVITY))
SetRooted(true);
if (HasUnitState(UNIT_STATE_CONFUSED) || HasAuraType(SPELL_AURA_MOD_CONFUSE))
SetConfused(true);
if (HasUnitState(UNIT_STATE_FLEEING) || HasAuraType(SPELL_AURA_MOD_FEAR))
SetFeared(true);
}
void Unit::SetStunned(bool apply)
{
if (apply)
{
SetTarget(ObjectGuid::Empty);
SetUnitFlag(UNIT_FLAG_STUNNED);
StopMoving();
if (GetTypeId() == TYPEID_PLAYER)
SetStandState(UNIT_STAND_STATE_STAND);
SetRooted(true);
CastStop();
}
else
{
if (IsAlive() && GetVictim())
SetTarget(EnsureVictim()->GetGUID());
// don't remove UNIT_FLAG_STUNNED for pet when owner is mounted (disabled pet's interface)
Unit* owner = GetCharmerOrOwner();
if (!owner || owner->GetTypeId() != TYPEID_PLAYER || !owner->ToPlayer()->IsMounted())
RemoveUnitFlag(UNIT_FLAG_STUNNED);
if (!HasUnitState(UNIT_STATE_ROOT)) // prevent moving if it also has root effect
SetRooted(false);
}
}
void Unit::SetRooted(bool apply)
{
if (apply)
{
// MOVEMENTFLAG_ROOT cannot be used in conjunction with MOVEMENTFLAG_MASK_MOVING (tested 3.3.5a)
// this will freeze clients. That's why we remove MOVEMENTFLAG_MASK_MOVING before
// setting MOVEMENTFLAG_ROOT
RemoveUnitMovementFlag(MOVEMENTFLAG_MASK_MOVING);
AddUnitMovementFlag(MOVEMENTFLAG_ROOT);
StopMoving();
}
else
RemoveUnitMovementFlag(MOVEMENTFLAG_ROOT);
static OpcodeServer const rootOpcodeTable[2][2] =
{
{ SMSG_MOVE_SPLINE_UNROOT, SMSG_MOVE_UNROOT },
{ SMSG_MOVE_SPLINE_ROOT, SMSG_MOVE_ROOT }
};
if (Player* playerMover = Unit::ToPlayer(GetUnitBeingMoved())) // unit controlled by a player.
{
WorldPackets::Movement::MoveSetFlag packet(rootOpcodeTable[apply][1]);
packet.MoverGUID = GetGUID();
packet.SequenceIndex = m_movementCounter++;
playerMover->SendDirectMessage(packet.Write());
WorldPackets::Movement::MoveUpdate moveUpdate;
moveUpdate.Status = &m_movementInfo;
SendMessageToSet(moveUpdate.Write(), playerMover);
}
else
{
WorldPackets::Movement::MoveSplineSetFlag packet(rootOpcodeTable[apply][0]);
packet.MoverGUID = GetGUID();
SendMessageToSet(packet.Write(), true);
}
}
void Unit::SetFeared(bool apply)
{
if (apply)
{
SetTarget(ObjectGuid::Empty);
Unit* caster = nullptr;
AuraEffectList const& fearAuras = GetAuraEffectsByType(SPELL_AURA_MOD_FEAR);
if (!fearAuras.empty())
caster = ObjectAccessor::GetUnit(*this, fearAuras.front()->GetCasterGUID());
if (!caster)
caster = getAttackerForHelper();
GetMotionMaster()->MoveFleeing(caster, fearAuras.empty() ? Milliseconds(sWorld->getIntConfig(CONFIG_CREATURE_FAMILY_FLEE_DELAY)) : 0ms); // caster == NULL processed in MoveFleeing
SetUnitFlag(UNIT_FLAG_FLEEING);
}
else
{
RemoveUnitFlag(UNIT_FLAG_FLEEING);
if (IsAlive())
{
GetMotionMaster()->Remove(FLEEING_MOTION_TYPE);
if (Unit const* victim = GetVictim())
SetTarget(victim->GetGUID());
if (!IsPlayer() && !IsInCombat())
GetMotionMaster()->MoveTargetedHome();
}
}
// block / allow control to real player in control (eg charmer)
if (GetTypeId() == TYPEID_PLAYER)
{
if (m_playerMovingMe)
m_playerMovingMe->SetClientControl(this, !apply);
}
}
void Unit::SetConfused(bool apply)
{
if (apply)
{
SetTarget(ObjectGuid::Empty);
GetMotionMaster()->MoveConfused();
}
else
{
if (IsAlive())
{
GetMotionMaster()->Remove(CONFUSED_MOTION_TYPE);
if (GetVictim())
SetTarget(EnsureVictim()->GetGUID());
}
}
// block / allow control to real player in control (eg charmer)
if (GetTypeId() == TYPEID_PLAYER)
{
if (m_playerMovingMe)
m_playerMovingMe->SetClientControl(this, !apply);
}
}
bool Unit::SetCharmedBy(Unit* charmer, CharmType type, AuraApplication const* aurApp)
{
if (!charmer)
return false;
// dismount players when charmed
if (GetTypeId() == TYPEID_PLAYER)
RemoveAurasByType(SPELL_AURA_MOUNTED);
if (charmer->GetTypeId() == TYPEID_PLAYER)
charmer->RemoveAurasByType(SPELL_AURA_MOUNTED);
ASSERT(type != CHARM_TYPE_POSSESS || charmer->GetTypeId() == TYPEID_PLAYER);
ASSERT((type == CHARM_TYPE_VEHICLE) == (GetVehicleKit() && GetVehicleKit()->IsControllableVehicle()));
TC_LOG_DEBUG("entities.unit", "SetCharmedBy: charmer {}, charmed {}, type {}.", charmer->GetGUID().ToString(), GetGUID().ToString(), uint32(type));
if (this == charmer)
{
TC_LOG_FATAL("entities.unit", "Unit::SetCharmedBy: Unit {} is trying to charm itself!", GetGUID().ToString());
return false;
}
//if (HasUnitState(UNIT_STATE_UNATTACKABLE))
// return false;
if (GetTypeId() == TYPEID_PLAYER && ToPlayer()->GetTransport())
{
TC_LOG_FATAL("entities.unit", "Unit::SetCharmedBy: {} is trying to charm Player {} on transport", charmer->GetGUID().ToString(), GetGUID().ToString());
return false;
}
// Already charmed
if (!GetCharmerGUID().IsEmpty())
{
TC_LOG_FATAL("entities.unit", "Unit::SetCharmedBy: {} has already been charmed but {} is trying to charm it!", GetGUID().ToString(), charmer->GetGUID().ToString());
return false;
}
CastStop();
AttackStop();
Player* playerCharmer = charmer->ToPlayer();
// Charmer stop charming
if (playerCharmer)
{
playerCharmer->StopCastingCharm();
playerCharmer->StopCastingBindSight();
}
// Charmed stop charming
if (GetTypeId() == TYPEID_PLAYER)
{
ToPlayer()->StopCastingCharm();
ToPlayer()->StopCastingBindSight();
}
// StopCastingCharm may remove a possessed pet?
if (!IsInWorld())
{
TC_LOG_FATAL("entities.unit", "Unit::SetCharmedBy: {} is not in world but {} is trying to charm it!", GetGUID().ToString(), charmer->GetGUID().ToString());
return false;
}
// charm is set by aura, and aura effect remove handler was called during apply handler execution
// prevent undefined behaviour
if (aurApp && aurApp->GetRemoveMode())
return false;
_oldFactionId = GetFaction();
SetFaction(charmer->GetFaction());
// Pause any Idle movement
PauseMovement(0, 0, false);
// Remove any active voluntary movement
GetMotionMaster()->Clear(MOTION_PRIORITY_NORMAL);
// Stop any remaining spline, if no involuntary movement is found
auto criteria = [](MovementGenerator const* movement) -> bool
{
return movement->Priority == MOTION_PRIORITY_HIGHEST;
};
if (!GetMotionMaster()->HasMovementGenerator(criteria))
StopMoving();
// Set charmed
charmer->SetCharm(this, true);
if (Player* player = ToPlayer())
{
if (player->isAFK())
player->ToggleAFK();
player->SetClientControl(this, false);
}
// charm is set by aura, and aura effect remove handler was called during apply handler execution
// prevent undefined behaviour
if (aurApp && aurApp->GetRemoveMode())
{
// properly clean up charm changes up to this point to avoid leaving the unit in partially charmed state
SetFaction(_oldFactionId);
GetMotionMaster()->InitializeDefault();
charmer->SetCharm(this, false);
return false;
}
// Pets already have a properly initialized CharmInfo, don't overwrite it.
if (type != CHARM_TYPE_VEHICLE && !GetCharmInfo())
{
InitCharmInfo();
if (type == CHARM_TYPE_POSSESS)
GetCharmInfo()->InitPossessCreateSpells();
else
GetCharmInfo()->InitCharmCreateSpells();
}
if (playerCharmer)
{
switch (type)
{
case CHARM_TYPE_VEHICLE:
SetUnitFlag(UNIT_FLAG_POSSESSED);
playerCharmer->SetClientControl(this, true);
playerCharmer->VehicleSpellInitialize();
break;
case CHARM_TYPE_POSSESS:
SetUnitFlag(UNIT_FLAG_POSSESSED);
charmer->SetUnitFlag(UNIT_FLAG_REMOVE_CLIENT_CONTROL);
playerCharmer->SetClientControl(this, true);
playerCharmer->PossessSpellInitialize();
AddUnitState(UNIT_STATE_POSSESSED);
break;
case CHARM_TYPE_CHARM:
if (GetTypeId() == TYPEID_UNIT && charmer->GetClass() == CLASS_WARLOCK)
{
CreatureTemplate const* cinfo = ToCreature()->GetCreatureTemplate();
if (cinfo && cinfo->type == CREATURE_TYPE_DEMON)
{
// to prevent client crash
SetClass(CLASS_MAGE);
// just to enable stat window
if (GetCharmInfo())
GetCharmInfo()->SetPetNumber(sObjectMgr->GeneratePetNumber(), true);
// if charmed two demons the same session, the 2nd gets the 1st one's name
SetPetNameTimestamp(uint32(GameTime::GetGameTime()));
}
}
playerCharmer->CharmSpellInitialize();
break;
default:
case CHARM_TYPE_CONVERT:
break;
}
}
AddUnitState(UNIT_STATE_CHARMED);
if ((GetTypeId() != TYPEID_PLAYER) || (charmer->GetTypeId() != TYPEID_PLAYER))
{
// AI will schedule its own change if appropriate
if (UnitAI* ai = GetAI())
ai->OnCharmed(false);
else
ScheduleAIChange();
}
return true;
}
void Unit::RemoveCharmedBy(Unit* charmer)
{
if (!IsCharmed())
return;
if (charmer)
ASSERT(charmer == GetCharmer());
else
charmer = GetCharmer();
ASSERT(charmer);
CharmType type;
if (HasUnitState(UNIT_STATE_POSSESSED))
type = CHARM_TYPE_POSSESS;
else if (charmer->IsOnVehicle(this))
type = CHARM_TYPE_VEHICLE;
else
type = CHARM_TYPE_CHARM;
CastStop();
AttackStop();
if (_oldFactionId)
{
SetFaction(_oldFactionId);
_oldFactionId = 0;
}
else
RestoreFaction();
///@todo Handle SLOT_IDLE motion resume
GetMotionMaster()->InitializeDefault();
// Vehicle should not attack its passenger after he exists the seat
if (type != CHARM_TYPE_VEHICLE)
LastCharmerGUID = charmer->GetGUID();
ASSERT(type != CHARM_TYPE_POSSESS || charmer->GetTypeId() == TYPEID_PLAYER);
ASSERT(type != CHARM_TYPE_VEHICLE || (GetTypeId() == TYPEID_UNIT && IsVehicle()));
charmer->SetCharm(this, false);
m_combatManager.RevalidateCombat();
Player* playerCharmer = charmer->ToPlayer();
if (playerCharmer)
{
switch (type)
{
case CHARM_TYPE_VEHICLE:
playerCharmer->SetClientControl(this, false);
playerCharmer->SetClientControl(charmer, true);
RemoveUnitFlag(UNIT_FLAG_POSSESSED);
break;
case CHARM_TYPE_POSSESS:
ClearUnitState(UNIT_STATE_POSSESSED);
playerCharmer->SetClientControl(this, false);
playerCharmer->SetClientControl(charmer, true);
charmer->RemoveUnitFlag(UNIT_FLAG_REMOVE_CLIENT_CONTROL);
RemoveUnitFlag(UNIT_FLAG_POSSESSED);
break;
case CHARM_TYPE_CHARM:
if (GetTypeId() == TYPEID_UNIT && charmer->GetClass() == CLASS_WARLOCK)
{
CreatureTemplate const* cinfo = ToCreature()->GetCreatureTemplate();
if (cinfo && cinfo->type == CREATURE_TYPE_DEMON)
{
SetClass(uint8(cinfo->unit_class));
if (GetCharmInfo())
GetCharmInfo()->SetPetNumber(0, true);
else
TC_LOG_ERROR("entities.unit", "Aura::HandleModCharm: {} has a charm aura but no charm info!", GetGUID().ToString());
}
}
break;
case CHARM_TYPE_CONVERT:
break;
}
}
if (Player* player = ToPlayer())
player->SetClientControl(this, true);
if (playerCharmer && this != charmer->GetFirstControlled())
playerCharmer->SendRemoveControlBar();
// a guardian should always have charminfo
if (!IsGuardian())
DeleteCharmInfo();
// reset confused movement for example
ApplyControlStatesIfNeeded();
if (GetTypeId() != TYPEID_PLAYER || charmer->GetTypeId() == TYPEID_UNIT)
{
if (UnitAI* charmedAI = GetAI())
charmedAI->OnCharmed(false); // AI will potentially schedule a charm ai update
else
ScheduleAIChange();
}
}
void Unit::RestoreFaction()
{
if (HasAuraType(SPELL_AURA_MOD_FACTION))
{
SetFaction(GetAuraEffectsByType(SPELL_AURA_MOD_FACTION).front()->GetMiscValue());
return;
}
if (GetTypeId() == TYPEID_PLAYER)
ToPlayer()->SetFactionForRace(GetRace());
else
{
if (HasUnitTypeMask(UNIT_MASK_MINION))
{
if (Unit* owner = GetOwner())
{
SetFaction(owner->GetFaction());
return;
}
}
if (CreatureTemplate const* cinfo = ToCreature()->GetCreatureTemplate()) // normal creature
SetFaction(cinfo->faction);
}
}
bool Unit::CreateVehicleKit(uint32 id, uint32 creatureEntry, bool loading /*= false*/)
{
VehicleEntry const* vehInfo = sVehicleStore.LookupEntry(id);
if (!vehInfo)
return false;
m_vehicleKit = Trinity::make_unique_trackable(this, vehInfo, creatureEntry);
m_updateFlag.Vehicle = true;
m_unitTypeMask |= UNIT_MASK_VEHICLE;
if (!loading)
SendSetVehicleRecId(id);
return true;
}
void Unit::RemoveVehicleKit(bool onRemoveFromWorld /*= false*/)
{
if (!m_vehicleKit)
return;
if (!onRemoveFromWorld)
SendSetVehicleRecId(0);
m_vehicleKit->Uninstall();
m_vehicleKit = nullptr;
m_updateFlag.Vehicle = false;
m_unitTypeMask &= ~UNIT_MASK_VEHICLE;
RemoveNpcFlag(UNIT_NPC_FLAG_SPELLCLICK | UNIT_NPC_FLAG_PLAYER_VEHICLE);
}
bool Unit::IsOnVehicle(Unit const* vehicle) const
{
return m_vehicle && m_vehicle == vehicle->GetVehicleKit();
}
Unit* Unit::GetVehicleBase() const
{
return m_vehicle ? m_vehicle->GetBase() : nullptr;
}
Unit* Unit::GetVehicleRoot() const
{
Unit* vehicleRoot = GetVehicleBase();
if (!vehicleRoot)
return nullptr;
for (;;)
{
if (!vehicleRoot->GetVehicleBase())
return vehicleRoot;
vehicleRoot = vehicleRoot->GetVehicleBase();
}
}
Creature* Unit::GetVehicleCreatureBase() const
{
if (Unit* veh = GetVehicleBase())
if (Creature* c = veh->ToCreature())
return c;
return nullptr;
}
ObjectGuid Unit::GetTransGUID() const
{
if (GetVehicle())
return GetVehicleBase()->GetGUID();
if (GetTransport())
return GetTransport()->GetTransportGUID();
return ObjectGuid::Empty;
}
TransportBase* Unit::GetDirectTransport() const
{
if (Vehicle* veh = GetVehicle())
return veh;
return GetTransport();
}
bool Unit::IsInPartyWith(Unit const* unit) const
{
if (this == unit)
return true;
Unit const* u1 = GetCharmerOrOwnerOrSelf();
Unit const* u2 = unit->GetCharmerOrOwnerOrSelf();
if (u1 == u2)
return true;
if (u1->GetTypeId() == TYPEID_PLAYER && u2->GetTypeId() == TYPEID_PLAYER)
return u1->ToPlayer()->IsInSameGroupWith(u2->ToPlayer());
else if ((u2->GetTypeId() == TYPEID_PLAYER && u1->GetTypeId() == TYPEID_UNIT && u1->ToCreature()->IsTreatedAsRaidUnit()) ||
(u1->GetTypeId() == TYPEID_PLAYER && u2->GetTypeId() == TYPEID_UNIT && u2->ToCreature()->IsTreatedAsRaidUnit()))
return true;
return u1->GetTypeId() == TYPEID_UNIT && u2->GetTypeId() == TYPEID_UNIT && u1->GetFaction() == u2->GetFaction();
}
bool Unit::IsInRaidWith(Unit const* unit) const
{
if (this == unit)
return true;
Unit const* u1 = GetCharmerOrOwnerOrSelf();
Unit const* u2 = unit->GetCharmerOrOwnerOrSelf();
if (u1 == u2)
return true;
if (u1->GetTypeId() == TYPEID_PLAYER && u2->GetTypeId() == TYPEID_PLAYER)
return u1->ToPlayer()->IsInSameRaidWith(u2->ToPlayer());
else if ((u2->GetTypeId() == TYPEID_PLAYER && u1->GetTypeId() == TYPEID_UNIT && u1->ToCreature()->IsTreatedAsRaidUnit()) ||
(u1->GetTypeId() == TYPEID_PLAYER && u2->GetTypeId() == TYPEID_UNIT && u2->ToCreature()->IsTreatedAsRaidUnit()))
return true;
return u1->GetTypeId() == TYPEID_UNIT && u2->GetTypeId() == TYPEID_UNIT && u1->GetFaction() == u2->GetFaction();
}
void Unit::GetPartyMembers(std::list &TagUnitMap)
{
Unit* owner = GetCharmerOrOwnerOrSelf();
Group* group = nullptr;
if (owner->GetTypeId() == TYPEID_PLAYER)
group = owner->ToPlayer()->GetGroup();
if (group)
{
uint8 subgroup = owner->ToPlayer()->GetSubGroup();
for (GroupReference const& itr : group->GetMembers())
{
Player* Target = itr.GetSource();
// IsHostileTo check duel and controlled by enemy
if (Target->IsInMap(owner) && Target->GetSubGroup() == subgroup && !IsHostileTo(Target))
{
if (Target->IsAlive())
TagUnitMap.push_back(Target);
if (Guardian* pet = Target->GetGuardianPet())
if (pet->IsAlive())
TagUnitMap.push_back(pet);
}
}
}
else
{
if ((owner == this || IsInMap(owner)) && owner->IsAlive())
TagUnitMap.push_back(owner);
if (Guardian* pet = owner->GetGuardianPet())
if ((pet == this || IsInMap(pet)) && pet->IsAlive())
TagUnitMap.push_back(pet);
}
}
bool Unit::IsContestedGuard() const
{
if (FactionTemplateEntry const* entry = GetFactionTemplateEntry())
return entry->IsContestedGuardFaction();
return false;
}
void Unit::SetPvP(bool state)
{
if (state)
SetPvpFlag(UNIT_BYTE2_FLAG_PVP);
else
RemovePvpFlag(UNIT_BYTE2_FLAG_PVP);
}
Aura* Unit::AddAura(uint32 spellId, Unit* target)
{
if (!target)
return nullptr;
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId, GetMap()->GetDifficultyID());
if (!spellInfo)
return nullptr;
return AddAura(spellInfo, MAX_EFFECT_MASK, target);
}
Aura* Unit::AddAura(SpellInfo const* spellInfo, uint32 effMask, Unit* target)
{
if (!spellInfo)
return nullptr;
if (!target->IsAlive() && !spellInfo->IsPassive() && !spellInfo->HasAttribute(SPELL_ATTR2_ALLOW_DEAD_TARGET))
return nullptr;
if (target->IsImmunedToSpell(spellInfo, this))
return nullptr;
for (SpellEffectInfo const& spellEffectInfo : spellInfo->GetEffects())
{
if (!(effMask & (1 << spellEffectInfo.EffectIndex)))
continue;
if (target->IsImmunedToSpellEffect(spellInfo, spellEffectInfo, this))
effMask &= ~(1 << spellEffectInfo.EffectIndex);
}
if (!effMask)
return nullptr;
ObjectGuid castId = ObjectGuid::Create(SPELL_CAST_SOURCE_NORMAL, GetMapId(), spellInfo->Id, GetMap()->GenerateLowGuid());
AuraCreateInfo createInfo(castId, spellInfo, GetMap()->GetDifficultyID(), effMask, target);
createInfo.SetCaster(this);
if (Aura* aura = Aura::TryRefreshStackOrCreate(createInfo))
{
aura->ApplyForTargets();
return aura;
}
return nullptr;
}
void Unit::SetAuraStack(uint32 spellId, Unit* target, uint32 stack)
{
Aura* aura = target->GetAura(spellId, GetGUID());
if (!aura)
aura = AddAura(spellId, target);
if (aura && stack)
aura->SetStackAmount(stack);
}
void Unit::SendPlaySpellVisual(Unit* target, uint32 spellVisualId, uint16 missReason, uint16 reflectStatus, float travelSpeed, bool speedAsTime /*= false*/, float launchDelay /*= 0.0f*/)
{
WorldPackets::Spells::PlaySpellVisual playSpellVisual;
playSpellVisual.Source = GetGUID();
playSpellVisual.Target = target->GetGUID();
playSpellVisual.TargetPosition = target->GetPosition();
playSpellVisual.SpellVisualID = spellVisualId;
playSpellVisual.TravelSpeed = travelSpeed;
playSpellVisual.MissReason = missReason;
playSpellVisual.ReflectStatus = reflectStatus;
playSpellVisual.SpeedAsTime = speedAsTime;
playSpellVisual.LaunchDelay = launchDelay;
SendMessageToSet(playSpellVisual.Write(), true);
}
void Unit::SendPlaySpellVisual(Position const& targetPosition, uint32 spellVisualId, uint16 missReason, uint16 reflectStatus, float travelSpeed, bool speedAsTime /*= false*/, float launchDelay /*= 0.0f*/)
{
WorldPackets::Spells::PlaySpellVisual playSpellVisual;
playSpellVisual.Source = GetGUID();
playSpellVisual.TargetPosition = targetPosition;
playSpellVisual.SpellVisualID = spellVisualId;
playSpellVisual.TravelSpeed = travelSpeed;
playSpellVisual.MissReason = missReason;
playSpellVisual.ReflectStatus = reflectStatus;
playSpellVisual.SpeedAsTime = speedAsTime;
playSpellVisual.LaunchDelay = launchDelay;
SendMessageToSet(playSpellVisual.Write(), true);
}
void Unit::SendCancelSpellVisual(uint32 id)
{
WorldPackets::Spells::CancelSpellVisual cancelSpellVisual;
cancelSpellVisual.Source = GetGUID();
cancelSpellVisual.SpellVisualID = id;
SendMessageToSet(cancelSpellVisual.Write(), true);
}
void Unit::SendPlaySpellVisualKit(uint32 id, uint32 type, uint32 duration) const
{
WorldPackets::Spells::PlaySpellVisualKit playSpellVisualKit;
playSpellVisualKit.Unit = GetGUID();
playSpellVisualKit.KitRecID = id;
playSpellVisualKit.KitType = type;
playSpellVisualKit.Duration = duration;
SendMessageToSet(playSpellVisualKit.Write(), true);
}
void Unit::SendCancelSpellVisualKit(uint32 id)
{
WorldPackets::Spells::CancelSpellVisualKit cancelSpellVisualKit;
cancelSpellVisualKit.Source = GetGUID();
cancelSpellVisualKit.SpellVisualKitID = id;
SendMessageToSet(cancelSpellVisualKit.Write(), true);
}
void Unit::CancelSpellMissiles(uint32 spellId, bool reverseMissile /*= false*/, bool abortSpell /*= false*/)
{
bool hasMissile = false;
if (abortSpell)
{
for (std::pair const& itr : m_Events.GetEvents())
{
if (Spell const* spell = Spell::ExtractSpellFromEvent(itr.second))
{
if (spell->GetSpellInfo()->Id == spellId)
{
itr.second->ScheduleAbort();
hasMissile = true;
}
}
}
}
else
hasMissile = true;
if (hasMissile)
{
WorldPackets::Spells::MissileCancel packet;
packet.OwnerGUID = GetGUID();
packet.SpellID = spellId;
packet.Reverse = reverseMissile;
SendMessageToSet(packet.Write(), false);
}
}
bool Unit::CanApplyResilience() const
{
return !IsVehicle() && GetOwnerGUID().IsPlayer();
}
/*static*/ void Unit::ApplyResilience(Unit const* victim, int32* damage)
{
// player mounted on multi-passenger mount is also classified as vehicle
if (victim->IsVehicle() && victim->GetTypeId() != TYPEID_PLAYER)
return;
Unit const* target = nullptr;
if (victim->GetTypeId() == TYPEID_PLAYER)
target = victim;
else // victim->GetTypeId() == TYPEID_UNIT
{
if (Unit* owner = victim->GetOwner())
if (owner->GetTypeId() == TYPEID_PLAYER)
target = owner;
}
if (!target)
return;
*damage -= target->GetDamageReduction(*damage);
}
int32 Unit::CalculateAOEAvoidance(int32 damage, uint32 schoolMask, bool npcCaster) const
{
damage = int32(float(damage) * GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_AOE_DAMAGE_AVOIDANCE, schoolMask));
if (npcCaster)
damage = int32(float(damage) * GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_CREATURE_AOE_DAMAGE_AVOIDANCE, schoolMask));
return damage;
}
// Melee based spells can be miss, parry or dodge on this step
// Crit or block - determined on damage calculation phase! (and can be both in some time)
float Unit::MeleeSpellMissChance(Unit const* victim, WeaponAttackType attType, SpellInfo const* spellInfo) const
{
if (spellInfo && spellInfo->HasAttribute(SPELL_ATTR7_NO_ATTACK_MISS))
return 0.f;
//calculate miss chance
float missChance = victim->GetUnitMissChance();
// melee attacks while dual wielding have +19% chance to miss
if (!spellInfo && haveOffhandWeapon() && !IsInFeralForm() && !HasAuraType(SPELL_AURA_IGNORE_DUAL_WIELD_HIT_PENALTY))
missChance += 19.0f;
// Spellmod from SpellModOp::HitChance
float resistMissChance = 100.0f;
if (spellInfo)
if (Player* modOwner = GetSpellModOwner())
modOwner->ApplySpellMod(spellInfo, SpellModOp::HitChance, resistMissChance);
missChance -= resistMissChance - 100.0f;
if (attType == RANGED_ATTACK)
missChance -= m_modRangedHitChance;
else
missChance -= m_modMeleeHitChance;
// miss chance from auras after calculating skill based miss
missChance -= GetTotalAuraModifier(SPELL_AURA_MOD_HIT_CHANCE);
if (attType == RANGED_ATTACK)
missChance -= victim->GetTotalAuraModifier(SPELL_AURA_MOD_ATTACKER_RANGED_HIT_CHANCE);
else
missChance -= victim->GetTotalAuraModifier(SPELL_AURA_MOD_ATTACKER_MELEE_HIT_CHANCE);
return std::max(missChance, 0.f);
}
void Unit::OnPhaseChange()
{
}
void Unit::UpdateObjectVisibility(bool forced)
{
if (!forced)
AddToNotify(NOTIFY_VISIBILITY_CHANGED);
else
{
WorldObject::UpdateObjectVisibility(true);
// call MoveInLineOfSight for nearby creatures
Trinity::AIRelocationNotifier notifier(*this);
Cell::VisitAllObjects(this, notifier, GetVisibilityRange());
}
}
void Unit::SendMoveKnockBack(Player* player, float speedXY, float speedZ, float vcos, float vsin)
{
WorldPackets::Movement::MoveKnockBack moveKnockBack;
moveKnockBack.MoverGUID = GetGUID();
moveKnockBack.SequenceIndex = m_movementCounter++;
moveKnockBack.Speeds.HorzSpeed = speedXY;
moveKnockBack.Speeds.VertSpeed = speedZ;
moveKnockBack.Direction = Position(vcos, vsin);
player->GetSession()->SendPacket(moveKnockBack.Write());
}
void Unit::KnockbackFrom(Position const& origin, float speedXY, float speedZ, Movement::SpellEffectExtraData const* spellEffectExtraData /*= nullptr*/)
{
Player* player = ToPlayer();
if (!player)
{
if (Unit* charmer = GetCharmer())
{
player = charmer->ToPlayer();
if (player && player->GetUnitBeingMoved() != this)
player = nullptr;
}
}
if (!player)
GetMotionMaster()->MoveKnockbackFrom(origin, speedXY, speedZ, spellEffectExtraData);
else
{
float o = GetPosition() == origin ? GetOrientation() + M_PI : origin.GetAbsoluteAngle(this);
if (speedXY < 0)
{
speedXY = -speedXY;
o = o - M_PI;
}
float vcos = std::cos(o);
float vsin = std::sin(o);
SendMoveKnockBack(player, speedXY, -speedZ, vcos, vsin);
}
}
float Unit::GetCombatRatingReduction(CombatRating cr) const
{
if (Player const* player = ToPlayer())
return player->GetRatingBonusValue(cr);
// Player's pet get resilience from owner
else if (IsPet() && GetOwner())
if (Player* owner = GetOwner()->ToPlayer())
return owner->GetRatingBonusValue(cr);
return 0.0f;
}
uint32 Unit::GetCombatRatingDamageReduction(CombatRating cr, float rate, float cap, uint32 damage) const
{
float percent = std::min(GetCombatRatingReduction(cr) * rate, cap);
return CalculatePct(damage, percent);
}
uint32 Unit::GetModelForForm(ShapeshiftForm form, uint32 spellId) const
{
// Hardcoded cases
switch (spellId)
{
case 7090: // Bear Form
return 29414;
case 35200: // Roc Form
return 4877;
case 24858: // Moonkin Form
{
if (HasAura(114301)) // Glyph of Stars
return 0;
break;
}
default:
break;
}
if (Player const* player = ToPlayer())
{
if (Aura* artifactAura = GetAura(ARTIFACTS_ALL_WEAPONS_GENERAL_WEAPON_EQUIPPED_PASSIVE))
if (Item* artifact = player->GetItemByGuid(artifactAura->GetCastItemGUID()))
if (ArtifactAppearanceEntry const* artifactAppearance = sArtifactAppearanceStore.LookupEntry(artifact->GetModifier(ITEM_MODIFIER_ARTIFACT_APPEARANCE_ID)))
if (ShapeshiftForm(artifactAppearance->OverrideShapeshiftFormID) == form)
return artifactAppearance->OverrideShapeshiftDisplayID;
if (ShapeshiftFormModelData const* formModelData = sDB2Manager.GetShapeshiftFormModelData(GetRace(), player->GetNativeGender(), form))
{
bool useRandom = false;
switch (form)
{
case FORM_CAT_FORM: useRandom = HasAura(210333); break; // Glyph of the Feral Chameleon
case FORM_TRAVEL_FORM: useRandom = HasAura(344336); break; // Glyph of the Swift Chameleon
case FORM_AQUATIC_FORM: useRandom = HasAura(344338); break; // Glyph of the Aquatic Chameleon
case FORM_DIRE_BEAR_FORM:
case FORM_BEAR_FORM: useRandom = HasAura(107059); break; // Glyph of the Ursol Chameleon
case FORM_FLIGHT_FORM_EPIC:
case FORM_FLIGHT_FORM: useRandom = HasAura(344342); break; // Glyph of the Aerial Chameleon
default:
break;
}
if (useRandom)
{
std::vector displayIds;
displayIds.reserve(formModelData->Choices->size());
for (std::size_t i = 0; i < formModelData->Choices->size(); ++i)
{
if (ChrCustomizationDisplayInfoEntry const* displayInfo = formModelData->Displays[i])
{
ChrCustomizationReqEntry const* choiceReq = sChrCustomizationReqStore.LookupEntry((*formModelData->Choices)[i]->ChrCustomizationReqID);
if (!choiceReq || player->GetSession()->MeetsChrCustomizationReq(choiceReq, Races(GetRace()), Classes(GetClass()), false,
MakeChrCustomizationChoiceRange(player->m_playerData->Customizations)))
displayIds.push_back(displayInfo->DisplayID);
}
}
if (!displayIds.empty())
return Trinity::Containers::SelectRandomContainerElement(displayIds);
}
else
{
if (uint32 formChoice = player->GetCustomizationChoice(formModelData->OptionID))
{
auto choiceItr = std::find_if(formModelData->Choices->begin(), formModelData->Choices->end(), [formChoice](ChrCustomizationChoiceEntry const* choice)
{
return choice->ID == formChoice;
});
if (choiceItr != formModelData->Choices->end())
if (ChrCustomizationDisplayInfoEntry const* displayInfo = formModelData->Displays[std::distance(formModelData->Choices->begin(), choiceItr)])
return displayInfo->DisplayID;
}
}
}
switch (form)
{
case FORM_GHOST_WOLF:
{
if (HasAura(58135)) // Glyph of Spectral Wolf
return 60247;
break;
}
default:
break;
}
}
SpellShapeshiftFormEntry const* formEntry = sSpellShapeshiftFormStore.LookupEntry(form);
if (formEntry && formEntry->CreatureDisplayID)
return formEntry->CreatureDisplayID;
return 0;
}
void Unit::JumpTo(float speedXY, float speedZ, float angle, Optional dest)
{
if (dest)
angle += GetRelativeAngle(*dest);
if (GetTypeId() == TYPEID_UNIT)
GetMotionMaster()->MoveJumpTo(angle, speedXY, speedZ);
else
{
float vcos = std::cos(angle+GetOrientation());
float vsin = std::sin(angle+GetOrientation());
SendMoveKnockBack(ToPlayer(), speedXY, -speedZ, vcos, vsin);
}
}
void Unit::HandleSpellClick(Unit* clicker, int8 seatId /*= -1*/)
{
bool spellClickHandled = false;
uint32 spellClickEntry = GetVehicleKit() ? GetVehicleKit()->GetCreatureEntry() : GetEntry();
TriggerCastFlags const flags = GetVehicleKit() ? TRIGGERED_IGNORE_CASTER_MOUNTED_OR_ON_VEHICLE : TRIGGERED_NONE;
auto clickBounds = sObjectMgr->GetSpellClickInfoMapBounds(spellClickEntry);
for (auto const& clickPair : clickBounds)
{
//! First check simple relations from clicker to clickee
if (!clickPair.second.IsFitToRequirements(clicker, this))
continue;
//! Check database conditions
if (!sConditionMgr->IsObjectMeetingSpellClickConditions(spellClickEntry, clickPair.second.spellId, clicker, this))
continue;
spellClickHandled = HandleSpellClick(clicker, seatId, clickPair.second.spellId, flags, &clickPair.second);
// if (!spellEntry) should be checked at npc_spellclick load
}
Creature* creature = ToCreature();
if (creature && creature->IsAIEnabled())
creature->AI()->OnSpellClick(clicker, spellClickHandled);
}
bool Unit::HandleSpellClick(Unit* clicker, int8 seatId, uint32 spellId, TriggerCastFlags flags /*= TRIGGERED_NONE*/, SpellClickInfo const* spellClickInfo /*= nullptr*/)
{
Unit* caster = clicker;
Unit* target = this;
ObjectGuid origCasterGUID = caster->GetGUID();
SpellCastResult castResult = SPELL_FAILED_SUCCESS;
if (spellClickInfo)
{
caster = (spellClickInfo->castFlags & NPC_CLICK_CAST_CASTER_CLICKER) ? clicker : this;
target = (spellClickInfo->castFlags & NPC_CLICK_CAST_TARGET_CLICKER) ? clicker : this;
origCasterGUID = (spellClickInfo->castFlags & NPC_CLICK_CAST_ORIG_CASTER_OWNER) ? GetOwnerGUID() : clicker->GetGUID();
}
if (!spellId)
{
TC_LOG_ERROR("sql.sql", "No valid spell specified for clickee {} and clicker {}!", target->GetGUID(), caster->GetGUID());
return false;
}
SpellInfo const* spellEntry = sSpellMgr->AssertSpellInfo(spellId, caster->GetMap()->GetDifficultyID());
uint8 effectIndex = 0;
bool hasControlVehicleAura = false;
for (SpellEffectInfo const& spellEffectInfo : spellEntry->GetEffects())
{
if (spellEffectInfo.ApplyAuraName == SPELL_AURA_CONTROL_VEHICLE)
{
hasControlVehicleAura = true;
break;
}
++effectIndex;
}
if (seatId > -1)
{
if (!hasControlVehicleAura)
{
if (!spellClickInfo)
TC_LOG_ERROR("sql.sql", "RideSpell {} specified in vehicle_accessory or vehicle_template_accessory is not a valid vehicle enter aura!", spellId);
else
TC_LOG_ERROR("sql.sql", "Spell {} specified in npc_spellclick_spells is not a valid vehicle enter aura!", spellId);
return false;
}
if (IsInMap(caster))
{
CastSpellExtraArgs args(flags);
args.OriginalCaster = origCasterGUID;
args.AddSpellMod(SpellValueMod(SPELLVALUE_BASE_POINT0 + effectIndex), seatId + 1);
castResult = caster->CastSpell(target, spellId, args);
}
else // This can happen during Player::_LoadAuras
{
std::array bp = { };
for (SpellEffectInfo const& spellEffectInfo : spellEntry->GetEffects())
bp[spellEffectInfo.EffectIndex] = int32(spellEffectInfo.BasePoints);
bp[effectIndex] = seatId;
AuraCreateInfo createInfo(ObjectGuid::Create(SPELL_CAST_SOURCE_NORMAL, GetMapId(), spellEntry->Id, GetMap()->GenerateLowGuid()), spellEntry, GetMap()->GetDifficultyID(), MAX_EFFECT_MASK, this);
createInfo
.SetCaster(clicker)
.SetBaseAmount(bp.data())
.SetCasterGUID(origCasterGUID);
Aura::TryRefreshStackOrCreate(createInfo);
}
}
else
{
if (IsInMap(caster))
castResult = caster->CastSpell(target, spellEntry->Id, CastSpellExtraArgs().SetOriginalCaster(origCasterGUID));
else
{
AuraCreateInfo createInfo(ObjectGuid::Create(SPELL_CAST_SOURCE_NORMAL, GetMapId(), spellEntry->Id, GetMap()->GenerateLowGuid()), spellEntry, GetMap()->GetDifficultyID(), MAX_EFFECT_MASK, this);
createInfo
.SetCaster(clicker)
.SetCasterGUID(origCasterGUID);
Aura::TryRefreshStackOrCreate(createInfo);
}
}
return castResult == SPELL_FAILED_SUCCESS;
}
void Unit::EnterVehicle(Unit* base, int8 seatId /*= -1*/)
{
CastSpellExtraArgs args(TRIGGERED_IGNORE_CASTER_MOUNTED_OR_ON_VEHICLE);
args.AddSpellMod(SPELLVALUE_BASE_POINT0, seatId + 1);
CastSpell(base, VEHICLE_SPELL_RIDE_HARDCODED, args);
}
void Unit::_EnterVehicle(Vehicle* vehicle, int8 seatId, AuraApplication const* aurApp)
{
// Must be called only from aura handler
ASSERT(aurApp);
if (!IsAlive() || GetVehicleKit() == vehicle || vehicle->GetBase()->IsOnVehicle(this))
return;
if (m_vehicle)
{
if (m_vehicle != vehicle)
{
TC_LOG_DEBUG("entities.vehicle", "EnterVehicle: {} exit {} and enter {}.", GetEntry(), m_vehicle->GetBase()->GetEntry(), vehicle->GetBase()->GetEntry());
ExitVehicle();
}
else if (seatId >= 0 && seatId == GetTransSeat())
return;
else
{
//Exit the current vehicle because unit will reenter in a new seat.
m_vehicle->GetBase()->RemoveAurasByType(SPELL_AURA_CONTROL_VEHICLE, GetGUID(), aurApp->GetBase());
}
}
if (aurApp->GetRemoveMode())
return;
if (Player* player = ToPlayer())
{
if (vehicle->GetBase()->GetTypeId() == TYPEID_PLAYER && player->IsInCombat())
{
vehicle->GetBase()->RemoveAura(const_cast(aurApp));
return;
}
if (Creature* vehicleBaseCreature = vehicle->GetBase()->ToCreature())
{
// If a player entered a vehicle that is part of a formation, remove it from said formation
if (CreatureGroup* creatureGroup = vehicleBaseCreature->GetFormation())
FormationMgr::RemoveCreatureFromGroup(creatureGroup, vehicleBaseCreature);
}
}
ASSERT(!m_vehicle);
(void)vehicle->AddVehiclePassenger(this, seatId);
}
void Unit::ChangeSeat(int8 seatId, bool next)
{
if (!m_vehicle)
return;
// Don't change if current and new seat are identical
if (seatId == GetTransSeat())
return;
SeatMap::const_iterator seat = (seatId < 0 ? m_vehicle->GetNextEmptySeat(GetTransSeat(), next) : m_vehicle->Seats.find(seatId));
// The second part of the check will only return true if seatId >= 0. @Vehicle::GetNextEmptySeat makes sure of that.
if (seat == m_vehicle->Seats.end() || !seat->second.IsEmpty())
return;
AuraEffect* rideVehicleEffect = nullptr;
AuraEffectList const& vehicleAuras = m_vehicle->GetBase()->GetAuraEffectsByType(SPELL_AURA_CONTROL_VEHICLE);
for (AuraEffectList::const_iterator itr = vehicleAuras.begin(); itr != vehicleAuras.end(); ++itr)
{
if ((*itr)->GetCasterGUID() != GetGUID())
continue;
// Make sure there is only one ride vehicle aura on target cast by the unit changing seat
ASSERT(!rideVehicleEffect);
rideVehicleEffect = *itr;
}
// Unit riding a vehicle must always have control vehicle aura on target
ASSERT(rideVehicleEffect);
rideVehicleEffect->ChangeAmount(seat->first + 1);
}
void Unit::ExitVehicle(Position const* /*exitPosition*/)
{
//! This function can be called at upper level code to initialize an exit from the passenger's side.
if (!m_vehicle)
return;
GetVehicleBase()->RemoveAurasByType(SPELL_AURA_CONTROL_VEHICLE, GetGUID());
//! The following call would not even be executed successfully as the
//! SPELL_AURA_CONTROL_VEHICLE unapply handler already calls _ExitVehicle without
//! specifying an exitposition. The subsequent call below would return on if (!m_vehicle).
/*_ExitVehicle(exitPosition);*/
//! To do:
//! We need to allow SPELL_AURA_CONTROL_VEHICLE unapply handlers in spellscripts
//! to specify exit coordinates and either store those per passenger, or we need to
//! init spline movement based on those coordinates in unapply handlers, and
//! relocate exiting passengers based on Unit::moveSpline data. Either way,
//! Coming Soon(TM)
}
void Unit::_ExitVehicle(Position const* exitPosition)
{
/// It's possible m_vehicle is NULL, when this function is called indirectly from @VehicleJoinEvent::Abort.
/// In that case it was not possible to add the passenger to the vehicle. The vehicle aura has already been removed
/// from the target in the aforementioned function and we don't need to do anything else at this point.
if (!m_vehicle)
return;
// This should be done before dismiss, because there may be some aura removal
VehicleSeatAddon const* seatAddon = m_vehicle->GetSeatAddonForSeatOfPassenger(this);
Vehicle* vehicle = m_vehicle->RemovePassenger(this);
if (!vehicle)
{
TC_LOG_ERROR("entities.vehicle", "RemovePassenger() couldn't remove current unit from vehicle. Debug info: {}", GetDebugInfo());
return;
}
Player* player = ToPlayer();
// If the player is on mounted duel and exits the mount, he should immediatly lose the duel
if (player && player->duel && player->duel->IsMounted)
player->DuelComplete(DUEL_FLED);
SetControlled(false, UNIT_STATE_ROOT); // SMSG_MOVE_FORCE_UNROOT, ~MOVEMENTFLAG_ROOT
AddUnitState(UNIT_STATE_MOVE);
if (player)
player->SetFallInformation(0, GetPositionZ());
Position pos;
// If we ask for a specific exit position, use that one. Otherwise allow scripts to modify it
if (exitPosition)
pos = *exitPosition;
else
{
// Set exit position to vehicle position and use the current orientation
pos = vehicle->GetBase()->GetPosition();
pos.SetOrientation(GetOrientation());
// Change exit position based on seat entry addon data
if (seatAddon)
{
if (seatAddon->ExitParameter == VehicleExitParameters::VehicleExitParamOffset)
pos.RelocateOffset({ seatAddon->ExitParameterX, seatAddon->ExitParameterY, seatAddon->ExitParameterZ, seatAddon->ExitParameterO });
else if (seatAddon->ExitParameter == VehicleExitParameters::VehicleExitParamDest)
pos.Relocate({ seatAddon->ExitParameterX, seatAddon->ExitParameterY, seatAddon->ExitParameterZ, seatAddon->ExitParameterO });
}
}
std::function initializer = [=, this, vehicleCollisionHeight = vehicle->GetBase()->GetCollisionHeight()](Movement::MoveSplineInit& init)
{
float height = pos.GetPositionZ() + vehicleCollisionHeight;
// Creatures without inhabit type air should begin falling after exiting the vehicle
if (GetTypeId() == TYPEID_UNIT && !CanFly() && height > GetMap()->GetWaterOrGroundLevel(GetPhaseShift(), pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ() + vehicleCollisionHeight, &height))
init.SetFall();
init.MoveTo(pos.GetPositionX(), pos.GetPositionY(), height, false);
init.SetFacing(pos.GetOrientation());
init.SetTransportExit();
};
GetMotionMaster()->LaunchMoveSpline(std::move(initializer), EVENT_VEHICLE_EXIT, MOTION_PRIORITY_HIGHEST);
if (player)
player->ResummonPetTemporaryUnSummonedIfAny();
if (vehicle->GetBase()->HasUnitTypeMask(UNIT_MASK_MINION) && vehicle->GetBase()->GetTypeId() == TYPEID_UNIT)
if (((Minion*)vehicle->GetBase())->GetOwner() == this)
vehicle->GetBase()->ToCreature()->DespawnOrUnsummon(vehicle->GetDespawnDelay());
if (HasUnitTypeMask(UNIT_MASK_ACCESSORY))
{
// Vehicle just died, we die too
if (vehicle->GetBase()->getDeathState() == JUST_DIED)
setDeathState(JUST_DIED);
// If for other reason we as minion are exiting the vehicle (ejected, master dismounted) - unsummon
else
ToTempSummon()->UnSummon(2000); // Approximation
}
RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags2::AbandonVehicle);
}
bool Unit::IsFalling() const
{
return m_movementInfo.HasMovementFlag(MOVEMENTFLAG_FALLING | MOVEMENTFLAG_FALLING_FAR) || movespline->isFalling();
}
bool Unit::CanSwim() const
{
// Mirror client behavior, if this method returns false then client will not use swimming animation and for players will apply gravity as if there was no water
if (HasUnitFlag(UNIT_FLAG_CANT_SWIM))
return false;
if (HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED)) // is player
return true;
if (HasUnitFlag2(UNIT_FLAG2_AI_WILL_ONLY_SWIM_IF_TARGET_SWIMS))
return false;
if (HasUnitFlag(UNIT_FLAG_PET_IN_COMBAT))
return true;
return HasUnitFlag(UNIT_FLAG_RENAME | UNIT_FLAG_CAN_SWIM);
}
void Unit::NearTeleportTo(Position const& pos, bool casting /*= false*/)
{
DisableSpline();
TeleportLocation target{ .Location = { GetMapId(), pos } };
if (GetTypeId() == TYPEID_PLAYER)
ToPlayer()->TeleportTo(target, TELE_TO_NOT_LEAVE_TRANSPORT | TELE_TO_NOT_LEAVE_COMBAT | TELE_TO_NOT_UNSUMMON_PET | (casting ? TELE_TO_SPELL : TELE_TO_NONE));
else
{
SendTeleportPacket(target);
UpdatePosition(pos, true);
UpdateObjectVisibility();
}
}
void Unit::SendTeleportPacket(TeleportLocation const& teleportLocation)
{
// SMSG_MOVE_UPDATE_TELEPORT is sent to nearby players to signal the teleport
// SMSG_MOVE_TELEPORT is sent to self in order to trigger CMSG_MOVE_TELEPORT_ACK and update the position server side
WorldPackets::Movement::MoveUpdateTeleport moveUpdateTeleport;
moveUpdateTeleport.Status = &m_movementInfo;
if (_movementForces)
moveUpdateTeleport.MovementForces = _movementForces->GetForces();
Unit* broadcastSource = this;
// should this really be the unit _being_ moved? not the unit doing the moving?
if (Player* playerMover = Unit::ToPlayer(GetUnitBeingMoved()))
{
WorldPackets::Movement::MoveTeleport moveTeleport;
moveTeleport.MoverGUID = GetGUID();
moveTeleport.Pos = teleportLocation.Location;
moveTeleport.TransportGUID = teleportLocation.TransportGuid;
moveTeleport.Facing = teleportLocation.Location.GetOrientation();
moveTeleport.SequenceIndex = m_movementCounter++;
playerMover->SendDirectMessage(moveTeleport.Write());
broadcastSource = playerMover;
}
else
{
// This is the only packet sent for creatures which contains MovementInfo structure
// we do not update m_movementInfo for creatures so it needs to be done manually here
moveUpdateTeleport.Status->guid = GetGUID();
moveUpdateTeleport.Status->time = getMSTime();
if (teleportLocation.TransportGuid)
{
Transport* transport = GetMap()->GetTransport(*teleportLocation.TransportGuid);
if (!transport)
return;
float x, y, z, o;
teleportLocation.Location.GetPosition(x, y, z, o);
transport->CalculatePassengerPosition(x, y, z, &o);
moveUpdateTeleport.Status->pos.Relocate(x, y, z, o);
moveUpdateTeleport.Status->transport.pos.Relocate(teleportLocation.Location);
}
else
moveUpdateTeleport.Status->pos.Relocate(teleportLocation.Location);
}
// Broadcast the packet to everyone except self.
broadcastSource->SendMessageToSet(moveUpdateTeleport.Write(), false);
}
bool Unit::UpdatePosition(float x, float y, float z, float orientation, bool teleport)
{
// prevent crash when a bad coord is sent by the client
if (!Trinity::IsValidMapCoord(x, y, z, orientation))
{
TC_LOG_DEBUG("entities.unit", "Unit::UpdatePosition({}, {}, {}) .. bad coordinates!", x, y, z);
return false;
}
// Check if angular distance changed
bool const turn = G3D::fuzzyGt(M_PI - fabs(fabs(GetOrientation() - orientation) - M_PI), 0.0f);
// G3D::fuzzyEq won't help here, in some cases magnitudes differ by a little more than G3D::eps, but should be considered equal
bool const relocated = (teleport ||
std::fabs(GetPositionX() - x) > 0.001f ||
std::fabs(GetPositionY() - y) > 0.001f ||
std::fabs(GetPositionZ() - z) > 0.001f);
if (relocated)
{
// move and update visible state if need
if (GetTypeId() == TYPEID_PLAYER)
GetMap()->PlayerRelocation(ToPlayer(), x, y, z, orientation);
else
GetMap()->CreatureRelocation(ToCreature(), x, y, z, orientation);
AuraEffectList& controlZoneAuras = GetAuraEffectsByType(SPELL_AURA_ACT_AS_CONTROL_ZONE);
for (AuraEffect const* auraEffect : controlZoneAuras)
if (GameObject* controlZone = GetGameObject(auraEffect->GetSpellInfo()->Id))
GetMap()->GameObjectRelocation(controlZone, x, y, z, orientation);
}
else if (turn)
UpdateOrientation(orientation);
_positionUpdateInfo.Relocated = relocated;
_positionUpdateInfo.Turned = turn;
if (IsFalling())
RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags2::Falling);
bool isInWater = IsInWater();
if (!IsFalling() || isInWater || IsFlying())
RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags2::Ground);
if (isInWater)
RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags2::Swimming);
return (relocated || turn);
}
bool Unit::UpdatePosition(Position const& pos, bool teleport)
{
return UpdatePosition(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), pos.GetOrientation(), teleport);
}
//! Only server-side orientation update, does not broadcast to client
void Unit::UpdateOrientation(float orientation)
{
SetOrientation(orientation);
if (IsVehicle())
GetVehicleKit()->RelocatePassengers();
}
//! Only server-side height update, does not broadcast to client
void Unit::UpdateHeight(float newZ)
{
Relocate(GetPositionX(), GetPositionY(), newZ);
if (IsVehicle())
GetVehicleKit()->RelocatePassengers();
}
// baseRage means damage taken when attacker = false
int32 Unit::RewardRage(uint32 baseRage)
{
float addRage = baseRage;
// talent who gave more rage on attack
AddPct(addRage, GetTotalAuraModifier(SPELL_AURA_MOD_RAGE_FROM_DAMAGE_DEALT));
addRage *= sWorld->getRate(RATE_POWER_RAGE_INCOME);
return ModifyPower(POWER_RAGE, uint32(addRage * 10), false);
}
void Unit::StopAttackFaction(uint32 faction_id)
{
if (Unit* victim = GetVictim())
{
if (victim->GetFactionTemplateEntry()->Faction == faction_id)
{
AttackStop();
if (IsNonMeleeSpellCast(false))
InterruptNonMeleeSpells(false);
// melee and ranged forced attack cancel
if (GetTypeId() == TYPEID_PLAYER)
ToPlayer()->SendAttackSwingCancelAttack();
}
}
AttackerSet const& attackers = getAttackers();
for (AttackerSet::const_iterator itr = attackers.begin(); itr != attackers.end();)
{
if ((*itr)->GetFactionTemplateEntry()->Faction == faction_id)
{
(*itr)->AttackStop();
itr = attackers.begin();
}
else
++itr;
}
std::vector refsToEnd;
for (auto const& pair : m_combatManager.GetPvECombatRefs())
if (pair.second->GetOther(this)->GetFactionTemplateEntry()->Faction == faction_id)
refsToEnd.push_back(pair.second);
for (CombatReference* ref : refsToEnd)
ref->EndCombat();
for (Unit* minion : m_Controlled)
minion->StopAttackFaction(faction_id);
}
void Unit::OutDebugInfo() const
{
TC_LOG_ERROR("entities.unit", "Unit::OutDebugInfo");
TC_LOG_DEBUG("entities.unit", "{} name {}", GetGUID().ToString(), GetName());
TC_LOG_DEBUG("entities.unit", "Owner {}, Minion {}, Charmer {}, Charmed {}", GetOwnerGUID().ToString(), GetMinionGUID().ToString(), GetCharmerGUID().ToString(), GetCharmedGUID().ToString());
TC_LOG_DEBUG("entities.unit", "In world {}, unit type mask {}", (uint32)(IsInWorld() ? 1 : 0), m_unitTypeMask);
if (IsInWorld())
TC_LOG_DEBUG("entities.unit", "Mapid {}", GetMapId());
std::ostringstream o;
o << "Summon Slot: ";
for (uint32 i = 0; i < MAX_SUMMON_SLOT; ++i)
o << m_SummonSlot[i].ToString() << ", ";
TC_LOG_DEBUG("entities.unit", "{}", o.str());
o.str("");
o << "Controlled List: ";
for (ControlList::const_iterator itr = m_Controlled.begin(); itr != m_Controlled.end(); ++itr)
o << (*itr)->GetGUID().ToString() << ", ";
TC_LOG_DEBUG("entities.unit", "{}", o.str());
o.str("");
o << "Aura List: ";
for (AuraApplicationMap::const_iterator itr = m_appliedAuras.begin(); itr != m_appliedAuras.end(); ++itr)
o << itr->first << ", ";
TC_LOG_DEBUG("entities.unit", "{}", o.str());
o.str("");
if (IsVehicle())
{
o << "Passenger List: ";
for (SeatMap::iterator itr = GetVehicleKit()->Seats.begin(); itr != GetVehicleKit()->Seats.end(); ++itr)
if (Unit* passenger = ObjectAccessor::GetUnit(*GetVehicleBase(), itr->second.Passenger.Guid))
o << passenger->GetGUID().ToString() << ", ";
TC_LOG_DEBUG("entities.unit", "{}", o.str());
}
if (GetVehicle())
TC_LOG_DEBUG("entities.unit", "On vehicle {}.", GetVehicleBase()->GetEntry());
}
void Unit::SendClearTarget()
{
WorldPackets::Combat::BreakTarget breakTarget;
breakTarget.UnitGUID = GetGUID();
SendMessageToSet(breakTarget.Write(), false);
}
int32 Unit::GetResistance(SpellSchoolMask mask) const
{
Optional resist;
for (int32 i = SPELL_SCHOOL_NORMAL; i < MAX_SPELL_SCHOOL; ++i)
{
int32 schoolResistance = GetResistance(SpellSchools(i));
if (mask & (1 << i) && (!resist || *resist > schoolResistance))
resist = schoolResistance;
}
return resist.value_or(0);
}
void CharmInfo::SetIsCommandAttack(bool val)
{
_isCommandAttack = val;
}
bool CharmInfo::IsCommandAttack()
{
return _isCommandAttack;
}
void CharmInfo::SetIsCommandFollow(bool val)
{
_isCommandFollow = val;
}
bool CharmInfo::IsCommandFollow()
{
return _isCommandFollow;
}
void CharmInfo::SaveStayPosition()
{
//! At this point a new spline destination is enabled because of Unit::StopMoving()
G3D::Vector3 stayPos = _unit->movespline->FinalDestination();
if (_unit->movespline->onTransport)
if (TransportBase* transport = _unit->GetDirectTransport())
transport->CalculatePassengerPosition(stayPos.x, stayPos.y, stayPos.z);
_stayX = stayPos.x;
_stayY = stayPos.y;
_stayZ = stayPos.z;
}
void CharmInfo::GetStayPosition(float &x, float &y, float &z)
{
x = _stayX;
y = _stayY;
z = _stayZ;
}
void CharmInfo::SetIsAtStay(bool val)
{
_isAtStay = val;
}
bool CharmInfo::IsAtStay()
{
return _isAtStay;
}
void CharmInfo::SetIsFollowing(bool val)
{
_isFollowing = val;
}
bool CharmInfo::IsFollowing()
{
return _isFollowing;
}
void CharmInfo::SetIsReturning(bool val)
{
_isReturning = val;
}
bool CharmInfo::IsReturning()
{
return _isReturning;
}
void Unit::SetInFront(WorldObject const* target)
{
if (!HasUnitState(UNIT_STATE_CANNOT_TURN))
SetOrientation(GetAbsoluteAngle(target));
}
void Unit::SetFacingTo(float ori, bool force)
{
// do not face when already moving
if (!force && (!IsStopped() || !movespline->Finalized()))
return;
Movement::MoveSplineInit init(this);
init.MoveTo(GetPositionX(), GetPositionY(), GetPositionZ(), false);
if (GetTransport())
init.DisableTransportPathTransformations(); // It makes no sense to target global orientation
init.SetFacing(ori);
//GetMotionMaster()->LaunchMoveSpline(std::move(init), EVENT_FACE, MOTION_PRIORITY_HIGHEST);
UpdateSplineMovement(init.Launch());
if (Creature* creature = ToCreature())
creature->AI()->MovementInform(EFFECT_MOTION_TYPE, EVENT_FACE);
}
void Unit::SetFacingToObject(WorldObject const* object, bool force)
{
// do not face when already moving
if (!force && (!IsStopped() || !movespline->Finalized()))
return;
/// @todo figure out under what conditions creature will move towards object instead of facing it where it currently is.
Movement::MoveSplineInit init(this);
init.MoveTo(GetPositionX(), GetPositionY(), GetPositionZ(), false);
init.SetFacing(GetAbsoluteAngle(object)); // when on transport, GetAbsoluteAngle will still return global coordinates (and angle) that needs transforming
//GetMotionMaster()->LaunchMoveSpline(std::move(init), EVENT_FACE, MOTION_PRIORITY_HIGHEST);
UpdateSplineMovement(init.Launch());
if (Creature* creature = ToCreature())
creature->AI()->MovementInform(EFFECT_MOTION_TYPE, EVENT_FACE);
}
void Unit::SetFacingToPoint(Position const& point, bool force)
{
// do not face when already moving
if (!force && (!IsStopped() || !movespline->Finalized()))
return;
/// @todo figure out under what conditions creature will move towards object instead of facing it where it currently is.
Movement::MoveSplineInit init(this);
init.MoveTo(GetPositionX(), GetPositionY(), GetPositionZ(), false);
if (GetTransport())
init.DisableTransportPathTransformations(); // It makes no sense to target global orientation
init.SetFacing(point.GetPositionX(), point.GetPositionY(), point.GetPositionZ());
//GetMotionMaster()->LaunchMoveSpline(std::move(init), EVENT_FACE, MOTION_PRIORITY_HIGHEST);
UpdateSplineMovement(init.Launch());
if (Creature* creature = ToCreature())
creature->AI()->MovementInform(EFFECT_MOTION_TYPE, EVENT_FACE);
}
bool Unit::SetWalk(bool enable)
{
if (enable == IsWalking())
return false;
if (enable)
AddUnitMovementFlag(MOVEMENTFLAG_WALKING);
else
RemoveUnitMovementFlag(MOVEMENTFLAG_WALKING);
static OpcodeServer const walkModeTable[2] = { SMSG_MOVE_SPLINE_SET_RUN_MODE, SMSG_MOVE_SPLINE_SET_WALK_MODE };
WorldPackets::Movement::MoveSplineSetFlag packet(walkModeTable[enable]);
packet.MoverGUID = GetGUID();
SendMessageToSet(packet.Write(), true);
return true;
}
bool Unit::SetDisableGravity(bool disable, bool updateAnimTier /*= true*/)
{
if (disable == IsGravityDisabled())
return false;
if (disable)
{
AddUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY);
RemoveUnitMovementFlag(MOVEMENTFLAG_SWIMMING | MOVEMENTFLAG_SPLINE_ELEVATION);
}
else
RemoveUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY);
static OpcodeServer const gravityOpcodeTable[2][2] =
{
{ SMSG_MOVE_SPLINE_ENABLE_GRAVITY, SMSG_MOVE_ENABLE_GRAVITY },
{ SMSG_MOVE_SPLINE_DISABLE_GRAVITY, SMSG_MOVE_DISABLE_GRAVITY }
};
if (Player* playerMover = Unit::ToPlayer(GetUnitBeingMoved()))
{
WorldPackets::Movement::MoveSetFlag packet(gravityOpcodeTable[disable][1]);
packet.MoverGUID = GetGUID();
packet.SequenceIndex = m_movementCounter++;
playerMover->SendDirectMessage(packet.Write());
WorldPackets::Movement::MoveUpdate moveUpdate;
moveUpdate.Status = &m_movementInfo;
SendMessageToSet(moveUpdate.Write(), playerMover);
}
else
{
WorldPackets::Movement::MoveSplineSetFlag packet(gravityOpcodeTable[disable][0]);
packet.MoverGUID = GetGUID();
SendMessageToSet(packet.Write(), true);
}
if (!GetVehicle())
{
if (IsAlive())
{
if (IsGravityDisabled() || IsHovering())
SetPlayHoverAnim(true);
else
SetPlayHoverAnim(false);
}
else if (IsPlayer()) // To update player who dies while flying/hovering
SetPlayHoverAnim(false, false);
}
if (IsCreature() && updateAnimTier && IsAlive() && !HasUnitState(UNIT_STATE_ROOT))
{
if (IsGravityDisabled())
SetAnimTier(AnimTier::Fly);
else if (IsHovering())
SetAnimTier(AnimTier::Hover);
else
SetAnimTier(AnimTier::Ground);
}
return true;
}
bool Unit::SetFall(bool enable)
{
if (enable == HasUnitMovementFlag(MOVEMENTFLAG_FALLING))
return false;
if (enable)
{
AddUnitMovementFlag(MOVEMENTFLAG_FALLING);
m_movementInfo.SetFallTime(0);
}
else
RemoveUnitMovementFlag(MOVEMENTFLAG_FALLING | MOVEMENTFLAG_FALLING_FAR);
return true;
}
bool Unit::SetSwim(bool enable)
{
if (enable == HasUnitMovementFlag(MOVEMENTFLAG_SWIMMING))
return false;
if (enable)
AddUnitMovementFlag(MOVEMENTFLAG_SWIMMING);
else
RemoveUnitMovementFlag(MOVEMENTFLAG_SWIMMING);
static OpcodeServer const swimOpcodeTable[2] = { SMSG_MOVE_SPLINE_STOP_SWIM, SMSG_MOVE_SPLINE_START_SWIM};
WorldPackets::Movement::MoveSplineSetFlag packet(swimOpcodeTable[enable]);
packet.MoverGUID = GetGUID();
SendMessageToSet(packet.Write(), true);
return true;
}
bool Unit::SetCanFly(bool enable)
{
if (enable == HasUnitMovementFlag(MOVEMENTFLAG_CAN_FLY))
return false;
if (enable)
{
AddUnitMovementFlag(MOVEMENTFLAG_CAN_FLY);
RemoveUnitMovementFlag(MOVEMENTFLAG_SWIMMING | MOVEMENTFLAG_SPLINE_ELEVATION);
}
else
RemoveUnitMovementFlag(MOVEMENTFLAG_CAN_FLY | MOVEMENTFLAG_MASK_MOVING_FLY);
static OpcodeServer const flyOpcodeTable[2][2] =
{
{ SMSG_MOVE_SPLINE_UNSET_FLYING, SMSG_MOVE_UNSET_CAN_FLY },
{ SMSG_MOVE_SPLINE_SET_FLYING, SMSG_MOVE_SET_CAN_FLY }
};
if (!enable && GetTypeId() == TYPEID_PLAYER)
ToPlayer()->SetFallInformation(0, GetPositionZ());
if (Player* playerMover = Unit::ToPlayer(GetUnitBeingMoved()))
{
WorldPackets::Movement::MoveSetFlag packet(flyOpcodeTable[enable][1]);
packet.MoverGUID = GetGUID();
packet.SequenceIndex = m_movementCounter++;
playerMover->SendDirectMessage(packet.Write());
WorldPackets::Movement::MoveUpdate moveUpdate;
moveUpdate.Status = &m_movementInfo;
SendMessageToSet(moveUpdate.Write(), playerMover);
}
else
{
WorldPackets::Movement::MoveSplineSetFlag packet(flyOpcodeTable[enable][0]);
packet.MoverGUID = GetGUID();
SendMessageToSet(packet.Write(), true);
}
return true;
}
bool Unit::SetWaterWalking(bool enable)
{
if (enable == HasUnitMovementFlag(MOVEMENTFLAG_WATERWALKING))
return false;
if (enable)
AddUnitMovementFlag(MOVEMENTFLAG_WATERWALKING);
else
RemoveUnitMovementFlag(MOVEMENTFLAG_WATERWALKING);
static OpcodeServer const waterWalkingOpcodeTable[2][2] =
{
{ SMSG_MOVE_SPLINE_SET_LAND_WALK, SMSG_MOVE_SET_LAND_WALK },
{ SMSG_MOVE_SPLINE_SET_WATER_WALK, SMSG_MOVE_SET_WATER_WALK }
};
if (Player* playerMover = Unit::ToPlayer(GetUnitBeingMoved()))
{
WorldPackets::Movement::MoveSetFlag packet(waterWalkingOpcodeTable[enable][1]);
packet.MoverGUID = GetGUID();
packet.SequenceIndex = m_movementCounter++;
playerMover->SendDirectMessage(packet.Write());
WorldPackets::Movement::MoveUpdate moveUpdate;
moveUpdate.Status = &m_movementInfo;
SendMessageToSet(moveUpdate.Write(), playerMover);
}
else
{
WorldPackets::Movement::MoveSplineSetFlag packet(waterWalkingOpcodeTable[enable][0]);
packet.MoverGUID = GetGUID();
SendMessageToSet(packet.Write(), true);
}
return true;
}
bool Unit::SetFeatherFall(bool enable)
{
// Temporarily disabled for short lived auras that unapply before client had time to ACK applying
//if (enable == HasUnitMovementFlag(MOVEMENTFLAG_FALLING_SLOW))
// return false;
if (enable)
AddUnitMovementFlag(MOVEMENTFLAG_FALLING_SLOW);
else
RemoveUnitMovementFlag(MOVEMENTFLAG_FALLING_SLOW);
static OpcodeServer const featherFallOpcodeTable[2][2] =
{
{ SMSG_MOVE_SPLINE_SET_NORMAL_FALL, SMSG_MOVE_SET_NORMAL_FALL },
{ SMSG_MOVE_SPLINE_SET_FEATHER_FALL, SMSG_MOVE_SET_FEATHER_FALL }
};
if (Player* playerMover = Unit::ToPlayer(GetUnitBeingMoved()))
{
WorldPackets::Movement::MoveSetFlag packet(featherFallOpcodeTable[enable][1]);
packet.MoverGUID = GetGUID();
packet.SequenceIndex = m_movementCounter++;
playerMover->SendDirectMessage(packet.Write());
WorldPackets::Movement::MoveUpdate moveUpdate;
moveUpdate.Status = &m_movementInfo;
SendMessageToSet(moveUpdate.Write(), playerMover);
}
else
{
WorldPackets::Movement::MoveSplineSetFlag packet(featherFallOpcodeTable[enable][0]);
packet.MoverGUID = GetGUID();
SendMessageToSet(packet.Write(), true);
}
return true;
}
bool Unit::SetHover(bool enable, bool updateAnimTier /*= true*/)
{
if (enable == HasUnitMovementFlag(MOVEMENTFLAG_HOVER))
return false;
float hoverHeight = m_unitData->HoverHeight;
if (enable)
{
//! No need to check height on ascent
AddUnitMovementFlag(MOVEMENTFLAG_HOVER);
if (hoverHeight && GetPositionZ() - GetFloorZ() < hoverHeight)
UpdateHeight(GetPositionZ() + hoverHeight);
}
else
{
RemoveUnitMovementFlag(MOVEMENTFLAG_HOVER);
//! Dying creatures will MoveFall from setDeathState
if (hoverHeight && (!isDying() || GetTypeId() != TYPEID_UNIT))
{
float newZ = std::max(GetFloorZ(), GetPositionZ() - hoverHeight);
UpdateAllowedPositionZ(GetPositionX(), GetPositionY(), newZ);
UpdateHeight(newZ);
}
}
static OpcodeServer const hoverOpcodeTable[2][2] =
{
{ SMSG_MOVE_SPLINE_UNSET_HOVER, SMSG_MOVE_UNSET_HOVERING },
{ SMSG_MOVE_SPLINE_SET_HOVER, SMSG_MOVE_SET_HOVERING }
};
if (Player* playerMover = Unit::ToPlayer(GetUnitBeingMoved()))
{
WorldPackets::Movement::MoveSetFlag packet(hoverOpcodeTable[enable][1]);
packet.MoverGUID = GetGUID();
packet.SequenceIndex = m_movementCounter++;
playerMover->SendDirectMessage(packet.Write());
WorldPackets::Movement::MoveUpdate moveUpdate;
moveUpdate.Status = &m_movementInfo;
SendMessageToSet(moveUpdate.Write(), playerMover);
}
else
{
WorldPackets::Movement::MoveSplineSetFlag packet(hoverOpcodeTable[enable][0]);
packet.MoverGUID = GetGUID();
SendMessageToSet(packet.Write(), true);
}
if (IsAlive())
{
if (IsGravityDisabled() || IsHovering())
SetPlayHoverAnim(true);
else
SetPlayHoverAnim(false);
}
else if (IsPlayer()) // To update player who dies while flying/hovering
SetPlayHoverAnim(false, false);
if (IsCreature() && updateAnimTier && IsAlive() && !HasUnitState(UNIT_STATE_ROOT))
{
if (IsGravityDisabled())
SetAnimTier(AnimTier::Fly);
else if (IsHovering())
SetAnimTier(AnimTier::Hover);
else
SetAnimTier(AnimTier::Ground);
}
return true;
}
bool Unit::SetCollision(bool disable)
{
if (disable == HasUnitMovementFlag(MOVEMENTFLAG_DISABLE_COLLISION))
return false;
if (disable)
AddUnitMovementFlag(MOVEMENTFLAG_DISABLE_COLLISION);
else
RemoveUnitMovementFlag(MOVEMENTFLAG_DISABLE_COLLISION);
static OpcodeServer const collisionOpcodeTable[2][2] =
{
{ SMSG_MOVE_SPLINE_ENABLE_COLLISION, SMSG_MOVE_ENABLE_COLLISION },
{ SMSG_MOVE_SPLINE_DISABLE_COLLISION, SMSG_MOVE_DISABLE_COLLISION }
};
if (Player* playerMover = Unit::ToPlayer(GetUnitBeingMoved()))
{
WorldPackets::Movement::MoveSetFlag packet(collisionOpcodeTable[disable][1]);
packet.MoverGUID = GetGUID();
packet.SequenceIndex = m_movementCounter++;
playerMover->SendDirectMessage(packet.Write());
WorldPackets::Movement::MoveUpdate moveUpdate;
moveUpdate.Status = &m_movementInfo;
SendMessageToSet(moveUpdate.Write(), playerMover);
}
else
{
WorldPackets::Movement::MoveSplineSetFlag packet(collisionOpcodeTable[disable][0]);
packet.MoverGUID = GetGUID();
SendMessageToSet(packet.Write(), true);
}
return true;
}
bool Unit::SetEnableFullSpeedTurning(bool enable)
{
if (GetTypeId() != TYPEID_PLAYER)
return false;
if (enable == HasExtraUnitMovementFlag(MOVEMENTFLAG2_FULL_SPEED_TURNING))
return false;
if (enable)
AddExtraUnitMovementFlag(MOVEMENTFLAG2_FULL_SPEED_TURNING);
else
RemoveExtraUnitMovementFlag(MOVEMENTFLAG2_FULL_SPEED_TURNING);
static constexpr OpcodeServer fullSpeedTurningOpcodeTable[2] =
{
SMSG_MOVE_DISABLE_FULL_SPEED_TURNING,
SMSG_MOVE_ENABLE_FULL_SPEED_TURNING
};
if (Player* playerMover = Unit::ToPlayer(GetUnitBeingMoved()))
{
WorldPackets::Movement::MoveSetFlag packet(fullSpeedTurningOpcodeTable[enable]);
packet.MoverGUID = GetGUID();
packet.SequenceIndex = m_movementCounter++;
playerMover->SendDirectMessage(packet.Write());
WorldPackets::Movement::MoveUpdate moveUpdate;
moveUpdate.Status = &m_movementInfo;
SendMessageToSet(moveUpdate.Write(), playerMover);
}
return true;
}
bool Unit::SetCanTransitionBetweenSwimAndFly(bool enable)
{
if (GetTypeId() != TYPEID_PLAYER)
return false;
if (enable == HasExtraUnitMovementFlag(MOVEMENTFLAG2_CAN_SWIM_TO_FLY_TRANS))
return false;
if (enable)
AddExtraUnitMovementFlag(MOVEMENTFLAG2_CAN_SWIM_TO_FLY_TRANS);
else
RemoveExtraUnitMovementFlag(MOVEMENTFLAG2_CAN_SWIM_TO_FLY_TRANS);
static OpcodeServer const swimToFlyTransOpcodeTable[2] =
{
SMSG_MOVE_DISABLE_TRANSITION_BETWEEN_SWIM_AND_FLY,
SMSG_MOVE_ENABLE_TRANSITION_BETWEEN_SWIM_AND_FLY
};
if (Player* playerMover = Unit::ToPlayer(GetUnitBeingMoved()))
{
WorldPackets::Movement::MoveSetFlag packet(swimToFlyTransOpcodeTable[enable]);
packet.MoverGUID = GetGUID();
packet.SequenceIndex = m_movementCounter++;
playerMover->SendDirectMessage(packet.Write());
WorldPackets::Movement::MoveUpdate moveUpdate;
moveUpdate.Status = &m_movementInfo;
SendMessageToSet(moveUpdate.Write(), playerMover);
}
return true;
}
bool Unit::SetCanTurnWhileFalling(bool enable)
{
// Temporarily disabled for short lived auras that unapply before client had time to ACK applying
//if (enable == HasExtraUnitMovementFlag(MOVEMENTFLAG2_CAN_TURN_WHILE_FALLING))
// return false;
if (enable)
AddExtraUnitMovementFlag(MOVEMENTFLAG2_CAN_TURN_WHILE_FALLING);
else
RemoveExtraUnitMovementFlag(MOVEMENTFLAG2_CAN_TURN_WHILE_FALLING);
static OpcodeServer const canTurnWhileFallingOpcodeTable[2] =
{
SMSG_MOVE_UNSET_CAN_TURN_WHILE_FALLING,
SMSG_MOVE_SET_CAN_TURN_WHILE_FALLING
};
if (Player* playerMover = Unit::ToPlayer(GetUnitBeingMoved()))
{
WorldPackets::Movement::MoveSetFlag packet(canTurnWhileFallingOpcodeTable[enable]);
packet.MoverGUID = GetGUID();
packet.SequenceIndex = m_movementCounter++;
playerMover->SendDirectMessage(packet.Write());
WorldPackets::Movement::MoveUpdate moveUpdate;
moveUpdate.Status = &m_movementInfo;
SendMessageToSet(moveUpdate.Write(), playerMover);
}
return true;
}
bool Unit::SetCanDoubleJump(bool enable)
{
if (enable == HasExtraUnitMovementFlag(MOVEMENTFLAG2_CAN_DOUBLE_JUMP))
return false;
if (enable)
AddExtraUnitMovementFlag(MOVEMENTFLAG2_CAN_DOUBLE_JUMP);
else
RemoveExtraUnitMovementFlag(MOVEMENTFLAG2_CAN_DOUBLE_JUMP);
static OpcodeServer const doubleJumpOpcodeTable[2] =
{
SMSG_MOVE_DISABLE_DOUBLE_JUMP,
SMSG_MOVE_ENABLE_DOUBLE_JUMP
};
if (Player* playerMover = Unit::ToPlayer(GetUnitBeingMoved()))
{
WorldPackets::Movement::MoveSetFlag packet(doubleJumpOpcodeTable[enable]);
packet.MoverGUID = GetGUID();
packet.SequenceIndex = m_movementCounter++;
playerMover->SendDirectMessage(packet.Write());
WorldPackets::Movement::MoveUpdate moveUpdate;
moveUpdate.Status = &m_movementInfo;
SendMessageToSet(moveUpdate.Write(), playerMover);
}
return true;
}
bool Unit::SetDisableInertia(bool disable)
{
if (disable == HasExtraUnitMovementFlag2(MOVEMENTFLAG3_DISABLE_INERTIA))
return false;
if (disable)
AddExtraUnitMovementFlag2(MOVEMENTFLAG3_DISABLE_INERTIA);
else
RemoveExtraUnitMovementFlag2(MOVEMENTFLAG3_DISABLE_INERTIA);
static OpcodeServer const disableInertiaOpcodeTable[2] =
{
SMSG_MOVE_ENABLE_INERTIA,
SMSG_MOVE_DISABLE_INERTIA
};
if (Player* playerMover = Unit::ToPlayer(GetUnitBeingMoved()))
{
WorldPackets::Movement::MoveSetFlag packet(disableInertiaOpcodeTable[disable]);
packet.MoverGUID = GetGUID();
packet.SequenceIndex = m_movementCounter++;
playerMover->SendDirectMessage(packet.Write());
WorldPackets::Movement::MoveUpdate moveUpdate;
moveUpdate.Status = &m_movementInfo;
SendMessageToSet(moveUpdate.Write(), playerMover);
}
return true;
}
bool Unit::SetCanAdvFly(bool enable)
{
if (enable == HasExtraUnitMovementFlag2(MOVEMENTFLAG3_CAN_ADV_FLY))
return false;
if (enable)
AddExtraUnitMovementFlag2(MOVEMENTFLAG3_CAN_ADV_FLY);
else
RemoveExtraUnitMovementFlag2(MOVEMENTFLAG3_CAN_ADV_FLY | MOVEMENTFLAG3_ADV_FLYING);
static OpcodeServer const advFlyOpcodeTable[2] =
{
SMSG_MOVE_UNSET_CAN_ADV_FLY,
SMSG_MOVE_SET_CAN_ADV_FLY
};
if (Player* playerMover = Unit::ToPlayer(GetUnitBeingMoved()))
{
WorldPackets::Movement::MoveSetFlag packet(advFlyOpcodeTable[enable]);
packet.MoverGUID = GetGUID();
packet.SequenceIndex = m_movementCounter++;
playerMover->SendDirectMessage(packet.Write());
WorldPackets::Movement::MoveUpdate moveUpdate;
moveUpdate.Status = &m_movementInfo;
SendMessageToSet(moveUpdate.Write(), playerMover);
}
return true;
}
bool Unit::SetMoveCantSwim(bool cantSwim)
{
if (cantSwim == HasExtraUnitMovementFlag2(MOVEMENTFLAG3_CANT_SWIM))
return false;
if (cantSwim)
AddExtraUnitMovementFlag2(MOVEMENTFLAG3_CANT_SWIM);
else
RemoveExtraUnitMovementFlag2(MOVEMENTFLAG3_CANT_SWIM);
static OpcodeServer const cantSwimOpcodeTable[2] =
{
SMSG_MOVE_UNSET_CANT_SWIM,
SMSG_MOVE_SET_CANT_SWIM,
};
if (Player* playerMover = Unit::ToPlayer(GetUnitBeingMoved()))
{
WorldPackets::Movement::MoveSetFlag packet(cantSwimOpcodeTable[cantSwim]);
packet.MoverGUID = GetGUID();
packet.SequenceIndex = m_movementCounter++;
playerMover->SendDirectMessage(packet.Write());
WorldPackets::Movement::MoveUpdate moveUpdate;
moveUpdate.Status = &m_movementInfo;
SendMessageToSet(moveUpdate.Write(), playerMover);
}
return true;
}
void Unit::SendSetVehicleRecId(uint32 vehicleId)
{
if (Player* player = ToPlayer())
{
WorldPackets::Vehicle::MoveSetVehicleRecID moveSetVehicleRec;
moveSetVehicleRec.MoverGUID = GetGUID();
moveSetVehicleRec.SequenceIndex = m_movementCounter++;
moveSetVehicleRec.VehicleRecID = vehicleId;
player->SendDirectMessage(moveSetVehicleRec.Write());
}
WorldPackets::Vehicle::SetVehicleRecID setVehicleRec;
setVehicleRec.VehicleGUID = GetGUID();
setVehicleRec.VehicleRecID = vehicleId;
SendMessageToSet(setVehicleRec.Write(), true);
}
void Unit::ApplyMovementForce(ObjectGuid id, Position origin, float magnitude, MovementForceType type, Position direction /*= {}*/, ObjectGuid transportGuid /*= ObjectGuid::Empty*/)
{
if (!_movementForces)
_movementForces = std::make_unique();
MovementForce force;
force.ID = id;
force.Origin = origin;
force.Direction = direction;
if (transportGuid.IsMOTransport())
force.TransportID = transportGuid.GetCounter();
force.Magnitude = magnitude;
force.Type = type;
if (_movementForces->Add(force))
{
if (Player const* movingPlayer = GetPlayerMovingMe())
{
WorldPackets::Movement::MoveApplyMovementForce applyMovementForce;
applyMovementForce.MoverGUID = GetGUID();
applyMovementForce.SequenceIndex = m_movementCounter++;
applyMovementForce.Force = &force;
movingPlayer->SendDirectMessage(applyMovementForce.Write());
}
else
{
WorldPackets::Movement::MoveUpdateApplyMovementForce updateApplyMovementForce;
updateApplyMovementForce.Status = &m_movementInfo;
updateApplyMovementForce.Force = &force;
SendMessageToSet(updateApplyMovementForce.Write(), true);
}
}
}
void Unit::RemoveMovementForce(ObjectGuid id)
{
if (!_movementForces)
return;
if (_movementForces->Remove(id))
{
if (Player const* movingPlayer = GetPlayerMovingMe())
{
WorldPackets::Movement::MoveRemoveMovementForce moveRemoveMovementForce;
moveRemoveMovementForce.MoverGUID = GetGUID();
moveRemoveMovementForce.SequenceIndex = m_movementCounter++;
moveRemoveMovementForce.ID = id;
movingPlayer->SendDirectMessage(moveRemoveMovementForce.Write());
}
else
{
WorldPackets::Movement::MoveUpdateRemoveMovementForce updateRemoveMovementForce;
updateRemoveMovementForce.Status = &m_movementInfo;
updateRemoveMovementForce.TriggerGUID = id;
SendMessageToSet(updateRemoveMovementForce.Write(), true);
}
}
if (_movementForces->IsEmpty())
_movementForces.reset();
}
bool Unit::SetIgnoreMovementForces(bool ignore)
{
if (ignore == HasExtraUnitMovementFlag(MOVEMENTFLAG2_IGNORE_MOVEMENT_FORCES))
return false;
if (ignore)
AddExtraUnitMovementFlag(MOVEMENTFLAG2_IGNORE_MOVEMENT_FORCES);
else
RemoveExtraUnitMovementFlag(MOVEMENTFLAG2_IGNORE_MOVEMENT_FORCES);
static OpcodeServer const ignoreMovementForcesOpcodeTable[2] =
{
SMSG_MOVE_UNSET_IGNORE_MOVEMENT_FORCES,
SMSG_MOVE_SET_IGNORE_MOVEMENT_FORCES
};
if (Player const* movingPlayer = GetPlayerMovingMe())
{
WorldPackets::Movement::MoveSetFlag packet(ignoreMovementForcesOpcodeTable[ignore]);
packet.MoverGUID = GetGUID();
packet.SequenceIndex = m_movementCounter++;
movingPlayer->SendDirectMessage(packet.Write());
WorldPackets::Movement::MoveUpdate moveUpdate;
moveUpdate.Status = &m_movementInfo;
SendMessageToSet(moveUpdate.Write(), movingPlayer);
}
return true;
}
void Unit::UpdateMovementForcesModMagnitude()
{
float modMagnitude = GetTotalAuraMultiplier(SPELL_AURA_MOD_MOVEMENT_FORCE_MAGNITUDE);
if (Player* movingPlayer = GetPlayerMovingMe())
{
WorldPackets::Movement::MoveSetSpeed setModMovementForceMagnitude(SMSG_MOVE_SET_MOD_MOVEMENT_FORCE_MAGNITUDE);
setModMovementForceMagnitude.MoverGUID = GetGUID();
setModMovementForceMagnitude.SequenceIndex = m_movementCounter++;
setModMovementForceMagnitude.Speed = modMagnitude;
movingPlayer->SendDirectMessage(setModMovementForceMagnitude.Write());
++movingPlayer->m_movementForceModMagnitudeChanges;
}
else
{
WorldPackets::Movement::MoveUpdateSpeed updateModMovementForceMagnitude(SMSG_MOVE_UPDATE_MOD_MOVEMENT_FORCE_MAGNITUDE);
updateModMovementForceMagnitude.Status = &m_movementInfo;
updateModMovementForceMagnitude.Speed = modMagnitude;
SendMessageToSet(updateModMovementForceMagnitude.Write(), true);
}
if (modMagnitude != 1.0f && !_movementForces)
_movementForces = std::make_unique();
if (_movementForces)
{
_movementForces->SetModMagnitude(modMagnitude);
if (_movementForces->IsEmpty())
_movementForces.reset();
}
}
void Unit::SetPlayHoverAnim(bool enable, bool sendUpdate /*= true*/)
{
if (IsPlayingHoverAnim() == enable)
return;
_playHoverAnim = enable;
if (!sendUpdate)
return;
WorldPackets::Misc::SetPlayHoverAnim data;
data.UnitGUID = GetGUID();
data.PlayHoverAnim = enable;
SendMessageToSet(data.Write(), true);
}
void Unit::CalculateHoverHeight()
{
float hoverHeight = DEFAULT_PLAYER_HOVER_HEIGHT;
float displayScale = DEFAULT_PLAYER_DISPLAY_SCALE;
uint32 displayId = IsMounted() ? GetMountDisplayId() : GetDisplayId();
// Get DisplayScale for creatures
if (IsCreature())
if (CreatureModel const* model = ToCreature()->GetCreatureTemplate()->GetModelWithDisplayId(displayId))
displayScale = model->DisplayScale;
if (CreatureDisplayInfoEntry const* displayInfo = sCreatureDisplayInfoStore.LookupEntry(displayId))
if (CreatureModelDataEntry const* modelData = sCreatureModelDataStore.LookupEntry(displayInfo->ModelID))
hoverHeight = modelData->HoverHeight * modelData->ModelScale * displayInfo->CreatureModelScale * displayScale;
SetHoverHeight(hoverHeight ? hoverHeight : DEFAULT_PLAYER_HOVER_HEIGHT);
}
bool Unit::IsSplineEnabled() const
{
return movespline->Initialized() && !movespline->Finalized();
}
UF::UpdateFieldFlag Unit::GetUpdateFieldFlagsFor(Player const* target) const
{
UF::UpdateFieldFlag flags = UF::UpdateFieldFlag::None;
if (target == this || GetOwnerGUID() == target->GetGUID())
flags |= UF::UpdateFieldFlag::Owner;
if (HasDynamicFlag(UNIT_DYNFLAG_SPECIALINFO))
if (HasAuraTypeWithCaster(SPELL_AURA_EMPATHY, target->GetGUID()))
flags |= UF::UpdateFieldFlag::Empath;
return flags;
}
void Unit::DestroyForPlayer(Player* target) const
{
if (Battleground* bg = target->GetBattleground())
{
if (bg->isArena())
{
WorldPackets::Battleground::DestroyArenaUnit destroyArenaUnit;
destroyArenaUnit.Guid = GetGUID();
target->GetSession()->SendPacket(destroyArenaUnit.Write());
}
}
WorldObject::DestroyForPlayer(target);
}
void Unit::ClearUpdateMask(bool remove)
{
m_values.ClearChangesMask(&Unit::m_unitData);
Object::ClearUpdateMask(remove);
}
int32 Unit::GetHighestExclusiveSameEffectSpellGroupValue(AuraEffect const* aurEff, AuraType auraType, bool checkMiscValue /*= false*/, int32 miscValue /*= 0*/) const
{
int32 val = 0;
SpellSpellGroupMapBounds spellGroup = sSpellMgr->GetSpellSpellGroupMapBounds(aurEff->GetSpellInfo()->GetFirstRankSpell()->Id);
for (SpellSpellGroupMap::const_iterator itr = spellGroup.first; itr != spellGroup.second ; ++itr)
{
if (sSpellMgr->GetSpellGroupStackRule(itr->second) == SPELL_GROUP_STACK_RULE_EXCLUSIVE_SAME_EFFECT)
{
AuraEffectList const& auraEffList = GetAuraEffectsByType(auraType);
for (AuraEffectList::const_iterator auraItr = auraEffList.begin(); auraItr != auraEffList.end(); ++auraItr)
{
if (aurEff != (*auraItr) && (!checkMiscValue || (*auraItr)->GetMiscValue() == miscValue) &&
sSpellMgr->IsSpellMemberOfSpellGroup((*auraItr)->GetSpellInfo()->Id, itr->second))
{
// absolute value only
if (abs(val) < abs((*auraItr)->GetAmount()))
val = (*auraItr)->GetAmount();
}
}
}
}
return val;
}
bool Unit::IsHighestExclusiveAura(Aura const* aura, bool removeOtherAuraApplications /*= false*/)
{
for (AuraEffect const* aurEff : aura->GetAuraEffects())
if (!IsHighestExclusiveAuraEffect(aura->GetSpellInfo(), aurEff->GetAuraType(), aurEff->GetAmount(), aura->GetEffectMask(), removeOtherAuraApplications))
return false;
return true;
}
bool Unit::IsHighestExclusiveAuraEffect(SpellInfo const* spellInfo, AuraType auraType, int32 effectAmount, uint32 auraEffectMask, bool removeOtherAuraApplications /*= false*/)
{
AuraEffectList const& auras = GetAuraEffectsByType(auraType);
for (Unit::AuraEffectList::const_iterator itr = auras.begin(); itr != auras.end();)
{
AuraEffect const* existingAurEff = (*itr);
++itr;
if (sSpellMgr->CheckSpellGroupStackRules(spellInfo, existingAurEff->GetSpellInfo()) == SPELL_GROUP_STACK_RULE_EXCLUSIVE_HIGHEST)
{
int64 diff = int64(abs(effectAmount)) - int64(abs(existingAurEff->GetAmount()));
if (!diff)
{
// treat the aura with more effects as stronger
diff = std::popcount(auraEffectMask) - std::popcount(existingAurEff->GetBase()->GetEffectMask());
}
if (diff > 0)
{
Aura const* base = existingAurEff->GetBase();
// no removing of area auras from the original owner, as that completely cancels them
if (removeOtherAuraApplications && (!base->IsArea() || base->GetOwner() != this))
{
if (AuraApplication* aurApp = existingAurEff->GetBase()->GetApplicationOfTarget(GetGUID()))
{
bool hasMoreThanOneEffect = base->HasMoreThanOneEffectForType(auraType);
uint32 removedAuras = m_removedAurasCount;
RemoveAura(aurApp);
if (hasMoreThanOneEffect || m_removedAurasCount > removedAuras + 1)
itr = auras.begin();
}
}
}
else if (diff < 0)
return false;
}
}
return true;
}
void Unit::Talk(std::string_view text, ChatMsg msgType, Language language, float textRange, WorldObject const* target)
{
Trinity::CustomChatTextBuilder builder(this, msgType, text, language, target);
Trinity::LocalizedDo localizer(builder);
Trinity::PlayerDistWorker > worker(this, textRange, localizer);
Cell::VisitWorldObjects(this, worker, textRange);
}
void Unit::Say(std::string_view text, Language language, WorldObject const* target /*= nullptr*/)
{
Talk(text, CHAT_MSG_MONSTER_SAY, language, sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_SAY), target);
}
void Unit::Yell(std::string_view text, Language language, WorldObject const* target /*= nullptr*/)
{
Talk(text, CHAT_MSG_MONSTER_YELL, language, sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_YELL), target);
}
void Unit::TextEmote(std::string_view text, WorldObject const* target /*= nullptr*/, bool isBossEmote /*= false*/)
{
Talk(text, isBossEmote ? CHAT_MSG_RAID_BOSS_EMOTE : CHAT_MSG_MONSTER_EMOTE, LANG_UNIVERSAL, sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_TEXTEMOTE), target);
}
void Unit::Whisper(std::string_view text, Language language, Player* target, bool isBossWhisper /*= false*/)
{
if (!target)
return;
LocaleConstant locale = target->GetSession()->GetSessionDbLocaleIndex();
WorldPackets::Chat::Chat packet;
packet.Initialize(isBossWhisper ? CHAT_MSG_RAID_BOSS_WHISPER : CHAT_MSG_MONSTER_WHISPER, language, this, target, text, 0, "", locale);
target->SendDirectMessage(packet.Write());
}
uint32 Unit::GetVirtualItemId(uint32 slot) const
{
if (slot >= MAX_EQUIPMENT_ITEMS)
return 0;
return m_unitData->VirtualItems[slot].ItemID;
}
uint16 Unit::GetVirtualItemAppearanceMod(uint32 slot) const
{
if (slot >= MAX_EQUIPMENT_ITEMS)
return 0;
return m_unitData->VirtualItems[slot].ItemAppearanceModID;
}
void Unit::SetVirtualItem(uint32 slot, uint32 itemId, uint16 appearanceModId /*= 0*/, uint16 itemVisual /*= 0*/)
{
if (slot >= MAX_EQUIPMENT_ITEMS)
return;
auto virtualItemField = m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::VirtualItems, slot);
SetUpdateFieldValue(virtualItemField.ModifyValue(&UF::VisibleItem::ItemID), itemId);
SetUpdateFieldValue(virtualItemField.ModifyValue(&UF::VisibleItem::ItemAppearanceModID), appearanceModId);
SetUpdateFieldValue(virtualItemField.ModifyValue(&UF::VisibleItem::ItemVisual), itemVisual);
}
void Unit::Talk(uint32 textId, ChatMsg msgType, float textRange, WorldObject const* target)
{
if (!sBroadcastTextStore.LookupEntry(textId))
{
TC_LOG_ERROR("entities.unit", "WorldObject::MonsterText: `broadcast_text` (ID: {}) was not found", textId);
return;
}
Trinity::BroadcastTextBuilder builder(this, msgType, textId, GetGender(), target);
Trinity::LocalizedDo localizer(builder);
Trinity::PlayerDistWorker > worker(this, textRange, localizer);
Cell::VisitWorldObjects(this, worker, textRange);
}
void Unit::Say(uint32 textId, WorldObject const* target /*= nullptr*/)
{
Talk(textId, CHAT_MSG_MONSTER_SAY, sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_SAY), target);
}
void Unit::Yell(uint32 textId, WorldObject const* target /*= nullptr*/)
{
Talk(textId, CHAT_MSG_MONSTER_YELL, sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_YELL), target);
}
void Unit::TextEmote(uint32 textId, WorldObject const* target /*= nullptr*/, bool isBossEmote /*= false*/)
{
Talk(textId, isBossEmote ? CHAT_MSG_RAID_BOSS_EMOTE : CHAT_MSG_MONSTER_EMOTE, sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_TEXTEMOTE), target);
}
void Unit::Whisper(uint32 textId, Player* target, bool isBossWhisper /*= false*/)
{
if (!target)
return;
BroadcastTextEntry const* bct = sBroadcastTextStore.LookupEntry(textId);
if (!bct)
{
TC_LOG_ERROR("entities.unit", "WorldObject::Whisper: `broadcast_text` was not {} found", textId);
return;
}
LocaleConstant locale = target->GetSession()->GetSessionDbLocaleIndex();
WorldPackets::Chat::Chat packet;
packet.Initialize(isBossWhisper ? CHAT_MSG_RAID_BOSS_WHISPER : CHAT_MSG_MONSTER_WHISPER, LANG_UNIVERSAL, this, target, DB2Manager::GetBroadcastTextValue(bct, locale, GetGender()), 0, "", locale);
target->SendDirectMessage(packet.Write());
}
void Unit::ClearBossEmotes(Optional zoneId, Player const* target) const
{
WorldPackets::Chat::ClearBossEmotes clearBossEmotes;
clearBossEmotes.Write();
if (target)
{
target->SendDirectMessage(clearBossEmotes.GetRawPacket());
return;
}
for (MapReference const& ref : GetMap()->GetPlayers())
if (!zoneId || DB2Manager::IsInArea(ref.GetSource()->GetAreaId(), *zoneId))
ref.GetSource()->SendDirectMessage(clearBossEmotes.GetRawPacket());
}
bool Unit::GetCastSpellInfoContext::AddSpell(uint32 spellId)
{
for (uint32& slot : VisitedSpells)
{
if (slot == spellId)
return false; // already exists
if (!slot)
{
slot = spellId;
return true;
}
}
return false; // no free slots left
}
SpellInfo const* Unit::GetCastSpellInfo(SpellInfo const* spellInfo, TriggerCastFlags& triggerFlag, GetCastSpellInfoContext* context) const
{
auto findMatchingAuraEffectIn = [this, spellInfo, &triggerFlag, context](AuraType type) -> SpellInfo const*
{
for (AuraEffect const* auraEffect : GetAuraEffectsByType(type))
{
bool matches = auraEffect->GetMiscValue() ? uint32(auraEffect->GetMiscValue()) == spellInfo->Id : auraEffect->IsAffectingSpell(spellInfo);
if (matches && context->AddSpell(auraEffect->GetAmount()))
{
if (SpellInfo const* newInfo = sSpellMgr->GetSpellInfo(auraEffect->GetAmount(), GetMap()->GetDifficultyID()))
{
if (auraEffect->GetSpellInfo()->HasAttribute(SPELL_ATTR8_IGNORE_SPELLCAST_OVERRIDE_COST))
triggerFlag |= TRIGGERED_IGNORE_POWER_COST;
else
triggerFlag &= ~TRIGGERED_IGNORE_POWER_COST;
if (auraEffect->GetSpellInfo()->HasAttribute(SPELL_ATTR11_IGNORE_SPELLCAST_OVERRIDE_SHAPESHIFT_REQUIREMENTS))
triggerFlag |= TRIGGERED_IGNORE_SHAPESHIFT;
else
triggerFlag &= ~TRIGGERED_IGNORE_SHAPESHIFT;
return newInfo;
}
}
}
return nullptr;
};
if (SpellInfo const* newInfo = findMatchingAuraEffectIn(SPELL_AURA_OVERRIDE_ACTIONBAR_SPELLS))
{
triggerFlag &= ~TRIGGERED_IGNORE_CAST_TIME;
return GetCastSpellInfo(newInfo, triggerFlag, context);
}
if (SpellInfo const* newInfo = findMatchingAuraEffectIn(SPELL_AURA_OVERRIDE_ACTIONBAR_SPELLS_TRIGGERED))
{
triggerFlag |= TRIGGERED_IGNORE_CAST_TIME;
return GetCastSpellInfo(newInfo, triggerFlag, context);
}
return spellInfo;
}
uint32 Unit::GetCastSpellXSpellVisualId(SpellInfo const* spellInfo) const
{
Unit::AuraEffectList const& visualOverrides = GetAuraEffectsByType(SPELL_AURA_OVERRIDE_SPELL_VISUAL);
for (AuraEffect const* effect : visualOverrides)
{
if (uint32(effect->GetMiscValue()) == spellInfo->Id)
{
if (SpellInfo const* visualSpell = sSpellMgr->GetSpellInfo(effect->GetMiscValueB(), GetMap()->GetDifficultyID()))
{
spellInfo = visualSpell;
break;
}
}
}
return WorldObject::GetCastSpellXSpellVisualId(spellInfo);
}
bool Unit::VisibleAuraSlotCompare::operator()(AuraApplication* left, AuraApplication* right) const
{
return left->GetSlot() < right->GetSlot();
}
// Returns collisionheight of the unit. If it is 0, it returns DEFAULT_COLLISION_HEIGHT.
float Unit::GetCollisionHeight() const
{
float scaleMod = GetObjectScale(); // 99% sure about this
if (IsMounted())
{
if (CreatureDisplayInfoEntry const* mountDisplayInfo = sCreatureDisplayInfoStore.LookupEntry(GetMountDisplayId()))
{
if (CreatureModelDataEntry const* mountModelData = sCreatureModelDataStore.LookupEntry(mountDisplayInfo->ModelID))
{
CreatureDisplayInfoEntry const* displayInfo = sCreatureDisplayInfoStore.AssertEntry(GetNativeDisplayId());
CreatureModelDataEntry const* modelData = sCreatureModelDataStore.AssertEntry(displayInfo->ModelID);
float const collisionHeight = scaleMod * ((mountModelData->MountHeight * mountDisplayInfo->CreatureModelScale) + (modelData->CollisionHeight * modelData->ModelScale * displayInfo->CreatureModelScale * 0.5f));
return collisionHeight == 0.0f ? DEFAULT_COLLISION_HEIGHT : collisionHeight;
}
}
}
//! Dismounting case - use basic default model data
CreatureDisplayInfoEntry const* displayInfo = sCreatureDisplayInfoStore.AssertEntry(GetNativeDisplayId());
CreatureModelDataEntry const* modelData = sCreatureModelDataStore.AssertEntry(displayInfo->ModelID);
float const collisionHeight = scaleMod * modelData->CollisionHeight * modelData->ModelScale * displayInfo->CreatureModelScale;
return collisionHeight == 0.0f ? DEFAULT_COLLISION_HEIGHT : collisionHeight;
}
void Unit::AddWorldEffect(int32 worldEffectId)
{
AddDynamicUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::WorldEffects)) = worldEffectId;
}
void Unit::RemoveWorldEffect(int32 worldEffectId)
{
int32 index = m_unitData->WorldEffects.FindIndex(worldEffectId);
if (index >= 0)
RemoveDynamicUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::WorldEffects), index);
}
void Unit::ClearWorldEffects()
{
ClearDynamicUpdateFieldValues(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::WorldEffects));
}
void Unit::SetVignette(uint32 vignetteId)
{
if (m_vignette)
{
if (m_vignette->Data->ID == vignetteId)
return;
Vignettes::Remove(*m_vignette, this);
m_vignette = nullptr;
}
if (VignetteEntry const* vignette = sVignetteStore.LookupEntry(vignetteId))
m_vignette = Vignettes::Create(vignette, this);
}
std::string Unit::GetDebugInfo() const
{
std::stringstream sstr;
sstr << WorldObject::GetDebugInfo() << "\n"
<< std::boolalpha
<< "IsAIEnabled: " << IsAIEnabled() << " DeathState: " << std::to_string(getDeathState())
<< " UnitMovementFlags: " << GetUnitMovementFlags() << " ExtraUnitMovementFlags: " << GetExtraUnitMovementFlags()
<< " Class: " << std::to_string(GetClass()) << "\n"
<< "" << (movespline ? movespline->ToString() : "Movespline: \n")
<< "GetCharmedGUID(): " << GetCharmedGUID().ToString() << "\n"
<< "GetCharmerGUID(): " << GetCharmerGUID().ToString() << "\n"
<< "" << (GetVehicleKit() ? GetVehicleKit()->GetDebugInfo() : "No vehicle kit") << "\n"
<< "m_Controlled size: " << m_Controlled.size();
size_t controlledCount = 0;
for (Unit* controlled : m_Controlled)
{
++controlledCount;
sstr << "\n" << "m_Controlled " << controlledCount << " : " << controlled->GetGUID().ToString();
}
return sstr.str();
}
DeclinedName::DeclinedName(UF::DeclinedNames const& uf)
{
for (std::size_t i = 0; i < MAX_DECLINED_NAME_CASES; ++i)
name[i] = uf.Name[i];
}