/* * 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]; }