/* * This file is part of the AzerothCore 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 "AreaDefines.h" #include "ArenaSpectator.h" #include "Battlefield.h" #include "BattlefieldMgr.h" #include "Battleground.h" #include "CellImpl.h" #include "CharacterCache.h" #include "CharmInfo.h" #include "Chat.h" #include "ChatPackets.h" #include "ChatTextBuilder.h" #include "Common.h" #include "ConditionMgr.h" #include "Creature.h" #include "CreatureAIImpl.h" #include "CreatureGroups.h" #include "DisableMgr.h" #include "DynamicVisibility.h" #include "Errors.h" #include "GameObjectAI.h" #include "GameTime.h" #include "GridNotifiersImpl.h" #include "Group.h" #include "Log.h" #include "MapMgr.h" #include "MoveSpline.h" #include "MoveSplineInit.h" #include "MovementGenerator.h" #include "ObjectAccessor.h" #include "ObjectMgr.h" #include "OutdoorPvP.h" #include "PassiveAI.h" #include "Pet.h" #include "PetAI.h" #include "PetPackets.h" #include "Player.h" #include "ReputationMgr.h" #include "ScriptMgr.h" #include "SharedDefines.h" #include "Spell.h" #include "SpellAuraDefines.h" #include "SpellAuraEffects.h" #include "SpellAuras.h" #include "SpellInfo.h" #include "SpellMgr.h" #include "TemporarySummon.h" #include "Totem.h" #include "TotemAI.h" #include "Transport.h" #include "UpdateFieldFlags.h" #include "UpdateFields.h" #include "Util.h" #include "Vehicle.h" #include "World.h" #include "WorldPacket.h" #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 }; // Used for prepare can/can`t triggr aura static bool InitTriggerAuraData(); // Define can trigger auras static bool isTriggerAura[TOTAL_AURAS]; // Define can't trigger auras (need for disable second trigger) static bool isNonTriggerAura[TOTAL_AURAS]; // Triggered always, even from triggered spells static bool isAlwaysTriggeredAura[TOTAL_AURAS]; // Prepare lists static bool procPrepared = InitTriggerAuraData(); DamageInfo::DamageInfo(Unit* _attacker, Unit* _victim, uint32 _damage, SpellInfo const* _spellInfo, SpellSchoolMask _schoolMask, DamageEffectType _damageType, uint32 cleanDamage) : m_attacker(_attacker), m_victim(_victim), m_damage(_damage), m_spellInfo(_spellInfo), m_schoolMask(_schoolMask), m_damageType(_damageType), m_attackType(BASE_ATTACK), m_cleanDamage(cleanDamage) { m_absorb = 0; m_resist = 0; m_block = 0; } DamageInfo::DamageInfo(CalcDamageInfo const& dmgInfo) : DamageInfo(DamageInfo(dmgInfo, 0), DamageInfo(dmgInfo, 1)) { } DamageInfo::DamageInfo(DamageInfo const& dmg1, DamageInfo const& dmg2) : m_attacker(dmg1.m_attacker), m_victim(dmg1.m_victim), m_damage(dmg1.m_damage + dmg2.m_damage), m_spellInfo(dmg1.m_spellInfo), m_schoolMask(SpellSchoolMask(dmg1.m_schoolMask | dmg2.m_schoolMask)), m_damageType(dmg1.m_damageType), m_attackType(dmg1.m_attackType), m_absorb(dmg1.m_absorb + dmg2.m_absorb), m_resist(dmg1.m_resist + dmg2.m_resist), m_block(dmg1.m_block), m_cleanDamage(dmg1.m_cleanDamage + dmg1.m_cleanDamage) { } DamageInfo::DamageInfo(CalcDamageInfo const& dmgInfo, uint8 damageIndex) : m_attacker(dmgInfo.attacker), m_victim(dmgInfo.target), m_damage(dmgInfo.damages[damageIndex].damage), m_spellInfo(nullptr), m_schoolMask(SpellSchoolMask(dmgInfo.damages[damageIndex].damageSchoolMask)), m_damageType(DIRECT_DAMAGE), m_attackType(dmgInfo.attackType), m_absorb(dmgInfo.damages[damageIndex].absorb), m_resist(dmgInfo.damages[damageIndex].resist), m_block(dmgInfo.blocked_amount), m_cleanDamage(dmgInfo.cleanDamage) { } DamageInfo::DamageInfo(SpellNonMeleeDamage const& spellNonMeleeDamage, DamageEffectType damageType) : m_attacker(spellNonMeleeDamage.attacker), m_victim(spellNonMeleeDamage.target), m_damage(spellNonMeleeDamage.damage), m_spellInfo(spellNonMeleeDamage.spellInfo), m_schoolMask(SpellSchoolMask(spellNonMeleeDamage.schoolMask)), m_damageType(damageType), m_absorb(spellNonMeleeDamage.absorb), m_resist(spellNonMeleeDamage.resist), m_block(spellNonMeleeDamage.blocked), m_cleanDamage(spellNonMeleeDamage.cleanDamage) { } void DamageInfo::ModifyDamage(int32 amount) { amount = std::min(amount, int32(GetDamage())); m_damage += amount; } void DamageInfo::AbsorbDamage(uint32 amount) { amount = std::min(amount, GetDamage()); m_absorb += amount; m_damage -= amount; } void DamageInfo::ResistDamage(uint32 amount) { amount = std::min(amount, GetDamage()); m_resist += amount; m_damage -= amount; } void DamageInfo::BlockDamage(uint32 amount) { amount = std::min(amount, GetDamage()); m_block += amount; m_damage -= amount; } uint32 DamageInfo::GetUnmitigatedDamage() const { return m_damage + m_cleanDamage + m_absorb + m_resist; } ProcEventInfo::ProcEventInfo(Unit* actor, Unit* actionTarget, Unit* procTarget, uint32 typeMask, uint32 spellTypeMask, uint32 spellPhaseMask, uint32 hitMask, Spell const* spell, DamageInfo* damageInfo, HealInfo* healInfo, SpellInfo const* triggeredByAuraSpell, int8 procAuraEffectIndex) : _actor(actor), _actionTarget(actionTarget), _procTarget(procTarget), _typeMask(typeMask), _spellTypeMask(spellTypeMask), _spellPhaseMask(spellPhaseMask), _hitMask(hitMask), _spell(spell), _damageInfo(damageInfo), _healInfo(healInfo), _triggeredByAuraSpell(triggeredByAuraSpell), _procAuraEffectIndex(procAuraEffectIndex) { _chance.reset(); } SpellInfo const* ProcEventInfo::GetSpellInfo() const { if (_spell) return _spell->GetSpellInfo(); if (_damageInfo) return _damageInfo->GetSpellInfo(); if (_healInfo) return _healInfo->GetSpellInfo(); return nullptr; } // we can disable this warning for this since it only // causes undefined behavior when passed to the base class constructor #ifdef _MSC_VER #pragma warning(disable:4355) #endif Unit::Unit() : WorldObject(), m_movedByPlayer(nullptr), m_lastSanctuaryTime(0), IsAIEnabled(false), NeedChangeAI(false), m_ControlledByPlayer(false), m_CreatedByPlayer(false), movespline(new Movement::MoveSpline()), i_AI(nullptr), i_disabledAI(nullptr), m_realRace(0), m_race(0), m_AutoRepeatFirstCast(false), m_procDeep(0), m_removedAurasCount(0), i_motionMaster(new MotionMaster(this)), m_regenTimer(0), m_ThreatMgr(this), m_vehicle(nullptr), m_vehicleKit(nullptr), m_unitTypeMask(UNIT_MASK_NONE), m_HostileRefMgr(this), m_comboTarget(nullptr), m_comboPoints(0) { #ifdef _MSC_VER #pragma warning(default:4355) #endif m_objectType |= TYPEMASK_UNIT; m_objectTypeId = TYPEID_UNIT; m_updateFlag = (UPDATEFLAG_LIVING | UPDATEFLAG_STATIONARY_POSITION); m_attackTimer[BASE_ATTACK] = 0; m_attackTimer[OFF_ATTACK] = 0; m_attackTimer[RANGED_ATTACK] = 0; m_modAttackSpeedPct[BASE_ATTACK] = 1.0f; m_modAttackSpeedPct[OFF_ATTACK] = 1.0f; m_modAttackSpeedPct[RANGED_ATTACK] = 1.0f; _dualWieldMode = DualWieldMode::AUTO; m_state = 0; m_deathState = DeathState::Alive; for (uint8 i = 0; i < CURRENT_MAX_SPELL; ++i) m_currentSpells[i] = nullptr; for (uint8 i = 0; i < MAX_SUMMON_SLOT; ++i) m_SummonSlot[i].Clear(); for (uint8 i = 0; i < MAX_GAMEOBJECT_SLOT; ++i) m_ObjectSlot[i].Clear(); m_auraUpdateIterator = m_ownedAuras.end(); m_interruptMask = 0; m_transform = 0; m_canModifyStats = false; for (uint8 i = 0; i < MAX_SPELL_IMMUNITY; ++i) m_spellImmune[i].clear(); for (uint8 i = 0; i < UNIT_MOD_END; ++i) { m_auraFlatModifiersGroup[i][BASE_VALUE] = 0.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][0] = BASE_MINDAMAGE; m_weaponDamage[i][MAXDAMAGE][0] = BASE_MAXDAMAGE; m_weaponDamage[i][MINDAMAGE][1] = 0.f; m_weaponDamage[i][MAXDAMAGE][1] = 0.f; } for (uint8 i = 0; i < MAX_STATS; ++i) m_createStats[i] = 0.0f; m_attacking = nullptr; m_modMeleeHitChance = 0.0f; m_modRangedHitChance = 0.0f; m_modSpellHitChance = 0.0f; m_baseSpellCritChance = 5; m_CombatTimer = 0; m_lastManaUse = 0; for (uint8 i = 0; i < MAX_SPELL_SCHOOL; ++i) m_threatModifier[i] = 1.0f; for (uint8 i = 0; i < MAX_MOVE_TYPE; ++i) m_speed_rate[i] = 1.0f; m_charmInfo = nullptr; _redirectThreatInfo = RedirectThreatInfo(); // remove aurastates allowing special moves for (uint8 i = 0; i < MAX_REACTIVE; ++i) m_reactiveTimer[i] = 0; m_cleanupDone = false; m_duringRemoveFromWorld = false; m_serverSideVisibility.SetValue(SERVERSIDE_VISIBILITY_GHOST, GHOST_VISIBILITY_ALIVE); m_last_notify_position.Relocate(-5000.0f, -5000.0f, -5000.0f, 0.0f); m_last_notify_mstime = 0; m_delayed_unit_relocation_timer = 0; m_delayed_unit_ai_notify_timer = 0; bRequestForcedVisibilityUpdate = false; m_applyResilience = false; _instantCast = false; _lastLiquid = nullptr; _oldFactionId = 0; _isWalkingBeforeCharm = false; _lastExtraAttackSpell = 0; } //////////////////////////////////////////////////////////// // Methods of class Unit Unit::~Unit() { // set current spells as deletable for (uint8 i = 0; i < CURRENT_MAX_SPELL; ++i) if (m_currentSpells[i]) { m_currentSpells[i]->SetReferencedFromCurrent(false); m_currentSpells[i] = nullptr; } _DeleteRemovedAuras(); delete i_motionMaster; delete m_charmInfo; delete movespline; ASSERT(!m_duringRemoveFromWorld); ASSERT(!m_attacking); ASSERT(m_attackers.empty()); // pussywizard: clear m_sharedVision along with back references if (!m_sharedVision.empty()) { do { Player* p = *(m_sharedVision.begin()); p->m_isInSharedVisionOf.erase(this); m_sharedVision.remove(p); } while (!m_sharedVision.empty()); } ASSERT(m_Controlled.empty()); ASSERT(m_appliedAuras.empty()); ASSERT(m_ownedAuras.empty()); ASSERT(m_removedAuras.empty()); ASSERT(m_gameObj.empty()); ASSERT(m_dynObj.empty()); if (m_movedByPlayer && m_movedByPlayer != this) LOG_INFO("misc", "Unit::~Unit (A1)"); HandleSafeUnitPointersOnDelete(this); } void Unit::Update(uint32 p_time) { sScriptMgr->OnUnitUpdate(this, 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; // pussywizard: if (!IsPlayer() || (!ToPlayer()->IsBeingTeleported() && !bRequestForcedVisibilityUpdate)) { if (m_delayed_unit_relocation_timer) { if (m_delayed_unit_relocation_timer <= p_time) { m_delayed_unit_relocation_timer = 0; //ExecuteDelayedUnitRelocationEvent(); FindMap()->i_objectsForDelayedVisibility.insert(this); } else m_delayed_unit_relocation_timer -= p_time; } if (m_delayed_unit_ai_notify_timer) { if (m_delayed_unit_ai_notify_timer <= p_time) { m_delayed_unit_ai_notify_timer = 0; ExecuteDelayedUnitAINotifyEvent(); } else m_delayed_unit_ai_notify_timer -= p_time; } } _UpdateSpells( p_time ); if (CanHaveThreatList() && GetThreatMgr().isNeedUpdateToClient(p_time)) SendThreatListUpdate(); // update combat timer only for players and pets (only pets with PetAI) if (IsInCombat() && (IsPlayer() || ((IsPet() || HasUnitTypeMask(UNIT_MASK_CONTROLLABLE_GUARDIAN)) && IsControlledByPlayer()))) { // Check UNIT_STATE_MELEE_ATTACKING or UNIT_STATE_CHASE (without UNIT_STATE_FOLLOW in this case) so pets can reach far away // targets without stopping half way there and running off. // These flags are reset after target dies or another command is given. if (m_HostileRefMgr.IsEmpty()) { // m_CombatTimer set at aura start and it will be freeze until aura removing if (m_CombatTimer <= p_time) ClearInCombat(); else m_CombatTimer -= 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)) { if (_lastExtraAttackSpell == SPELL_SWORD_SPECIALIZATION || _lastExtraAttackSpell == SPELL_HACK_AND_SLASH || victim->IsWithinMeleeRange(this)) { HandleProcExtraAttackFor(victim, count); } } } _lastExtraAttackSpell = 0; } // not implemented before 3.0.2 // xinef: if attack time > 0, reduce by diff // if on next update, attack time < 0 assume player didnt attack - set to 0 bool suspendAttackTimer = false; bool suspendRangedAttackTimer = false; if (IsPlayer() && HasUnitState(UNIT_STATE_CASTING)) { for (Spell* spell : m_currentSpells) { if (spell) { if (spell->GetSpellInfo()->HasAttribute(SPELL_ATTR2_DO_NOT_RESET_COMBAT_TIMERS)) { if (spell->IsChannelActive()) { suspendRangedAttackTimer = true; } suspendAttackTimer = true; break; } } } } if (!suspendAttackTimer) { if (int32 base_attack = getAttackTimer(BASE_ATTACK)) { setAttackTimer(BASE_ATTACK, base_attack > 0 ? base_attack - (int32) p_time : 0); } if (int32 off_attack = getAttackTimer(OFF_ATTACK)) { setAttackTimer(OFF_ATTACK, off_attack > 0 ? off_attack - (int32) p_time : 0); } } if (!suspendRangedAttackTimer) { if (int32 ranged_attack = getAttackTimer(RANGED_ATTACK)) { setAttackTimer(RANGED_ATTACK, ranged_attack > 0 ? ranged_attack - (int32)p_time : 0); } } // update abilities available only for fraction of time UpdateReactives(p_time); ModifyAuraState(AURA_STATE_HEALTHLESS_20_PERCENT, IsAlive() ? HealthBelowPct(20) : false); ModifyAuraState(AURA_STATE_HEALTHLESS_35_PERCENT, IsAlive() ? HealthBelowPct(35) : false); ModifyAuraState(AURA_STATE_HEALTH_ABOVE_75_PERCENT, IsAlive() ? HealthAbovePct(75) : false); UpdateSplineMovement(p_time); GetMotionMaster()->UpdateMotion(p_time); InvalidateValuesUpdateCache(); } bool Unit::haveOffhandWeapon() const { if (Player const* player = ToPlayer()) return player->GetWeaponForAttack(OFF_ATTACK, true); return CanDualWield(); } void Unit::MonsterMoveWithSpeed(float x, float y, float z, float speed) { Movement::MoveSplineInit init(this); init.MoveTo(x, y, z); init.SetVelocity(speed); init.Launch(); } void Unit::SendMonsterMove(float NewPosX, float NewPosY, float NewPosZ, uint32 TransitTime, SplineFlags sf) { WorldPacket data(SMSG_MONSTER_MOVE, 1 + 12 + 4 + 1 + 4 + 4 + 4 + 12 + GetPackGUID().size()); data << GetPackGUID(); data << uint8(0); // new in 3.1 data << GetPositionX() << GetPositionY() << GetPositionZ(); data << GameTime::GetGameTimeMS().count(); data << uint8(0); data << uint32(sf); data << TransitTime; // Time in between points data << uint32(1); // 1 single waypoint data << NewPosX << NewPosY << NewPosZ; // the single waypoint Point B SendMessageToSet(&data, true); } class SplineHandler { public: SplineHandler(Unit* unit) : _unit(unit) { } bool operator()(Movement::MoveSpline::UpdateResult result) { if ((result & (Movement::MoveSpline::Result_NextSegment | Movement::MoveSpline::Result_JustArrived)) && _unit->IsCreature() && _unit->GetMotionMaster()->GetCurrentMovementGeneratorType() == ESCORT_MOTION_TYPE && _unit->movespline->GetId() == _unit->GetMotionMaster()->GetCurrentSplineId()) { _unit->ToCreature()->AI()->MovementInform(ESCORT_MOTION_TYPE, _unit->movespline->currentPathIdx() - 1); } return true; } private: Unit* _unit; }; void Unit::UpdateSplineMovement(uint32 t_diff) { if (movespline->Finalized()) return; // xinef: process movementinform // this code cant be placed inside EscortMovementGenerator, because we cant delete active MoveGen while it is updated SplineHandler handler(this); movespline->updateState(t_diff, handler); // Xinef: Spline was cleared by StopMoving, return if (!movespline->Initialized()) { DisableSpline(); return; } bool arrived = movespline->Finalized(); if (arrived) { DisableSpline(); if (movespline->HasAnimation() && IsCreature() && IsAlive()) SetAnimTier(AnimTier(movespline->GetAnimationType())); } // pussywizard: update always! not every 400ms, because movement generators need the actual position //m_movesplineTimer.Update(t_diff); //if (m_movesplineTimer.Passed() || arrived) UpdateSplinePosition(); } void Unit::UpdateSplinePosition() { //static uint32 const positionUpdateDelay = 400; //m_movesplineTimer.Reset(positionUpdateDelay); 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); } // Xinef: if we had spline running update orientation along with position //if (HasUnitState(UNIT_STATE_CANNOT_TURN)) // loc.orientation = GetOrientation(); if (IsPlayer() || IsPet()) UpdatePosition(loc.x, loc.y, loc.z, loc.orientation); else ToCreature()->SetPosition(loc.x, loc.y, loc.z, loc.orientation); } void Unit::DisableSpline() { m_movementInfo.RemoveMovementFlag(MovementFlags(MOVEMENTFLAG_SPLINE_ENABLED | MOVEMENTFLAG_FORWARD | MOVEMENTFLAG_BACKWARD)); movespline->_Interrupt(); } void Unit::resetAttackTimer(WeaponAttackType type) { int32 time = int32(GetAttackTime(type) * m_modAttackSpeedPct[type]); m_attackTimer[type] = std::min(m_attackTimer[type] + time, time); } 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::IsWithinMeleeRange(Unit const* obj, float dist) 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 maxdist = dist + GetMeleeRange(obj); if ((IsPlayer() || obj->IsPlayer()) && HasLeewayMovement() && obj->HasLeewayMovement()) maxdist += LEEWAY_BONUS_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::IsWithinRange(Unit const* obj, float dist) const { if (!obj || !IsInMap(obj) || !InSamePhase(obj)) { return false; } auto dx = GetPositionX() - obj->GetPositionX(); auto dy = GetPositionY() - obj->GetPositionY(); auto dz = GetPositionZ() - obj->GetPositionZ(); auto distsq = dx * dx + dy * dy + dz * dz; return distsq <= dist * dist; } bool Unit::IsWithinBoundaryRadius(const Unit* obj) const { if (!obj || !IsInMap(obj) || !InSamePhase(obj)) return false; float objBoundaryRadius = std::max(obj->GetBoundaryRadius(), MIN_MELEE_REACH); return IsInDist(obj, objBoundaryRadius); } bool Unit::GetRandomContactPoint(Unit const* obj, float& x, float& y, float& z, bool force) const { float combat_reach = GetCombatReach(); if (combat_reach < 0.1f) // sometimes bugged for players combat_reach = DEFAULT_COMBAT_REACH; uint32 attacker_number = getAttackers().size(); if (attacker_number > 0) --attacker_number; Creature const* c = obj->ToCreature(); if (c) if (c->isWorldBoss() || c->IsDungeonBoss() || (obj->IsPet() && const_cast(obj)->ToPet()->isControlled())) attacker_number = 0; // pussywizard: pets and bosses just come to target from their angle GetNearPoint(obj, x, y, z, isMoving() ? (obj->GetCombatReach() > 7.75f ? obj->GetCombatReach() - 7.5f : 0.25f) : obj->GetCombatReach(), 0.0f, GetAngle(obj) + (attacker_number ? (static_cast(M_PI / 2) - static_cast(M_PI) * (float)rand_norm()) * float(attacker_number) / combat_reach * 0.3f : 0)); // pussywizard if (std::fabs(this->GetPositionZ() - z) > this->GetCollisionHeight() || !IsWithinLOS(x, y, z)) { x = this->GetPositionX(); y = this->GetPositionY(); z = this->GetPositionZ(); obj->UpdateAllowedPositionZ(x, y, z); } float maxDist = GetMeleeRange(obj); if (GetExactDistSq(x, y, z) >= maxDist * maxDist) { if (force) { x = this->GetPositionX(); y = this->GetPositionY(); z = this->GetPositionZ(); return true; } return false; } return true; } Unit* Unit::getAttackerForHelper() const { if (GetVictim() != nullptr) return GetVictim(); if (!IsEngaged()) return nullptr; if (!m_attackers.empty()) return *(m_attackers.begin()); return nullptr; } void Unit::UpdateInterruptMask() { m_interruptMask = 0; for (AuraApplicationList::const_iterator i = m_interruptableAuras.begin(); i != m_interruptableAuras.end(); ++i) m_interruptMask |= (*i)->GetBase()->GetSpellInfo()->AuraInterruptFlags; if (Spell* spell = m_currentSpells[CURRENT_CHANNELED_SPELL]) if (spell->getState() == SPELL_STATE_CASTING) m_interruptMask |= spell->m_spellInfo->ChannelInterruptFlags; } bool Unit::HasAuraTypeWithFamilyFlags(AuraType auraType, uint32 familyName, uint32 familyFlags) const { if (!HasAuraType(auraType)) return false; AuraEffectList const& auras = GetAuraEffectsByType(auraType); for (AuraEffectList::const_iterator itr = auras.begin(); itr != auras.end(); ++itr) if (SpellInfo const* iterSpellProto = (*itr)->GetSpellInfo()) if (iterSpellProto->SpellFamilyName == familyName && iterSpellProto->SpellFamilyFlags[0] & 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()->AuraInterruptFlags & AURA_INTERRUPT_FLAG_TAKE_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_TRANSFORM, excludeAura)); } void Unit::DealDamageMods(Unit const* victim, uint32& damage, uint32* absorb) { if (!victim || !victim->IsAlive() || victim->IsInFlight() || (victim->IsCreature() && victim->ToCreature()->IsEvadingAttacks())) { if (absorb) *absorb += damage; damage = 0; } } uint32 Unit::DealDamage(Unit* attacker, Unit* victim, uint32 damage, CleanDamage const* cleanDamage, DamageEffectType damagetype, SpellSchoolMask damageSchoolMask, SpellInfo const* spellProto, bool durabilityLoss, bool /*allowGM*/, Spell const* damageSpell /*= nullptr*/) { damage = sScriptMgr->DealDamage(attacker, victim, damage, damagetype); // Xinef: initialize damage done for rage calculations // Xinef: its rare to modify damage in hooks, however training dummy's sets damage to 0 uint32 rage_damage = damage + ((cleanDamage != nullptr) ? cleanDamage->absorbed_damage : 0); //if (attacker) { if (victim->IsAIEnabled) victim->GetAI()->DamageTaken(attacker, damage, damagetype, damageSchoolMask); if (attacker && attacker->IsAIEnabled) attacker->GetAI()->DamageDealt(victim, damage, damagetype, damageSchoolMask); } // Hook for OnDamage Event sScriptMgr->OnDamage(attacker, victim, damage); if (victim->IsPlayer() && attacker != victim) { // Signal to pets that their owner was attacked Pet* pet = victim->ToPlayer()->GetPet(); if (pet && pet->IsAlive()) pet->AI()->OwnerAttackedBy(attacker); } //Dont deal damage to unit if .cheat god is enable. if (victim->IsPlayer()) { if (victim->ToPlayer()->GetCommandStatus(CHEAT_GOD)) { return 0; } } // Signal the pet it was attacked so the AI can respond if needed if (victim->IsCreature() && attacker != victim && victim->IsPet() && victim->IsAlive()) victim->ToPet()->AI()->AttackedBy(attacker); if (damagetype != NODAMAGE) { // interrupting auras with AURA_INTERRUPT_FLAG_DAMAGE before checking !damage (absorbed damage breaks that type of auras) if (spellProto) { if (attacker && damagetype != DOT && spellProto->DmgClass == SPELL_DAMAGE_CLASS_MELEE && !(spellProto->GetSchoolMask() & SPELL_SCHOOL_MASK_HOLY)) attacker->DealDamageShieldDamage(victim); if (!spellProto->HasAttribute(SPELL_ATTR4_REACTIVE_DAMAGE_PROC)) victim->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_TAKE_DAMAGE, spellProto->Id); } else victim->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_TAKE_DAMAGE, 0); // interrupt spells with SPELL_INTERRUPT_FLAG_ABORT_ON_DMG on absorbed damage (no dots) if (!damage && damagetype != DOT && cleanDamage && cleanDamage->absorbed_damage) { if (victim != attacker && victim->IsPlayer()) { if (Spell* spell = victim->m_currentSpells[CURRENT_GENERIC_SPELL]) { if (spell->getState() == SPELL_STATE_PREPARING) { uint32 interruptFlags = spell->m_spellInfo->InterruptFlags; if (interruptFlags & SPELL_INTERRUPT_FLAG_ABORT_ON_DMG) { 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 AuraEffectList vCopyDamageCopy(victim->GetAuraEffectsByType(SPELL_AURA_SHARE_DAMAGE_PCT)); // copy damage to casters of this aura for (AuraEffectList::iterator 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 shareDamage = CalculatePct(damage, (*i)->GetAmount()); uint32 shareAbsorb = 0; uint32 shareResist = 0; if (shareDamageTarget->IsImmunedToDamageOrSchool(damageSchoolMask)) { shareAbsorb = shareDamage; shareDamage = 0; } else { DamageInfo sharedDamageInfo(attacker, shareDamageTarget, shareDamage, spellProto, damageSchoolMask, damagetype); Unit::CalcAbsorbResist(sharedDamageInfo, true); shareAbsorb = sharedDamageInfo.GetAbsorb(); shareResist = sharedDamageInfo.GetResist(); shareDamage = sharedDamageInfo.GetDamage(); Unit::DealDamageMods(shareDamageTarget, shareDamage, &shareAbsorb); } if (attacker && shareDamageTarget->IsPlayer()) { attacker->SendSpellNonMeleeDamageLog(shareDamageTarget, spell, shareDamage, damageSchoolMask, shareAbsorb, shareResist, damagetype == DIRECT_DAMAGE, 0, false, true); } Unit::DealDamage(attacker, shareDamageTarget, shareDamage, cleanDamage, NODAMAGE, damageSchoolMask, spellProto, false, false, damageSpell); } } // Rage from Damage made (only from direct weapon damage) if (attacker && cleanDamage && damagetype == DIRECT_DAMAGE && attacker != victim && attacker->HasActivePowerType(POWER_RAGE)) { uint32 weaponSpeedHitFactor; switch (cleanDamage->attackType) { case BASE_ATTACK: case OFF_ATTACK: { weaponSpeedHitFactor = uint32(attacker->GetAttackTime(cleanDamage->attackType) / 1000.0f * (cleanDamage->attackType == BASE_ATTACK ? 3.5f : 1.75f)); if (cleanDamage->hitOutCome == MELEE_HIT_CRIT) weaponSpeedHitFactor *= 2; attacker->RewardRage(rage_damage, weaponSpeedHitFactor, true); break; } case RANGED_ATTACK: break; default: break; } } if (!damage) { // Rage from absorbed damage if (cleanDamage && cleanDamage->absorbed_damage) { if (victim->HasActivePowerType(POWER_RAGE)) victim->RewardRage(cleanDamage->absorbed_damage, 0, false); if (attacker && attacker->HasActivePowerType(POWER_RAGE)) attacker->RewardRage(cleanDamage->absorbed_damage, 0, true); } return 0; } LOG_DEBUG("entities.unit", "DealDamageStart"); uint32 health = victim->GetHealth(); LOG_DEBUG("entities.unit", "deal dmg:{} to health:{} ", damage, health); // duel ends when player has 1 or less hp bool duel_hasEnded = false; bool duel_wasMounted = false; if (victim->IsPlayer() && victim->ToPlayer()->duel && damage >= (health - 1)) { // xinef: situation not possible earlier, just return silently. 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 || victim->ToPlayer()->duel->Opponent->GetGUID() == attacker->GetOwnerGUID()) damage = health - 1; duel_hasEnded = true; } else if (victim->IsVehicle() && damage >= (health - 1) && victim->GetCharmer() && victim->GetCharmer()->IsPlayer()) { Player* victimRider = victim->GetCharmer()->ToPlayer(); if (victimRider && victimRider->duel && victimRider->duel->IsMounted) { // xinef: situation not possible earlier, just return silently. if (!attacker) return 0; // prevent kill only if killed in duel and killed by opponent or opponent controlled creature if (victimRider->duel->Opponent == attacker || victimRider->duel->Opponent->GetGUID() == attacker->GetCharmerGUID()) damage = health - 1; duel_wasMounted = true; duel_hasEnded = true; } } if (attacker && attacker != victim) if (Player* killer = attacker->GetCharmerOrOwnerPlayerOrPlayerItself()) { // pussywizard: don't allow GMs to deal damage in normal way (this leaves no evidence in logs!), they have commands to do so //if (!allowGM && killer->GetSession()->GetSecurity() && killer->GetSession()->GetSecurity() <= SEC_ADMINISTRATOR) // return 0; if (Battleground* bg = killer->GetBattleground()) { bg->UpdatePlayerScore(killer, SCORE_DAMAGE_DONE, damage); killer->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_DAMAGE_DONE, damage, 0, victim); // pussywizard: InBattleground() optimization } //killer->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HIGHEST_HIT_DEALT, damage); // pussywizard: optimization } if (victim->IsPlayer()) ;//victim->ToPlayer()->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HIGHEST_HIT_RECEIVED, damage); // pussywizard: optimization else if (!victim->IsControlledByPlayer() || victim->IsVehicle()) { if (!victim->ToCreature()->hasLootRecipient()) victim->ToCreature()->SetLootRecipient(attacker); if (!attacker || attacker->IsControlledByPlayer() || attacker->IsCreatedByPlayer()) { uint32 unDamage = health < damage ? health : damage; bool damagedByPlayer = unDamage && attacker && (attacker->IsPlayer() || attacker->m_movedByPlayer != nullptr); victim->ToCreature()->LowerPlayerDamageReq(unDamage, damagedByPlayer); } } // Sparring if (victim->CanSparringWith(attacker)) { if (damage >= victim->GetHealth()) damage = 0; uint32 sparringHealth = victim->GetHealth() * (victim->ToCreature()->GetSparringPct() / 100); if (victim->GetHealth() - damage <= sparringHealth) damage = 0; } if (health <= damage) { LOG_DEBUG("entities.unit", "DealDamage: victim just died"); //if (attacker && victim->IsPlayer() && victim != attacker) //victim->ToPlayer()->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_TOTAL_DAMAGE_RECEIVED, health); // pussywizard: optimization Unit::Kill(attacker, victim, durabilityLoss, cleanDamage ? cleanDamage->attackType : BASE_ATTACK, spellProto, damageSpell); } else { LOG_DEBUG("entities.unit", "DealDamageAlive"); //if (victim->IsPlayer()) // victim->ToPlayer()->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_TOTAL_DAMAGE_RECEIVED, damage); // pussywizard: optimization victim->ModifyHealth(- (int32)damage); if (damagetype == DIRECT_DAMAGE || damagetype == SPELL_DIRECT_DAMAGE) victim->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_DIRECT_DAMAGE, spellProto ? spellProto->Id : 0); if (!victim->IsPlayer()) { /// @fix: Hack to avoid premature leashing if (damagetype != DOT && damage > 0 && !victim->GetOwnerGUID().IsPlayer() && (!spellProto || !spellProto->HasAura(SPELL_AURA_DAMAGE_SHIELD))) victim->ToCreature()->UpdateLeashExtensionTime(); if (attacker && attacker != victim) { if (spellProto && victim->CanHaveThreatList() && !victim->HasUnitState(UNIT_STATE_EVADE) && !victim->IsInCombatWith(attacker)) victim->CombatStart(attacker, !(spellProto->AttributesEx3 & SPELL_ATTR3_SUPPRESS_TARGET_PROCS)); victim->AddThreat(attacker, float(damage), damageSchoolMask, spellProto); } } else // victim is a player { // random durability for items (HIT TAKEN) if (roll_chance_f(sWorld->getRate(RATE_DURABILITY_LOSS_DAMAGE))) { EquipmentSlots slot = EquipmentSlots(urand(0, EQUIPMENT_SLOT_END - 1)); victim->ToPlayer()->DurabilityPointLossForEquipSlot(slot); } } // Rage from damage received if (attacker != victim && victim->HasActivePowerType(POWER_RAGE)) { uint32 rageDamage = damage + (cleanDamage ? cleanDamage->absorbed_damage : 0); victim->RewardRage(rageDamage, 0, false); } if (attacker && attacker->IsPlayer()) { // random durability for items (HIT DONE) if (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 && damage && (!spellProto || !(spellProto->HasAttribute(SPELL_ATTR3_TREAT_AS_PERIODIC) || spellProto->HasAttribute(SPELL_ATTR7_DONT_CAUSE_SPELL_PUSHBACK)))) { if (victim != attacker && victim->IsPlayer()) // does not support creature push_back { if (damagetype != DOT && !(damageSpell && damageSpell->m_targets.HasDstChannel())) { if (Spell* spell = victim->m_currentSpells[CURRENT_GENERIC_SPELL]) { if (spell->getState() == SPELL_STATE_PREPARING) { uint32 interruptFlags = spell->m_spellInfo->InterruptFlags; if (interruptFlags & SPELL_INTERRUPT_FLAG_ABORT_ON_DMG) { victim->InterruptNonMeleeSpells(false); } else if (interruptFlags & SPELL_INTERRUPT_FLAG_PUSH_BACK) { spell->Delayed(); } } } if (Spell* spell = victim->m_currentSpells[CURRENT_CHANNELED_SPELL]) if (spell->getState() == SPELL_STATE_CASTING) { if ((spell->m_spellInfo->ChannelInterruptFlags & CHANNEL_FLAG_DELAY) != 0) { 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); } } LOG_DEBUG("entities.unit", "DealDamageEnd returned {} damage", damage); return damage; } /** * @brief Interrupt the unit cast for all the current spells */ void Unit::CastStop(uint32 except_spellid, bool withInstant) { 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, withInstant); } SpellCastResult Unit::CastSpell(SpellCastTargets const& targets, SpellInfo const* spellInfo, CustomSpellValues const* value, TriggerCastFlags triggerFlags, Item* castItem, AuraEffect const* triggeredByAura, ObjectGuid originalCaster) { if (!spellInfo) { LOG_ERROR("entities.unit", "CastSpell: unknown spell by caster {}", GetGUID().ToString()); return SPELL_FAILED_SPELL_UNAVAILABLE; } /// @todo: this is a workaround - not needed anymore, but required for some scripts :( if (!originalCaster && triggeredByAura) { originalCaster = triggeredByAura->GetCasterGUID(); } Spell* spell = new Spell(this, spellInfo, triggerFlags, originalCaster); if (value) { for (CustomSpellValues::const_iterator itr = value->begin(); itr != value->end(); ++itr) { spell->SetSpellValue(itr->first, itr->second); } } spell->m_CastItem = castItem; return spell->prepare(&targets, triggeredByAura); } SpellCastResult Unit::CastSpell(Unit* victim, uint32 spellId, bool triggered, Item* castItem, AuraEffect const* triggeredByAura, ObjectGuid originalCaster) { return CastSpell(victim, spellId, triggered ? TRIGGERED_FULL_MASK : TRIGGERED_NONE, castItem, triggeredByAura, originalCaster); } SpellCastResult Unit::CastSpell(Unit* victim, uint32 spellId, TriggerCastFlags triggerFlags /*= TRIGGER_NONE*/, Item* castItem /*= nullptr*/, AuraEffect const* triggeredByAura /*= nullptr*/, ObjectGuid originalCaster /*= ObjectGuid::Empty*/) { SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); if (!spellInfo) { LOG_ERROR("entities.unit", "CastSpell: unknown spell {} by caster {}", spellId, GetGUID().ToString()); return SPELL_FAILED_SPELL_UNAVAILABLE; } return CastSpell(victim, spellInfo, triggerFlags, castItem, triggeredByAura, originalCaster); } SpellCastResult Unit::CastSpell(Unit* victim, SpellInfo const* spellInfo, bool triggered, Item* castItem/*= nullptr*/, AuraEffect const* triggeredByAura /*= nullptr*/, ObjectGuid originalCaster /*= ObjectGuid::Empty*/) { return CastSpell(victim, spellInfo, triggered ? TRIGGERED_FULL_MASK : TRIGGERED_NONE, castItem, triggeredByAura, originalCaster); } SpellCastResult Unit::CastSpell(Unit* victim, SpellInfo const* spellInfo, TriggerCastFlags triggerFlags, Item* castItem, AuraEffect const* triggeredByAura, ObjectGuid originalCaster) { SpellCastTargets targets; targets.SetUnitTarget(victim); return CastSpell(targets, spellInfo, nullptr, triggerFlags, castItem, triggeredByAura, originalCaster); } SpellCastResult Unit::CastCustomSpell(Unit* target, uint32 spellId, int32 const* bp0, int32 const* bp1, int32 const* bp2, bool triggered, Item* castItem, AuraEffect const* triggeredByAura, ObjectGuid originalCaster) { CustomSpellValues values; if (bp0) values.AddSpellMod(SPELLVALUE_BASE_POINT0, *bp0); if (bp1) values.AddSpellMod(SPELLVALUE_BASE_POINT1, *bp1); if (bp2) values.AddSpellMod(SPELLVALUE_BASE_POINT2, *bp2); return CastCustomSpell(spellId, values, target, triggered ? TRIGGERED_FULL_MASK : TRIGGERED_NONE, castItem, triggeredByAura, originalCaster); } SpellCastResult Unit::CastCustomSpell(uint32 spellId, SpellValueMod mod, int32 value, Unit* target, bool triggered, Item* castItem, AuraEffect const* triggeredByAura, ObjectGuid originalCaster) { CustomSpellValues values; values.AddSpellMod(mod, value); return CastCustomSpell(spellId, values, target, triggered ? TRIGGERED_FULL_MASK : TRIGGERED_NONE, castItem, triggeredByAura, originalCaster); } SpellCastResult Unit::CastCustomSpell(uint32 spellId, SpellValueMod mod, int32 value, Unit* target, TriggerCastFlags triggerFlags, Item* castItem, AuraEffect const* triggeredByAura, ObjectGuid originalCaster) { CustomSpellValues values; values.AddSpellMod(mod, value); return CastCustomSpell(spellId, values, target, triggerFlags, castItem, triggeredByAura, originalCaster); } SpellCastResult Unit::CastCustomSpell(uint32 spellId, CustomSpellValues const& value, Unit* victim, TriggerCastFlags triggerFlags, Item* castItem, AuraEffect const* triggeredByAura, ObjectGuid originalCaster) { SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); if (!spellInfo) { LOG_ERROR("entities.unit", "CastSpell: unknown spell {} by caster {}", spellId, GetGUID().ToString()); return SPELL_FAILED_SPELL_UNAVAILABLE; } return CastCustomSpell(spellInfo, value, victim, triggerFlags, castItem, triggeredByAura, originalCaster); } SpellCastResult Unit::CastCustomSpell(SpellInfo const* spellInfo, CustomSpellValues const& value, Unit* victim, TriggerCastFlags triggerFlags, Item* castItem, AuraEffect const* triggeredByAura, ObjectGuid originalCaster) { SpellCastTargets targets; targets.SetUnitTarget(victim); return CastSpell(targets, spellInfo, &value, triggerFlags, castItem, triggeredByAura, originalCaster); } SpellCastResult Unit::CastSpell(float x, float y, float z, uint32 spellId, bool triggered, Item* castItem, AuraEffect const* triggeredByAura, ObjectGuid originalCaster) { SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); if (!spellInfo) { LOG_ERROR("entities.unit", "CastSpell: unknown spell {} by caster {}", spellId, GetGUID().ToString()); return SPELL_FAILED_SPELL_UNAVAILABLE; } SpellCastTargets targets; targets.SetDst(x, y, z, GetOrientation()); return CastSpell(targets, spellInfo, nullptr, triggered ? TRIGGERED_FULL_MASK : TRIGGERED_NONE, castItem, triggeredByAura, originalCaster); } SpellCastResult Unit::CastSpell(GameObject* go, uint32 spellId, bool triggered, Item* castItem, AuraEffect* triggeredByAura, ObjectGuid originalCaster) { SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); if (!spellInfo) { LOG_ERROR("entities.unit", "CastSpell: unknown spell {} by caster {}", spellId, GetGUID().ToString()); return SPELL_FAILED_SPELL_UNAVAILABLE; } SpellCastTargets targets; targets.SetGOTarget(go); return CastSpell(targets, spellInfo, nullptr, triggered ? TRIGGERED_FULL_MASK : TRIGGERED_NONE, castItem, triggeredByAura, originalCaster); } void Unit::CalculateSpellDamageTaken(SpellNonMeleeDamage* damageInfo, int32 damage, SpellInfo const* spellInfo, WeaponAttackType attackType, bool crit) { if (damage < 0) return; Unit* victim = damageInfo->target; if (!victim || !victim->IsAlive()) return; SpellSchoolMask damageSchoolMask = SpellSchoolMask(damageInfo->schoolMask); uint32 crTypeMask = victim->GetCreatureTypeMask(); // Script Hook For CalculateSpellDamageTaken -- Allow scripts to change the Damage post class mitigation calculations sScriptMgr->ModifySpellDamageTaken(damageInfo->target, damageInfo->attacker, damage, spellInfo); if (victim->GetAI()) { victim->GetAI()->OnCalculateSpellDamageReceived(damage, this); } int32 cleanDamage = 0; if (Unit::IsDamageReducedByArmor(damageSchoolMask, spellInfo)) { int32 oldDamage = damage; damage = Unit::CalcArmorReducedDamage(this, victim, damage, spellInfo, 0, attackType); cleanDamage = oldDamage - damage; } bool blocked = false; // Per-school calc switch (spellInfo->DmgClass) { // Melee and Ranged Spells case SPELL_DAMAGE_CLASS_RANGED: case SPELL_DAMAGE_CLASS_MELEE: { // Physical Damage if (damageSchoolMask & SPELL_SCHOOL_MASK_NORMAL) { // Get blocked status blocked = isSpellBlocked(victim, spellInfo, attackType); } 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->Id, SPELLMOD_CRIT_DAMAGE_BONUS, crit_bonus); damage += crit_bonus; // Apply SPELL_AURA_MOD_ATTACKER_RANGED_CRIT_DAMAGE or SPELL_AURA_MOD_ATTACKER_MELEE_CRIT_DAMAGE float critPctDamageMod = 0.0f; if (attackType == RANGED_ATTACK) critPctDamageMod += victim->GetTotalAuraModifier(SPELL_AURA_MOD_ATTACKER_RANGED_CRIT_DAMAGE); else critPctDamageMod += victim->GetTotalAuraModifier(SPELL_AURA_MOD_ATTACKER_MELEE_CRIT_DAMAGE); // Increase crit damage from SPELL_AURA_MOD_CRIT_DAMAGE_BONUS critPctDamageMod += GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_CRIT_DAMAGE_BONUS, spellInfo->GetSchoolMask()); // Increase crit damage from SPELL_AURA_MOD_CRIT_PERCENT_VERSUS critPctDamageMod += GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_CRIT_PERCENT_VERSUS, crTypeMask); if (critPctDamageMod != 0) AddPct(damage, critPctDamageMod); } // Spell weapon based damage CAN BE crit & blocked at same time if (blocked) { damageInfo->blocked = victim->GetShieldBlockValue(); // double blocked amount if block is critical if (victim->isBlockCritical()) damageInfo->blocked *= 2; if (damage < int32(damageInfo->blocked)) damageInfo->blocked = uint32(damage); damage -= damageInfo->blocked; cleanDamage += damageInfo->blocked; } int32 resilienceReduction = damage; if (CanApplyResilience()) { if (attackType != RANGED_ATTACK) { Unit::ApplyResilience(victim, nullptr, &resilienceReduction, crit, CR_CRIT_TAKEN_MELEE); } else { Unit::ApplyResilience(victim, nullptr, &resilienceReduction, crit, CR_CRIT_TAKEN_RANGED); } } resilienceReduction = damage - resilienceReduction; damage -= resilienceReduction; cleanDamage += resilienceReduction; 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); } int32 resilienceReduction = damage; if (CanApplyResilience()) { Unit::ApplyResilience(victim, nullptr, &resilienceReduction, crit, CR_CRIT_TAKEN_SPELL); } resilienceReduction = damage - resilienceReduction; damage -= resilienceReduction; cleanDamage += resilienceReduction; break; } default: break; } damageInfo->cleanDamage = std::max(0, cleanDamage); damageInfo->damage = std::max(0, damage); // Calculate absorb resist if (damageInfo->damage > 0) { DamageInfo dmgInfo(*damageInfo, SPELL_DIRECT_DAMAGE); Unit::CalcAbsorbResist(dmgInfo); damageInfo->absorb = dmgInfo.GetAbsorb(); damageInfo->resist = dmgInfo.GetResist(); damageInfo->damage = dmgInfo.GetDamage(); } } void Unit::DealSpellDamage(SpellNonMeleeDamage* damageInfo, bool durabilityLoss, Spell const* spell /*= nullptr*/) { if (damageInfo == 0) return; Unit* victim = damageInfo->target; if (!victim) return; if (!victim->IsAlive() || victim->IsInFlight() || (victim->IsCreature() && victim->ToCreature()->IsEvadingAttacks())) return; SpellInfo const* spellProto = damageInfo->spellInfo; if (!spellProto) { LOG_DEBUG("entities.unit", "Unit::DealSpellDamage has wrong damageInfo"); 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), spellProto, durabilityLoss, false, spell); } // @todo for melee need create structure as in void Unit::CalculateMeleeDamage(Unit* victim, CalcDamageInfo* damageInfo, WeaponAttackType attackType, const bool sittingVictim) { damageInfo->attacker = this; damageInfo->target = victim; for (uint8 i = 0; i < MAX_ITEM_PROTO_DAMAGES; ++i) { damageInfo->damages[i].damageSchoolMask = GetMeleeDamageSchoolMask(attackType, i); damageInfo->damages[i].damage = 0; damageInfo->damages[i].absorb = 0; damageInfo->damages[i].resist = 0; } damageInfo->attackType = attackType; damageInfo->cleanDamage = 0; damageInfo->blocked_amount = 0; damageInfo->TargetState = 0; damageInfo->HitInfo = 0; damageInfo->procAttacker = PROC_FLAG_NONE; damageInfo->procVictim = PROC_FLAG_NONE; damageInfo->procEx = PROC_EX_NONE; 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_DONE_MELEE_AUTO_ATTACK | PROC_FLAG_DONE_MAINHAND_ATTACK; damageInfo->procVictim = PROC_FLAG_TAKEN_MELEE_AUTO_ATTACK; break; case OFF_ATTACK: damageInfo->procAttacker = PROC_FLAG_DONE_MELEE_AUTO_ATTACK | PROC_FLAG_DONE_OFFHAND_ATTACK; damageInfo->procVictim = PROC_FLAG_TAKEN_MELEE_AUTO_ATTACK; damageInfo->HitInfo = HITINFO_OFFHAND; break; default: return; } // School Immune check uint8 immunedMask = 0; bool hasNonPhysicalSchoolMask = false; for (uint8 i = 0; i < MAX_ITEM_PROTO_DAMAGES; ++i) { if (damageInfo->target->IsImmunedToDamageOrSchool(SpellSchoolMask(damageInfo->damages[i].damageSchoolMask))) { immunedMask |= (1 << i); if (damageInfo->damages[i].damageSchoolMask != SPELL_SCHOOL_MASK_NORMAL) { hasNonPhysicalSchoolMask = true; } } } // School Immune check if (immunedMask & ((1 << 0) | (1 << 1))) { if (hasNonPhysicalSchoolMask || immunedMask == ((1 << 0) | (1 << 1))) { damageInfo->HitInfo |= HITINFO_NORMALSWING; damageInfo->TargetState = VICTIMSTATE_IS_IMMUNE; damageInfo->procEx |= PROC_EX_IMMUNE; return; } } for (uint8 i = 0; i < MAX_ITEM_PROTO_DAMAGES; ++i) { // only players have secondary weapon damage if (i > 0 && !IsPlayer()) { break; } if (immunedMask & (1 << i)) { continue; } SpellSchoolMask schoolMask = SpellSchoolMask(damageInfo->damages[i].damageSchoolMask); bool const addPctMods = (schoolMask & SPELL_SCHOOL_MASK_NORMAL); uint32 damage = 0; uint8 itemDamagesMask = (IsPlayer()) ? (1 << i) : 0; damage += CalculateDamage(damageInfo->attackType, false, addPctMods, itemDamagesMask); // Add melee damage bonus damage = MeleeDamageBonusDone(damageInfo->target, damage, damageInfo->attackType, nullptr, schoolMask); damage = damageInfo->target->MeleeDamageBonusTaken(this, damage, damageInfo->attackType, nullptr, schoolMask); // Script Hook For CalculateMeleeDamage -- Allow scripts to change the Damage pre class mitigation calculations sScriptMgr->ModifyMeleeDamage(damageInfo->target, damageInfo->attacker, damage); if (victim->GetAI()) { victim->GetAI()->OnCalculateMeleeDamageReceived(damage, this); } // Calculate armor reduction if (IsDamageReducedByArmor((SpellSchoolMask)(damageInfo->damages[i].damageSchoolMask))) { damageInfo->damages[i].damage = Unit::CalcArmorReducedDamage(this, damageInfo->target, damage, nullptr, 0, damageInfo->attackType); damageInfo->cleanDamage += damage - damageInfo->damages[i].damage; } else { damageInfo->damages[i].damage = damage; } } damageInfo->hitOutCome = RollMeleeOutcomeAgainst(damageInfo->target, damageInfo->attackType); // If the victim was a sitting player and we didn't roll a miss, then crit. if (sittingVictim && damageInfo->hitOutCome != MELEE_HIT_MISS) { damageInfo->hitOutCome = MELEE_HIT_CRIT; } switch (damageInfo->hitOutCome) { case MELEE_HIT_EVADE: damageInfo->HitInfo |= HITINFO_MISS | HITINFO_SWINGNOHITSOUND; damageInfo->TargetState = VICTIMSTATE_EVADES; damageInfo->procEx |= PROC_EX_EVADE; for (uint8 i = 0; i < MAX_ITEM_PROTO_DAMAGES; ++i) { damageInfo->damages[i].damage = 0; } damageInfo->cleanDamage = 0; return; case MELEE_HIT_MISS: damageInfo->HitInfo |= HITINFO_MISS; damageInfo->TargetState = VICTIMSTATE_INTACT; damageInfo->procEx |= PROC_EX_MISS; for (uint8 i = 0; i < MAX_ITEM_PROTO_DAMAGES; ++i) { damageInfo->damages[i].damage = 0; } damageInfo->cleanDamage = 0; break; case MELEE_HIT_NORMAL: damageInfo->TargetState = VICTIMSTATE_HIT; damageInfo->procEx |= PROC_EX_NORMAL_HIT; break; case MELEE_HIT_CRIT: { damageInfo->HitInfo |= HITINFO_CRITICALHIT; damageInfo->TargetState = VICTIMSTATE_HIT; damageInfo->procEx |= PROC_EX_CRITICAL_HIT; // Crit bonus calc for (uint8 i = 0; i < MAX_ITEM_PROTO_DAMAGES; ++i) { damageInfo->damages[i].damage *= 2; float mod = 0.0f; // Apply SPELL_AURA_MOD_ATTACKER_RANGED_CRIT_DAMAGE or SPELL_AURA_MOD_ATTACKER_MELEE_CRIT_DAMAGE if (damageInfo->attackType == RANGED_ATTACK) { mod += damageInfo->target->GetTotalAuraModifier(SPELL_AURA_MOD_ATTACKER_RANGED_CRIT_DAMAGE); } else { mod += damageInfo->target->GetTotalAuraModifier(SPELL_AURA_MOD_ATTACKER_MELEE_CRIT_DAMAGE); // Increase crit damage from SPELL_AURA_MOD_CRIT_DAMAGE_BONUS mod += (GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_CRIT_DAMAGE_BONUS, damageInfo->damages[i].damageSchoolMask) - 1.0f) * 100; } uint32 crTypeMask = damageInfo->target->GetCreatureTypeMask(); // Increase crit damage from SPELL_AURA_MOD_CRIT_PERCENT_VERSUS mod += GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_CRIT_PERCENT_VERSUS, crTypeMask); if (mod != 0) { AddPct(damageInfo->damages[i].damage, mod); } } break; } case MELEE_HIT_PARRY: damageInfo->TargetState = VICTIMSTATE_PARRY; damageInfo->procEx |= PROC_EX_PARRY; damageInfo->cleanDamage = 0; for (uint8 i = 0; i < MAX_ITEM_PROTO_DAMAGES; ++i) { damageInfo->cleanDamage += damageInfo->damages[i].damage; damageInfo->damages[i].damage = 0; } break; case MELEE_HIT_DODGE: damageInfo->TargetState = VICTIMSTATE_DODGE; damageInfo->procEx |= PROC_EX_DODGE; damageInfo->cleanDamage = 0; for (uint8 i = 0; i < MAX_ITEM_PROTO_DAMAGES; ++i) { damageInfo->cleanDamage += damageInfo->damages[i].damage; damageInfo->damages[i].damage = 0; } break; case MELEE_HIT_BLOCK: { damageInfo->TargetState = VICTIMSTATE_HIT; damageInfo->HitInfo |= HITINFO_BLOCK; damageInfo->procEx |= PROC_EX_BLOCK; damageInfo->blocked_amount = damageInfo->target->GetShieldBlockValue(); // double blocked amount if block is critical if (damageInfo->target->isBlockCritical()) damageInfo->blocked_amount += damageInfo->blocked_amount; uint32 remainingBlock = damageInfo->blocked_amount; uint8 fullBlockMask = 0; for (uint8 i = 0; i < MAX_ITEM_PROTO_DAMAGES; ++i) { if (remainingBlock && remainingBlock >= damageInfo->damages[i].damage) { fullBlockMask |= (1 << i); remainingBlock -= damageInfo->damages[i].damage; damageInfo->cleanDamage += damageInfo->damages[i].damage; damageInfo->damages[i].damage = 0; } else { damageInfo->cleanDamage += remainingBlock; damageInfo->damages[i].damage -= remainingBlock; remainingBlock = 0; } } // full block if (fullBlockMask == ((1 << 0) | (1 << 1))) { damageInfo->TargetState = VICTIMSTATE_BLOCKS; damageInfo->procEx |= PROC_EX_FULL_BLOCK; damageInfo->blocked_amount -= remainingBlock; } break; } case MELEE_HIT_GLANCING: { damageInfo->HitInfo |= HITINFO_GLANCING; damageInfo->TargetState = VICTIMSTATE_HIT; damageInfo->procEx |= PROC_EX_NORMAL_HIT; int32 leveldif = int32(victim->GetLevel()) - int32(GetLevel()); if (leveldif > 3) leveldif = 3; float reducePercent = 1 - leveldif * 0.1f; for (uint8 i = 0; i < MAX_ITEM_PROTO_DAMAGES; ++i) { uint32 reducedDamage = uint32(reducePercent * damageInfo->damages[i].damage); damageInfo->cleanDamage += damageInfo->damages[i].damage - reducedDamage; damageInfo->damages[i].damage = reducedDamage; } break; } case MELEE_HIT_CRUSHING: damageInfo->HitInfo |= HITINFO_CRUSHING; damageInfo->TargetState = VICTIMSTATE_HIT; damageInfo->procEx |= PROC_EX_NORMAL_HIT; // 150% normal damage for (uint8 i = 0; i < MAX_ITEM_PROTO_DAMAGES; ++i) { damageInfo->damages[i].damage += (damageInfo->damages[i].damage / 2); } break; default: break; } // Always apply HITINFO_AFFECTS_VICTIM in case its not a miss if (!(damageInfo->HitInfo & HITINFO_MISS)) damageInfo->HitInfo |= HITINFO_AFFECTS_VICTIM; uint32 tmpHitInfo[MAX_ITEM_PROTO_DAMAGES] = { }; for (uint8 i = 0; i < MAX_ITEM_PROTO_DAMAGES; ++i) { int32 dmg = damageInfo->damages[i].damage; int32 cleanDamage = damageInfo->cleanDamage; // attackType is checked already for BASE_ATTACK or OFF_ATTACK so it can't be RANGED_ATTACK here if (CanApplyResilience()) { int32 resilienceReduction = damageInfo->damages[i].damage; Unit::ApplyResilience(victim, nullptr, &resilienceReduction, (damageInfo->hitOutCome == MELEE_HIT_CRIT), CR_CRIT_TAKEN_MELEE); resilienceReduction = damageInfo->damages[i].damage - resilienceReduction; dmg -= resilienceReduction; cleanDamage += resilienceReduction; } damageInfo->damages[i].damage = std::max(0, dmg); damageInfo->cleanDamage = std::max(0, cleanDamage); // Calculate absorb resist if (damageInfo->damages[i].damage > 0) { damageInfo->procVictim |= PROC_FLAG_TAKEN_DAMAGE; // Calculate absorb & resists DamageInfo dmgInfo(*damageInfo, i); Unit::CalcAbsorbResist(dmgInfo); damageInfo->damages[i].absorb = dmgInfo.GetAbsorb(); damageInfo->damages[i].resist = dmgInfo.GetResist(); if (damageInfo->damages[i].absorb) { tmpHitInfo[i] |= (damageInfo->damages[i].damage - damageInfo->damages[i].absorb == 0 ? HITINFO_FULL_ABSORB : HITINFO_PARTIAL_ABSORB); } if (damageInfo->damages[i].resist) { tmpHitInfo[i] |= (damageInfo->damages[i].damage - damageInfo->damages[i].resist == 0 ? HITINFO_FULL_RESIST : HITINFO_PARTIAL_RESIST); } damageInfo->damages[i].damage = dmgInfo.GetDamage(); } } // set proper HitInfo flags if ((tmpHitInfo[0] & HITINFO_FULL_ABSORB) != 0) { // set partial absorb when secondary damage isn't full absorbed damageInfo->HitInfo |= ((tmpHitInfo[1] & HITINFO_PARTIAL_ABSORB) != 0) ? HITINFO_PARTIAL_ABSORB : HITINFO_FULL_ABSORB; } else { damageInfo->HitInfo |= (tmpHitInfo[0] & HITINFO_PARTIAL_ABSORB); } if ((tmpHitInfo[0] & HITINFO_FULL_RESIST) != 0) { // set partial resist when secondary damage isn't full resisted damageInfo->HitInfo |= ((tmpHitInfo[1] & HITINFO_PARTIAL_RESIST) != 0) ? HITINFO_PARTIAL_RESIST : HITINFO_FULL_RESIST; } else { damageInfo->HitInfo |= (tmpHitInfo[0] & HITINFO_PARTIAL_RESIST); } if (damageInfo->HitInfo & (HITINFO_PARTIAL_ABSORB | HITINFO_FULL_ABSORB)) { damageInfo->procEx |= PROC_EX_ABSORB; } if (damageInfo->HitInfo & HITINFO_FULL_RESIST) { damageInfo->procEx |= PROC_EX_RESIST; } } void Unit::DealMeleeDamage(CalcDamageInfo* damageInfo, bool durabilityLoss) { Unit* victim = damageInfo->target; auto canTakeMeleeDamage = [&]() { return victim->IsAlive() && !victim->IsInFlight() && (!victim->IsCreature() || !victim->ToCreature()->IsEvadingAttacks()); }; if (!canTakeMeleeDamage()) { return; } // Hmmmm dont like this emotes client must by self do all animations if (damageInfo->HitInfo & HITINFO_CRITICALHIT) victim->HandleEmoteCommand(EMOTE_ONESHOT_WOUND_CRITICAL); if (damageInfo->blocked_amount && damageInfo->TargetState != VICTIMSTATE_BLOCKS) victim->HandleEmoteCommand(EMOTE_ONESHOT_PARRY_SHIELD); // Parry haste is enabled if it's not a creature or if the creature doesn't have the NO_PARRY_HASTEN flag. bool isParryHasteEnabled = !victim->IsCreature() || !victim->ToCreature()->GetCreatureTemplate()->HasFlagsExtra(CREATURE_FLAG_EXTRA_NO_PARRY_HASTEN); if (damageInfo->TargetState == VICTIMSTATE_PARRY && isParryHasteEnabled) { // Get attack timers float offtime = float(victim->getAttackTimer(OFF_ATTACK)); float basetime = float(victim->getAttackTimer(BASE_ATTACK)); // Reduce attack time if (victim->HasOffhandWeaponForAttack() && offtime < basetime) { float percent20 = victim->GetAttackTime(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->GetAttackTime(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)); } } } for (uint8 i = 0; i < MAX_ITEM_PROTO_DAMAGES; ++i) { if (!canTakeMeleeDamage() || (!damageInfo->damages[i].damage && !damageInfo->damages[i].absorb && !damageInfo->damages[i].resist)) { continue; } // Call default DealDamage CleanDamage cleanDamage(damageInfo->cleanDamage, damageInfo->damages[i].absorb, damageInfo->attackType, damageInfo->hitOutCome); Unit::DealDamage(this, victim, damageInfo->damages[i].damage, &cleanDamage, DIRECT_DAMAGE, SpellSchoolMask(damageInfo->damages[i].damageSchoolMask), nullptr, durabilityLoss); } // gain rage if attack is fully blocked, dodged or parried if (HasActivePowerType(POWER_RAGE) && (damageInfo->TargetState == VICTIMSTATE_BLOCKS || damageInfo->TargetState == VICTIMSTATE_DODGE || damageInfo->TargetState == VICTIMSTATE_PARRY)) { switch (damageInfo->attackType) { case BASE_ATTACK: case OFF_ATTACK: { uint32 weaponSpeedHitFactor = uint32(GetAttackTime(damageInfo->attackType) / 1000.0f * (damageInfo->attackType == BASE_ATTACK ? 3.5f : 1.75f)); RewardRage(damageInfo->cleanDamage, weaponSpeedHitFactor, true); break; } default: break; } } // If this is a creature and it attacks from behind it has a probability to daze it's victim if ((damageInfo->damages[0].damage + damageInfo->damages[1].damage) && ((damageInfo->hitOutCome == MELEE_HIT_CRIT || damageInfo->hitOutCome == MELEE_HIT_CRUSHING || damageInfo->hitOutCome == MELEE_HIT_NORMAL || damageInfo->hitOutCome == MELEE_HIT_GLANCING) && !IsPlayer() && !ToCreature()->IsControlledByPlayer() && !victim->HasInArc(M_PI, this) && (victim->IsPlayer() || !victim->ToCreature()->isWorldBoss()) && !victim->IsVehicle())) { // -probability is between 0% and 40% // 20% base chance float Probability = 20.0f; // there is a newbie protection, at level 10 just 7% base chance; assuming linear function if (victim->GetLevel() < 30) Probability = 0.65f * victim->GetLevel() + 0.5f; uint32 VictimDefense = victim->GetDefenseSkillValue(); uint32 VictimAuraDefense = -victim->GetTotalAuraModifier(SPELL_AURA_MOD_ATTACKER_MELEE_CRIT_CHANCE) * 25; uint32 AttackerMeleeSkill = GetUnitMeleeSkill(); // xinef: fix daze mechanics Probability -= ((float)VictimDefense + (float)VictimAuraDefense - AttackerMeleeSkill) * 0.1428f; if (Probability > 40.0f) Probability = 40.0f; // Daze application if (sWorld->getBoolConfig(CONFIG_ENABLE_DAZE)) if (roll_chance_f(std::max(0.0f, Probability))) CastSpell(victim, 1604, true); } if (IsPlayer()) ToPlayer()->CastItemCombatSpell(victim, damageInfo->attackType, damageInfo->procVictim, damageInfo->procEx); // Do effect if any damage done to target if (damageInfo->damages[0].damage + damageInfo->damages[1].damage) DealDamageShieldDamage(victim); } void Unit::DealDamageShieldDamage(Unit* victim) { // 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 AuraEffectList vDamageShieldsCopy(victim->GetAuraEffectsByType(SPELL_AURA_DAMAGE_SHIELD)); for (AuraEffectList::const_iterator dmgShieldItr = vDamageShieldsCopy.begin(); dmgShieldItr != vDamageShieldsCopy.end(); ++dmgShieldItr) { SpellInfo const* i_spellProto = (*dmgShieldItr)->GetSpellInfo(); // Damage shield can be resisted... if (SpellMissInfo missInfo = victim->SpellHitResult(this, i_spellProto, false)) { victim->SendSpellMiss(this, i_spellProto->Id, missInfo); continue; } // ...or immuned if (IsImmunedToDamageOrSchool(i_spellProto)) { victim->SendSpellDamageImmune(this, i_spellProto->Id); continue; } uint32 damage = uint32(std::max(0, (*dmgShieldItr)->GetAmount())); // xinef: done calculated at amount calculation if (Unit* caster = (*dmgShieldItr)->GetCaster()) { damage = caster->SpellDamageBonusDone(this, i_spellProto, damage, SPELL_DIRECT_DAMAGE, (*dmgShieldItr)->GetEffIndex()); damage = this->SpellDamageBonusTaken(caster, i_spellProto, damage, SPELL_DIRECT_DAMAGE); } uint32 absorb = 0; DamageInfo dmgInfo(victim, this, damage, i_spellProto, i_spellProto->GetSchoolMask(), SPELL_DIRECT_DAMAGE); Unit::CalcAbsorbResist(dmgInfo); absorb = dmgInfo.GetAbsorb(); damage = dmgInfo.GetDamage(); Unit::DealDamageMods(this, damage, &absorb); /// @todo: Move this to a packet handler WorldPacket data(SMSG_SPELLDAMAGESHIELD, (8 + 8 + 4 + 4 + 4 + 4)); data << victim->GetGUID(); data << GetGUID(); data << uint32(i_spellProto->Id); data << uint32(damage); // Damage int32 overkill = int32(damage) - int32(GetHealth()); data << uint32(overkill > 0 ? overkill : 0); // Overkill data << uint32(i_spellProto->GetSchoolMask()); victim->SendMessageToSet(&data, true); Unit::DealDamage(victim, this, damage, 0, SPELL_DIRECT_DAMAGE, i_spellProto->GetSchoolMask(), i_spellProto, true); } } void Unit::HandleEmoteCommand(uint32 emoteId) { WorldPackets::Chat::Emote packet; packet.EmoteID = emoteId; packet.Guid = GetGUID(); SendMessageToSet(packet.Write(), true); } bool Unit::IsDamageReducedByArmor(SpellSchoolMask schoolMask, SpellInfo const* spellInfo, uint8 effIndex) { // only physical spells damage gets reduced by armor if ((schoolMask & SPELL_SCHOOL_MASK_NORMAL) == 0) return false; if (spellInfo) { // there are spells with no specific attribute but they have "ignores armor" in tooltip if (spellInfo->HasAttribute(SPELL_ATTR0_CU_IGNORE_ARMOR)) return false; // bleeding effects are not reduced by armor if (effIndex != MAX_SPELL_EFFECTS) { if (spellInfo->Effects[effIndex].ApplyAuraName == SPELL_AURA_PERIODIC_DAMAGE || spellInfo->Effects[effIndex].Effect == SPELL_EFFECT_SCHOOL_DAMAGE) if (spellInfo->GetEffectMechanicMask(effIndex) & (1 << MECHANIC_BLEED)) return false; } } return true; } uint32 Unit::CalcArmorReducedDamage(Unit const* attacker, Unit const* victim, const uint32 damage, SpellInfo const* spellInfo, uint8 attackerLevel, WeaponAttackType /*attackType*/) { float armor = float(victim->GetArmor()); // Ignore enemy armor by SPELL_AURA_MOD_TARGET_RESISTANCE aura if (attacker) { armor += attacker->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_TARGET_RESISTANCE, SPELL_SCHOOL_MASK_NORMAL); if (spellInfo) if (Player* modOwner = attacker->GetSpellModOwner()) modOwner->ApplySpellMod(spellInfo->Id, SPELLMOD_IGNORE_ARMOR, armor); AuraEffectList const& ResIgnoreAurasAb = attacker->GetAuraEffectsByType(SPELL_AURA_MOD_ABILITY_IGNORE_TARGET_RESIST); for (AuraEffectList::const_iterator j = ResIgnoreAurasAb.begin(); j != ResIgnoreAurasAb.end(); ++j) { if ((*j)->GetMiscValue() & SPELL_SCHOOL_MASK_NORMAL && (*j)->IsAffectedOnSpell(spellInfo)) armor = std::floor(AddPct(armor, -(*j)->GetAmount())); } AuraEffectList const& ResIgnoreAuras = attacker->GetAuraEffectsByType(SPELL_AURA_MOD_IGNORE_TARGET_RESIST); for (AuraEffectList::const_iterator j = ResIgnoreAuras.begin(); j != ResIgnoreAuras.end(); ++j) { if ((*j)->GetMiscValue() & SPELL_SCHOOL_MASK_NORMAL) armor = std::floor(AddPct(armor, -(*j)->GetAmount())); } // Apply Player CR_ARMOR_PENETRATION rating and buffs from stances\specializations etc. if (attacker->IsPlayer()) { float bonusPct = 0; bonusPct += attacker->GetTotalAuraModifier(SPELL_AURA_MOD_ARMOR_PENETRATION_PCT, [spellInfo,attacker](AuraEffect const* aurEff) { if (aurEff->GetSpellInfo()->EquippedItemClass == -1) { if (!spellInfo || aurEff->IsAffectedOnSpell(spellInfo) || aurEff->GetMiscValue() & spellInfo->GetSchoolMask()) return true; else if (!aurEff->GetMiscValue() && !aurEff->HasSpellClassMask()) return true; } else { if (attacker->ToPlayer()->HasItemFitToSpellRequirements(aurEff->GetSpellInfo())) return true; } return false; }); float maxArmorPen = 0; if (victim->GetLevel() < 60) maxArmorPen = float(400 + 85 * victim->GetLevel()); else maxArmorPen = 400 + 85 * victim->GetLevel() + 4.5f * 85 * (victim->GetLevel() - 59); // Cap armor penetration to this number maxArmorPen = std::min((armor + maxArmorPen) / 3, armor); // Figure out how much armor do we ignore float armorPen = CalculatePct(maxArmorPen, bonusPct + attacker->ToPlayer()->GetRatingBonusValue(CR_ARMOR_PENETRATION)); // Got the value, apply it armor -= std::min(armorPen, maxArmorPen); } } if (armor < 0.0f) armor = 0.0f; float levelModifier = attacker ? attacker->GetLevel() : attackerLevel; if (levelModifier > 59) levelModifier = levelModifier + (4.5f * (levelModifier - 59)); float tmpvalue = 0.1f * armor / (8.5f * levelModifier + 40); tmpvalue = tmpvalue / (1.0f + tmpvalue); if (tmpvalue < 0.0f) tmpvalue = 0.0f; if (tmpvalue > 0.75f) tmpvalue = 0.75f; return uint32(std::ceil(std::max(damage * (1.0f - tmpvalue), 0.0f))); } float Unit::GetEffectiveResistChance(Unit const* owner, SpellSchoolMask schoolMask, Unit const* victim) { float victimResistance = float(victim->GetResistance(schoolMask)); if (owner) { // Xinef: pets inherit 100% of masters penetration // Xinef: excluding traps Player const* player = owner->GetSpellModOwner(); if (player && owner->GetEntry() != WORLD_TRIGGER) { victimResistance += float(player->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_TARGET_RESISTANCE, schoolMask)); victimResistance -= float(player->GetSpellPenetrationItemMod()); } else victimResistance += float(owner->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_TARGET_RESISTANCE, schoolMask)); } victimResistance = std::max(victimResistance, 0.0f); if (owner) victimResistance += std::max((float(victim->GetLevel()) - float(owner->GetLevel())) * 5.0f, 0.0f); static uint32 const BOSS_LEVEL = 83; static float const BOSS_RESISTANCE_CONSTANT = 510.0f; uint32 level = victim->GetLevel(); float resistanceConstant = 0.0f; if (level == BOSS_LEVEL) resistanceConstant = BOSS_RESISTANCE_CONSTANT; else resistanceConstant = level * 5.0f; return victimResistance / (victimResistance + resistanceConstant); } void Unit::CalcAbsorbResist(DamageInfo& dmgInfo, bool Splited) { Unit* victim = dmgInfo.GetVictim(); Unit* attacker = dmgInfo.GetAttacker(); uint32 damage = dmgInfo.GetDamage(); SpellSchoolMask schoolMask = dmgInfo.GetSchoolMask(); SpellInfo const* spellInfo = dmgInfo.GetSpellInfo(); if (!victim || !victim->IsAlive() || !damage) return; // Magic damage, check for resists // Ignore spells that cant be resisted // Xinef: holy resistance exists for npcs if (!(schoolMask & SPELL_SCHOOL_MASK_NORMAL) && (!(schoolMask & SPELL_SCHOOL_MASK_HOLY) || victim->IsCreature()) && (!spellInfo || (!spellInfo->HasAttribute(SPELL_ATTR0_CU_BINARY_SPELL) && !spellInfo->HasAttribute(SPELL_ATTR4_NO_CAST_LOG)))) { float averageResist = Unit::GetEffectiveResistChance(attacker, schoolMask, victim); float discreteResistProbability[11]; for (uint32 i = 0; i < 11; ++i) { discreteResistProbability[i] = 0.5f - 2.5f * std::fabs(0.1f * i - averageResist); if (discreteResistProbability[i] < 0.0f) discreteResistProbability[i] = 0.0f; } if (averageResist <= 0.1f) { discreteResistProbability[0] = 1.0f - 7.5f * averageResist; discreteResistProbability[1] = 5.0f * averageResist; discreteResistProbability[2] = 2.5f * averageResist; } float r = float(rand_norm()); uint32 i = 0; float probabilitySum = discreteResistProbability[0]; while (r >= probabilitySum && i < 10) probabilitySum += discreteResistProbability[++i]; float damageResisted = float(damage * i / 10); if (damageResisted) // if equal to 0, checking these is pointless { if (attacker) { AuraEffectList const& ResIgnoreAurasAb = attacker->GetAuraEffectsByType(SPELL_AURA_MOD_ABILITY_IGNORE_TARGET_RESIST); for (AuraEffectList::const_iterator j = ResIgnoreAurasAb.begin(); j != ResIgnoreAurasAb.end(); ++j) if (((*j)->GetMiscValue() & schoolMask) && (*j)->IsAffectedOnSpell(spellInfo)) AddPct(damageResisted, -(*j)->GetAmount()); AuraEffectList const& ResIgnoreAuras = attacker->GetAuraEffectsByType(SPELL_AURA_MOD_IGNORE_TARGET_RESIST); for (AuraEffectList::const_iterator j = ResIgnoreAuras.begin(); j != ResIgnoreAuras.end(); ++j) if ((*j)->GetMiscValue() & schoolMask) AddPct(damageResisted, -(*j)->GetAmount()); } // pussywizard: if (spellInfo && spellInfo->HasAttribute(SPELL_ATTR0_CU_SCHOOLMASK_NORMAL_WITH_MAGIC)) { uint32 damageAfterArmor = Unit::CalcArmorReducedDamage(attacker, victim, damage, spellInfo, 0, BASE_ATTACK); uint32 armorReduction = damage - damageAfterArmor; if (armorReduction < damageResisted) // pick the lower one, the weakest resistance counts damageResisted = armorReduction; } } dmgInfo.ResistDamage(uint32(damageResisted)); } // Ignore Absorption Auras float auraAbsorbMod = 0; if (attacker) { AuraEffectList const& AbsIgnoreAurasA = attacker->GetAuraEffectsByType(SPELL_AURA_MOD_TARGET_ABSORB_SCHOOL); for (AuraEffectList::const_iterator itr = AbsIgnoreAurasA.begin(); itr != AbsIgnoreAurasA.end(); ++itr) { if (!((*itr)->GetMiscValue() & schoolMask)) continue; if ((*itr)->GetAmount() > auraAbsorbMod) auraAbsorbMod = float((*itr)->GetAmount()); } AuraEffectList const& AbsIgnoreAurasB = attacker->GetAuraEffectsByType(SPELL_AURA_MOD_TARGET_ABILITY_ABSORB_SCHOOL); for (AuraEffectList::const_iterator itr = AbsIgnoreAurasB.begin(); itr != AbsIgnoreAurasB.end(); ++itr) { if (!((*itr)->GetMiscValue() & schoolMask)) continue; if (((*itr)->GetAmount() > auraAbsorbMod) && (*itr)->IsAffectedOnSpell(spellInfo)) auraAbsorbMod = float((*itr)->GetAmount()); } RoundToInterval(auraAbsorbMod, 0.0f, 100.0f); } // 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 AuraEffectList vSchoolAbsorbCopy(victim->GetAuraEffectsByType(SPELL_AURA_SCHOOL_ABSORB)); std::sort(vSchoolAbsorbCopy.begin(), vSchoolAbsorbCopy.end(), Acore::AbsorbAuraOrderPred()); // absorb without mana cost for (AuraEffectList::iterator itr = vSchoolAbsorbCopy.begin(); (itr != vSchoolAbsorbCopy.end()) && (dmgInfo.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(victim->GetGUID()); if (!aurApp) continue; if (!(absorbAurEff->GetMiscValue() & schoolMask)) 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, dmgInfo, tempAbsorb, defaultPrevented); currentAbsorb = tempAbsorb; if (defaultPrevented) continue; // absorb must be smaller than the damage itself currentAbsorb = RoundToInterval(currentAbsorb, 0, int32(dmgInfo.GetDamage())); // xinef: do this after absorb is rounded to damage... AddPct(currentAbsorb, -auraAbsorbMod); dmgInfo.AbsorbDamage(currentAbsorb); tempAbsorb = currentAbsorb; absorbAurEff->GetBase()->CallScriptEffectAfterAbsorbHandlers(absorbAurEff, aurApp, dmgInfo, tempAbsorb); // Check if our aura is using amount to count damage if (absorbAurEff->GetAmount() >= 0) { // Reduce shield amount absorbAurEff->SetAmount(absorbAurEff->GetAmount() - currentAbsorb); // Aura cannot absorb anything more - remove it if (absorbAurEff->GetAmount() <= 0) absorbAurEff->GetBase()->Remove(AURA_REMOVE_BY_ENEMY_SPELL); } } // absorb by mana cost AuraEffectList vManaShieldCopy(victim->GetAuraEffectsByType(SPELL_AURA_MANA_SHIELD)); for (AuraEffectList::const_iterator itr = vManaShieldCopy.begin(); (itr != vManaShieldCopy.end()) && (dmgInfo.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(victim->GetGUID()); if (!aurApp) continue; // check damage school mask if (!(absorbAurEff->GetMiscValue() & schoolMask)) 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 = currentAbsorb; bool defaultPrevented = false; absorbAurEff->GetBase()->CallScriptEffectManaShieldHandlers(absorbAurEff, aurApp, dmgInfo, tempAbsorb, defaultPrevented); currentAbsorb = tempAbsorb; if (defaultPrevented) continue; // absorb must be smaller than the damage itself currentAbsorb = RoundToInterval(currentAbsorb, 0, int32(dmgInfo.GetDamage())); // xinef: do this after absorb is rounded to damage... AddPct(currentAbsorb, -auraAbsorbMod); int32 manaReduction = currentAbsorb; // lower absorb amount by talents if (float manaMultiplier = absorbAurEff->GetSpellInfo()->Effects[absorbAurEff->GetEffIndex()].CalcValueMultiplier(absorbAurEff->GetCaster())) manaReduction = int32(float(manaReduction) * manaMultiplier); int32 manaTaken = -victim->ModifyPower(POWER_MANA, -manaReduction); // take case when mana has ended up into account currentAbsorb = currentAbsorb ? int32(float(currentAbsorb) * (float(manaTaken) / float(manaReduction))) : 0; dmgInfo.AbsorbDamage(currentAbsorb); tempAbsorb = currentAbsorb; absorbAurEff->GetBase()->CallScriptEffectAfterManaShieldHandlers(absorbAurEff, aurApp, dmgInfo, tempAbsorb); // Check if our aura is using amount to count damage if (absorbAurEff->GetAmount() >= 0) { absorbAurEff->SetAmount(absorbAurEff->GetAmount() - currentAbsorb); if ((absorbAurEff->GetAmount() <= 0)) absorbAurEff->GetBase()->Remove(AURA_REMOVE_BY_ENEMY_SPELL); } } // split damage auras - only when not damaging self // Xinef: not true - Warlock Hellfire if (/*victim != attacker &&*/ !Splited) { // 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 AuraEffectList vSplitDamageFlatCopy(victim->GetAuraEffectsByType(SPELL_AURA_SPLIT_DAMAGE_FLAT)); // Not used by any spell for (AuraEffectList::iterator itr = vSplitDamageFlatCopy.begin(); (itr != vSplitDamageFlatCopy.end()) && (dmgInfo.GetDamage() > 0); ++itr) { // Check if aura was removed during iteration - we don't need to work on such auras if (!((*itr)->GetBase()->IsAppliedOnTarget(victim->GetGUID()))) continue; // check damage school mask if (!((*itr)->GetMiscValue() & schoolMask)) continue; // Damage can be splitted only if aura has an alive caster Unit* caster = (*itr)->GetCaster(); if (!caster || (caster == victim) || !caster->IsInWorld() || !caster->IsAlive()) continue; // Limit effect range to spell's cast range. (Only for single target auras, AreaAuras don't need it) // Ignore LOS attribute is only used for the cast portion of the spell SpellInfo const* splitSpellInfo = (*itr)->GetSpellInfo(); if (!splitSpellInfo->Effects[(*itr)->GetEffIndex()].IsAreaAuraEffect()) if (!caster->IsWithinDist(victim, splitSpellInfo->GetMaxRange(splitSpellInfo->IsPositive(), caster))) continue; int32 splitDamage = (*itr)->GetAmount(); // absorb must be smaller than the damage itself splitDamage = RoundToInterval(splitDamage, 0, int32(dmgInfo.GetDamage())); dmgInfo.AbsorbDamage(splitDamage); uint32 splitted = splitDamage; uint32 splitted_absorb = 0; uint32 splitted_resist = 0; uint32 procAttacker = 0, procVictim = 0, procEx = PROC_EX_NORMAL_HIT; DamageInfo splittedDmgInfo(attacker, caster, splitted, spellInfo, schoolMask, dmgInfo.GetDamageType()); if (caster->IsImmunedToDamageOrSchool(schoolMask)) { procEx |= PROC_EX_IMMUNE; splittedDmgInfo.AbsorbDamage(splitted); } else { Unit::CalcAbsorbResist(splittedDmgInfo, true); Unit::DealDamageMods(caster, splitted, &splitted_absorb); } splitted_absorb = splittedDmgInfo.GetAbsorb(); splitted_resist = splittedDmgInfo.GetResist(); splitted = splittedDmgInfo.GetDamage(); // create procs createProcFlags(spellInfo, BASE_ATTACK, false, procAttacker, procVictim); caster->ProcDamageAndSpellFor(true, attacker, procVictim, procEx, BASE_ATTACK, spellInfo, splitted, nullptr, -1, nullptr, &splittedDmgInfo); if (attacker) { attacker->SendSpellNonMeleeDamageLog(caster, (*itr)->GetSpellInfo(), splitted, schoolMask, splitted_absorb, splitted_resist, false, 0, false, true); } CleanDamage cleanDamage = CleanDamage(splitted, 0, BASE_ATTACK, MELEE_HIT_NORMAL); Unit::DealDamage(attacker, caster, splitted, &cleanDamage, DIRECT_DAMAGE, schoolMask, (*itr)->GetSpellInfo(), 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 AuraEffectList vSplitDamagePctCopy(victim->GetAuraEffectsByType(SPELL_AURA_SPLIT_DAMAGE_PCT)); for (AuraEffectList::iterator itr = vSplitDamagePctCopy.begin(); (itr != vSplitDamagePctCopy.end()) && (dmgInfo.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(victim->GetGUID()); if (!aurApp) continue; // check damage school mask if (!((*itr)->GetMiscValue() & schoolMask)) continue; // Damage can be splitted only if aura has an alive caster Unit* caster = (*itr)->GetCaster(); if (!caster || (caster == victim) || !caster->IsInWorld() || !caster->IsAlive()) continue; // Limit effect range to spell's cast range. (Only for single target auras, AreaAuras don't need it) // Ignore LOS attribute is only used for the cast portion of the spell SpellInfo const* splitSpellInfo = (*itr)->GetSpellInfo(); if (!splitSpellInfo->Effects[(*itr)->GetEffIndex()].IsAreaAuraEffect()) if (!caster->IsWithinDist(victim, splitSpellInfo->GetMaxRange(splitSpellInfo->IsPositive(), caster))) continue; uint32 splitDamage = CalculatePct(dmgInfo.GetDamage(), (*itr)->GetAmount()); SpellSchoolMask splitSchoolMask = schoolMask; (*itr)->GetBase()->CallScriptEffectSplitHandlers(*itr, aurApp, dmgInfo, splitDamage); // absorb must be smaller than the damage itself splitDamage = RoundToInterval(splitDamage, uint32(0), uint32(dmgInfo.GetDamage())); // Roar of Sacrifice, dont absorb it if (splitSpellInfo->Id != 53480) dmgInfo.AbsorbDamage(splitDamage); else splitSchoolMask = SPELL_SCHOOL_MASK_NATURE; uint32 splitted = splitDamage; uint32 splitted_absorb = 0; uint32 splitted_resist = 0; uint32 procAttacker = 0, procVictim = 0, procEx = PROC_EX_NORMAL_HIT; DamageInfo splittedDmgInfo(attacker, caster, splitted, spellInfo, splitSchoolMask, dmgInfo.GetDamageType()); if (caster->IsImmunedToDamageOrSchool(schoolMask)) { procEx |= PROC_EX_IMMUNE; splittedDmgInfo.AbsorbDamage(splitted); } else { Unit::CalcAbsorbResist(splittedDmgInfo, true); Unit::DealDamageMods(caster, splitted, &splitted_absorb); } splitted_absorb = splittedDmgInfo.GetAbsorb(); splitted_resist = splittedDmgInfo.GetResist(); splitted = splittedDmgInfo.GetDamage(); // create procs createProcFlags(spellInfo, BASE_ATTACK, false, procAttacker, procVictim); caster->ProcDamageAndSpellFor(true, attacker, procVictim, procEx, BASE_ATTACK, spellInfo, splitted); if (attacker) { attacker->SendSpellNonMeleeDamageLog(caster, splitSpellInfo, splitted, splitSchoolMask, splitted_absorb, splitted_resist, false, 0, false, true); } CleanDamage cleanDamage = CleanDamage(splitted, 0, BASE_ATTACK, MELEE_HIT_NORMAL); Unit::DealDamage(attacker, caster, splitted, &cleanDamage, DIRECT_DAMAGE, splitSchoolMask, splitSpellInfo, false); } } } void Unit::CalcHealAbsorb(HealInfo& healInfo) { if (!healInfo.GetHeal()) return; int32 const healing = static_cast(healInfo.GetHeal()); int32 absorbAmount = 0; // Need remove expired auras after bool existExpired = false; // absorb without mana cost AuraEffectList const& vHealAbsorb = healInfo.GetTarget()->GetAuraEffectsByType(SPELL_AURA_SCHOOL_HEAL_ABSORB); for (AuraEffectList::const_iterator i = vHealAbsorb.begin(); i != vHealAbsorb.end() && absorbAmount <= healing; ++i) { if (!((*i)->GetMiscValue() & healInfo.GetSpellInfo()->SchoolMask)) continue; // Max Amount can be absorbed by this aura int32 currentAbsorb = (*i)->GetAmount(); // Found empty aura (impossible but..) if (currentAbsorb <= 0) { existExpired = true; continue; } // currentAbsorb - damage can be absorbed by shield // If need absorb less damage if (healing < currentAbsorb + absorbAmount) currentAbsorb = healing - absorbAmount; absorbAmount += currentAbsorb; // Reduce shield amount (*i)->SetAmount((*i)->GetAmount() - currentAbsorb); // Need remove it later if ((*i)->GetAmount() <= 0) existExpired = true; } // Remove all expired absorb auras if (existExpired) { for (AuraEffectList::const_iterator i = vHealAbsorb.begin(); i != vHealAbsorb.end();) { AuraEffect* auraEff = *i; ++i; if (auraEff->GetAmount() <= 0) { uint32 removedAuras = healInfo.GetTarget()->m_removedAurasCount; auraEff->GetBase()->Remove(AURA_REMOVE_BY_ENEMY_SPELL); if (healInfo.GetTarget()->m_removedAurasCount > removedAuras) i = vHealAbsorb.begin(); } } } if (absorbAmount > 0) healInfo.AbsorbHeal(absorbAmount); } void Unit::AttackerStateUpdate(Unit* victim, WeaponAttackType attType /*= BASE_ATTACK*/, bool extra /*= false*/, bool ignoreCasting /*= false*/) { if (HasUnitFlag(UNIT_FLAG_PACIFIED)) { return; } if (HasUnitState(UNIT_STATE_CANNOT_AUTOATTACK) && !extra && !ignoreCasting) { return; } if (!victim->IsAlive()) return; if ((attType == BASE_ATTACK || attType == OFF_ATTACK) && !IsWithinLOSInMap(victim)) return; // CombatStart puts the target into stand state, so we need to cache sit state here to know if we should crit later const bool sittingVictim = victim->IsPlayer() && (victim->IsSitState() || victim->getStandState() == UNIT_STAND_STATE_SLEEP); CombatStart(victim); RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_MELEE_ATTACK); if (attType != BASE_ATTACK && attType != OFF_ATTACK) return; // ignore ranged case if (!extra && _lastExtraAttackSpell) { _lastExtraAttackSpell = 0; } bool meleeAttack = true; // melee attack spell casted at main hand attack only - no normal melee dmg dealt if (attType == BASE_ATTACK && m_currentSpells[CURRENT_MELEE_SPELL] && !extra) { meleeAttack = false; // The melee attack is replaced by the melee spell Spell* meleeSpell = m_currentSpells[CURRENT_MELEE_SPELL]; SpellCastResult castResult = meleeSpell->CheckCast(false); if (castResult != SPELL_CAST_OK) { meleeSpell->SendCastResult(castResult); meleeSpell->SendInterrupted(0); meleeSpell->finish(false); meleeSpell->SetExecutedCurrently(false); if (castResult == SPELL_FAILED_NO_POWER) { // Not enough rage, do a regular melee attack instead meleeAttack = true; } } else { meleeSpell->cast(true); } } if (meleeAttack) { // attack can be redirected to another target victim = GetMeleeHitRedirectTarget(victim); CalcDamageInfo damageInfo; CalculateMeleeDamage(victim, &damageInfo, attType, sittingVictim); // Send log damage message to client for (uint8 i = 0; i < MAX_ITEM_PROTO_DAMAGES; ++i) { Unit::DealDamageMods(victim, damageInfo.damages[i].damage, &damageInfo.damages[i].absorb); } // Related to sparring system. Allow attack animations even if there are no damages if (victim->CanSparringWith(damageInfo.attacker)) damageInfo.HitInfo |= HITINFO_FAKE_DAMAGE; SendAttackStateUpdate(&damageInfo); //TriggerAurasProcOnEvent(damageInfo); _lastDamagedTargetGuid = victim->GetGUID(); DealMeleeDamage(&damageInfo, true); DamageInfo dmgInfo(damageInfo); Unit::ProcDamageAndSpell(damageInfo.attacker, damageInfo.target, damageInfo.procAttacker, damageInfo.procVictim, damageInfo.procEx, dmgInfo.GetDamage(), damageInfo.attackType, nullptr, nullptr, -1, nullptr, &dmgInfo); if (IsPlayer()) LOG_DEBUG("entities.unit", "AttackerStateUpdate: (Player) {} attacked {} for {} dmg, absorbed {}, blocked {}, resisted {}.", GetGUID().ToString(), victim->GetGUID().ToString(), dmgInfo.GetDamage(), dmgInfo.GetAbsorb(), dmgInfo.GetBlock(), dmgInfo.GetResist()); else LOG_DEBUG("entities.unit", "AttackerStateUpdate: (NPC) {} attacked {} for {} dmg, absorbed {}, blocked {}, resisted {}.", GetGUID().ToString(), victim->GetGUID().ToString(), dmgInfo.GetDamage(), dmgInfo.GetAbsorb(), dmgInfo.GetBlock(), dmgInfo.GetResist()); // 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 (IsPlayer() && !m_Controlled.empty()) for (Unit::ControlSet::iterator itr = m_Controlled.begin(); itr != m_Controlled.end(); ++itr) if (Unit* pet = *itr) if (pet->IsAlive() && pet->IsCreature()) pet->ToCreature()->AI()->OwnerAttacked(victim); } } bool Unit::GetMeleeAttackPoint(Unit* attacker, Position& pos) { if (!attacker) { return false; } AttackerSet attackers = getAttackers(); if (attackers.size() <= 1) // if the attackers are not more than one { return false; } float meleeReach = GetExactDist2d(attacker); if (meleeReach <= 0) { return false; } float minAngle = 0; Unit *refUnit = nullptr; uint32 validAttackers = 0; double attackerSize = attacker->GetCollisionRadius(); for (auto const& otherAttacker: attackers) { // if the otherAttacker is not valid, skip if (!otherAttacker || otherAttacker->GetGUID() == attacker->GetGUID() || !otherAttacker->IsWithinMeleeRange(this) || otherAttacker->isMoving()) { continue; } float curretAngle = atan(attacker->GetExactDist2d(otherAttacker) / meleeReach); if (minAngle == 0 || curretAngle < minAngle) { minAngle = curretAngle; refUnit = otherAttacker; } validAttackers++; } if (!validAttackers || !refUnit) { return false; } float contactDist = attackerSize + refUnit->GetCollisionRadius(); float requiredAngle = atan(contactDist / meleeReach); float attackersAngle = atan(attacker->GetExactDist2d(refUnit) / meleeReach); // in instance: the more attacker there are, the higher will be the tollerance // outside: creatures should not intersecate float angleTollerance = attacker->GetMap()->IsDungeon() ? requiredAngle - requiredAngle * tanh(validAttackers / 5.0f) : requiredAngle; if (attackersAngle > angleTollerance) { return false; } double angle = atan(contactDist / meleeReach); float angularRadius = frand(0.1f, 0.3f) + angle; int8 direction = (urand(0, 1) ? -1 : 1); float currentAngle = GetAngle(refUnit); float absAngle = currentAngle + angularRadius * direction; float x, y, z; float distance = meleeReach - GetObjectSize(); GetNearPoint(attacker, x, y, z, distance, 0.0f, absAngle); if (!GetMap()->CanReachPositionAndGetValidCoords(this, x, y, z, true, true)) { GetNearPoint(attacker, x, y, z, distance, 0.0f, absAngle * -1); // try the other side if (!GetMap()->CanReachPositionAndGetValidCoords(this, x, y, z, true, true)) { return false; } } pos.Relocate(x, y, z); return true; } 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) { if (ObjectGuid selection = GetTarget()) { targetGUID = selection; // Spell was cast directly (not triggered by aura) } else return; } extraAttacksTargets[targetGUID] += count; } MeleeHitOutcome Unit::RollMeleeOutcomeAgainst(Unit const* victim, WeaponAttackType attType) const { // This is only wrapper // Miss chance based on melee //float miss_chance = MeleeMissChanceCalc(victim, attType); float miss_chance = MeleeSpellMissChance(victim, attType, int32(GetWeaponSkillValue(attType, victim)) - int32(victim->GetMaxSkillValueForLevel(this)), 0); // Critical hit chance float crit_chance = GetUnitCriticalChance(attType, victim); if (crit_chance < 0) crit_chance = 0; float dodge_chance = victim->GetUnitDodgeChance(); float block_chance = victim->GetUnitBlockChance(); float parry_chance = victim->GetUnitParryChance(); // Useful if want to specify crit & miss chances for melee, else it could be removed //LOG_DEBUG("entities.unit", "MELEE OUTCOME: miss {} crit {} dodge {} parry {} block {}", miss_chance, crit_chance, dodge_chance, parry_chance, block_chance); return RollMeleeOutcomeAgainst(victim, attType, int32(crit_chance * 100), int32(miss_chance * 100), int32(dodge_chance * 100), int32(parry_chance * 100), int32(block_chance * 100)); } MeleeHitOutcome Unit::RollMeleeOutcomeAgainst(Unit const* victim, WeaponAttackType attType, int32 crit_chance, int32 miss_chance, int32 dodge_chance, int32 parry_chance, int32 block_chance) const { if (victim->IsCreature() && victim->ToCreature()->IsEvadingAttacks()) { return MELEE_HIT_EVADE; } int32 attackerMaxSkillValueForLevel = GetMaxSkillValueForLevel(victim); int32 victimMaxSkillValueForLevel = victim->GetMaxSkillValueForLevel(this); int32 attackerWeaponSkill = GetWeaponSkillValue(attType, victim); int32 victimDefenseSkill = victim->GetDefenseSkillValue(this); sScriptMgr->OnBeforeRollMeleeOutcomeAgainst(this, victim, attType, attackerMaxSkillValueForLevel, victimMaxSkillValueForLevel, attackerWeaponSkill, victimDefenseSkill, crit_chance, miss_chance, dodge_chance, parry_chance, block_chance); // bonus from skills is 0.04% int32 skillBonus = 4 * (attackerWeaponSkill - victimMaxSkillValueForLevel); int32 sum = 0, tmp = 0; int32 roll = urand (0, 10000); LOG_DEBUG("entities.unit", "RollMeleeOutcomeAgainst: skill bonus of {} for attacker", skillBonus); //LOG_DEBUG("entities.unit", "RollMeleeOutcomeAgainst: rolled {}, miss {}, dodge {}, parry {}, block {}, crit {}", // roll, miss_chance, dodge_chance, parry_chance, block_chance, crit_chance); tmp = miss_chance; if (tmp > 0 && roll < (sum += tmp)) { LOG_DEBUG("entities.unit", "RollMeleeOutcomeAgainst: MISS"); return MELEE_HIT_MISS; } // Dodge chance // only players can't dodge if attacker is behind if (victim->IsPlayer() && !victim->HasInArc(M_PI, this) && !victim->HasIgnoreHitDirectionAura()) { //LOG_DEBUG("entities.unit", "RollMeleeOutcomeAgainst: attack came from behind and victim was a player."); } // Xinef: do not allow to dodge with CREATURE_FLAG_EXTRA_NO_DODGE flag else if (victim->IsPlayer() || !(victim->ToCreature()->HasFlagsExtra(CREATURE_FLAG_EXTRA_NO_DODGE))) { // Reduce dodge chance by attacker expertise rating if (IsPlayer()) dodge_chance -= int32(ToPlayer()->GetExpertiseDodgeOrParryReduction(attType) * 100); else dodge_chance -= GetTotalAuraModifier(SPELL_AURA_MOD_EXPERTISE) * 25; // Modify dodge chance by attacker SPELL_AURA_MOD_COMBAT_RESULT_CHANCE dodge_chance += GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_COMBAT_RESULT_CHANCE, VICTIMSTATE_DODGE) * 100; dodge_chance = int32 (float (dodge_chance) * GetTotalAuraMultiplier(SPELL_AURA_MOD_ENEMY_DODGE)); tmp = dodge_chance; // xinef: if casting or stunned - cant dodge if (victim->IsNonMeleeSpellCast(false, false, true) || victim->HasUnitState(UNIT_STATE_CONTROLLED)) tmp = 0; if ((tmp > 0) // check if unit _can_ dodge && ((tmp -= skillBonus) > 0) && roll < (sum += tmp)) { LOG_DEBUG("entities.unit", "RollMeleeOutcomeAgainst: DODGE <{}, {})", sum - tmp, sum); return MELEE_HIT_DODGE; } } // parry & block chances // check if attack comes from behind, nobody can parry or block if attacker is behind if (!victim->HasInArc(M_PI, this) && !victim->HasIgnoreHitDirectionAura()) { LOG_DEBUG("entities.unit", "RollMeleeOutcomeAgainst: attack came from behind."); } else { // Reduce parry chance by attacker expertise rating if (IsPlayer()) parry_chance -= int32(ToPlayer()->GetExpertiseDodgeOrParryReduction(attType) * 100); else parry_chance -= GetTotalAuraModifier(SPELL_AURA_MOD_EXPERTISE) * 25; if (victim->IsPlayer() || !(victim->ToCreature()->HasFlagsExtra(CREATURE_FLAG_EXTRA_NO_PARRY))) { tmp = parry_chance; // xinef: cant parry while casting or while stunned if (victim->IsNonMeleeSpellCast(false, false, true) || victim->HasUnitState(UNIT_STATE_CONTROLLED)) tmp = 0; if (tmp > 0 // check if unit _can_ parry && (tmp -= skillBonus) > 0 && roll < (sum += tmp)) { LOG_DEBUG("entities.unit", "RollMeleeOutcomeAgainst: PARRY <{}, {})", sum - tmp, sum); return MELEE_HIT_PARRY; } } if (victim->IsPlayer() || !(victim->ToCreature()->HasFlagsExtra(CREATURE_FLAG_EXTRA_NO_BLOCK))) { tmp = block_chance; // xinef: cant block while casting or while stunned if (victim->IsNonMeleeSpellCast(false, false, true) || victim->HasUnitState(UNIT_STATE_CONTROLLED)) tmp = 0; if (tmp > 0 // check if unit _can_ block && (tmp -= skillBonus) > 0 && roll < (sum += tmp)) { LOG_DEBUG("entities.unit", "RollMeleeOutcomeAgainst: BLOCK <{}, {})", sum - tmp, sum); return MELEE_HIT_BLOCK; } } } // 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 (attType != RANGED_ATTACK && (IsPlayer() || IsPet()) && !victim->IsPlayer() && !victim->IsPet() && GetLevel() < victim->getLevelForTarget(this)) { // cap possible value (with bonuses > max skill) int32 skill = attackerWeaponSkill; int32 maxskill = attackerMaxSkillValueForLevel; skill = (skill > maxskill) ? maxskill : skill; tmp = (10 + (victimDefenseSkill - skill)) * 100; tmp = tmp > 4000 ? 4000 : tmp; if (roll < (sum += tmp)) { LOG_DEBUG("entities.unit", "RollMeleeOutcomeAgainst: GLANCING <{}, {})", sum - 4000, sum); return MELEE_HIT_GLANCING; } } // mobs can score crushing blows if they're 4 or more levels above victim if (getLevelForTarget(victim) >= victim->getLevelForTarget(this) + 4 && // can be from by creature (if can) or from controlled player that considered as creature !IsControlledByPlayer() && !(IsCreature() && ToCreature()->HasFlagsExtra(CREATURE_FLAG_EXTRA_NO_CRUSHING_BLOWS))) { // when their weapon skill is 15 or more above victim's defense skill tmp = victimDefenseSkill; int32 tmpmax = victimMaxSkillValueForLevel; // having defense above your maximum (from items, talents etc.) has no effect tmp = tmp > tmpmax ? tmpmax : tmp; // tmp = mob's level * 5 - player's current defense skill tmp = attackerMaxSkillValueForLevel - tmp; if (tmp >= 15) { // add 2% chance per lacking skill point, min. is 15% tmp = tmp * 200 - 1500; if (roll < (sum += tmp)) { LOG_DEBUG("entities.unit", "RollMeleeOutcomeAgainst: CRUSHING <{}, {})", sum - tmp, sum); return MELEE_HIT_CRUSHING; } } } // Critical chance tmp = crit_chance; if (tmp > 0 && roll < (sum += tmp)) { LOG_DEBUG("entities.unit", "RollMeleeOutcomeAgainst: CRIT <{}, {})", sum - tmp, sum); if (IsCreature() && (ToCreature()->HasFlagsExtra(CREATURE_FLAG_EXTRA_NO_CRIT))) LOG_DEBUG("entities.unit", "RollMeleeOutcomeAgainst: CRIT DISABLED)"); else return MELEE_HIT_CRIT; } LOG_DEBUG("entities.unit", "RollMeleeOutcomeAgainst: NORMAL"); return MELEE_HIT_NORMAL; } uint32 Unit::CalculateDamage(WeaponAttackType attType, bool normalized, bool addTotalPct, uint8 itemDamagesMask /*= 0*/) { float minDamage = 0.0f; float maxDamage = 0.0f; if (normalized || !addTotalPct || itemDamagesMask) { // get both by default if (!itemDamagesMask) { itemDamagesMask = (1 << 0) | (1 << 1); } for (uint8 i = 0; i < MAX_ITEM_PROTO_DAMAGES; ++i) { if (itemDamagesMask & (1 << i)) { float minTmp, maxTmp; CalculateMinMaxDamage(attType, normalized, addTotalPct, minTmp, maxTmp, i); minDamage += minTmp; maxDamage += maxTmp; } } } else { switch (attType) { case RANGED_ATTACK: minDamage = GetFloatValue(UNIT_FIELD_MINRANGEDDAMAGE); maxDamage = GetFloatValue(UNIT_FIELD_MAXRANGEDDAMAGE); break; case BASE_ATTACK: minDamage = GetFloatValue(UNIT_FIELD_MINDAMAGE); maxDamage = GetFloatValue(UNIT_FIELD_MAXDAMAGE); break; case OFF_ATTACK: minDamage = GetFloatValue(UNIT_FIELD_MINOFFHANDDAMAGE); maxDamage = GetFloatValue(UNIT_FIELD_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)); } float Unit::CalculateLevelPenalty(SpellInfo const* spellProto) const { if (!IsPlayer()) return 1.0f; if (spellProto->SpellLevel <= 0 || spellProto->SpellLevel >= spellProto->MaxLevel) return 1.0f; float LvlPenalty = 0.0f; // xinef: added brackets if (spellProto->SpellLevel < 20) LvlPenalty = (20.0f - spellProto->SpellLevel) * 3.75f; float LvlFactor = (float(spellProto->SpellLevel) + 6.0f) / float(GetLevel()); if (LvlFactor > 1.0f) LvlFactor = 1.0f; return AddPct(LvlFactor, -LvlPenalty); } void Unit::SendMeleeAttackStart(Unit* victim, Player* sendTo) { WorldPacket data(SMSG_ATTACKSTART, 8 + 8); data << GetGUID(); data << victim->GetGUID(); if (sendTo) sendTo->SendDirectMessage(&data); else SendMessageToSet(&data, true); LOG_DEBUG("entities.unit", "WORLD: Sent SMSG_ATTACKSTART"); } /** * @brief Send to the client SMSG_ATTACKSTOP but doesn't clear UNIT_STATE_MELEE_ATTACKING on server side * or interrupt spells. Unless you know exactly what you're doing, use AttackStop() or RemoveAllAttackers() instead */ void Unit::SendMeleeAttackStop(Unit* victim) { // pussywizard: calling SendMeleeAttackStop without clearing UNIT_STATE_MELEE_ATTACKING and then AttackStart the same player may spoil npc rotating! // pussywizard: this happens in some boss scripts, just add clearing here // ClearUnitState(UNIT_STATE_MELEE_ATTACKING); // commented out for now WorldPacket data(SMSG_ATTACKSTOP, (8 + 8 + 4)); data << GetPackGUID(); if (victim) { data << victim->GetPackGUID(); data << (uint32)victim->isDead(); } SendMessageToSet(&data, true); LOG_DEBUG("entities.unit", "WORLD: Sent SMSG_ATTACKSTOP"); if (victim) LOG_DEBUG("entities.unit", "{} {} stopped attacking {} {}", (IsPlayer() ? "Player" : "Creature"), GetGUID().ToString(), (victim->IsPlayer() ? "player" : "creature"), victim->GetGUID().ToString()); else LOG_DEBUG("entities.unit", "{} {} stopped attacking", (IsPlayer() ? "Player" : "Creature"), GetGUID().ToString()); } bool Unit::isSpellBlocked(Unit* victim, SpellInfo const* spellProto, WeaponAttackType attackType) { // These spells can't be blocked if (spellProto && spellProto->HasAttribute(SPELL_ATTR0_NO_ACTIVE_DEFENSE)) return false; if (victim->HasIgnoreHitDirectionAura() || victim->HasInArc(M_PI, this)) { // Check creatures flags_extra for disable block if (victim->IsCreature() && victim->ToCreature()->HasFlagsExtra(CREATURE_FLAG_EXTRA_NO_BLOCK)) return false; float blockChance = victim->GetUnitBlockChance(); blockChance += (int32(GetWeaponSkillValue(attackType)) - int32(victim->GetMaxSkillValueForLevel())) * 0.04f; // xinef: cant block while casting or while stunned if (blockChance < 0.0f || victim->IsNonMeleeSpellCast(false, false, true) || victim->HasUnitState(UNIT_STATE_CONTROLLED)) blockChance = 0.0f; if (roll_chance_f(blockChance)) return true; } return false; } bool Unit::isBlockCritical() { if (roll_chance_i(GetTotalAuraModifier(SPELL_AURA_MOD_BLOCK_CRIT_CHANCE))) return true; return false; } int32 Unit::GetMechanicResistChance(SpellInfo const* spell) { if (!spell) return 0; int32 resist_mech = 0; for (uint8 eff = 0; eff < MAX_SPELL_EFFECTS; ++eff) { if (!spell->Effects[eff].IsEffect()) break; int32 effect_mech = spell->GetEffectMechanic(eff); if (effect_mech) { int32 temp = GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_MECHANIC_RESISTANCE, effect_mech); if (resist_mech < temp) resist_mech = temp; } } return resist_mech; } // Melee based spells hit result calculations SpellMissInfo Unit::MeleeSpellHitResult(Unit* victim, SpellInfo const* spellInfo) { // Spells with SPELL_ATTR3_ALWAYS_HIT will additionally fully ignore // resist and deflect chances if (spellInfo->HasAttribute(SPELL_ATTR3_ALWAYS_HIT)) 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; int32 attackerWeaponSkill; // skill value for these spells (for example judgements) is 5* level if (spellInfo->DmgClass == SPELL_DAMAGE_CLASS_RANGED && !spellInfo->IsRangedWeaponSpell()) attackerWeaponSkill = GetLevel() * 5; // bonus from skills is 0.04% per skill Diff else attackerWeaponSkill = int32(GetWeaponSkillValue(attType, victim)); int32 skillDiff = attackerWeaponSkill - int32(victim->GetMaxSkillValueForLevel(this)); uint32 roll = urand (0, 10000); uint32 missChance = uint32(MeleeSpellMissChance(victim, attType, skillDiff, spellInfo->Id) * 100.0f); // Roll miss uint32 tmp = missChance; if (roll < tmp) return SPELL_MISS_MISS; bool canDodge = !spellInfo->HasAttribute(SPELL_ATTR7_NO_ATTACK_DODGE); bool canParry = !spellInfo->HasAttribute(SPELL_ATTR7_NO_ATTACK_PARRY); bool canBlock = spellInfo->HasAttribute(SPELL_ATTR3_COMPLETELY_BLOCKED) && !spellInfo->HasAttribute(SPELL_ATTR0_CU_DIRECT_DAMAGE); // Same spells cannot be parry/dodge if (spellInfo->HasAttribute(SPELL_ATTR0_NO_ACTIVE_DEFENSE)) return SPELL_MISS_NONE; // Chance resist mechanic int32 resist_chance = victim->GetMechanicResistChance(spellInfo) * 100; tmp += resist_chance; if (roll < tmp) return SPELL_MISS_RESIST; // Ranged attacks can only miss, resist and deflect if (attType == RANGED_ATTACK) { // only if in front if (!victim->HasUnitState(UNIT_STATE_STUNNED) && (victim->HasInArc(M_PI, this) || victim->HasIgnoreHitDirectionAura())) { int32 deflect_chance = victim->GetTotalAuraModifier(SPELL_AURA_DEFLECT_SPELLS) * 100; tmp += deflect_chance; if (roll < tmp) return SPELL_MISS_DEFLECT; } canDodge = false; canParry = false; } // Check for attack from behind // xinef: if from behind or spell requires cast from behind if (!victim->HasInArc(M_PI, this)) { if (!victim->HasIgnoreHitDirectionAura() || spellInfo->HasAttribute(SPELL_ATTR0_CU_REQ_CASTER_BEHIND_TARGET)) { // Can`t dodge from behind in PvP (but its possible in PvE) if (victim->IsPlayer()) { canDodge = false; } // Can`t parry or block canParry = false; canBlock = false; } } // Check creatures flags_extra for disable parry if (victim->IsCreature()) { uint32 flagEx = victim->ToCreature()->GetCreatureTemplate()->flags_extra; // Xinef: no dodge flag if (flagEx & CREATURE_FLAG_EXTRA_NO_DODGE) canDodge = false; if (flagEx & CREATURE_FLAG_EXTRA_NO_PARRY) canParry = false; // Check creatures flags_extra for disable block if (flagEx & CREATURE_FLAG_EXTRA_NO_BLOCK) canBlock = false; } // Ignore combat result aura AuraEffectList const& ignore = GetAuraEffectsByType(SPELL_AURA_IGNORE_COMBAT_RESULT); for (AuraEffectList::const_iterator i = ignore.begin(); i != ignore.end(); ++i) { if (!(*i)->IsAffectedOnSpell(spellInfo)) continue; switch ((*i)->GetMiscValue()) { case MELEE_HIT_DODGE: canDodge = false; break; case MELEE_HIT_BLOCK: canBlock = false; break; case MELEE_HIT_PARRY: canParry = false; break; default: LOG_DEBUG("entities.unit", "Spell {} SPELL_AURA_IGNORE_COMBAT_RESULT has unhandled state {}", (*i)->GetId(), (*i)->GetMiscValue()); break; } } if (canDodge) { // Roll dodge int32 dodgeChance = int32(victim->GetUnitDodgeChance() * 100.0f) - skillDiff * 4; // Reduce enemy dodge chance by SPELL_AURA_MOD_COMBAT_RESULT_CHANCE dodgeChance += GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_COMBAT_RESULT_CHANCE, VICTIMSTATE_DODGE) * 100; dodgeChance = int32(float(dodgeChance) * GetTotalAuraMultiplier(SPELL_AURA_MOD_ENEMY_DODGE)); // Reduce dodge chance by attacker expertise rating if (IsPlayer()) dodgeChance -= int32(ToPlayer()->GetExpertiseDodgeOrParryReduction(attType) * 100.0f); else dodgeChance -= GetTotalAuraModifier(SPELL_AURA_MOD_EXPERTISE) * 25; // xinef: cant dodge while casting or while stunned if (dodgeChance < 0 || victim->IsNonMeleeSpellCast(false, false, true) || victim->HasUnitState(UNIT_STATE_CONTROLLED)) dodgeChance = 0; tmp += dodgeChance; if (roll < tmp) return SPELL_MISS_DODGE; } if (canParry) { // Roll parry int32 parryChance = int32(victim->GetUnitParryChance() * 100.0f) - skillDiff * 4; // Reduce parry chance by attacker expertise rating if (IsPlayer()) parryChance -= int32(ToPlayer()->GetExpertiseDodgeOrParryReduction(attType) * 100.0f); else parryChance -= GetTotalAuraModifier(SPELL_AURA_MOD_EXPERTISE) * 25; // xinef: cant parry while casting or while stunned if (parryChance < 0 || victim->IsNonMeleeSpellCast(false, false, true) || victim->HasUnitState(UNIT_STATE_CONTROLLED)) parryChance = 0; tmp += parryChance; if (roll < tmp) return SPELL_MISS_PARRY; } if (canBlock) { int32 blockChance = int32(victim->GetUnitBlockChance() * 100.0f) - skillDiff * 4; // xinef: cant block while casting or while stunned if (blockChance < 0 || victim->IsNonMeleeSpellCast(false, false, true) || victim->HasUnitState(UNIT_STATE_CONTROLLED)) blockChance = 0; tmp += blockChance; if (roll < tmp) return SPELL_MISS_BLOCK; } return SPELL_MISS_NONE; } SpellMissInfo Unit::MagicSpellHitResult(Unit* victim, SpellInfo const* spellInfo) { // Can`t miss on dead target (on skinning for example) if (!victim->IsAlive() && !victim->IsPlayer()) return SPELL_MISS_NONE; // vehicles cant miss if (IsVehicle()) return SPELL_MISS_NONE; // Spells with SPELL_ATTR3_ALWAYS_HIT will additionally fully ignore // resist and deflect chances // xinef: skip all calculations, proof: Toxic Tolerance quest if (spellInfo->HasAttribute(SPELL_ATTR3_ALWAYS_HIT)) return SPELL_MISS_NONE; if (spellInfo->HasAttribute(SPELL_ATTR7_NO_ATTACK_MISS)) { return SPELL_MISS_NONE; } SpellSchoolMask schoolMask = spellInfo->GetSchoolMask(); int32 thisLevel = getLevelForTarget(victim); if (IsCreature() && ToCreature()->IsTrigger()) thisLevel = std::max(thisLevel, spellInfo->SpellLevel); int32 levelDiff = int32(victim->getLevelForTarget(this)) - thisLevel; int32 MISS_CHANCE_MULTIPLIER; if (sWorld->getBoolConfig(CONFIG_MISS_CHANCE_MULTIPLIER_ONLY_FOR_PLAYERS) && !IsPlayer()) // keep it as it was originally (7 and 11) { MISS_CHANCE_MULTIPLIER = victim->IsPlayer() ? 7 : 11; } else { MISS_CHANCE_MULTIPLIER = sWorld->getRate( victim->IsPlayer() ? RATE_MISS_CHANCE_MULTIPLIER_TARGET_PLAYER : RATE_MISS_CHANCE_MULTIPLIER_TARGET_CREATURE); } // Base hit chance from attacker and victim levels int32 modHitChance = levelDiff < 3 ? 96 - levelDiff : 94 - (levelDiff - 2) * MISS_CHANCE_MULTIPLIER; // Spellmod from SPELLMOD_RESIST_MISS_CHANCE if (Player* modOwner = GetSpellModOwner()) modOwner->ApplySpellMod(spellInfo->Id, SPELLMOD_RESIST_MISS_CHANCE, modHitChance); // Increase from attacker SPELL_AURA_MOD_INCREASES_SPELL_PCT_TO_HIT auras modHitChance += GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_INCREASES_SPELL_PCT_TO_HIT, schoolMask); // Spells with SPELL_ATTR3_ALWAYS_HIT will ignore target's avoidance effects // xinef: imo it should completly ignore all calculations, eg: 14792. Hits 80 level players on blizz without any problems //if (!spell->HasAttribute(SPELL_ATTR3_ALWAYS_HIT)) { // Chance hit from victim SPELL_AURA_MOD_ATTACKER_SPELL_HIT_CHANCE auras modHitChance += victim->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_ATTACKER_SPELL_HIT_CHANCE, schoolMask); // Reduce spell hit chance for Area of effect spells from victim SPELL_AURA_MOD_AOE_AVOIDANCE aura if (spellInfo->IsAffectingArea()) modHitChance -= victim->GetTotalAuraModifier(SPELL_AURA_MOD_AOE_AVOIDANCE); // Decrease hit chance from victim rating bonus if (victim->IsPlayer()) modHitChance -= int32(victim->ToPlayer()->GetRatingBonusValue(CR_HIT_TAKEN_SPELL)); } int32 HitChance = modHitChance * 100; // Increase hit chance from attacker SPELL_AURA_MOD_SPELL_HIT_CHANCE and attacker ratings // Xinef: Totems should inherit casters ratings? if (IsTotem()) { if (Unit* owner = GetOwner()) HitChance += int32(owner->m_modSpellHitChance * 100.0f); } else HitChance += int32(m_modSpellHitChance * 100.0f); if (HitChance < 100) HitChance = 100; else if (HitChance > 10000) HitChance = 10000; int32 tmp = 10000 - HitChance; int32 rand = irand(1, 10000); // Needs to be 1 to 10000 to avoid the 1/10000 chance to miss on 100% hit rating if (rand < tmp) return SPELL_MISS_MISS; // Chance resist mechanic (select max value from every mechanic spell effect) int32 resist_chance = victim->GetMechanicResistChance(spellInfo) * 100; tmp += resist_chance; // Chance resist debuff if (!spellInfo->IsPositive() && !spellInfo->HasAttribute(SPELL_ATTR4_NO_CAST_LOG)) { bool bNegativeAura = true; for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) { // Xinef: Check if effect exists! if (spellInfo->Effects[i].IsEffect() && spellInfo->Effects[i].ApplyAuraName == 0) { bNegativeAura = false; break; } } if (bNegativeAura) { tmp += victim->GetMaxPositiveAuraModifierByMiscValue(SPELL_AURA_MOD_DEBUFF_RESISTANCE, int32(spellInfo->Dispel)) * 100; tmp += victim->GetMaxNegativeAuraModifierByMiscValue(SPELL_AURA_MOD_DEBUFF_RESISTANCE, int32(spellInfo->Dispel)) * 100; } // Players resistance for binary spells if (spellInfo->HasAttribute(SPELL_ATTR0_CU_BINARY_SPELL) && (spellInfo->GetSchoolMask() & (SPELL_SCHOOL_MASK_NORMAL | SPELL_SCHOOL_MASK_HOLY)) == 0) tmp += int32(Unit::GetEffectiveResistChance(this, spellInfo->GetSchoolMask(), victim) * 10000.0f); // 100 for spell calculations, and 100 for return value percentage } // Roll chance if (rand < tmp) return SPELL_MISS_RESIST; // cast by caster in front of victim if (!victim->HasUnitState(UNIT_STATE_STUNNED) && (victim->HasInArc(M_PI, this) || victim->HasIgnoreHitDirectionAura())) { int32 deflect_chance = victim->GetTotalAuraModifier(SPELL_AURA_DEFLECT_SPELLS) * 100; tmp += deflect_chance; if (rand < tmp) return SPELL_MISS_DEFLECT; } return SPELL_MISS_NONE; } // Calculate spell hit result can be: // Every spell can: Evade/Immune/Reflect/Sucesful hit // For melee based spells: // Miss // Dodge // Parry // For spells // Resist SpellMissInfo Unit::SpellHitResult(Unit* victim, SpellInfo const* spell, bool CanReflect) { // Check for immune if (victim->IsImmunedToSpell(spell)) return SPELL_MISS_IMMUNE; // All positive spells can`t miss /// @todo: client not show miss log for this spells - so need find info for this in dbc and use it! if ((spell->IsPositive() || spell->HasEffect(SPELL_EFFECT_DISPEL)) && (!IsHostileTo(victim))) // prevent from affecting enemy by "positive" spell return SPELL_MISS_NONE; // Check for immune // xinef: check for school immunity only if (victim->IsImmunedToSchool(spell)) return SPELL_MISS_IMMUNE; if (this == victim) return SPELL_MISS_NONE; // Return evade for units in evade mode if (victim->IsCreature() && victim->ToCreature()->IsEvadingAttacks() && !spell->HasAura(SPELL_AURA_CONTROL_VEHICLE) && !spell->HasAttribute(SPELL_ATTR0_CU_IGNORE_EVADE) && !spell->HasAttribute(SPELL_ATTR1_AURA_STAYS_AFTER_COMBAT)) return SPELL_MISS_EVADE; // Try victim reflect spell if (CanReflect) { int32 reflectchance = victim->GetTotalAuraModifier(SPELL_AURA_REFLECT_SPELLS); reflectchance += victim->GetTotalAuraModifierByMiscMask(SPELL_AURA_REFLECT_SPELLS_SCHOOL, spell->GetSchoolMask()); if (reflectchance > 0 && roll_chance_i(reflectchance)) { // Start triggers for remove charges if need (trigger only for victim, and mark as active spell) //ProcDamageAndSpell(victim, PROC_FLAG_NONE, PROC_FLAG_TAKEN_SPELL_MAGIC_DMG_CLASS_NEG, PROC_EX_REFLECT, 1, BASE_ATTACK, spell); return SPELL_MISS_REFLECT; } } switch (spell->DmgClass) { case SPELL_DAMAGE_CLASS_RANGED: case SPELL_DAMAGE_CLASS_MELEE: return MeleeSpellHitResult(victim, spell); case SPELL_DAMAGE_CLASS_NONE: { if (spell->SpellFamilyName) { return SPELL_MISS_NONE; } // Xinef: apply DAMAGE_CLASS_MAGIC conditions to damaging DAMAGE_CLASS_NONE spells for (uint8 i = EFFECT_0; i < MAX_SPELL_EFFECTS; ++i) if (spell->Effects[i].Effect && spell->Effects[i].Effect != SPELL_EFFECT_SCHOOL_DAMAGE) if (spell->Effects[i].ApplyAuraName != SPELL_AURA_PERIODIC_DAMAGE) return SPELL_MISS_NONE; [[fallthrough]]; } case SPELL_DAMAGE_CLASS_MAGIC: return MagicSpellHitResult(victim, spell); } return SPELL_MISS_NONE; } SpellMissInfo Unit::SpellHitResult(Unit* victim, Spell const* spell, bool CanReflect) { SpellInfo const* spellInfo = spell->GetSpellInfo(); // Check for immune if (victim->IsImmunedToSpell(spellInfo, spell)) { return SPELL_MISS_IMMUNE; } // All positive spells can`t miss /// @todo: client not show miss log for this spells - so need find info for this in dbc and use it! if ((spellInfo->IsPositive() || spellInfo->HasEffect(SPELL_EFFECT_DISPEL)) && (!IsHostileTo(victim))) // prevent from affecting enemy by "positive" spell { return SPELL_MISS_NONE; } // Check for immune // xinef: check for school immunity only if (victim->IsImmunedToSchool(spell)) { return SPELL_MISS_IMMUNE; } if (this == victim) { return SPELL_MISS_NONE; } // Return evade for units in evade mode if (victim->IsCreature() && victim->ToCreature()->IsEvadingAttacks() && !spellInfo->HasAura(SPELL_AURA_CONTROL_VEHICLE) && !spellInfo->HasAttribute(SPELL_ATTR0_CU_IGNORE_EVADE) && !spellInfo->HasAttribute(SPELL_ATTR1_AURA_STAYS_AFTER_COMBAT)) { return SPELL_MISS_EVADE; } // Try victim reflect spell if (CanReflect) { int32 reflectchance = victim->GetTotalAuraModifier(SPELL_AURA_REFLECT_SPELLS); reflectchance += victim->GetTotalAuraModifierByMiscMask(SPELL_AURA_REFLECT_SPELLS_SCHOOL, spellInfo->GetSchoolMask()); if (reflectchance > 0 && roll_chance_i(reflectchance)) { // Start triggers for remove charges if need (trigger only for victim, and mark as active spell) //ProcDamageAndSpell(victim, PROC_FLAG_NONE, PROC_FLAG_TAKEN_SPELL_MAGIC_DMG_CLASS_NEG, PROC_EX_REFLECT, 1, BASE_ATTACK, spell); return SPELL_MISS_REFLECT; } } switch (spellInfo->DmgClass) { case SPELL_DAMAGE_CLASS_RANGED: case SPELL_DAMAGE_CLASS_MELEE: return MeleeSpellHitResult(victim, spellInfo); case SPELL_DAMAGE_CLASS_NONE: { if (spellInfo->SpellFamilyName) { return SPELL_MISS_NONE; } // Xinef: apply DAMAGE_CLASS_MAGIC conditions to damaging DAMAGE_CLASS_NONE spells for (uint8 i = EFFECT_0; i < MAX_SPELL_EFFECTS; ++i) { if (spellInfo->Effects[i].Effect && spellInfo->Effects[i].Effect != SPELL_EFFECT_SCHOOL_DAMAGE) { if (spellInfo->Effects[i].ApplyAuraName != SPELL_AURA_PERIODIC_DAMAGE) { return SPELL_MISS_NONE; } } } [[fallthrough]]; } case SPELL_DAMAGE_CLASS_MAGIC: return MagicSpellHitResult(victim, spellInfo); } return SPELL_MISS_NONE; } uint32 Unit::GetDefenseSkillValue(Unit const* target) const { if (IsPlayer()) { // in PvP use full skill instead current skill value uint32 value = (target && target->IsPlayer()) ? ToPlayer()->GetMaxSkillValue(SKILL_DEFENSE) : ToPlayer()->GetSkillValue(SKILL_DEFENSE); value += uint32(ToPlayer()->GetRatingBonusValue(CR_DEFENSE_SKILL)); return value; } else return GetUnitMeleeSkill(target); } float Unit::GetUnitDodgeChance() const { if (IsPlayer()) return ToPlayer()->GetRealDodge(); //GetFloatValue(PLAYER_DODGE_PERCENTAGE); else { if (ToCreature()->IsTotem()) return 0.0f; else { float dodge = ToCreature()->isWorldBoss() ? 5.85f : 5.0f; // Xinef: bosses should have 6.5% dodge (5.9 + 0.6 from defense skill difference) dodge += GetTotalAuraModifier(SPELL_AURA_MOD_DODGE_PERCENT); return dodge > 0.0f ? dodge : 0.0f; } } } float Unit::GetUnitParryChance() const { float chance = 0.0f; if (Player const* player = ToPlayer()) { if (player->CanParry()) { Item* tmpitem = player->GetWeaponForAttack(BASE_ATTACK, true); if (!tmpitem) tmpitem = player->GetWeaponForAttack(OFF_ATTACK, true); if (tmpitem) chance = player->GetRealParry(); //GetFloatValue(PLAYER_PARRY_PERCENTAGE); } } else if (IsCreature()) { if (ToCreature()->isWorldBoss()) chance = 13.4f; // + 0.6 by skill diff else if (GetCreatureType() == CREATURE_TYPE_HUMANOID) chance = 5.0f; // Xinef: if aura is present, type should not matter chance += GetTotalAuraModifier(SPELL_AURA_MOD_PARRY_PERCENT); } return chance > 0.0f ? chance : 0.0f; } float Unit::GetUnitMissChance(WeaponAttackType attType) const { float miss_chance = 5.00f; if (Player const* player = ToPlayer()) miss_chance += player->GetMissPercentageFromDefence(); if (attType == RANGED_ATTACK) miss_chance -= GetTotalAuraModifier(SPELL_AURA_MOD_ATTACKER_RANGED_HIT_CHANCE); else miss_chance -= GetTotalAuraModifier(SPELL_AURA_MOD_ATTACKER_MELEE_HIT_CHANCE); return miss_chance; } float Unit::GetUnitBlockChance() const { if (Player const* player = ToPlayer()) { if (player->CanBlock()) { Item* tmpitem = player->GetUseableItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND); if (tmpitem && !tmpitem->IsBroken() && tmpitem->GetTemplate()->Block) return GetFloatValue(PLAYER_BLOCK_PERCENTAGE); } // is player but has no block ability or no not broken shield equipped return 0.0f; } else { if (ToCreature()->IsTotem()) return 0.0f; else { float block = 5.0f; block += GetTotalAuraModifier(SPELL_AURA_MOD_BLOCK_PERCENT); return block > 0.0f ? block : 0.0f; } } } float Unit::GetUnitCriticalChance(WeaponAttackType attackType, Unit const* victim) const { float crit; if (IsPlayer()) { switch (attackType) { case BASE_ATTACK: crit = GetFloatValue(PLAYER_CRIT_PERCENTAGE); break; case OFF_ATTACK: crit = GetFloatValue(PLAYER_OFFHAND_CRIT_PERCENTAGE); break; case RANGED_ATTACK: crit = GetFloatValue(PLAYER_RANGED_CRIT_PERCENTAGE); break; // Just for good manner default: crit = 0.0f; break; } } else { crit = 5.0f; crit += GetTotalAuraModifier(SPELL_AURA_MOD_WEAPON_CRIT_PERCENT); crit += GetTotalAuraModifier(SPELL_AURA_MOD_CRIT_PCT); } // flat aura mods if (attackType == RANGED_ATTACK) crit += victim->GetTotalAuraModifier(SPELL_AURA_MOD_ATTACKER_RANGED_CRIT_CHANCE); else crit += victim->GetTotalAuraModifier(SPELL_AURA_MOD_ATTACKER_MELEE_CRIT_CHANCE); crit += victim->GetTotalAuraModifier(SPELL_AURA_MOD_CRIT_CHANCE_FOR_CASTER, [this](AuraEffect const* aurEff) { return GetGUID() == aurEff->GetCasterGUID(); }); // reduce crit chance from Rating for players if (attackType != RANGED_ATTACK) Unit::ApplyResilience(victim, &crit, nullptr, false, CR_CRIT_TAKEN_MELEE); else Unit::ApplyResilience(victim, &crit, nullptr, false, CR_CRIT_TAKEN_RANGED); // Apply crit chance from defence skill crit += (int32(GetMaxSkillValueForLevel(victim)) - int32(victim->GetDefenseSkillValue(this))) * 0.04f; // xinef: SPELL_AURA_MOD_ATTACKER_SPELL_AND_WEAPON_CRIT_CHANCE should be calculated at the end crit += victim->GetTotalAuraModifier(SPELL_AURA_MOD_ATTACKER_SPELL_AND_WEAPON_CRIT_CHANCE); if (crit < 0.0f) crit = 0.0f; return crit; } uint32 Unit::GetWeaponSkillValue (WeaponAttackType attType, Unit const* target) const { uint32 value = 0; if (Player const* player = ToPlayer()) { Item* item = player->GetWeaponForAttack(attType, true); // feral or unarmed skill only for base attack if (attType != BASE_ATTACK && !item) return 0; if (IsInFeralForm()) return GetMaxSkillValueForLevel(); // always maximized SKILL_FERAL_COMBAT in fact // weapon skill or (unarmed for base attack) uint32 skill = SKILL_UNARMED; if (item) skill = item->GetSkill(); // in PvP use full skill instead current skill value value = (target && target->IsControlledByPlayer()) ? player->GetMaxSkillValue(skill) : player->GetSkillValue(skill); // Modify value from ratings value += uint32(player->GetRatingBonusValue(CR_WEAPON_SKILL)); switch (attType) { case BASE_ATTACK: value += uint32(player->GetRatingBonusValue(CR_WEAPON_SKILL_MAINHAND)); break; case OFF_ATTACK: value += uint32(player->GetRatingBonusValue(CR_WEAPON_SKILL_OFFHAND)); break; case RANGED_ATTACK: value += uint32(player->GetRatingBonusValue(CR_WEAPON_SKILL_RANGED)); break; default: break; } } else value = GetUnitMeleeSkill(target); return value; } void Unit::_DeleteRemovedAuras() { while (!m_removedAuras.empty()) { delete m_removedAuras.front(); m_removedAuras.pop_front(); } } void Unit::_UpdateSpells(uint32 time) { if (m_currentSpells[CURRENT_AUTOREPEAT_SPELL]) _UpdateAutoRepeatSpell(); // remove finished spells from current pointers for (uint32 i = 0; i < CURRENT_MAX_SPELL; ++i) { if (m_currentSpells[i] && m_currentSpells[i]->getState() == SPELL_STATE_FINISHED) { m_currentSpells[i]->SetReferencedFromCurrent(false); m_currentSpells[i] = 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 (VisibleAuraMap::iterator itr = m_visibleAuras.begin(); itr != m_visibleAuras.end(); ++itr) if (itr->second->IsNeedClientUpdate()) itr->second->ClientUpdate(); _DeleteRemovedAuras(); if (!m_gameObj.empty()) { for (GameObjectList::iterator itr = m_gameObj.begin(); itr != m_gameObj.end();) { if (GameObject* go = ObjectAccessor::GetGameObject(*this, *itr)) if (!go->isSpawned()) { go->SetOwnerGUID(ObjectGuid::Empty); go->SetRespawnTime(0); go->Delete(); m_gameObj.erase(itr++); continue; } ++itr; } } } void Unit::_UpdateAutoRepeatSpell() { SpellInfo const* spellProto = nullptr; if (m_currentSpells[CURRENT_AUTOREPEAT_SPELL]) { spellProto = m_currentSpells[CURRENT_AUTOREPEAT_SPELL]->m_spellInfo; } if (!spellProto) { return; } static uint32 const HUNTER_AUTOSHOOT = 75; // Check "realtime" interrupts if ((IsPlayer() && ToPlayer()->isMoving() && spellProto->Id != HUNTER_AUTOSHOOT) || IsNonMeleeSpellCast(false, false, true, spellProto->Id == HUNTER_AUTOSHOOT)) { // cancel wand shoot if (spellProto->Id != HUNTER_AUTOSHOOT) InterruptSpell(CURRENT_AUTOREPEAT_SPELL); m_AutoRepeatFirstCast = true; return; } // Apply delay (Hunter's autoshoot not affected) if (m_AutoRepeatFirstCast && getAttackTimer(RANGED_ATTACK) < 500 && spellProto->Id != HUNTER_AUTOSHOOT) { setAttackTimer(RANGED_ATTACK, 500); } m_AutoRepeatFirstCast = false; // Check for ranged attack timer if (isAttackReady(RANGED_ATTACK)) { SpellCastResult result = m_currentSpells[CURRENT_AUTOREPEAT_SPELL]->CheckCast(true); if (result != SPELL_CAST_OK) { if (spellProto->Id != HUNTER_AUTOSHOOT) { InterruptSpell(CURRENT_AUTOREPEAT_SPELL); } return; } // We want to shoot Spell* spell = new Spell(this, spellProto, TRIGGERED_FULL_MASK); spell->prepare(&(m_currentSpells[CURRENT_AUTOREPEAT_SPELL]->m_targets)); // Reset attack resetAttackTimer(RANGED_ATTACK); } } bool Unit::CanSparringWith(Unit const* attacker) const { if (!IsCreature() || IsCharmedOwnedByPlayerOrPlayer()) return false; if (!attacker) return false; if (!attacker->IsCreature() || attacker->IsCharmedOwnedByPlayerOrPlayer()) return false; if (Creature const* creature = ToCreature()) if (!creature->GetSparringPct()) return false; return true; } void Unit::SetCurrentCastedSpell(Spell* pSpell) { ASSERT(pSpell); // nullptr may be never passed here, use InterruptSpell or InterruptNonMeleeSpells CurrentSpellTypes CSpellType = pSpell->GetCurrentContainer(); if (pSpell == m_currentSpells[CSpellType]) // avoid breaking self return; bool bySelf = m_currentSpells[CSpellType] && m_currentSpells[CSpellType]->m_spellInfo->Id == pSpell->m_spellInfo->Id; // break same type spell if it is not delayed InterruptSpell(CSpellType, false, true, bySelf); // special breakage effects: switch (CSpellType) { case CURRENT_GENERIC_SPELL: { // generic spells always break channeled not delayed spells if (Spell* s = GetCurrentSpell(CURRENT_CHANNELED_SPELL)) { if (!s->GetSpellInfo()->IsActionAllowedChannel()) { 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]->m_spellInfo->Id != 75) InterruptSpell(CURRENT_AUTOREPEAT_SPELL); m_AutoRepeatFirstCast = true; } // melee spells breaking if (m_currentSpells[CURRENT_MELEE_SPELL]) { // break melee spells if cast time if (pSpell->GetCastTime() > 0) { InterruptSpell(CURRENT_MELEE_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, true, true, bySelf); // it also does break autorepeat if not Auto Shot if (m_currentSpells[CURRENT_AUTOREPEAT_SPELL] && m_currentSpells[CURRENT_AUTOREPEAT_SPELL]->m_spellInfo->Id != 75) InterruptSpell(CURRENT_AUTOREPEAT_SPELL); AddUnitState(UNIT_STATE_CASTING); break; } case CURRENT_AUTOREPEAT_SPELL: { // only Auto Shoot does not break anything if (pSpell->m_spellInfo->Id != 75) { // generic autorepeats break generic non-delayed and channeled non-delayed spells if (Spell* s = GetCurrentSpell(CURRENT_CHANNELED_SPELL)) { if (!s->GetSpellInfo()->IsActionAllowedChannel()) { InterruptSpell(CURRENT_CHANNELED_SPELL, false); } } InterruptSpell(CURRENT_CHANNELED_SPELL, false); } // special action: set first cast flag m_AutoRepeatFirstCast = true; break; } default: // other spell types don't break anything now break; } // 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, bool bySelf) { //LOG_DEBUG("entities.unit", "Interrupt spell for unit {}.", GetEntry()); Spell* spell = m_currentSpells[spellType]; if (spell && (withDelayed || spell->getState() != SPELL_STATE_DELAYED) && (withInstant || spell->GetCastTime() > 0 || spell->getState() == SPELL_STATE_CASTING)) // xinef: or spell is in casting state (channeled spells only) { // 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 (IsPlayer()) ToPlayer()->SendAutoRepeatCancel(this); if (spell->getState() != SPELL_STATE_FINISHED) spell->cancel(bySelf); else { m_currentSpells[spellType] = nullptr; spell->SetReferencedFromCurrent(false); } if (IsCreature() && IsAIEnabled) ToCreature()->AI()->OnSpellCastFinished(spell->GetSpellInfo(), SPELL_FINISHED_CANCELED); } } void Unit::FinishSpell(CurrentSpellTypes spellType, bool ok /*= true*/) { Spell* spell = m_currentSpells[spellType]; if (!spell) return; if (spellType == CURRENT_CHANNELED_SPELL) spell->SendChannelUpdate(0); spell->finish(ok); } 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_DELAYED)) { 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, bool bySelf) { // 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, bySelf); // 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, bySelf); // 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, bySelf); } Spell* Unit::GetFirstCurrentCastingSpell() const { for (uint32 i = 0; i < CURRENT_MAX_SPELL; i++) if (m_currentSpells[i] && m_currentSpells[i]->GetCastTimeRemaining() > 0) return m_currentSpells[i]; return nullptr; } Spell* Unit::FindCurrentSpellBySpellId(uint32 spell_id) const { for (uint32 i = 0; i < CURRENT_MAX_SPELL; i++) if (m_currentSpells[i] && m_currentSpells[i]->m_spellInfo->Id == spell_id) return m_currentSpells[i]; 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; } // 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()->IsActionAllowedChannel()) { return false; } } } // prohibit movement for all other spell casts return true; } 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 * M_PI - arc, target); } bool Unit::isInAccessiblePlaceFor(Creature const* c) const { if (c->GetMapId() == MAP_THE_RING_OF_VALOR) { // skip transport check, check for being below floor level if (this->GetPositionZ() < 28.0f) return false; if (BattlegroundMap* bgMap = c->GetMap()->ToBattlegroundMap()) if (Battleground* bg = bgMap->GetBG()) if (bg->GetStartTime() < 80133) // 60000ms preparation time + 20133ms elevator rise time return false; } else if (c->GetMapId() == MAP_ICECROWN_CITADEL) { // if static transport doesn't match - return false if (c->GetTransport() != this->GetTransport() && ((c->GetTransport() && c->GetTransport()->IsStaticTransport()) || (this->GetTransport() && this->GetTransport()->IsStaticTransport()))) return false; // special handling for ICC (map 631), for non-flying pets in Gunship Battle, for trash npcs this is done via CanAIAttack if (c->GetOwnerGUID().IsPlayer() && !c->CanFly()) { if (c->GetTransport() != this->GetTransport()) return false; if (this->GetTransport()) { if (c->GetPositionY() < 2033.0f) { if (this->GetPositionY() > 2033.0f) return false; } else if (c->GetPositionY() < 2438.0f) { if (this->GetPositionY() < 2033.0f || this->GetPositionY() > 2438.0f) return false; } else if (this->GetPositionY() < 2438.0f) return false; } } } else { // pussywizard: prevent any bugs by passengers exiting transports or normal creatures flying away if (c->GetTransport() != this->GetTransport()) return false; } LiquidStatus liquidStatus = GetLiquidData().Status; bool isInWater = (liquidStatus & MAP_LIQUID_STATUS_IN_CONTACT) != 0; // In water or jumping in water if (isInWater || (liquidStatus == LIQUID_MAP_ABOVE_WATER && (IsFalling() || (ToPlayer() && ToPlayer()->IsFalling())))) { return c->CanEnterWater(); } else { return c->CanWalk() || c->CanFly(); } } void Unit::ProcessPositionDataChanged(PositionFullTerrainStatus const& data) { WorldObject::ProcessPositionDataChanged(data); ProcessTerrainStatusUpdate(); } void Unit::ProcessTerrainStatusUpdate() { if (IsCreature()) ToCreature()->UpdateMovementFlags(); if (IsFlying() || (!IsControlledByPlayer())) return; LiquidData const& liquidData = GetLiquidData(); // remove appropriate auras if we are swimming/not swimming respectively - exact mirror of client logic if (liquidData.Status & MAP_LIQUID_STATUS_SWIMMING && (liquidData.Level - GetPositionZ()) > GetCollisionHeight() * 0.75f) // Shallow water at ~75% of collision height) RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_NOT_ABOVEWATER); else RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_NOT_UNDERWATER); // liquid aura handling LiquidTypeEntry const* curLiquid = nullptr; if ((liquidData.Status & MAP_LIQUID_STATUS_SWIMMING)) curLiquid = sLiquidTypeStore.LookupEntry(liquidData.Entry); if (curLiquid != _lastLiquid) { if (_lastLiquid && _lastLiquid->SpellId) RemoveAurasDueToSpell(_lastLiquid->SpellId); // Set _lastLiquid before casting liquid spell to avoid infinite loops _lastLiquid = curLiquid; Player* player = GetCharmerOrOwnerPlayerOrPlayerItself(); if (curLiquid && curLiquid->SpellId && (!player || !player->IsGameMaster())) CastSpell(this, curLiquid->SpellId, true); } } SafeUnitPointer::~SafeUnitPointer() { if (ptr != defaultValue && ptr) ptr->RemovePointedBy(this); ptr = defaultValue; } void SafeUnitPointer::SetPointedTo(Unit* u) { if (ptr != defaultValue && ptr) ptr->RemovePointedBy(this); ptr = u; if (ptr != defaultValue && ptr) ptr->AddPointedBy(this); } void SafeUnitPointer::UnitDeleted() { LOG_INFO("misc", "SafeUnitPointer::UnitDeleted !!!"); if (defaultValue) { if (Player* p = defaultValue->ToPlayer()) { LOG_INFO("misc", "SafeUnitPointer::UnitDeleted (A1) - {}, {}, {}, {}, {}, {}, {}, {}", p->GetGUID().ToString(), p->GetMapId(), p->GetInstanceId(), p->FindMap()->GetId(), p->IsInWorld() ? 1 : 0, p->IsDuringRemoveFromWorld() ? 1 : 0, p->IsBeingTeleported() ? 1 : 0, p->isBeingLoaded() ? 1 : 0); if (ptr) LOG_INFO("misc", "SafeUnitPointer::UnitDeleted (A2)"); p->GetSession()->KickPlayer("Unit deleted"); } } else if (ptr) LOG_INFO("misc", "SafeUnitPointer::UnitDeleted (B1)"); ptr = defaultValue; } void Unit::HandleSafeUnitPointersOnDelete(Unit* thisUnit) { if (thisUnit->SafeUnitPointerSet.empty()) return; for (std::set::iterator itr = thisUnit->SafeUnitPointerSet.begin(); itr != thisUnit->SafeUnitPointerSet.end(); ++itr) (*itr)->UnitDeleted(); thisUnit->SafeUnitPointerSet.clear(); } bool Unit::IsInWater() const { return (GetLiquidData().Status & MAP_LIQUID_STATUS_SWIMMING) != 0; } bool Unit::IsUnderWater() const { return GetLiquidData().Status == LIQUID_MAP_UNDER_WATER; } void Unit::DeMorph() { SetDisplayId(GetNativeDisplayId()); } 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 (uint32 i = 0 ; i < MAX_SPELL_EFFECTS; ++i) if (AuraEffect const* aurEff = aura->GetEffect(i)) 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, uint8 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) { int32 diff = abs(effectAmount) - abs(existingAurEff->GetAmount()); if (!diff) for (int32 i = 0; i < MAX_SPELL_EFFECTS; ++i) diff += int32((auraEffectMask & (1 << i)) >> i) - int32((existingAurEff->GetBase()->GetEffectMask() & (1 << i)) >> i); 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) itr = auras.begin(); } } } else if (diff < 0) return false; } } return true; } Aura* Unit::_TryStackingOrRefreshingExistingAura(SpellInfo const* newAura, uint8 effMask, Unit* caster, int32* baseAmount /*= nullptr*/, Item* castItem /*= nullptr*/, ObjectGuid casterGUID /*= ObjectGuid::Empty*/, bool periodicReset /*= false*/) { ASSERT(casterGUID || caster); if (!casterGUID) casterGUID = caster->GetGUID(); // passive and Incanter's Absorption and auras with different type can stack with themselves any number of times if (!newAura->IsMultiSlotAura()) { // check if cast item changed ObjectGuid castItemGUID; if (castItem) castItemGUID = castItem->GetGUID(); // find current aura from spell and change it's stackamount, or refresh it's duration if (Aura* foundAura = GetOwnedAura(newAura->Id, newAura->HasAttribute(SPELL_ATTR0_CU_SINGLE_AURA_STACK) ? ObjectGuid::Empty : casterGUID, newAura->HasAttribute(SPELL_ATTR0_CU_ENCHANT_PROC) ? castItemGUID : ObjectGuid::Empty, 0)) { // effect masks do not match // extremely rare case // let's just recreate aura if (effMask != foundAura->GetEffectMask()) return nullptr; // update basepoints with new values - effect amount will be recalculated in ModStackAmount for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) { if (!foundAura->HasEffect(i)) continue; int bp; if (baseAmount) bp = *(baseAmount + i); else bp = foundAura->GetSpellInfo()->Effects[i].BasePoints; int32* oldBP = const_cast(&(foundAura->GetEffect(i)->m_baseAmount)); *oldBP = bp; } // correct cast item guid if needed if (castItemGUID != foundAura->GetCastItemGUID()) { ObjectGuid* oldGUID = const_cast(&foundAura->m_castItemGuid); *oldGUID = castItemGUID; } // try to increase stack amount foundAura->ModStackAmount(1, AURA_REMOVE_BY_DEFAULT, periodicReset); sScriptMgr->OnAuraApply(this, foundAura); return foundAura; } } return nullptr; } void Unit::_AddAura(UnitAura* aura, Unit* caster) { ASSERT(!m_cleanupDone); m_ownedAuras.insert(AuraMap::value_type(aura->GetId(), aura)); _RemoveNoStackAurasDueToAura(aura, true); if (aura->IsRemoved()) return; aura->SetIsSingleTarget(caster && (aura->GetSpellInfo()->IsSingleTarget() || aura->HasEffectType(SPELL_AURA_CONTROL_VEHICLE))); 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 (player mounts with passengers) */ // register single target aura caster->GetSingleCastAuras().push_back(aura); // remove other single target auras Unit::AuraList& scAuras = caster->GetSingleCastAuras(); for (Unit::AuraList::iterator itr = scAuras.begin(); itr != scAuras.end();) { if ((*itr) != aura && (*itr)->IsSingleTargetWith(aura)) { (*itr)->Remove(); itr = scAuras.begin(); } else ++itr; } } } // 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, uint8 effMask) { // can't apply aura on unit which is going to be deleted - to not create a memory leak ASSERT(!m_cleanupDone); // aura musn't be removed ASSERT(!aura->IsRemoved()); // 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) // Xinef: Added IsAllowingDeadTarget check if (!IsAlive() && !aurSpellInfo->IsDeathPersistent() && !aurSpellInfo->IsAllowingDeadTarget() && (!IsPlayer() || !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)); // xinef: do not insert our application to interruptible list if application target is not the owner (area auras) // xinef: even if it gets removed, it will be reapplied in a second if (aurSpellInfo->AuraInterruptFlags && this == aura->GetOwner()) { m_interruptableAuras.push_back(aurApp); AddInterruptMask(aurSpellInfo->AuraInterruptFlags); } 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, uint8 effMask) { Aura* aura = aurApp->GetBase(); _RemoveNoStackAurasDueToAura(aura, false); if (aurApp->GetRemoveMode()) return; Unit* caster = aura->GetCaster(); // Update target aura state flag SpellInfo const* spellInfo = aura->GetSpellInfo(); if (AuraStateType aState = spellInfo->GetAuraState()) { uint32 aStateMask = (1 << (aState - 1)); // force update so the new caster registers it if ((aStateMask & PER_CASTER_AURA_STATE_MASK) && HasFlag(UNIT_FIELD_AURASTATE, aStateMask)) ForceValuesUpdateAtIndex(UNIT_FIELD_AURASTATE); else ModifyAuraState(aState, true); } if (aurApp->GetRemoveMode()) return; // Sitdown on apply aura req seated if (spellInfo->AuraInterruptFlags & AURA_INTERRUPT_FLAG_NOT_SEATED && !IsSitState()) SetStandState(UNIT_STAND_STATE_SIT); if (aurApp->GetRemoveMode()) return; aura->HandleAuraSpecificMods(aurApp, caster, true, false); // apply effects of the aura for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) { if (effMask & 1 << i && (!aurApp->GetRemoveMode())) aurApp->_HandleEffect(i, true); } sScriptMgr->OnAuraApply(this, aura); } // 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(); LOG_DEBUG("spells.aura", "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); // xinef: do not insert our application to interruptible list if application target is not the owner (area auras) // xinef: event if it gets removed, it will be reapplied in a second if (aura->GetSpellInfo()->AuraInterruptFlags && this == aura->GetOwner()) { m_interruptableAuras.remove(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 (uint8 itr = 0; itr < MAX_SPELL_EFFECTS; ++itr) { if (aurApp->HasEffect(itr)) aurApp->_HandleEffect(itr, 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 && IsTotem() && GetGUID() == aura->GetCasterGUID()) { if (ToTotem()->GetSpell() == aura->GetId() && ToTotem()->GetTotemType() == TOTEM_PASSIVE) ToTotem()->setDeathState(DeathState::JustDied); } // 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) ForceValuesUpdateAtIndex(UNIT_FIELD_AURASTATE); } } aura->HandleAuraSpecificMods(aurApp, caster, false, false); // only way correctly remove all auras from list //if (removedAuras != m_removedAurasCount) new aura may be added i = m_appliedAuras.begin(); sScriptMgr->OnAuraRemove(this, aurApp, removeMode); if (this->ToCreature() && this->ToCreature()->IsAIEnabled) this->ToCreature()->AI()->OnAuraRemove(aurApp, removeMode); } 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) // xinef: this check makes caster to have 2 area auras thanks to spec switch // if (spellProto->IsPassiveStackableWithRanks()) // return; ASSERT(aura); if (!IsHighestExclusiveAura(aura)) { aura->Remove(); return; } if (owned) RemoveOwnedAuras([aura](Aura const* ownedAura) { return !aura->CanStackWith(ownedAura); }); else RemoveAppliedAuras([aura](AuraApplication const* appliedAura) { return !aura->CanStackWith(appliedAura->GetBase()); }); } void Unit::_RegisterAuraEffect(AuraEffect* aurEff, bool apply) { if (apply) m_modAuras[aurEff->GetAuraType()].push_back(aurEff); else m_modAuras[aurEff->GetAuraType()].erase(std::remove(m_modAuras[aurEff->GetAuraType()].begin(), m_modAuras[aurEff->GetAuraType()].end(), aurEff), m_modAuras[aurEff->GetAuraType()].end()); } // All aura base removes should go threw 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.end()) ++m_auraUpdateIterator; m_ownedAuras.erase(i); m_removedAuras.push_back(aura); // Unregister single target aura if (aura->IsSingleTarget()) aura->UnregisterSingleTarget(); aura->_Remove(removeMode); i = m_ownedAuras.begin(); } void Unit::RemoveOwnedAura(uint32 spellId, ObjectGuid casterGUID, uint8 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); 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, uint8 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, uint8 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 (uint8 itr = 0; itr < MAX_SPELL_EFFECTS; ++itr) { if (aurApp->HasEffect(itr)) aurApp->_HandleEffect(itr, 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) { // Prevent Arena Preparation aura from being removed by player actions // It's an invisibility spell so any interaction/spell cast etc. removes it. // Should only be removed by the arena script, once the match starts. if (aurApp->GetBase()->HasEffectType(SPELL_AURA_ARENA_PREPARATION)) { return; } 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::RemoveOwnedAuras(std::function const& check) { for (AuraMap::iterator iter = m_ownedAuras.begin(); iter != m_ownedAuras.end();) { if (check(iter->second)) { RemoveOwnedAura(iter); continue; } ++iter; } } void Unit::RemoveAppliedAuras(std::function const& check) { for (AuraApplicationMap::iterator iter = m_appliedAuras.begin(); iter != m_appliedAuras.end();) { if (check(iter->second)) { RemoveAura(iter); continue; } ++iter; } } void Unit::RemoveOwnedAuras(uint32 spellId, std::function const& check) { for (AuraMap::iterator iter = m_ownedAuras.lower_bound(spellId); iter != m_ownedAuras.upper_bound(spellId);) { if (check(iter->second)) { RemoveOwnedAura(iter); continue; } ++iter; } } void Unit::RemoveAppliedAuras(uint32 spellId, std::function const& check) { for (AuraApplicationMap::iterator iter = m_appliedAuras.lower_bound(spellId); iter != m_appliedAuras.upper_bound(spellId);) { if (check(iter->second)) { RemoveAura(iter); continue; } ++iter; } } void Unit::RemoveAurasDueToSpell(uint32 spellId, ObjectGuid casterGUID, uint8 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) { 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(-1, removeMode); return; } else ++iter; } } void Unit::RemoveAurasDueToSpellByDispel(uint32 spellId, uint32 dispellerSpellId, ObjectGuid casterGUID, Unit* 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); switch (aura->GetSpellInfo()->SpellFamilyName) { case SPELLFAMILY_HUNTER: { // Noxious Stings if (aura->GetSpellInfo()->SpellFamilyFlags[1] & 0x1000) { if (Unit* caster = aura->GetCaster()) { if (AuraEffect* aureff = caster->GetAuraEffect(SPELL_AURA_OVERRIDE_CLASS_SCRIPTS, SPELLFAMILY_HUNTER, 3521, 1)) { if (Aura* noxious = Aura::TryCreate(aura->GetSpellInfo(), aura->GetEffectMask(), dispeller, caster)) { noxious->SetDuration(aura->GetDuration() * aureff->GetAmount() / 100); if (aura->GetUnitOwner()) if (const std::vector* spell_triggered = sSpellMgr->GetSpellLinked(-int32(aura->GetId()))) for (std::vector::const_iterator itr = spell_triggered->begin(); itr != spell_triggered->end(); ++itr) aura->GetUnitOwner()->RemoveAurasDueToSpell(*itr); } } } } break; } case SPELLFAMILY_DEATHKNIGHT: { // Icy Clutch, remove with Frost Fever if (aura->GetSpellInfo()->SpellFamilyFlags[1] & 0x4000000) { if (AuraEffect* aureff = GetAuraEffect(SPELL_AURA_MOD_DECREASE_SPEED, SPELLFAMILY_DEATHKNIGHT, 0, 0x40000, 0, casterGUID)) RemoveAurasDueToSpell(aureff->GetId()); } } default: break; } return; } else ++iter; } } void Unit::RemoveAurasDueToSpellBySteal(uint32 spellId, ObjectGuid casterGUID, Unit* stealer) { AuraMapBoundsNonConst range = m_ownedAuras.equal_range(spellId); for (AuraMap::iterator iter = range.first; iter != range.second;) { Aura* aura = iter->second; if (aura->GetCasterGUID() == casterGUID) { int32 damage[MAX_SPELL_EFFECTS]; int32 baseDamage[MAX_SPELL_EFFECTS]; uint8 effMask = 0; uint8 recalculateMask = 0; Unit* caster = aura->GetCaster(); for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) { if (aura->GetEffect(i)) { baseDamage[i] = aura->GetEffect(i)->GetBaseAmount(); damage[i] = aura->GetEffect(i)->GetAmount(); effMask |= (1 << i); if (aura->GetEffect(i)->CanBeRecalculated()) recalculateMask |= (1 << i); } else { baseDamage[i] = 0; damage[i] = 0; } } 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 (Aura* oldAura = stealer->GetAura(aura->GetId(), aura->GetCasterGUID())) { if (stealCharge) oldAura->ModCharges(1); else oldAura->ModStackAmount(1); 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(); // Xinef: if stealer has same aura Aura* curAura = stealer->GetAura(aura->GetId()); if (!curAura || (!curAura->IsPermanent() && curAura->GetDuration() < (int32)dur)) if (Aura* newAura = Aura::TryRefreshStackOrCreate(aura->GetSpellInfo(), effMask, stealer, nullptr, &baseDamage[0], nullptr, aura->GetCasterGUID())) { // 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_back(aura); } // FIXME: using aura->GetMaxDuration() maybe not blizzlike but it fixes stealing of spells like Innervate newAura->SetLoadedState(aura->GetMaxDuration(), int32(dur), stealCharge ? 1 : aura->GetCharges(), 1, recalculateMask, &damage[0]); newAura->ApplyForTargets(); } } if (stealCharge) aura->ModCharges(-1, AURA_REMOVE_BY_ENEMY_SPELL); else aura->ModStackAmount(-1, 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; } for (AuraMap::iterator iter = m_ownedAuras.lower_bound(spellId); iter != m_ownedAuras.upper_bound(spellId);) { if (iter->second->GetCastItemGUID() == castItemGuid) { RemoveOwnedAura(iter, AURA_REMOVE_BY_DEFAULT); iter = m_ownedAuras.lower_bound(spellId); } else ++iter; } } void Unit::RemoveAurasByType(AuraType auraType, ObjectGuid casterGUID, Aura* except, bool negative, bool positive) { // simple check if list is empty if (m_modAuras[auraType].empty()) return; for (AuraEffectList::iterator iter = m_modAuras[auraType].begin(); iter != m_modAuras[auraType].end();) { Aura* aura = (*iter)->GetBase(); AuraApplication* aurApp = aura->GetApplicationOfTarget(GetGUID()); ++iter; if (aura != except && (!casterGUID || aura->GetCasterGUID() == casterGUID) && ((negative && !aurApp->IsPositive()) || (positive && aurApp->IsPositive()))) { uint32 removedAuras = m_removedAurasCount; RemoveAura(aurApp); if (m_removedAurasCount > removedAuras) 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() { // 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()) RemoveOwnedAura(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) { aura->Remove(); iter = scAuras.begin(); } else ++iter; } } void Unit::RemoveAurasWithInterruptFlags(uint32 flag, uint32 except, bool isAutoshot /*= false*/) { if (!(m_interruptMask & flag)) return; // interrupt auras for (AuraApplicationList::iterator iter = m_interruptableAuras.begin(); iter != m_interruptableAuras.end();) { Aura* aura = (*iter)->GetBase(); ++iter; if ((aura->GetSpellInfo()->AuraInterruptFlags & flag) && (!except || aura->GetId() != except)) { uint32 removedAuras = m_removedAurasCount; RemoveAura(aura); 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_CASTING && (spell->m_spellInfo->ChannelInterruptFlags & flag) && spell->m_spellInfo->Id != except) { // Do not interrupt if auto shot if (!(isAutoshot && spell->m_spellInfo->HasAttribute(SPELL_ATTR2_DO_NOT_RESET_COMBAT_TIMERS))) { InterruptNonMeleeSpells(false, spell->m_spellInfo->Id); } } } UpdateInterruptMask(); } void Unit::RemoveAurasWithFamily(SpellFamilyNames family, uint32 familyFlag1, uint32 familyFlag2, uint32 familyFlag3, 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.HasFlag(familyFlag1, familyFlag2, familyFlag3)) { RemoveAura(iter); continue; } } ++iter; } } void Unit::RemoveMovementImpairingAuras(bool withRoot) { if (withRoot) RemoveAurasWithMechanic(1 << MECHANIC_ROOT); // Snares for (AuraApplicationMap::iterator iter = m_appliedAuras.begin(); iter != m_appliedAuras.end();) { Aura const* aura = iter->second->GetBase(); if (aura->GetSpellInfo()->Mechanic == MECHANIC_SNARE || aura->GetSpellInfo()->HasEffectMechanic(MECHANIC_SNARE)) { RemoveAura(iter); continue; } ++iter; } } void Unit::RemoveAurasWithMechanic(uint32 mechanic_mask, AuraRemoveMode removemode, uint32 except) { for (AuraApplicationMap::iterator iter = m_appliedAuras.begin(); iter != m_appliedAuras.end();) { Aura const* aura = iter->second->GetBase(); if (!except || aura->GetId() != except) { if (aura->GetSpellInfo()->GetAllEffectsMechanicMask() & mechanic_mask) { RemoveAura(iter, removemode); continue; } } ++iter; } } void Unit::RemoveAurasByShapeShift() { uint32 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) || (aura->GetSpellInfo()->SpellFamilyName == SPELLFAMILY_WARRIOR && (aura->GetSpellInfo()->SpellFamilyFlags[1] & 0x20)))) { 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 while (!m_appliedAuras.empty() || !m_ownedAuras.empty()) { 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); } } 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_ENETRING_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::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->GetSpellInfo()->HasAttribute(SPELL_ATTR7_DISABLE_AURA_WHILE_DEAD)) && !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->GetSpellInfo()->HasAttribute(SPELL_ATTR7_DISABLE_AURA_WHILE_DEAD)) && !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); } } // pussywizard: replaced with Unit::RemoveEvadeAuras() /*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); } }*/ // Xinef: We should not remove passive auras on evade, if npc has player owner (scripted one cast auras) void Unit::RemoveEvadeAuras() { for (AuraApplicationMap::iterator iter = m_appliedAuras.begin(); iter != m_appliedAuras.end();) { Aura const* aura = iter->second->GetBase(); SpellInfo const* spellInfo = aura->GetSpellInfo(); if (spellInfo->HasAttribute(SPELL_ATTR0_CU_IGNORE_EVADE) || spellInfo->HasAttribute(SPELL_ATTR1_AURA_STAYS_AFTER_COMBAT) || spellInfo->HasAura(SPELL_AURA_CONTROL_VEHICLE) || spellInfo->HasAura(SPELL_AURA_CLONE_CASTER) || (aura->IsPassive() && GetOwnerGUID().IsPlayer())) ++iter; else _UnapplyAura(iter, AURA_REMOVE_BY_DEFAULT); } for (AuraMap::iterator iter = m_ownedAuras.begin(); iter != m_ownedAuras.end();) { Aura* aura = iter->second; SpellInfo const* spellInfo = aura->GetSpellInfo(); if (spellInfo->HasAttribute(SPELL_ATTR0_CU_IGNORE_EVADE) || spellInfo->HasAttribute(SPELL_ATTR1_AURA_STAYS_AFTER_COMBAT) || spellInfo->HasAura(SPELL_AURA_CONTROL_VEHICLE) || spellInfo->HasAura(SPELL_AURA_CLONE_CASTER) || (aura->IsPassive() && GetOwnerGUID().IsPlayer())) ++iter; else RemoveOwnedAura(iter, AURA_REMOVE_BY_DEFAULT); } } 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(); LOG_DEBUG("spells.aura", "Aura {} partially interrupted on unit {}, new duration: {} ms", aura->GetId(), GetGUID().ToString(), aura->GetDuration()); } } } 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 name, uint32 iconId, uint8 effIndex) const { AuraEffectList const& auras = GetAuraEffectsByType(type); for (Unit::AuraEffectList::const_iterator itr = auras.begin(); itr != auras.end(); ++itr) { if (effIndex != (*itr)->GetEffIndex()) continue; SpellInfo const* spell = (*itr)->GetSpellInfo(); if (spell->SpellIconID == iconId && spell->SpellFamilyName == name) return *itr; } return nullptr; } AuraEffect* Unit::GetAuraEffect(AuraType type, SpellFamilyNames family, uint32 familyFlag1, uint32 familyFlag2, uint32 familyFlag3, 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.HasFlag(familyFlag1, familyFlag2, familyFlag3)) { if (casterGUID && (*i)->GetCasterGUID() != casterGUID) continue; return (*i); } } return nullptr; } AuraEffect* Unit::GetAuraEffectDummy(uint32 spellid) const { AuraEffectList const& auras = GetAuraEffectsByType(SPELL_AURA_DUMMY); for (Unit::AuraEffectList::const_iterator itr = auras.begin(); itr != auras.end(); ++itr) { if ((*itr)->GetId() == spellid) return *itr; } return nullptr; } AuraApplication* Unit::GetAuraApplication(uint32 spellId, ObjectGuid casterGUID, ObjectGuid itemCasterGUID, uint8 reqEffMask, AuraApplication* except) const { AuraApplicationMapBounds range = m_appliedAuras.equal_range(spellId); for (; range.first != range.second; ++range.first) { AuraApplication* app = range.first->second; Aura const* aura = app->GetBase(); if (((aura->GetEffectMask() & reqEffMask) == reqEffMask) && (!casterGUID || aura->GetCasterGUID() == casterGUID) && (!itemCasterGUID || aura->GetCastItemGUID() == itemCasterGUID) && (!except || except != app)) { return app; } } return nullptr; } Aura* Unit::GetAura(uint32 spellId, ObjectGuid casterGUID, ObjectGuid itemCasterGUID, uint8 reqEffMask) const { AuraApplication* aurApp = GetAuraApplication(spellId, casterGUID, itemCasterGUID, reqEffMask); return aurApp ? aurApp->GetBase() : nullptr; } AuraApplication* Unit::GetAuraApplicationOfRankedSpell(uint32 spellId, ObjectGuid casterGUID, ObjectGuid itemCasterGUID, uint8 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, uint8 reqEffMask) const { AuraApplication* aurApp = GetAuraApplicationOfRankedSpell(spellId, casterGUID, itemCasterGUID, reqEffMask); return aurApp ? aurApp->GetBase() : nullptr; } void Unit::GetDispellableAuraList(Unit* caster, uint32 dispelMask, DispelChargesList& dispelList, SpellInfo const* dispelSpell) { // we should not be able to dispel diseases if the target is affected by unholy blight if (dispelMask & (1 << DISPEL_DISEASE) && HasAura(50536)) dispelMask &= ~(1 << DISPEL_DISEASE); ReputationRank rank = GetReactionTo(caster, IsCharmed()); bool positive = rank >= REP_FRIENDLY; // Neutral unit not at war with caster should be treated as a friendly unit if (rank == REP_NEUTRAL) { if (Player* casterPlayer = caster->GetAffectingPlayer()) { if (FactionTemplateEntry const* factionTemplateEntry = GetFactionTemplateEntry()) { if (FactionEntry const* factionEntry = sFactionStore.LookupEntry(factionTemplateEntry->faction)) { if (factionEntry->CanBeSetAtWar()) { positive = !casterPlayer->GetReputationMgr().IsAtWar(factionEntry); } } } } } Unit::VisibleAuraMap const* visibleAuras = GetVisibleAuras(); for (Unit::VisibleAuraMap::const_iterator itr = visibleAuras->begin(); itr != visibleAuras->end(); ++itr) { Aura* aura = itr->second->GetBase(); // don't try to remove passive auras if (aura->IsPassive()) continue; if (aura->GetSpellInfo()->GetDispelMask() & dispelMask) { if (aura->GetSpellInfo()->Dispel == DISPEL_MAGIC) { // do not remove positive auras if friendly target // negative auras if non-friendly target if (itr->second->IsPositive() == positive) continue; } // Banish should only be dispelled by Mass Dispel if (aura->GetSpellInfo()->Mechanic == MECHANIC_BANISH && !dispelSpell->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES)) { 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 dispel_charges = aura->GetSpellInfo()->HasAttribute(SPELL_ATTR7_DISPEL_REMOVES_CHARGES); uint8 charges = dispel_charges ? aura->GetCharges() : aura->GetStackAmount(); if (charges > 0) dispelList.push_back(std::make_pair(aura, 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::HasAuras(SearchMethod sm, std::vector& spellIds) const { if (sm == SearchMethod::MatchAll) { for (auto const& spellId : spellIds) if (!HasAura(spellId)) return false; return true; } else if (sm == SearchMethod::MatchAny) { for (auto const& spellId : spellIds) if (HasAura(spellId)) return true; return false; } else { LOG_ERROR("entities.unit", "Unit::HasAuras using non-supported SearchMethod {}", sm); return false; } } bool Unit::HasAura(uint32 spellId, ObjectGuid casterGUID, ObjectGuid itemCasterGUID, uint8 reqEffMask) const { if (GetAuraApplication(spellId, casterGUID, itemCasterGUID, reqEffMask)) return true; return false; } bool Unit::HasAuraType(AuraType auraType) const { return (!m_modAuras[auraType].empty()); } bool Unit::HasAuraTypeWithCaster(AuraType auratype, ObjectGuid caster) const { AuraEffectList const& mTotalAuraList = GetAuraEffectsByType(auratype); for (AuraEffectList::const_iterator i = mTotalAuraList.begin(); i != mTotalAuraList.end(); ++i) if (caster == (*i)->GetCasterGUID()) return true; return false; } bool Unit::HasVisibleAuraType(AuraType auraType) const { AuraEffectList const& mAuraList = GetAuraEffectsByType(auraType); for (AuraEffectList::const_iterator i = mAuraList.begin(); i != mAuraList.end(); ++i) if ((*i)->GetBase()->CanBeSentToClient()) 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)->IsAffectedOnSpell(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->GetSpellInfo()->Effects[aura->GetEffIndex()].TriggerSpell == triggerSpell) { return true; } } return false; } bool Unit::HasNegativeAuraWithInterruptFlag(uint32 flag, ObjectGuid guid) { if (!(m_interruptMask & flag)) return false; for (AuraApplicationList::iterator iter = m_interruptableAuras.begin(); iter != m_interruptableAuras.end(); ++iter) { if (!(*iter)->IsPositive() && (*iter)->GetBase()->GetSpellInfo()->AuraInterruptFlags & flag && (!guid || (*iter)->GetBase()->GetCasterGUID() == guid)) return true; } return false; } bool Unit::HasNegativeAuraWithAttribute(uint32 flag, ObjectGuid guid) { for (AuraApplicationMap::const_iterator iter = m_appliedAuras.begin(); iter != m_appliedAuras.end(); ++iter) { Aura const* aura = iter->second->GetBase(); if (!iter->second->IsPositive() && aura->GetSpellInfo()->Attributes & flag && (!guid || aura->GetCasterGUID() == guid)) return true; } return false; } bool Unit::HasAuraWithMechanic(uint32 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 & (1 << spellInfo->Mechanic))) return true; for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) if (iter->second->HasEffect(i) && spellInfo->Effects[i].Effect && spellInfo->Effects[i].Mechanic) if (mechanicMask & (1 << spellInfo->Effects[i].Mechanic)) 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)->IsAffectedOnSpell(spell)) return (*i); } return nullptr; } uint32 Unit::GetDiseasesByCaster(ObjectGuid casterGUID, uint8 mode) { static const AuraType diseaseAuraTypes[] = { SPELL_AURA_PERIODIC_DAMAGE, // Frost Fever and Blood Plague SPELL_AURA_LINKED, // Crypt Fever and Ebon Plague SPELL_AURA_NONE }; ObjectGuid drwGUID; if (Player* playerCaster = ObjectAccessor::GetPlayer(*this, casterGUID)) drwGUID = playerCaster->getRuneWeaponGUID(); uint32 diseases = 0; for (uint8 index = 0; diseaseAuraTypes[index] != SPELL_AURA_NONE; ++index) { for (AuraEffectList::iterator i = m_modAuras[diseaseAuraTypes[index]].begin(); i != m_modAuras[diseaseAuraTypes[index]].end();) { // Get auras with disease dispel type by caster if ((*i)->GetSpellInfo()->Dispel == DISPEL_DISEASE && ((*i)->GetCasterGUID() == casterGUID || (*i)->GetCasterGUID() == drwGUID)) // if its caster or his dancing rune weapon { ++diseases; if (mode == 1) { RemoveAura((*i)->GetId(), (*i)->GetCasterGUID()); i = m_modAuras[diseaseAuraTypes[index]].begin(); continue; } // used for glyph of scourge strike else if (mode == 2) { Aura* aura = (*i)->GetBase(); uint32 countMin = aura->GetMaxDuration(); uint32 countMax = aura->GetSpellInfo()->GetMaxDuration() + 9000; if (AuraEffect const* epidemic = (*i)->GetCaster()->GetAuraEffectOfRankedSpell(49036, EFFECT_0)) countMax += epidemic->GetAmount(); if (countMin < countMax) { aura->SetDuration(uint32(aura->GetDuration() + 3000)); aura->SetMaxDuration(countMin + 3000); } } } ++i; } } 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 const& [_, amount] : sameEffectSpellGroup) modifier += amount; 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->IsAffectedOnSpell(affectedSpell)) return true; return false; }); } float Unit::GetTotalAuraMultiplierByAffectMask(AuraType auraType, SpellInfo const* affectedSpell) const { return GetTotalAuraMultiplier(auraType, [affectedSpell](AuraEffect const* aurEff) -> bool { if (aurEff->IsAffectedOnSpell(affectedSpell)) return true; return false; }); } int32 Unit::GetMaxPositiveAuraModifierByAffectMask(AuraType auraType, SpellInfo const* affectedSpell) const { return GetMaxPositiveAuraModifier(auraType, [affectedSpell](AuraEffect const* aurEff) -> bool { if (aurEff->IsAffectedOnSpell(affectedSpell)) return true; return false; }); } int32 Unit::GetMaxNegativeAuraModifierByAffectMask(AuraType auraType, SpellInfo const* affectedSpell) const { return GetMaxNegativeAuraModifier(auraType, [affectedSpell](AuraEffect const* aurEff) -> bool { if (aurEff->IsAffectedOnSpell(affectedSpell)) return true; return false; }); } void Unit::UpdateResistanceBuffModsMod(SpellSchools school) { float modPos = 0.0f; float modNeg = 0.0f; // these auras are always positive modPos = GetMaxPositiveAuraModifierByMiscMask(SPELL_AURA_MOD_RESISTANCE_EXCLUSIVE, 1 << school); modPos += GetTotalAuraModifier(SPELL_AURA_MOD_RESISTANCE, [school](AuraEffect const* aurEff) -> bool { if ((aurEff->GetMiscValue() & (1 << school)) && aurEff->GetAmount() > 0) return true; return false; }); modNeg = GetTotalAuraModifier(SPELL_AURA_MOD_RESISTANCE, [school](AuraEffect const* aurEff) -> bool { if ((aurEff->GetMiscValue() & (1 << school)) && aurEff->GetAmount() < 0) return true; return false; }); float factor = GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_RESISTANCE_PCT, 1 << school); modPos *= factor; modNeg *= factor; SetFloatValue(UNIT_FIELD_RESISTANCEBUFFMODSPOSITIVE + AsUnderlyingType(school), modPos); SetFloatValue(UNIT_FIELD_RESISTANCEBUFFMODSNEGATIVE + AsUnderlyingType(school), modNeg); } 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; 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; SetFloatValue(UNIT_FIELD_POSSTAT0 + AsUnderlyingType(stat), modPos); SetFloatValue(UNIT_FIELD_NEGSTAT0 + AsUnderlyingType(stat), modNeg); } void Unit::_RegisterDynObject(DynamicObject* dynObj) { m_dynObj.push_back(dynObj); } void Unit::_UnregisterDynObject(DynamicObject* dynObj) { m_dynObj.remove(dynObj); } DynamicObject* Unit::GetDynObject(uint32 spellId) { if (m_dynObj.empty()) return nullptr; for (DynObjectList::const_iterator i = m_dynObj.begin(); i != m_dynObj.end(); ++i) { DynamicObject* dynObj = *i; if (dynObj->GetSpellId() == spellId) return dynObj; } return nullptr; } bool Unit::RemoveDynObject(uint32 spellId) { if (m_dynObj.empty()) return false; bool result = false; for (DynObjectList::iterator i = m_dynObj.begin(); i != m_dynObj.end();) { DynamicObject* dynObj = *i; if (dynObj->GetSpellId() == spellId) { dynObj->Remove(); i = m_dynObj.begin(); result = true; } else ++i; } return result; } void Unit::RemoveAllDynObjects() { while (!m_dynObj.empty()) m_dynObj.front()->Remove(); } GameObject* Unit::GetGameObject(uint32 spellId) const { for (GameObjectList::const_iterator itr = m_gameObj.begin(); itr != m_gameObj.end(); ++itr) if (GameObject* go = ObjectAccessor::GetGameObject(*this, *itr)) if (go->GetSpellId() == spellId) return go; return nullptr; } void Unit::AddGameObject(GameObject* gameObj) { if (!gameObj || gameObj->GetOwnerGUID()) return; m_gameObj.push_back(gameObj->GetGUID()); gameObj->SetOwnerGUID(GetGUID()); if (IsPlayer() && gameObj->GetSpellId()) { SpellInfo const* createBySpell = sSpellMgr->GetSpellInfo(gameObj->GetSpellId()); // Need disable spell use for owner if (createBySpell && createBySpell->IsCooldownStartedOnEvent()) // note: item based cooldowns and cooldown spell mods with charges ignored (unknown existing cases) ToPlayer()->AddSpellAndCategoryCooldowns(createBySpell, 0, nullptr, true); } } 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); if (IsPlayer()) { SpellInfo const* createBySpell = sSpellMgr->GetSpellInfo(spellid); // Need activate spell use for owner if (createBySpell && createBySpell->IsCooldownStartedOnEvent()) // note: item based cooldowns and cooldown spell mods with charges ignored (unknown existing cases) ToPlayer()->SendCooldownEvent(createBySpell); } } m_gameObj.remove(gameObj->GetGUID()); if (del) { gameObj->SetRespawnTime(0); gameObj->Delete(); } } void Unit::RemoveGameObject(uint32 spellid, bool del) { if (m_gameObj.empty()) return; for (GameObjectList::iterator itr = m_gameObj.begin(); itr != m_gameObj.end();) { if (GameObject* go = ObjectAccessor::GetGameObject(*this, *itr)) { if (spellid > 0 && go->GetSpellId() != spellid) { ++itr; continue; } go->SetOwnerGUID(ObjectGuid::Empty); if (del) { go->SetRespawnTime(0); go->Delete(); } } m_gameObj.erase(itr++); } } void Unit::RemoveAllGameObjects() { while(!m_gameObj.empty()) { GameObject* go = ObjectAccessor::GetGameObject(*this, *m_gameObj.begin()); if (go) { go->SetOwnerGUID(ObjectGuid::Empty); go->SetRespawnTime(0); go->Delete(); } m_gameObj.erase(m_gameObj.begin()); } } void Unit::SendSpellNonMeleeReflectLog(SpellNonMeleeDamage* log, Unit* attacker) { // Xinef: function for players only, placed in unit because of cosmetics if (!IsPlayer()) return; WorldPacket data(SMSG_SPELLNONMELEEDAMAGELOG, (16 + 4 + 4 + 4 + 1 + 4 + 4 + 1 + 1 + 4 + 4 + 1)); // we guess size // If we are in cheat mode we swap absorb with damage and set damage to 0, this way we can still debug damage but our HP bar will not drop uint32 damage = log->damage; uint32 absorb = log->absorb; if (log->target->IsPlayer() && log->target->ToPlayer()->GetCommandStatus(CHEAT_GOD)) { absorb = damage; damage = 0; } data << log->target->GetPackGUID(); data << attacker->GetPackGUID(); data << uint32(log->spellInfo->Id); data << uint32(damage); // damage amount int32 overkill = damage - log->target->GetHealth(); data << uint32(overkill > 0 ? overkill : 0); // overkill data << uint8 (log->schoolMask); // damage school data << uint32(absorb); // AbsorbedDamage data << uint32(log->resist); // resist data << uint8 (log->physicalLog); // if 1, then client show spell name (example: %s's ranged shot hit %s for %u school or %s suffers %u school damage from %s's spell_name data << uint8 (log->unused); // unused data << uint32(log->blocked); // blocked data << uint32(log->HitInfo); data << uint8 (0); // flag to use extend data ToPlayer()->SendDirectMessage(&data); } void Unit::SendSpellNonMeleeDamageLog(SpellNonMeleeDamage* log) { WorldPacket data(SMSG_SPELLNONMELEEDAMAGELOG, (16 + 4 + 4 + 4 + 1 + 4 + 4 + 1 + 1 + 4 + 4 + 1)); // we guess size //IF we are in cheat mode we swap absorb with damage and set damage to 0, this way we can still debug damage but our hp bar will not drop uint32 damage = log->damage; uint32 absorb = log->absorb; if (log->target->IsPlayer() && log->target->ToPlayer()->GetCommandStatus(CHEAT_GOD)) { absorb = damage; damage = 0; } data << log->target->GetPackGUID(); data << log->attacker->GetPackGUID(); data << uint32(log->spellInfo->Id); data << uint32(damage); // damage amount int32 overkill = damage - log->target->GetHealth(); data << uint32(overkill > 0 ? overkill : 0); // overkill data << uint8 (log->schoolMask); // damage school data << uint32(absorb); // AbsorbedDamage data << uint32(log->resist); // resist data << uint8 (log->physicalLog); // if 1, then client show spell name (example: %s's ranged shot hit %s for %u school or %s suffers %u school damage from %s's spell_name data << uint8 (log->unused); // unused data << uint32(log->blocked); // blocked data << uint32(log->HitInfo); data << uint32(log->HitInfo); data << uint8(log->HitInfo & (SPELL_HIT_TYPE_CRIT_DEBUG | SPELL_HIT_TYPE_HIT_DEBUG | SPELL_HIT_TYPE_ATTACK_TABLE_DEBUG)); //if (log->HitInfo & SPELL_HIT_TYPE_CRIT_DEBUG) //{ // data << float(log->CritRoll); // data << float(log->CritNeeded); //} //if (log->HitInfo & SPELL_HIT_TYPE_HIT_DEBUG) //{ // data << float(log->HitRoll); // data << float(log->HitNeeded); //} //if (log->HitInfo & SPELL_HIT_TYPE_ATTACK_TABLE_DEBUG) //{ // data << float(log->MissChance); // data << float(log->DodgeChance); // data << float(log->ParryChance); // data << float(log->BlockChance); // data << float(log->GlanceChance); // data << float(log->CrushChance); //} SendMessageToSet(&data, true); } void Unit::SendSpellNonMeleeDamageLog(Unit* target, SpellInfo const* spellInfo, uint32 Damage, SpellSchoolMask damageSchoolMask, uint32 AbsorbedDamage, uint32 Resist, bool PhysicalDamage, uint32 Blocked, bool CriticalHit /*= false*/, bool Split /*= false*/) { SpellNonMeleeDamage log(this, target, spellInfo, damageSchoolMask); log.damage = Damage; log.absorb = AbsorbedDamage; log.resist = Resist; log.physicalLog = PhysicalDamage; log.blocked = Blocked; log.HitInfo = 0; if (CriticalHit) { log.HitInfo |= SPELL_HIT_TYPE_CRIT; } if (Split) { log.HitInfo |= SPELL_HIT_TYPE_SPLIT; } SendSpellNonMeleeDamageLog(&log); } void Unit::ProcDamageAndSpell(Unit* actor, Unit* victim, uint32 procAttacker, uint32 procVictim, uint32 procExtra, uint32 amount, WeaponAttackType attType, SpellInfo const* procSpellInfo, SpellInfo const* procAura, int8 procAuraEffectIndex, Spell const* procSpell, DamageInfo* damageInfo, HealInfo* healInfo, uint32 procPhase) { // Not much to do if no flags are set. if (procAttacker && actor) actor->ProcDamageAndSpellFor(false, victim, procAttacker, procExtra, attType, procSpellInfo, amount, procAura, procAuraEffectIndex, procSpell, damageInfo, healInfo, procPhase); // Now go on with a victim's events'n'auras // Not much to do if no flags are set or there is no victim if (victim && victim->IsAlive() && procVictim) victim->ProcDamageAndSpellFor(true, actor, procVictim, procExtra, attType, procSpellInfo, amount, procAura, procAuraEffectIndex, procSpell, damageInfo, healInfo, procPhase); } void Unit::SendPeriodicAuraLog(SpellPeriodicAuraLogInfo* pInfo) { AuraEffect const* aura = pInfo->auraEff; WorldPacket data(SMSG_PERIODICAURALOG, 30); data << GetPackGUID(); data << aura->GetCasterGUID().WriteAsPacked(); data << uint32(aura->GetId()); // spellId data << uint32(1); // count data << uint32(aura->GetAuraType()); // auraId switch (aura->GetAuraType()) { case SPELL_AURA_PERIODIC_DAMAGE: case SPELL_AURA_PERIODIC_DAMAGE_PERCENT: { //IF we are in cheat mode we swap absorb with damage and set damage to 0, this way we can still debug damage but our hp bar will not drop uint32 damage = pInfo->damage; uint32 absorb = pInfo->absorb; if (IsPlayer() && ToPlayer()->GetCommandStatus(CHEAT_GOD)) { absorb = damage; damage = 0; } data << uint32(damage); // damage data << uint32(pInfo->overDamage); // overkill? data << uint32(aura->GetSpellInfo()->GetSchoolMask()); data << uint32(absorb); // absorb data << uint32(pInfo->resist); // resist data << uint8(pInfo->critical); // new 3.1.2 critical tick } break; case SPELL_AURA_PERIODIC_HEAL: case SPELL_AURA_OBS_MOD_HEALTH: data << uint32(pInfo->damage); // damage data << uint32(pInfo->overDamage); // overheal data << uint32(pInfo->absorb); // absorb data << uint8(pInfo->critical); // new 3.1.2 critical tick break; case SPELL_AURA_OBS_MOD_POWER: case SPELL_AURA_PERIODIC_ENERGIZE: data << uint32(aura->GetMiscValue()); // power type data << uint32(pInfo->damage); // damage break; case SPELL_AURA_PERIODIC_MANA_LEECH: data << uint32(aura->GetMiscValue()); // power type data << uint32(pInfo->damage); // amount data << float(pInfo->multiplier); // gain multiplier break; default: LOG_ERROR("entities.unit", "Unit::SendPeriodicAuraLog: unknown aura {}", uint32(aura->GetAuraType())); return; } SendMessageToSet(&data, true); } void Unit::SendSpellMiss(Unit* target, uint32 spellID, SpellMissInfo missInfo) { WorldPacket data(SMSG_SPELLLOGMISS, (4 + 8 + 1 + 4 + 8 + 1)); data << uint32(spellID); data << GetGUID(); data << uint8(0); // can be 0 or 1 data << uint32(1); // target count // for (i = 0; i < target count; ++i) data << target->GetGUID(); // target GUID data << uint8(missInfo); // end loop SendMessageToSet(&data, true); } void Unit::SendSpellDamageResist(Unit* target, uint32 spellId) { WorldPacket data(SMSG_PROCRESIST, 8 + 8 + 4 + 1); data << GetGUID(); data << target->GetGUID(); data << uint32(spellId); data << uint8(0); // bool - log format: 0-default, 1-debug SendMessageToSet(&data, true); } void Unit::SendSpellDamageImmune(Unit* target, uint32 spellId) { WorldPacket data(SMSG_SPELLORDAMAGE_IMMUNE, 8 + 8 + 4 + 1); data << GetGUID(); data << target->GetGUID(); data << uint32(spellId); data << uint8(0); // bool - log format: 0-default, 1-debug SendMessageToSet(&data, true); } void Unit::SendAttackStateUpdate(CalcDamageInfo* damageInfo) { LOG_DEBUG("entities.unit", "WORLD: Sending SMSG_ATTACKERSTATEUPDATE"); uint32 tmpDamage[MAX_ITEM_PROTO_DAMAGES] = { }; uint32 tmpAbsorb[MAX_ITEM_PROTO_DAMAGES] = { }; for (uint8 i = 0; i < MAX_ITEM_PROTO_DAMAGES; ++i) { //IF we are in cheat mode we swap absorb with damage and set damage to 0, this way we can still debug damage but our hp bar will not drop tmpDamage[i] = damageInfo->damages[i].damage; tmpAbsorb[i] = damageInfo->damages[i].absorb; if (damageInfo->target->IsPlayer() && damageInfo->target->ToPlayer()->GetCommandStatus(CHEAT_GOD)) { tmpAbsorb[i] = tmpDamage[i]; tmpDamage[i] = 0; } } uint32 count = 1; if (tmpDamage[1] || tmpAbsorb[1] || damageInfo->damages[1].resist) { ++count; } std::size_t const maxsize = 4 + 5 + 5 + 4 + 4 + 1 + count * (4 + 4 + 4 + 4 + 4) + 1 + 4 + 4 + 4 + 4 + 4 * 12; WorldPacket data(SMSG_ATTACKERSTATEUPDATE, maxsize); // we guess size data << uint32(damageInfo->HitInfo); data << damageInfo->attacker->GetPackGUID(); data << damageInfo->target->GetPackGUID(); data << uint32(tmpDamage[0] + tmpDamage[1]); // Full damage int32 overkill = tmpDamage[0] + tmpDamage[1] - damageInfo->target->GetHealth(); data << uint32(overkill < 0 ? 0 : overkill); // Overkill data << uint8(count); // Sub damage count for (uint32 i = 0; i < count; ++i) { data << uint32(damageInfo->damages[i].damageSchoolMask); // School of sub damage data << float(tmpDamage[i]); // sub damage data << uint32(tmpDamage[i]); // Sub Damage } if (damageInfo->HitInfo & (HITINFO_FULL_ABSORB | HITINFO_PARTIAL_ABSORB)) { for (uint32 i = 0; i < count; ++i) { data << uint32(tmpAbsorb[i]); // Absorb } } if (damageInfo->HitInfo & (HITINFO_FULL_RESIST | HITINFO_PARTIAL_RESIST)) { for (uint32 i = 0; i < count; ++i) { data << uint32(damageInfo->damages[i].resist); // Resist } } data << uint8(damageInfo->TargetState); data << uint32(0); // Unknown attackerstate data << uint32(0); // Melee spellid if (damageInfo->HitInfo & HITINFO_BLOCK) data << uint32(damageInfo->blocked_amount); if (damageInfo->HitInfo & HITINFO_RAGE_GAIN) data << uint32(0); //! Probably used for debugging purposes, as it is not known to appear on retail servers if (damageInfo->HitInfo & HITINFO_UNK1) { data << uint32(0); data << float(0); data << float(0); data << float(0); data << float(0); data << float(0); data << float(0); data << float(0); data << float(0); data << float(0); // Found in a loop with 1 iteration data << float(0); // ditto ^ data << uint32(0); } SendMessageToSet(&data, true); } void Unit::SendAttackStateUpdate(uint32 HitInfo, Unit* target, uint8 /*SwingType*/, SpellSchoolMask damageSchoolMask, uint32 Damage, uint32 AbsorbDamage, uint32 Resist, VictimState TargetState, uint32 BlockedAmount) { CalcDamageInfo dmgInfo; dmgInfo.HitInfo = HitInfo; dmgInfo.attacker = this; dmgInfo.target = target; dmgInfo.damages[0].damage = Damage - AbsorbDamage - Resist - BlockedAmount; dmgInfo.damages[0].damageSchoolMask = damageSchoolMask; dmgInfo.damages[0].absorb = AbsorbDamage; dmgInfo.damages[0].resist = Resist; dmgInfo.damages[1].damage = 0; dmgInfo.damages[1].damageSchoolMask = 0; dmgInfo.damages[1].absorb = 0; dmgInfo.damages[1].resist = 0; dmgInfo.TargetState = TargetState; dmgInfo.blocked_amount = BlockedAmount; SendAttackStateUpdate(&dmgInfo); } //victim may be nullptr bool Unit::HandleDummyAuraProc(Unit* victim, uint32 damage, AuraEffect* triggeredByAura, SpellInfo const* procSpell, uint32 procFlag, uint32 procEx, uint32 cooldown, ProcEventInfo const& eventInfo) { SpellInfo const* dummySpell = triggeredByAura->GetSpellInfo(); uint32 effIndex = triggeredByAura->GetEffIndex(); int32 triggerAmount = triggeredByAura->GetAmount(); Spell const* spellProc = eventInfo.GetProcSpell(); Item* castItem = triggeredByAura->GetBase()->GetCastItemGUID() && IsPlayer() ? ToPlayer()->GetItemByGuid(triggeredByAura->GetBase()->GetCastItemGUID()) : nullptr; uint32 triggered_spell_id = 0; uint32 cooldown_spell_id = 0; // for random trigger, will be one of the triggered spell to avoid repeatable triggers // otherwise, it's the triggered_spell_id by default Unit* target = victim; int32 basepoints0 = 0; ObjectGuid originalCaster; switch (dummySpell->SpellFamilyName) { case SPELLFAMILY_GENERIC: { switch (dummySpell->Id) { // Overkill case 58426: { triggered_spell_id = 58427; break; } // Unstable Power case 24658: { if (!procSpell || procSpell->Id == 24659) return false; // Need remove one 24659 aura RemoveAuraFromStack(24659); return true; } // Restless Strength case 24661: { // Need remove one 24662 aura RemoveAuraFromStack(24662); return true; } // Mark of Malice case 33493: { if (triggeredByAura->GetBase()->GetCharges() > 1) return true; target = this; triggered_spell_id = 33494; break; } // Twisted Reflection (boss spell) case 21063: triggered_spell_id = 21064; break; // Vampiric Aura (boss spell) case 38196: { basepoints0 = 3 * damage; // 300% if (basepoints0 < 0) return false; triggered_spell_id = 31285; target = this; break; } // Aura of Madness (Darkmoon Card: Madness trinket) //===================================================== // 39511 Sociopath: +35 strength (Paladin, Rogue, Druid, Warrior) // 40997 Delusional: +70 attack power (Rogue, Hunter, Paladin, Warrior, Druid) // 40998 Kleptomania: +35 agility (Warrior, Rogue, Paladin, Hunter, Druid) // 40999 Megalomania: +41 damage/healing (Druid, Shaman, Priest, Warlock, Mage, Paladin) // 41002 Paranoia: +35 spell/melee/ranged crit strike rating (All classes) // 41005 Manic: +35 haste (spell, melee and ranged) (All classes) // 41009 Narcissism: +35 intellect (Druid, Shaman, Priest, Warlock, Mage, Paladin, Hunter) // 41011 Martyr Complex: +35 stamina (All classes) // 41406 Dementia: Every 5 seconds either gives you +5% damage/healing. (Druid, Shaman, Priest, Warlock, Mage, Paladin) // 41409 Dementia: Every 5 seconds either gives you -5% damage/healing. (Druid, Shaman, Priest, Warlock, Mage, Paladin) case 39446: { if (!IsPlayer() || !IsAlive()) return false; // Select class defined buff switch (getClass()) { case CLASS_PALADIN: // 39511, 40997, 40998, 40999, 41002, 41005, 41009, 41011, 41409 case CLASS_DRUID: // 39511, 40997, 40998, 40999, 41002, 41005, 41009, 41011, 41409 triggered_spell_id = RAND(39511, 40997, 40998, 40999, 41002, 41005, 41009, 41011, 41409); cooldown_spell_id = 39511; break; case CLASS_ROGUE: // 39511, 40997, 40998, 41002, 41005, 41011 case CLASS_WARRIOR: // 39511, 40997, 40998, 41002, 41005, 41011 case CLASS_DEATH_KNIGHT: triggered_spell_id = RAND(39511, 40997, 40998, 41002, 41005, 41011); cooldown_spell_id = 39511; break; case CLASS_PRIEST: // 40999, 41002, 41005, 41009, 41011, 41406, 41409 case CLASS_SHAMAN: // 40999, 41002, 41005, 41009, 41011, 41406, 41409 case CLASS_MAGE: // 40999, 41002, 41005, 41009, 41011, 41406, 41409 case CLASS_WARLOCK: // 40999, 41002, 41005, 41009, 41011, 41406, 41409 triggered_spell_id = RAND(40999, 41002, 41005, 41009, 41011, 41406, 41409); cooldown_spell_id = 40999; break; case CLASS_HUNTER: // 40997, 40999, 41002, 41005, 41009, 41011, 41406, 41409 triggered_spell_id = RAND(40997, 40999, 41002, 41005, 41009, 41011, 41406, 41409); cooldown_spell_id = 40997; break; default: return false; } target = this; if (roll_chance_i(10)) ToPlayer()->Say("This is Madness!", LANG_UNIVERSAL); /// @todo: It should be moved to database, shouldn't it? break; } // Sunwell Exalted Caster Neck (??? neck) // cast ??? Light's Wrath if Exalted by Aldor // cast ??? Arcane Bolt if Exalted by Scryers case 46569: return false; // old unused version // Sunwell Exalted Caster Neck (Shattered Sun Pendant of Acumen neck) // cast 45479 Light's Wrath if Exalted by Aldor // cast 45429 Arcane Bolt if Exalted by Scryers case 45481: { Player* player = ToPlayer(); if (!player) return false; // Get Aldor reputation rank if (player->GetReputationRank(932) == REP_EXALTED) { target = this; triggered_spell_id = 45479; break; } // Get Scryers reputation rank if (player->GetReputationRank(934) == REP_EXALTED) { // triggered at positive/self casts also, current attack target used then if (target && IsFriendlyTo(target)) { target = GetVictim(); if (!target) { target = player->GetSelectedUnit(); if (!target) return false; } if (IsFriendlyTo(target)) return false; } triggered_spell_id = 45429; break; } return false; } // Sunwell Exalted Melee Neck (Shattered Sun Pendant of Might neck) // cast 45480 Light's Strength if Exalted by Aldor // cast 45428 Arcane Strike if Exalted by Scryers case 45482: { if (!IsPlayer()) return false; // Get Aldor reputation rank if (ToPlayer()->GetReputationRank(932) == REP_EXALTED) { target = this; triggered_spell_id = 45480; break; } // Get Scryers reputation rank if (ToPlayer()->GetReputationRank(934) == REP_EXALTED) { triggered_spell_id = 45428; break; } return false; } // Sunwell Exalted Tank Neck (Shattered Sun Pendant of Resolve neck) // cast 45431 Arcane Insight if Exalted by Aldor // cast 45432 Light's Ward if Exalted by Scryers case 45483: { if (!IsPlayer()) return false; // Get Aldor reputation rank if (ToPlayer()->GetReputationRank(932) == REP_EXALTED) { target = this; triggered_spell_id = 45432; break; } // Get Scryers reputation rank if (ToPlayer()->GetReputationRank(934) == REP_EXALTED) { target = this; triggered_spell_id = 45431; break; } return false; } // Sunwell Exalted Healer Neck (Shattered Sun Pendant of Restoration neck) // cast 45478 Light's Salvation if Exalted by Aldor // cast 45430 Arcane Surge if Exalted by Scryers case 45484: { if (!IsPlayer()) return false; // Get Aldor reputation rank if (ToPlayer()->GetReputationRank(932) == REP_EXALTED) { target = this; triggered_spell_id = 45478; break; } // Get Scryers reputation rank if (ToPlayer()->GetReputationRank(934) == REP_EXALTED) { triggered_spell_id = 45430; break; } return false; } // Kill command case 58914: { // Remove aura stack from pet RemoveAuraFromStack(58914); Unit* owner = GetOwner(); if (!owner) return true; // reduce the owner's aura stack owner->RemoveAuraFromStack(34027); return true; } // Vampiric Touch (generic, used by some boss) case 52723: case 60501: { triggered_spell_id = 52724; basepoints0 = damage / 2; target = this; break; } // Divine purpose case 31871: case 31872: { // Roll chane if (!victim || !victim->IsAlive() || !roll_chance_i(triggerAmount)) return false; // Remove any stun effect on target victim->RemoveAurasWithMechanic(1 << MECHANIC_STUN, AURA_REMOVE_BY_ENEMY_SPELL); return true; } // Glyph of Life Tap case 63320: { triggered_spell_id = 63321; // Life Tap break; } case 71519: // Deathbringer's Will Normal { if (!IsPlayer() || HasSpellCooldown(71484)) return false; AddSpellCooldown(71484, 0, cooldown); std::vector RandomSpells; switch (getClass()) { case CLASS_WARRIOR: case CLASS_PALADIN: case CLASS_DEATH_KNIGHT: RandomSpells.push_back(71484); RandomSpells.push_back(71491); RandomSpells.push_back(71492); break; case CLASS_SHAMAN: case CLASS_ROGUE: RandomSpells.push_back(71486); RandomSpells.push_back(71485); RandomSpells.push_back(71492); break; case CLASS_DRUID: RandomSpells.push_back(71484); RandomSpells.push_back(71485); RandomSpells.push_back(71492); break; case CLASS_HUNTER: RandomSpells.push_back(71486); RandomSpells.push_back(71491); RandomSpells.push_back(71485); break; default: return false; } if (RandomSpells.empty()) // shouldn't happen return false; uint8 rand_spell = irand(0, (RandomSpells.size() - 1)); CastSpell(target, RandomSpells[rand_spell], true, castItem, triggeredByAura, originalCaster); break; } case 71562: // Deathbringer's Will Heroic { if (!IsPlayer() || HasSpellCooldown(71561)) return false; AddSpellCooldown(71561, 0, cooldown); std::vector RandomSpells; switch (getClass()) { case CLASS_WARRIOR: case CLASS_PALADIN: case CLASS_DEATH_KNIGHT: RandomSpells.push_back(71561); RandomSpells.push_back(71559); RandomSpells.push_back(71560); break; case CLASS_SHAMAN: case CLASS_ROGUE: RandomSpells.push_back(71558); RandomSpells.push_back(71556); RandomSpells.push_back(71560); break; case CLASS_DRUID: RandomSpells.push_back(71561); RandomSpells.push_back(71556); RandomSpells.push_back(71560); break; case CLASS_HUNTER: RandomSpells.push_back(71558); RandomSpells.push_back(71559); RandomSpells.push_back(71556); break; default: return false; } if (RandomSpells.empty()) // shouldn't happen return false; uint8 rand_spell = irand(0, (RandomSpells.size() - 1)); CastSpell(target, RandomSpells[rand_spell], true, castItem, triggeredByAura, originalCaster); break; } // Freya, Petrified Bark case 62933: case 62337: { if (!victim) return false; int32 dmg = damage; victim->CastCustomSpell(this, 62379, &dmg, 0, 0, true); return true; } // Trial of the Champion, Earth Shield case 67534: { const int32 dmg = (int32)damage; CastCustomSpell(this, 67535, &dmg, nullptr, nullptr, true, 0, triggeredByAura, triggeredByAura->GetCasterGUID()); return true; } // Trial of the Crusader, Faction Champions, Retaliation case 65932: { // check attack comes not from behind if (!victim || !HasInArc(M_PI, victim)) return false; triggered_spell_id = 65934; break; } // Pit of Saron, Tyrannus, Overlord's Brand case 69172: // everything except for DoTs { if (!target) return false; if (Unit* caster = triggeredByAura->GetCaster()) { if (procFlag & (PROC_FLAG_DONE_SPELL_NONE_DMG_CLASS_POS | PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_POS)) { int32 dmg = 5.5f * damage; target->CastCustomSpell(caster, 69190, &dmg, 0, 0, true); } else { if (caster->GetVictim()) { int32 dmg = damage; target->CastCustomSpell(caster->GetVictim(), 69189, &dmg, 0, 0, true); } } } return true; } // Pit of Saron, Tyrannus, Overlord's Brand case 69173: // only DoTs { if (!target) return false; if (Unit* caster = triggeredByAura->GetCaster()) { if (procEx & PROC_EX_INTERNAL_HOT) { int32 dmg = 5.5f * damage; target->CastCustomSpell(caster, 69190, &dmg, 0, 0, true); } else { if (caster->GetVictim()) { int32 dmg = damage; target->CastCustomSpell(caster->GetVictim(), 69189, &dmg, 0, 0, true); } } } return true; } // Icecrown Citadel, Lady Deathwhisper, Vampiric Might case 70674: { if (Unit* caster = triggeredByAura->GetCaster()) { int32 dmg = 3 * damage; caster->CastCustomSpell(caster, 70677, &dmg, 0, 0, true); } return true; } // Item: Purified Shard of the Gods case 69755: { triggered_spell_id = ((procFlag & PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_POS) ? 69733 : 69729); break; } // Item: Shiny Shard of the Gods case 69739: { triggered_spell_id = ((procFlag & PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_POS) ? 69734 : 69730); break; } // VoA: Meteor Fists koralon case 66725: case 68161: { triggered_spell_id = 66765; // handled by spell_difficulty break; } } break; } case SPELLFAMILY_MAGE: { // Magic Absorption if (dummySpell->SpellIconID == 459) // only this spell has SpellIconID == 459 and dummy aura { if (!HasActivePowerType(POWER_MANA)) return false; // mana reward basepoints0 = CalculatePct(int32(GetMaxPower(POWER_MANA)), triggerAmount); target = this; triggered_spell_id = 29442; break; } // Hot Streak if (dummySpell->SpellIconID == 2999) { if (effIndex != 0) return false; AuraEffect* counter = triggeredByAura->GetBase()->GetEffect(EFFECT_1); if (!counter) return true; // Count spell criticals in a row in second aura if (procEx & PROC_EX_CRITICAL_HIT) { counter->SetAmount(counter->GetAmount() * 2); if (counter->GetAmount() < 100) // not enough return true; // Crititcal counted -> roll chance if (roll_chance_i(triggerAmount)) CastSpell(this, 48108, true, castItem, triggeredByAura); } counter->SetAmount(25); return true; } // Incanter's Regalia set (add trigger chance to Mana Shield) if (dummySpell->SpellFamilyFlags[0] & 0x8000) { if (!IsPlayer()) return false; target = this; triggered_spell_id = 37436; break; } switch (dummySpell->Id) { // Glyph of Polymorph case 56375: { if (!target) return false; target->RemoveAurasByType(SPELL_AURA_PERIODIC_DAMAGE, ObjectGuid::Empty, target->GetAura(32409)); // SW:D shall not be removed. target->RemoveAurasByType(SPELL_AURA_PERIODIC_DAMAGE_PERCENT); target->RemoveAurasByType(SPELL_AURA_PERIODIC_LEECH); return true; } // Glyph of Icy Veins case 56374: { RemoveAurasByType(SPELL_AURA_HASTE_SPELLS, ObjectGuid::Empty, 0, true, false); RemoveAurasByType(SPELL_AURA_MOD_DECREASE_SPEED); return true; } // Glyph of Ice Block case 56372: { Player* player = ToPlayer(); if (!player) return false; SpellCooldowns const cooldowns = player->GetSpellCooldowns(); // remove cooldowns on all ranks of Frost Nova for (SpellCooldowns::const_iterator itr = cooldowns.begin(); itr != cooldowns.end(); ++itr) { SpellInfo const* cdSpell = sSpellMgr->GetSpellInfo(itr->first); // Frost Nova if (cdSpell && cdSpell->SpellFamilyName == SPELLFAMILY_MAGE && cdSpell->SpellFamilyFlags[0] & 0x00000040) player->RemoveSpellCooldown(cdSpell->Id, true); } break; } } break; } case SPELLFAMILY_WARRIOR: { // Second Wind if (dummySpell->SpellIconID == 1697) { // only for spells and hit/crit (trigger start always) and not start from self casted spells (5530 Mace Stun Effect for example) if (procSpell == 0 || !(procEx & (PROC_EX_NORMAL_HIT | PROC_EX_CRITICAL_HIT)) || this == victim) return false; // Need stun or root mechanic if (!(procSpell->GetAllEffectsMechanicMask() & ((1 << MECHANIC_ROOT) | (1 << MECHANIC_STUN)))) return false; switch (dummySpell->Id) { case 29838: triggered_spell_id = 29842; break; case 29834: triggered_spell_id = 29841; break; case 42770: triggered_spell_id = 42771; break; default: LOG_ERROR("entities.unit", "Unit::HandleDummyAuraProc: non handled spell id: {} (SW)", dummySpell->Id); return false; } target = this; break; } break; } case SPELLFAMILY_WARLOCK: { switch (dummySpell->Id) { // Nightfall case 18094: case 18095: // Glyph of corruption case 56218: { target = this; triggered_spell_id = 17941; break; } // Soul Leech case 30293: case 30295: case 30296: { // Improved Soul Leech AuraEffectList const& SoulLeechAuras = GetAuraEffectsByType(SPELL_AURA_DUMMY); for (Unit::AuraEffectList::const_iterator i = SoulLeechAuras.begin(); i != SoulLeechAuras.end(); ++i) { if ((*i)->GetId() == 54117 || (*i)->GetId() == 54118) { if ((*i)->GetEffIndex() != 0) continue; basepoints0 = int32((*i)->GetAmount()); target = GetGuardianPet(); if (target) { // regen mana for pet CastCustomSpell(target, 54607, &basepoints0, nullptr, nullptr, true, castItem, triggeredByAura); } // regen mana for caster CastCustomSpell(this, 59117, &basepoints0, nullptr, nullptr, true, castItem, triggeredByAura); // Get second aura of spell for replenishment effect on party if (AuraEffect const* aurEff = (*i)->GetBase()->GetEffect(EFFECT_1)) { // Replenishment - roll chance if (roll_chance_i(aurEff->GetAmount())) { CastSpell(this, 57669, true, castItem, triggeredByAura); } } break; } } // health basepoints0 = CalculatePct(int32(damage), triggerAmount); target = this; triggered_spell_id = 30294; break; } // Shadowflame (Voidheart Raiment set bonus) case 37377: { triggered_spell_id = 37379; break; } // Pet Healing (Corruptor Raiment or Rift Stalker Armor) case 37381: { target = GetGuardianPet(); if (!target) return false; // heal amount basepoints0 = CalculatePct(int32(damage), triggerAmount); triggered_spell_id = 37382; break; } // Shadowflame Hellfire (Voidheart Raiment set bonus) case 39437: { triggered_spell_id = 37378; break; } } break; } case SPELLFAMILY_PRIEST: { // Body and Soul if (dummySpell->SpellIconID == 2218) { // Proc only from Abolish desease on self cast if (procSpell->Id != 552 || victim != this || !roll_chance_i(triggerAmount)) return false; triggered_spell_id = 64136; target = this; break; } switch (dummySpell->Id) { // Vampiric Embrace case 15286: { if (!victim || !victim->IsAlive() || procSpell->SpellFamilyFlags[1] & 0x80000) return false; // heal amount int32 total = CalculatePct(int32(damage), triggerAmount); int32 team = total / 5; int32 self = total - team; CastCustomSpell(this, 15290, &team, &self, nullptr, true, castItem, triggeredByAura); return true; // no hidden cooldown } // Priest Tier 6 Trinket (Ashtongue Talisman of Acumen) case 40438: { // Shadow Word: Pain if (procSpell->SpellFamilyFlags[0] & 0x8000) triggered_spell_id = 40441; // Renew else if (procSpell->SpellFamilyFlags[0] & 0x40) triggered_spell_id = 40440; else return false; target = this; break; } // Improved Shadowform case 47570: case 47569: { if (!roll_chance_i(triggerAmount)) return false; RemoveMovementImpairingAuras(true); break; } // Glyph of Dispel Magic case 55677: { // Dispel Magic shares spellfamilyflag with abolish disease if (procSpell->SpellIconID != 74) return false; if (!target || !target->IsFriendlyTo(this)) return false; basepoints0 = int32(target->CountPctFromMaxHealth(triggerAmount)); triggered_spell_id = 56131; break; } // Oracle Healing Bonus ("Garments of the Oracle" set) case 26169: { // heal amount basepoints0 = int32(CalculatePct(damage, 10)); target = this; triggered_spell_id = 26170; break; } // Frozen Shadoweave (Shadow's Embrace set) warning! its not only priest set case 39372: { if (!procSpell || (procSpell->GetSchoolMask() & (SPELL_SCHOOL_MASK_FROST | SPELL_SCHOOL_MASK_SHADOW)) == 0) return false; // heal amount basepoints0 = CalculatePct(int32(damage), triggerAmount); target = this; triggered_spell_id = 39373; break; } // Greater Heal (Vestments of Faith (Priest Tier 3) - 4 pieces bonus) case 28809: { triggered_spell_id = 28810; break; } // Priest T10 Healer 2P Bonus case 70770: // Flash Heal if (procSpell->SpellFamilyFlags[0] & 0x800) { triggered_spell_id = 70772; SpellInfo const* blessHealing = sSpellMgr->GetSpellInfo(triggered_spell_id); if (!blessHealing || !victim) return false; basepoints0 = int32(CalculatePct(damage, triggerAmount) / (blessHealing->GetMaxDuration() / blessHealing->Effects[0].Amplitude)); victim->CastDelayedSpellWithPeriodicAmount(this, triggered_spell_id, SPELL_AURA_PERIODIC_HEAL, basepoints0); return true; } break; } break; } case SPELLFAMILY_DRUID: { switch (dummySpell->Id) { // Glyph of Innervate case 54832: { if (procSpell->SpellIconID != 62) return false; int32 mana_perc = triggeredByAura->GetSpellInfo()->Effects[triggeredByAura->GetEffIndex()].CalcValue(); basepoints0 = int32(CalculatePct(GetCreatePowers(POWER_MANA), mana_perc) / 10); triggered_spell_id = 54833; target = this; break; } // Glyph of Starfire case 54845: { triggered_spell_id = 54846; break; } // Glyph of Shred case 54815: { if (!target) return false; // try to find spell Rip on the target if (AuraEffect const* AurEff = target->GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_DRUID, 0x00800000, 0x0, 0x0, GetGUID())) { // Rip's max duration, note: spells which modifies Rip's duration also counted like Glyph of Rip uint32 CountMin = AurEff->GetBase()->GetMaxDuration(); // just Rip's max duration without other spells uint32 CountMax = AurEff->GetSpellInfo()->GetMaxDuration(); // add possible auras' and Glyph of Shred's max duration CountMax += 3 * triggerAmount * IN_MILLISECONDS; // Glyph of Shred -> +6 seconds CountMax += HasAura(54818) ? 4 * IN_MILLISECONDS : 0; // Glyph of Rip -> +4 seconds CountMax += HasAura(60141) ? 4 * IN_MILLISECONDS : 0; // Rip Duration/Lacerate Damage -> +4 seconds // if min < max -> that means caster didn't cast 3 shred yet // so set Rip's duration and max duration if (CountMin < CountMax) { AurEff->GetBase()->SetDuration(AurEff->GetBase()->GetDuration() + triggerAmount * IN_MILLISECONDS); AurEff->GetBase()->SetMaxDuration(CountMin + triggerAmount * IN_MILLISECONDS); return true; } } // if not found Rip return false; } // Glyph of Rake case 54821: { if (procSpell->SpellVisual[0] == 750 && procSpell->Effects[1].ApplyAuraName == 3) { if (target && target->IsCreature()) { triggered_spell_id = 54820; break; } } return false; } // Leader of the Pack case 24932: { if (triggerAmount <= 0) return false; basepoints0 = int32(CountPctFromMaxHealth(triggerAmount)); target = this; triggered_spell_id = 34299; if (triggeredByAura->GetCasterGUID() != GetGUID()) break; int32 basepoints1 = CalculatePct(GetMaxPower(Powers(POWER_MANA)), triggerAmount * 2); // Improved Leader of the Pack // Check cooldown of heal spell cooldown if (IsPlayer() && !ToPlayer()->HasSpellCooldown(34299)) CastCustomSpell(this, 68285, &basepoints1, 0, 0, true, 0, triggeredByAura); break; } // Healing Touch (Dreamwalker Raiment set) case 28719: { // mana back basepoints0 = int32(CalculatePct(spellProc->GetPowerCost(), 30)); target = this; triggered_spell_id = 28742; break; } // Glyph of Rejuvenation case 54754: { if (!victim || !victim->HealthBelowPct(uint32(triggerAmount))) return false; basepoints0 = CalculatePct(int32(damage), triggerAmount); triggered_spell_id = 54755; break; } // Healing Touch Refund (Idol of Longevity trinket) case 28847: { target = this; triggered_spell_id = 28848; break; } // Mana Restore (Malorne Raiment set / Malorne Regalia set) case 37288: case 37295: { target = this; triggered_spell_id = 37238; break; } // Druid Tier 6 Trinket case 40442: { float chance; // Starfire if (procSpell->SpellFamilyFlags[0] & 0x4) { triggered_spell_id = 40445; chance = 25.0f; } // Rejuvenation else if (procSpell->SpellFamilyFlags[0] & 0x10) { triggered_spell_id = 40446; chance = 25.0f; } // Mangle (Bear) and Mangle (Cat) else if (procSpell->SpellFamilyFlags[1] & 0x00000440) { triggered_spell_id = 40452; chance = 40.0f; } else return false; if (!roll_chance_f(chance)) return false; target = this; break; } // Maim Interrupt case 44835: { // Deadly Interrupt Effect triggered_spell_id = 32747; break; } // Item - Druid T10 Restoration 4P Bonus (Rejuvenation) case 70664: { // xinef: proc only from normal Rejuvenation, and proc rejuvenation if (!victim || !procSpell || procSpell->SpellIconID != 64) return false; Player* caster = ToPlayer(); if (!caster) return false; if (!caster->GetGroup() && victim == this) return false; CastCustomSpell(70691, SPELLVALUE_BASE_POINT0, damage, victim, true); return true; } } // Eclipse if (dummySpell->SpellIconID == 2856 && IsPlayer()) { if (!procSpell || effIndex != 0) return false; bool isWrathSpell = (procSpell->SpellFamilyFlags[0] & 1); if (!roll_chance_f(dummySpell->ProcChance * (isWrathSpell ? 0.6f : 1.0f))) return false; target = this; if (target->HasAura(isWrathSpell ? 48517 : 48518)) return false; triggered_spell_id = isWrathSpell ? 48518 : 48517; break; } [[fallthrough]]; /// @todo: Not sure whether the fallthrough was a mistake (forgetting a break) or intended. This should be double-checked. } case SPELLFAMILY_ROGUE: { switch (dummySpell->Id) { // Glyph of Backstab case 56800: { if (victim) if (AuraEffect* aurEff = victim->GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_ROGUE, 0x100000, 0, 0, GetGUID())) if (Aura* aur = aurEff->GetBase()) if (!aur->IsRemoved() && aur->GetDuration() > 0) if ((aur->GetApplyTime() + aur->GetMaxDuration() / 1000 + 5) > (GameTime::GetGameTime().count() + aur->GetDuration() / 1000)) { aur->SetDuration(aur->GetDuration() + 2000); return true; } return false; } // Deadly Throw Interrupt case 32748: { // Prevent cast Deadly Throw Interrupt on self from last effect (apply dummy) of Deadly Throw if (this == victim) return false; triggered_spell_id = 32747; break; } } // Master of subtlety if (dummySpell->SpellIconID == 2114) { triggered_spell_id = 31665; basepoints0 = triggerAmount; break; } // Cut to the Chase if (dummySpell->SpellIconID == 2909) { // "refresh your Slice and Dice duration to its 5 combo point maximum" // lookup Slice and Dice if (AuraEffect const* aur = GetAuraEffect(SPELL_AURA_MOD_MELEE_HASTE, SPELLFAMILY_ROGUE, 0x40000, 0, 0)) { aur->GetBase()->SetDuration(aur->GetSpellInfo()->GetMaxDuration(), true); return true; } return false; } // Deadly Brew else if (dummySpell->SpellIconID == 2963) { triggered_spell_id = 3409; break; } // Quick Recovery else if (dummySpell->SpellIconID == 2116) { if (!procSpell) return false; // energy cost save basepoints0 = CalculatePct(int32(procSpell->ManaCost), triggerAmount); if (basepoints0 <= 0) return false; target = this; triggered_spell_id = 31663; break; } break; } case SPELLFAMILY_HUNTER: { switch (dummySpell->SpellIconID) { case 2236: // Thrill of the Hunt { if (!procSpell) return false; Spell* spell = ToPlayer()->m_spellModTakingSpell; // Disable charge drop because of Lock and Load if (spell) ToPlayer()->SetSpellModTakingSpell(spell, false); // Explosive Shot if (procSpell->SpellFamilyFlags[2] & 0x200) { if (!victim) return false; if (AuraEffect const* pEff = victim->GetAuraEffect(SPELL_AURA_PERIODIC_DUMMY, SPELLFAMILY_HUNTER, 0x0, 0x80000000, 0x0, GetGUID())) basepoints0 = pEff->GetSpellInfo()->CalcPowerCost(this, SpellSchoolMask(pEff->GetSpellInfo()->SchoolMask)) * 4 / 10 / 3; } else basepoints0 = procSpell->CalcPowerCost(this, SpellSchoolMask(procSpell->SchoolMask)) * 4 / 10; if (spell) ToPlayer()->SetSpellModTakingSpell(spell, true); if (basepoints0 <= 0) return false; target = this; triggered_spell_id = 34720; break; } case 3406: // Hunting Party { triggered_spell_id = 57669; target = this; break; } case 3560: // Rapid Recuperation { // This effect only from Rapid Killing (mana regen) if (!(procSpell->SpellFamilyFlags[1] & 0x01000000)) return false; target = this; switch (dummySpell->Id) { case 53228: // Rank 1 triggered_spell_id = 56654; break; case 53232: // Rank 2 triggered_spell_id = 58882; break; } break; } } switch (dummySpell->Id) { case 57870: // Glyph of Mend Pet { if (!victim) return false; victim->CastSpell(victim, 57894, true, nullptr, nullptr, GetGUID()); return true; } } break; } case SPELLFAMILY_PALADIN: { // Light's Beacon - Beacon of Light if (dummySpell->Id == 53651) { if (!victim) return false; // Do not proc from Glyph of Holy Light and Judgement of Light if (procSpell->Id == 20267 || procSpell->Id == 54968) { return false; } Unit* beaconTarget = triggeredByAura->GetBase()->GetCaster(); if (!beaconTarget || beaconTarget == this || !beaconTarget->GetAura(53563, victim->GetGUID())) return false; basepoints0 = int32(damage); triggered_spell_id = procSpell->IsRankOf(sSpellMgr->GetSpellInfo(635)) ? 53652 : 53654; victim->CastCustomSpell(beaconTarget, triggered_spell_id, &basepoints0, nullptr, nullptr, true, 0, triggeredByAura, victim->GetGUID()); return true; } // Judgements of the Wise if (dummySpell->SpellIconID == 3017) { target = this; triggered_spell_id = 31930; // replenishment CastSpell(this, 57669, true, castItem, triggeredByAura); break; } // Righteous Vengeance if (dummySpell->SpellIconID == 3025) { if (!victim) return false; // 4 damage tick basepoints0 = triggerAmount * damage / 400; triggered_spell_id = 61840; // Add remaining ticks to damage done victim->CastDelayedSpellWithPeriodicAmount(this, triggered_spell_id, SPELL_AURA_PERIODIC_DAMAGE, basepoints0); return true; } // Sheath of Light if (dummySpell->SpellIconID == 3030) { // 4 healing tick basepoints0 = triggerAmount * damage / 400; triggered_spell_id = 54203; break; } switch (dummySpell->Id) { // Judgement of Light case 20185: { if (!victim || !victim->IsAlive()) return false; auto* caster = triggeredByAura->GetBase()->GetCaster(); if (!caster || !victim->IsFriendlyTo(caster)) return false; // 2% of base health basepoints0 = int32(victim->CountPctFromMaxHealth(2)); victim->CastCustomSpell(victim, 20267, &basepoints0, 0, 0, true, 0, triggeredByAura); return true; } // Judgement of Wisdom case 20186: { if (!victim || !victim->IsAlive() || !victim->HasActivePowerType(POWER_MANA)) return false; auto* caster = triggeredByAura->GetBase()->GetCaster(); if (!caster || !victim->IsFriendlyTo(caster)) return false; // 2% of base mana basepoints0 = int32(CalculatePct(victim->GetCreateMana(), 2)); victim->CastCustomSpell(victim, 20268, &basepoints0, nullptr, nullptr, true, 0, triggeredByAura); return true; } // Holy Power (Redemption Armor set) case 28789: { if (!victim) return false; // Set class defined buff switch (victim->getClass()) { case CLASS_PALADIN: case CLASS_PRIEST: case CLASS_SHAMAN: case CLASS_DRUID: triggered_spell_id = 28795; // Increases the friendly target's mana regeneration by $s1 per 5 sec. for $d. break; case CLASS_MAGE: case CLASS_WARLOCK: triggered_spell_id = 28793; // Increases the friendly target's spell damage and healing by up to $s1 for $d. break; case CLASS_HUNTER: case CLASS_ROGUE: triggered_spell_id = 28791; // Increases the friendly target's attack power by $s1 for $d. break; case CLASS_WARRIOR: triggered_spell_id = 28790; // Increases the friendly target's armor break; default: return false; } break; } // Seal of Vengeance (damage calc on apply aura) case 31801: { if (effIndex != 0 || !victim) // effect 1, 2 used by seal unleashing code return false; // At melee attack or Hammer of the Righteous spell damage considered as melee attack bool stacker = !procSpell || procSpell->Id == 53595; // spells with SPELL_DAMAGE_CLASS_MELEE excluding Judgements bool damager = procSpell && (procSpell->EquippedItemClass != -1 || (procSpell->SpellIconID == 243 && procSpell->SpellVisual[0] == 39)); if (!stacker && !damager) return false; triggered_spell_id = 31803; if (Aura* aur = victim->GetAura(triggered_spell_id, GetGUID())) { if (aur->GetStackAmount() == 5) { if (stacker) aur->RefreshDuration(); } } CastSpell(victim, 42463, true, castItem, triggeredByAura); // Seal of Vengeance if (!stacker) return false; break; } // Seal of Corruption case 53736: { if (effIndex != 0 || !victim) // effect 1, 2 used by seal unleashing code return false; // At melee attack or Hammer of the Righteous spell damage considered as melee attack bool stacker = !procSpell || procSpell->Id == 53595; // spells with SPELL_DAMAGE_CLASS_MELEE excluding Judgements bool damager = procSpell && (procSpell->EquippedItemClass != -1 || (procSpell->SpellIconID == 243 && procSpell->SpellVisual[0] == 39)); if (!stacker && !damager) return false; triggered_spell_id = 53742; if (Aura* aur = victim->GetAura(triggered_spell_id, GetGUID())) { if (aur->GetStackAmount() == 5) { if (stacker) aur->RefreshDuration(); } } CastSpell(victim, 53739, true, castItem, triggeredByAura); // Seal of Corruption if (!stacker) return false; break; } // Spiritual Attunement case 31785: case 33776: { // if healed by another unit (victim) if (this == victim) return false; // dont allow non-positive dots to proc if (!procSpell || !procSpell->IsPositive()) return false; HealInfo const* healInfo = eventInfo.GetHealInfo(); if (!healInfo) { return false; } uint32 effectiveHeal = healInfo->GetEffectiveHeal(); if (effectiveHeal) { // heal amount basepoints0 = int32(CalculatePct(effectiveHeal, triggerAmount)); target = this; if (basepoints0) triggered_spell_id = 31786; } break; } // Paladin Tier 6 Trinket (Ashtongue Talisman of Zeal) case 40470: { if (!procSpell) return false; float chance = 0.0f; // Flash of light/Holy light if (procSpell->SpellFamilyFlags[0] & 0xC0000000) { triggered_spell_id = 40471; chance = 15.0f; } // Judgement (any) else if (procSpell->SpellFamilyFlags[0] & 0x800000) { triggered_spell_id = 40472; chance = 50.0f; } else return false; if (!roll_chance_f(chance)) return false; break; } // Glyph of Holy Light case 54937: { triggered_spell_id = 54968; basepoints0 = CalculatePct(int32(damage), triggerAmount); break; } // Item - Paladin T8 Holy 2P Bonus case 64890: { triggered_spell_id = 64891; basepoints0 = triggerAmount * damage / 300; break; } case 71406: // Tiny Abomination in a Jar case 71545: // Tiny Abomination in a Jar (Heroic) { if (!victim || !victim->IsAlive()) return false; CastSpell(this, 71432, true, nullptr, triggeredByAura); Aura const* dummy = GetAura(71432); if (!dummy || dummy->GetStackAmount() < (dummySpell->Id == 71406 ? 8 : 7)) return false; RemoveAurasDueToSpell(71432); triggered_spell_id = 71433; // default main hand attack // roll if offhand if (Player const* player = ToPlayer()) if (player->GetWeaponForAttack(OFF_ATTACK, true) && urand(0, 1)) triggered_spell_id = 71434; target = victim; break; } // Item - Icecrown 25 Normal Dagger Proc case 71880: { switch (getPowerType()) { case POWER_MANA: triggered_spell_id = 71881; break; case POWER_RAGE: triggered_spell_id = 71883; break; case POWER_ENERGY: triggered_spell_id = 71882; break; case POWER_RUNIC_POWER: triggered_spell_id = 71884; break; default: return false; } break; } // Item - Icecrown 25 Heroic Dagger Proc case 71892: { switch (getPowerType()) { case POWER_MANA: triggered_spell_id = 71888; break; case POWER_RAGE: triggered_spell_id = 71886; break; case POWER_ENERGY: triggered_spell_id = 71887; break; case POWER_RUNIC_POWER: triggered_spell_id = 71885; break; default: return false; } break; } } break; } case SPELLFAMILY_SHAMAN: { switch (dummySpell->Id) { // Tidal Force case 55198: { // Remove aura stack from caster RemoveAuraFromStack(55166); // drop charges return false; } // Totemic Power (The Earthshatterer set) case 28823: { if (!victim) return false; // Set class defined buff switch (victim->getClass()) { case CLASS_PALADIN: case CLASS_PRIEST: case CLASS_SHAMAN: case CLASS_DRUID: triggered_spell_id = 28824; // Increases the friendly target's mana regeneration by $s1 per 5 sec. for $d. break; case CLASS_MAGE: case CLASS_WARLOCK: triggered_spell_id = 28825; // Increases the friendly target's spell damage and healing by up to $s1 for $d. break; case CLASS_HUNTER: case CLASS_ROGUE: triggered_spell_id = 28826; // Increases the friendly target's attack power by $s1 for $d. break; case CLASS_WARRIOR: triggered_spell_id = 28827; // Increases the friendly target's armor break; default: return false; } break; } // Lesser Healing Wave (Totem of Flowing Water Relic) case 28849: { target = this; triggered_spell_id = 28850; break; } // Windfury Weapon (Passive) 1-8 Ranks case 33757: { Player* player = ToPlayer(); if (!player || !castItem || !castItem->IsEquipped() || !victim || !victim->IsAlive()) return false; if (triggeredByAura->GetBase() && castItem->GetGUID() != triggeredByAura->GetBase()->GetCastItemGUID()) return false; WeaponAttackType attType = player->GetAttackBySlot(castItem->GetSlot()); if ((attType != BASE_ATTACK && attType != OFF_ATTACK) || (attType == BASE_ATTACK && procFlag & PROC_FLAG_DONE_OFFHAND_ATTACK) || (attType == OFF_ATTACK && procFlag & PROC_FLAG_DONE_MAINHAND_ATTACK)) return false; // Now amount of extra power stored in 1 effect of Enchant spell // Get it by item enchant id uint32 spellId; switch (castItem->GetEnchantmentId(EnchantmentSlot(TEMP_ENCHANTMENT_SLOT))) { case 283: spellId = 8232; break; // 1 Rank case 284: spellId = 8235; break; // 2 Rank case 525: spellId = 10486; break; // 3 Rank case 1669: spellId = 16362; break; // 4 Rank case 2636: spellId = 25505; break; // 5 Rank case 3785: spellId = 58801; break; // 6 Rank case 3786: spellId = 58803; break; // 7 Rank case 3787: spellId = 58804; break; // 8 Rank default: { LOG_ERROR("entities.unit", "Unit::HandleDummyAuraProc: non handled item enchantment (rank?) {} for spell id: {} (Windfury)", castItem->GetEnchantmentId(EnchantmentSlot(TEMP_ENCHANTMENT_SLOT)), dummySpell->Id); return false; } } SpellInfo const* windfurySpellInfo = sSpellMgr->GetSpellInfo(spellId); if (!windfurySpellInfo) { LOG_ERROR("entities.unit", "Unit::HandleDummyAuraProc: non-existing spell id: {} (Windfury)", spellId); return false; } int32 extra_attack_power = CalculateSpellDamage(victim, windfurySpellInfo, 1); // Value gained from additional AP basepoints0 = int32(extra_attack_power / 14.0f * GetAttackTime(attType) / 1000); if (procFlag & PROC_FLAG_DONE_MAINHAND_ATTACK) triggered_spell_id = 25504; if (procFlag & PROC_FLAG_DONE_OFFHAND_ATTACK) triggered_spell_id = 33750; // custom cooldown processing case if (player->HasSpellCooldown(dummySpell->Id)) return false; // apply cooldown before cast to prevent processing itself player->AddSpellCooldown(dummySpell->Id, 0, 3 * IN_MILLISECONDS); // Attack Twice for (uint32 i = 0; i < 2; ++i) CastCustomSpell(victim, triggered_spell_id, &basepoints0, nullptr, nullptr, true, castItem, triggeredByAura); return true; } // Shaman Tier 6 Trinket case 40463: { if (!procSpell) return false; float chance; if (procSpell->SpellFamilyFlags[0] & 0x1) { triggered_spell_id = 40465; // Lightning Bolt chance = 15.0f; } else if (procSpell->SpellFamilyFlags[0] & 0x80) { triggered_spell_id = 40465; // Lesser Healing Wave chance = 10.0f; } else if (procSpell->SpellFamilyFlags[1] & 0x00000010) { triggered_spell_id = 40466; // Stormstrike chance = 50.0f; } else return false; if (!roll_chance_f(chance)) return false; target = this; break; } // Glyph of Healing Wave case 55440: { // Not proc from self heals if (this == victim) return false; basepoints0 = CalculatePct(int32(damage), triggerAmount); target = this; triggered_spell_id = 55533; break; } // Spirit Hunt case 58877: { // Cast on owner target = GetOwner(); if (!target) return false; basepoints0 = CalculatePct(int32(damage), triggerAmount); triggered_spell_id = 58879; // Heal wolf CastCustomSpell(this, triggered_spell_id, &basepoints0, nullptr, nullptr, true, castItem, triggeredByAura, originalCaster); break; } // Shaman T9 Elemental 4P Bonus case 67228: { // Lava Burst if (procSpell->SpellFamilyFlags[1] & 0x1000) { triggered_spell_id = 71824; SpellInfo const* triggeredSpell = sSpellMgr->GetSpellInfo(triggered_spell_id); if (!triggeredSpell) return false; basepoints0 = CalculatePct(int32(damage), triggerAmount) / (triggeredSpell->GetMaxDuration() / triggeredSpell->Effects[0].Amplitude); } break; } // Item - Shaman T10 Elemental 4P Bonus case 70817: { if (!target) return false; // try to find spell Flame Shock on the target if (AuraEffect const* aurEff = target->GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_SHAMAN, 0x10000000, 0x0, 0x0, GetGUID())) { Aura* flameShock = aurEff->GetBase(); int32 extraTime = 2 * aurEff->GetAmplitude(); flameShock->SetMaxDuration(flameShock->GetMaxDuration() + extraTime); flameShock->SetDuration(flameShock->GetDuration() + extraTime); return true; } // if not found Flame Shock return false; } break; } // Frozen Power if (dummySpell->SpellIconID == 3780) { if (!target) return false; if (GetDistance(target) < 15.0f) return false; float chance = (float)triggerAmount; if (!roll_chance_f(chance)) return false; triggered_spell_id = 63685; break; } // Ancestral Awakening if (dummySpell->SpellIconID == 3065) { triggered_spell_id = 52759; basepoints0 = CalculatePct(int32(damage), triggerAmount); target = this; break; } // Flametongue Weapon (Passive) if (dummySpell->SpellFamilyFlags[0] & 0x200000) { if (!IsPlayer() || !victim || !victim->IsAlive() || !castItem || !castItem->IsEquipped()) return false; WeaponAttackType attType = Player::GetAttackBySlot(castItem->GetSlot()); if ((attType != BASE_ATTACK && attType != OFF_ATTACK) || (attType == BASE_ATTACK && procFlag & PROC_FLAG_DONE_OFFHAND_ATTACK) || (attType == OFF_ATTACK && procFlag & PROC_FLAG_DONE_MAINHAND_ATTACK)) return false; float fire_onhit = float(CalculatePct(dummySpell->Effects[EFFECT_0]. CalcValue(), 1.0f)); float add_spellpower = (float)(SpellBaseDamageBonusDone(SPELL_SCHOOL_MASK_FIRE) + victim->SpellBaseDamageBonusTaken(SPELL_SCHOOL_MASK_FIRE)); // 1.3speed = 5%, 2.6speed = 10%, 4.0 speed = 15%, so, 1.0speed = 3.84% ApplyPct(add_spellpower, 3.84f); // Enchant on Off-Hand and ready? if (castItem->GetSlot() == EQUIPMENT_SLOT_OFFHAND && procFlag & PROC_FLAG_DONE_OFFHAND_ATTACK) { float BaseWeaponSpeed = GetAttackTime(OFF_ATTACK) / 1000.0f; // Value1: add the tooltip damage by swingspeed + Value2: add spelldmg by swingspeed basepoints0 = int32((fire_onhit * BaseWeaponSpeed) + (add_spellpower * BaseWeaponSpeed)); triggered_spell_id = 10444; } // Enchant on Main-Hand and ready? else if (castItem->GetSlot() == EQUIPMENT_SLOT_MAINHAND && procFlag & PROC_FLAG_DONE_MAINHAND_ATTACK) { float BaseWeaponSpeed = GetAttackTime(BASE_ATTACK) / 1000.0f; // Value1: add the tooltip damage by swingspeed + Value2: add spelldmg by swingspeed basepoints0 = int32((fire_onhit * BaseWeaponSpeed) + (add_spellpower * BaseWeaponSpeed)); triggered_spell_id = 10444; } // If not ready, we should return, shouldn't we?! else return false; CastCustomSpell(victim, triggered_spell_id, &basepoints0, nullptr, nullptr, true, castItem, triggeredByAura); return true; } // Improved Water Shield if (dummySpell->SpellIconID == 2287) { if (!procSpell) return false; // Default chance for Healing Wave and Riptide float chance = (float)triggeredByAura->GetAmount(); if (procSpell->SpellFamilyFlags[0] & 0x80) // Lesser Healing Wave - 0.6 of default chance *= 0.6f; else if (procSpell->SpellFamilyFlags[0] & 0x100) // Chain heal - 0.3 of default chance *= 0.3f; if (!roll_chance_f(chance)) return false; // Water Shield if (AuraEffect const* aurEff = GetAuraEffect(SPELL_AURA_PROC_TRIGGER_SPELL, SPELLFAMILY_SHAMAN, 0, 0x00000020, 0)) { uint32 spell = aurEff->GetSpellInfo()->Effects[aurEff->GetEffIndex()].TriggerSpell; CastSpell(this, spell, true, castItem, triggeredByAura); return true; } return false; } // Lightning Overload if (dummySpell->SpellIconID == 2018) // only this spell have SpellFamily Shaman SpellIconID == 2018 and dummy aura { if (!procSpell || !IsPlayer() || !victim) return false; uint32 spell = 45284; // chain lightning only procs 1/3 of the time if (procSpell->SpellFamilyFlags[0] & 0x2) { if (!roll_chance_i(33)) return false; spell = 45297; } if (procEx & PROC_EX_CRITICAL_HIT) damage /= 2; // do not reduce damage-spells have correct basepoints damage /= 2; int32 dmg = damage; // Cast CastCustomSpell(victim, spell, &dmg, 0, 0, true, castItem, triggeredByAura); return true; } // Static Shock if (dummySpell->SpellIconID == 3059) { // Lightning Shield if (AuraEffect const* aurEff = GetAuraEffect(SPELL_AURA_PROC_TRIGGER_SPELL, SPELLFAMILY_SHAMAN, 0x400, 0, 0)) { uint32 spell = sSpellMgr->GetSpellWithRank(26364, aurEff->GetSpellInfo()->GetRank()); CastSpell(target, spell, true, castItem, triggeredByAura); aurEff->GetBase()->DropCharge(); return true; } return false; } break; } case SPELLFAMILY_DEATHKNIGHT: { // Improved Blood Presence if (dummySpell->SpellIconID == 2636) { if (!IsPlayer()) return false; basepoints0 = CalculatePct(int32(damage), triggerAmount); break; } // Butchery if (dummySpell->SpellIconID == 2664) { basepoints0 = triggerAmount; triggered_spell_id = 50163; target = this; break; } // Mark of Blood if (dummySpell->Id == 49005) { /// @todo: need more info (cooldowns/PPM) triggered_spell_id = 61607; break; } // Unholy Blight if (dummySpell->Id == 49194) { triggered_spell_id = 50536; SpellInfo const* unholyBlight = sSpellMgr->GetSpellInfo(triggered_spell_id); if (!unholyBlight || !victim) return false; basepoints0 = CalculatePct(int32(damage), triggerAmount); //Glyph of Unholy Blight if (AuraEffect* glyph = GetAuraEffect(63332, 0)) AddPct(basepoints0, glyph->GetAmount()); basepoints0 = basepoints0 / (unholyBlight->GetMaxDuration() / unholyBlight->Effects[0].Amplitude); victim->CastDelayedSpellWithPeriodicAmount(this, triggered_spell_id, SPELL_AURA_PERIODIC_DAMAGE, basepoints0); return true; } // Vendetta if (dummySpell->SpellFamilyFlags[0] & 0x10000) { basepoints0 = int32(CountPctFromMaxHealth(triggerAmount)); triggered_spell_id = 50181; target = this; break; } // Necrosis if (dummySpell->SpellIconID == 2709) { basepoints0 = CalculatePct(int32(damage), triggerAmount); triggered_spell_id = 51460; break; } // Threat of Thassarian if (dummySpell->SpellIconID == 2023) { // Must Dual Wield if (!procSpell || !HasOffhandWeaponForAttack()) return false; // Chance as basepoints for dummy aura if (!roll_chance_i(triggerAmount)) return false; switch (procSpell->Id) { // Obliterate case 49020: triggered_spell_id = 66198; break; // Rank 1 case 51423: triggered_spell_id = 66972; break; // Rank 2 case 51424: triggered_spell_id = 66973; break; // Rank 3 case 51425: triggered_spell_id = 66974; break; // Rank 4 // Frost Strike case 49143: triggered_spell_id = 66196; break; // Rank 1 case 51416: triggered_spell_id = 66958; break; // Rank 2 case 51417: triggered_spell_id = 66959; break; // Rank 3 case 51418: triggered_spell_id = 66960; break; // Rank 4 case 51419: triggered_spell_id = 66961; break; // Rank 5 case 55268: triggered_spell_id = 66962; break; // Rank 6 // Plague Strike case 45462: triggered_spell_id = 66216; break; // Rank 1 case 49917: triggered_spell_id = 66988; break; // Rank 2 case 49918: triggered_spell_id = 66989; break; // Rank 3 case 49919: triggered_spell_id = 66990; break; // Rank 4 case 49920: triggered_spell_id = 66991; break; // Rank 5 case 49921: triggered_spell_id = 66992; break; // Rank 6 // Death Strike case 49998: triggered_spell_id = 66188; break; // Rank 1 case 49999: triggered_spell_id = 66950; break; // Rank 2 case 45463: triggered_spell_id = 66951; break; // Rank 3 case 49923: triggered_spell_id = 66952; break; // Rank 4 case 49924: triggered_spell_id = 66953; break; // Rank 5 // Rune Strike case 56815: triggered_spell_id = 66217; break; // Rank 1 // Blood Strike case 45902: triggered_spell_id = 66215; break; // Rank 1 case 49926: triggered_spell_id = 66975; break; // Rank 2 case 49927: triggered_spell_id = 66976; break; // Rank 3 case 49928: triggered_spell_id = 66977; break; // Rank 4 case 49929: triggered_spell_id = 66978; break; // Rank 5 case 49930: triggered_spell_id = 66979; break; // Rank 6 default: return false; } // This should do, restore spell mod so next attack can also use this! // crit chance for first strike is already computed ToPlayer()->RestoreSpellMods(m_currentSpells[CURRENT_GENERIC_SPELL], 51124, nullptr); // Killing Machine ToPlayer()->RestoreSpellMods(m_currentSpells[CURRENT_GENERIC_SPELL], 49796, nullptr); // Deathchill // Xinef: Somehow basepoints are divided by 2 which is later divided by 2 (offhand multiplier) SpellInfo const* triggerEntry = sSpellMgr->GetSpellInfo(triggered_spell_id); if (triggerEntry->SchoolMask & SPELL_SCHOOL_MASK_NORMAL) basepoints0 = triggerEntry->Effects[EFFECT_0].BasePoints * 2; SetCantProc(true); if (basepoints0) CastCustomSpell(target, triggered_spell_id, &basepoints0, nullptr, nullptr, true, castItem, triggeredByAura, originalCaster); else CastSpell(target, triggered_spell_id, true, castItem, triggeredByAura, originalCaster); SetCantProc(false); return true; } // Runic Power Back on Snare/Root if (dummySpell->Id == 61257) { // only for spells and hit/crit (trigger start always) and not start from self casted spells if (procSpell == 0 || !(procEx & (PROC_EX_NORMAL_HIT | PROC_EX_CRITICAL_HIT)) || this == victim) return false; // Need snare or root mechanic if (!(procSpell->GetAllEffectsMechanicMask() & ((1 << MECHANIC_ROOT) | (1 << MECHANIC_SNARE)))) return false; triggered_spell_id = 61258; target = this; break; } // Sudden Doom if (dummySpell->SpellIconID == 1939 && IsPlayer()) { SpellChainNode const* chain = nullptr; // get highest rank of the Death Coil spell PlayerSpellMap const& sp_list = ToPlayer()->GetSpellMap(); for (PlayerSpellMap::const_iterator itr = sp_list.begin(); itr != sp_list.end(); ++itr) { // check if shown in spell book if (!itr->second->Active || !itr->second->IsInSpec(ToPlayer()->GetActiveSpec()) || itr->second->State == PLAYERSPELL_REMOVED) continue; SpellInfo const* spellProto = sSpellMgr->GetSpellInfo(itr->first); if (!spellProto) continue; if (spellProto->SpellFamilyName == SPELLFAMILY_DEATHKNIGHT && spellProto->SpellFamilyFlags[0] & 0x2000) { SpellChainNode const* newChain = sSpellMgr->GetSpellChainNode(itr->first); // No chain entry or entry lower than found entry if (!chain || !newChain || (chain->rank < newChain->rank)) { triggered_spell_id = itr->first; chain = newChain; } else continue; // Found spell is last in chain - do not need to look more // Optimisation for most common case if (chain && chain->last->Id == itr->first) break; } } } break; } case SPELLFAMILY_POTION: { // alchemist's stone if (dummySpell->Id == 17619) { if (procSpell->SpellFamilyName == SPELLFAMILY_POTION) { for (uint8 i = 0; i < MAX_SPELL_EFFECTS; i++) { if (procSpell->Effects[i].Effect == SPELL_EFFECT_HEAL) { triggered_spell_id = 21399; } else if (procSpell->Effects[i].Effect == SPELL_EFFECT_ENERGIZE) { triggered_spell_id = 21400; } else continue; basepoints0 = int32(CalculateSpellDamage(this, procSpell, i) * 0.4f); CastCustomSpell(this, triggered_spell_id, &basepoints0, nullptr, nullptr, true, nullptr, triggeredByAura); } return true; } } break; } case SPELLFAMILY_PET: { switch (dummySpell->SpellIconID) { // Guard Dog case 201: { if (!victim) return false; triggered_spell_id = 54445; target = this; float addThreat = float(CalculatePct(procSpell->Effects[0].CalcValue(this), triggerAmount)); victim->AddThreat(this, addThreat); break; } // Silverback case 1582: triggered_spell_id = dummySpell->Id == 62765 ? 62801 : 62800; target = this; break; } break; } default: break; } // if not handled by custom case, get triggered spell from dummySpell proto if (!triggered_spell_id) triggered_spell_id = dummySpell->Effects[triggeredByAura->GetEffIndex()].TriggerSpell; // processed charge only counting case if (!triggered_spell_id) return true; SpellInfo const* triggerEntry = sSpellMgr->GetSpellInfo(triggered_spell_id); if (!triggerEntry) { LOG_ERROR("entities.unit", "Unit::HandleDummyAuraProc: Spell {} has non-existing triggered spell {}", dummySpell->Id, triggered_spell_id); return false; } if (cooldown_spell_id == 0) cooldown_spell_id = triggered_spell_id; if (cooldown) { if (HasSpellCooldown(cooldown_spell_id)) return false; AddSpellCooldown(cooldown_spell_id, 0, cooldown); } if (basepoints0) CastCustomSpell(target, triggered_spell_id, &basepoints0, nullptr, nullptr, true, castItem, triggeredByAura, originalCaster); else CastSpell(target, triggered_spell_id, true, castItem, triggeredByAura, originalCaster); return true; } // Used in case when access to whole aura is needed // All procs should be handled like this... bool Unit::HandleAuraProc(Unit* victim, uint32 damage, Aura* triggeredByAura, SpellInfo const* /*procSpell*/, uint32 /*procFlag*/, uint32 procEx, uint32 cooldown, bool* handled) { SpellInfo const* dummySpell = triggeredByAura->GetSpellInfo(); switch (dummySpell->SpellFamilyName) { case SPELLFAMILY_GENERIC: switch (dummySpell->Id) { // Nevermelting Ice Crystal case 71564: RemoveAuraFromStack(71564); *handled = true; break; // Gaseous Bloat case 70672: case 72455: case 72832: case 72833: { if (Unit* caster = triggeredByAura->GetCaster()) if (victim && caster->GetGUID() == victim->GetGUID()) { *handled = true; uint32 stack = triggeredByAura->GetStackAmount(); int32 const mod = (GetMap()->GetSpawnMode() & 1) ? 1500 : 1250; int32 dmg = 0; for (uint8 i = 1; i <= stack; ++i) dmg += mod * i; caster->CastCustomSpell(70701, SPELLVALUE_BASE_POINT0, dmg); } break; } // Ball of Flames Proc case 71756: case 72782: case 72783: case 72784: RemoveAuraFromStack(dummySpell->Id); *handled = true; break; // Discerning Eye of the Beast case 59915: { CastSpell(this, 59914, true); // 59914 already has correct basepoints in DBC, no need for custom bp *handled = true; break; } // Swift Hand of Justice case 59906: { int32 bp0 = CalculatePct(GetMaxHealth(), dummySpell->Effects[EFFECT_0]. CalcValue()); CastCustomSpell(this, 59913, &bp0, nullptr, nullptr, true); *handled = true; break; } } break; case SPELLFAMILY_MAGE: { // Combustion switch (dummySpell->Id) { case 11129: { *handled = true; Unit* caster = triggeredByAura->GetCaster(); if (!caster || !damage) return false; // last charge and crit if (triggeredByAura->GetCharges() <= 1 && (procEx & PROC_EX_CRITICAL_HIT)) return true; // charge counting (will removed) CastSpell(this, 28682, true); return procEx & PROC_EX_CRITICAL_HIT; } // Empowered Fire case 31656: case 31657: case 31658: { *handled = true; SpellInfo const* spInfo = sSpellMgr->GetSpellInfo(67545); if (!spInfo) return false; int32 bp0 = int32(CalculatePct(GetMaxPower(POWER_MANA), spInfo->Effects[0].CalcValue())); CastCustomSpell(this, 67545, &bp0, nullptr, nullptr, true, nullptr, triggeredByAura->GetEffect(EFFECT_0), GetGUID()); return true; } } break; } case SPELLFAMILY_DEATHKNIGHT: { // Blood of the North // Reaping // Death Rune Mastery // xinef: Icon 22 is used for item bonus, skip if (dummySpell->SpellIconID == 3041 || (dummySpell->SpellIconID == 22 && dummySpell->Id != 62459) || dummySpell->SpellIconID == 2622) { *handled = true; // Convert recently used Blood Rune to Death Rune if (Player* player = ToPlayer()) { if (!player->IsClass(CLASS_DEATH_KNIGHT, CLASS_CONTEXT_ABILITY)) return false; // xinef: not true //RuneType rune = ToPlayer()->GetLastUsedRune(); // can't proc from death rune use //if (rune == RUNE_DEATH) // return false; AuraEffect* aurEff = triggeredByAura->GetEffect(EFFECT_0); if (!aurEff) return false; // Reset amplitude - set death rune remove timer to 30s aurEff->ResetPeriodic(true); uint32 runesLeft; if (dummySpell->SpellIconID == 2622) runesLeft = 2; else runesLeft = 1; for (uint8 i = 0; i < MAX_RUNES && runesLeft; ++i) { if (dummySpell->SpellIconID == 2622) { if (player->GetCurrentRune(i) == RUNE_DEATH || player->GetBaseRune(i) == RUNE_BLOOD) continue; } else { if (player->GetCurrentRune(i) == RUNE_DEATH || player->GetBaseRune(i) != RUNE_BLOOD) continue; } if (player->GetRuneCooldown(i) != player->GetRuneBaseCooldown(i, false)) continue; --runesLeft; // Mark aura as used player->AddRuneByAuraEffect(i, RUNE_DEATH, aurEff); } return true; } return false; } break; } case SPELLFAMILY_WARRIOR: { switch (dummySpell->Id) { // Item - Warrior T10 Protection 4P Bonus case 70844: { int32 basepoints0 = CalculatePct(GetMaxHealth(), dummySpell->Effects[EFFECT_1]. CalcValue()); CastCustomSpell(this, 70845, &basepoints0, nullptr, nullptr, true); break; } default: break; } break; } case SPELLFAMILY_SHAMAN: { // Flurry if ((dummySpell->SpellFamilyFlags[1] & 0x00000200) != 0) { if (cooldown) { if (HasSpellCooldown(dummySpell->Id)) { *handled = true; break; } AddSpellCooldown(dummySpell->Id, 0, cooldown); } } break; } } return false; } bool Unit::HandleProcTriggerSpell(Unit* victim, uint32 damage, AuraEffect* triggeredByAura, SpellInfo const* procSpell, uint32 procFlags, uint32 procEx, uint32 cooldown, uint32 procPhase, ProcEventInfo& eventInfo) { // Get triggered aura spell info SpellInfo const* auraSpellInfo = triggeredByAura->GetSpellInfo(); // Basepoints of trigger aura int32 triggerAmount = triggeredByAura->GetAmount(); // Set trigger spell id, target, custom basepoints uint32 trigger_spell_id = auraSpellInfo->Effects[triggeredByAura->GetEffIndex()].TriggerSpell; Unit* target = nullptr; int32 basepoints0 = 0; if (triggeredByAura->GetAuraType() == SPELL_AURA_PROC_TRIGGER_SPELL_WITH_VALUE) basepoints0 = triggerAmount; Item* castItem = triggeredByAura->GetBase()->GetCastItemGUID() && IsPlayer() ? ToPlayer()->GetItemByGuid(triggeredByAura->GetBase()->GetCastItemGUID()) : nullptr; // Try handle unknown trigger spells //if (sSpellMgr->GetSpellInfo(trigger_spell_id) == nullptr) { switch (auraSpellInfo->SpellFamilyName) { case SPELLFAMILY_GENERIC: switch (auraSpellInfo->Id) { case 43820: // Charm of the Witch Doctor (Amani Charm of the Witch Doctor trinket) // Pct value stored in dummy if (!victim) return false; basepoints0 = victim->GetCreateHealth() * auraSpellInfo->Effects[1].CalcValue() / 100; target = victim; break; case 57345: // Darkmoon Card: Greatness { float stat = 0.0f; // strength if (GetStat(STAT_STRENGTH) > stat) { trigger_spell_id = 60229; stat = GetStat(STAT_STRENGTH); } // agility if (GetStat(STAT_AGILITY) > stat) { trigger_spell_id = 60233; stat = GetStat(STAT_AGILITY); } // intellect if (GetStat(STAT_INTELLECT) > stat) { trigger_spell_id = 60234; stat = GetStat(STAT_INTELLECT);} // spirit if (GetStat(STAT_SPIRIT) > stat) { trigger_spell_id = 60235; } break; } case 67702: // Death's Choice, Item - Coliseum 25 Normal Melee Trinket { if (!damage) return false; float stat = 0.0f; // strength if (GetStat(STAT_STRENGTH) > stat) { trigger_spell_id = 67708; stat = GetStat(STAT_STRENGTH); } // agility if (GetStat(STAT_AGILITY) > stat) { trigger_spell_id = 67703; } break; } case 67771: // Death's Choice (heroic), Item - Coliseum 25 Heroic Melee Trinket { if (!damage) return false; float stat = 0.0f; // strength if (GetStat(STAT_STRENGTH) > stat) { trigger_spell_id = 67773; stat = GetStat(STAT_STRENGTH); } // agility if (GetStat(STAT_AGILITY) > stat) { trigger_spell_id = 67772; } break; } case 27522: // Mana Drain Trigger case 40336: // Mana Drain Trigger case 46939: // Black Bow of the Betrayer { // On successful melee or ranged attack gain $29471s1 mana and if possible drain $27526s1 mana from the target. if (IsAlive()) CastSpell(this, 29471, true, castItem, triggeredByAura); if (victim && victim->IsAlive()) CastSpell(victim, 27526, true, castItem, triggeredByAura); return true; } // Forge of Souls, Devourer of Souls, Mirrored Soul case 69023: { int32 dmg = damage * 0.45f; if (dmg > 0) if (Aura* a = GetAura(69023)) if (Unit* c = a->GetCaster()) CastCustomSpell(c, 69034, &dmg, 0, 0, true); return true; } // Soul-Trader Beacon proc aura case 50051: { if (!victim) return false; if (Creature* cr = GetCompanionPet()) cr->CastSpell(victim, 50101, true); return false; } } break; case SPELLFAMILY_MAGE: if (auraSpellInfo->SpellIconID == 2127) // Blazing Speed { switch (auraSpellInfo->Id) { case 31641: // Rank 1 case 31642: // Rank 2 trigger_spell_id = 31643; break; default: LOG_ERROR("entities.unit", "Unit::HandleProcTriggerSpell: Spell {} miss posibly Blazing Speed", auraSpellInfo->Id); return false; } } else if (auraSpellInfo->Id == 71761) // Deep Freeze Immunity State (only permanent) { Creature* creature = victim->ToCreature(); if (!creature || !creature->HasMechanicTemplateImmunity(1 << (MECHANIC_STUN - 1))) return false; } break; case SPELLFAMILY_WARLOCK: { // Nether Protection if (auraSpellInfo->SpellIconID == 1985) { if (!procSpell) return false; switch (GetFirstSchoolInMask(procSpell->GetSchoolMask())) { case SPELL_SCHOOL_NORMAL: return false; // ignore case SPELL_SCHOOL_HOLY: trigger_spell_id = 54370; break; case SPELL_SCHOOL_FIRE: trigger_spell_id = 54371; break; case SPELL_SCHOOL_NATURE: trigger_spell_id = 54375; break; case SPELL_SCHOOL_FROST: trigger_spell_id = 54372; break; case SPELL_SCHOOL_SHADOW: trigger_spell_id = 54374; break; case SPELL_SCHOOL_ARCANE: trigger_spell_id = 54373; break; default: return false; } } break; } case SPELLFAMILY_PRIEST: { // Blessed Recovery if (auraSpellInfo->SpellIconID == 1875) { switch (auraSpellInfo->Id) { case 27811: trigger_spell_id = 27813; break; case 27815: trigger_spell_id = 27817; break; case 27816: trigger_spell_id = 27818; break; default: LOG_ERROR("entities.unit", "Unit::HandleProcTriggerSpell: Spell {} not handled in BR", auraSpellInfo->Id); return false; } basepoints0 = CalculatePct(int32(damage), triggerAmount) / 3; target = this; // Add remaining ticks to healing done CastDelayedSpellWithPeriodicAmount(this, trigger_spell_id, SPELL_AURA_PERIODIC_HEAL, basepoints0); return true; } break; } case SPELLFAMILY_DRUID: { switch (auraSpellInfo->Id) { // Druid Forms Trinket case 37336: { switch (GetShapeshiftForm()) { case FORM_NONE: trigger_spell_id = 37344; break; case FORM_CAT: trigger_spell_id = 37341; break; case FORM_BEAR: case FORM_DIREBEAR: trigger_spell_id = 37340; break; case FORM_TREE: trigger_spell_id = 37342; break; case FORM_MOONKIN: trigger_spell_id = 37343; break; default: return false; } break; } // Druid T9 Feral Relic (Lacerate, Swipe, Mangle, and Shred) case 67353: { switch (GetShapeshiftForm()) { case FORM_CAT: trigger_spell_id = 67355; break; case FORM_BEAR: case FORM_DIREBEAR: trigger_spell_id = 67354; break; default: return false; } break; } default: break; } break; } case SPELLFAMILY_HUNTER: { if (auraSpellInfo->SpellIconID == 3247) // Piercing Shots { if (!victim) return false; switch (auraSpellInfo->Id) { case 53234: // Rank 1 case 53237: // Rank 2 case 53238: // Rank 3 trigger_spell_id = 63468; break; default: LOG_ERROR("entities.unit", "Unit::HandleProcTriggerSpell: Spell {} miss posibly Piercing Shots", auraSpellInfo->Id); return false; } SpellInfo const* TriggerPS = sSpellMgr->GetSpellInfo(trigger_spell_id); if (!TriggerPS) return false; basepoints0 = CalculatePct(int32(damage), triggerAmount) / (TriggerPS->GetMaxDuration() / TriggerPS->Effects[0].Amplitude); victim->CastDelayedSpellWithPeriodicAmount(this, trigger_spell_id, SPELL_AURA_PERIODIC_DAMAGE, basepoints0); return true; } // Item - Hunter T9 4P Bonus (Steady Shot) else if (auraSpellInfo->Id == 67151) { if (!IsPlayer() || !ToPlayer()->GetPet()) return false; target = ToPlayer()->GetPet(); trigger_spell_id = 68130; break; } break; } case SPELLFAMILY_PALADIN: { switch (auraSpellInfo->Id) { case 37657: // Lightning Capacitor case 54841: // Thunder Capacitor case 67712: // Item - Coliseum 25 Normal Caster Trinket case 67758: // Item - Coliseum 25 Heroic Caster Trinket { if (!victim || !victim->IsAlive() || !IsPlayer()) return false; uint32 stack_spell_id = 0; switch (auraSpellInfo->Id) { case 37657: stack_spell_id = 37658; trigger_spell_id = 37661; break; case 54841: stack_spell_id = 54842; trigger_spell_id = 54843; break; case 67712: stack_spell_id = 67713; trigger_spell_id = 67714; break; case 67758: stack_spell_id = 67759; trigger_spell_id = 67760; break; } if (cooldown && ToPlayer()->HasSpellCooldown(stack_spell_id)) { return false; } CastSpell(this, stack_spell_id, true, nullptr, triggeredByAura); Aura* dummy = GetAura(stack_spell_id); if (!dummy || dummy->GetStackAmount() < triggerAmount) { return false; } if (cooldown) { ToPlayer()->AddSpellCooldown(stack_spell_id, 0, cooldown); } RemoveAurasDueToSpell(stack_spell_id); CastSpell(victim, trigger_spell_id, true, nullptr, triggeredByAura); return true; } default: // Illumination if (auraSpellInfo->SpellIconID == 241) { if (!procSpell) return false; // procspell is triggered spell but we need mana cost of original casted spell uint32 originalSpellId = procSpell->Id; // Holy Shock heal if (procSpell->SpellFamilyFlags[1] & 0x00010000) { switch (procSpell->Id) { case 25914: originalSpellId = 20473; break; case 25913: originalSpellId = 20929; break; case 25903: originalSpellId = 20930; break; case 27175: originalSpellId = 27174; break; case 33074: originalSpellId = 33072; break; case 48820: originalSpellId = 48824; break; case 48821: originalSpellId = 48825; break; default: LOG_ERROR("entities.unit", "Unit::HandleProcTriggerSpell: Spell {} not handled in HShock", procSpell->Id); return false; } } SpellInfo const* originalSpell = sSpellMgr->GetSpellInfo(originalSpellId); if (!originalSpell) { LOG_ERROR("entities.unit", "Unit::HandleProcTriggerSpell: Spell {} unknown but selected as original in Illu", originalSpellId); return false; } // percent stored in effect 1 (class scripts) base points int32 cost = int32(originalSpell->ManaCost + CalculatePct(GetCreateMana(), originalSpell->ManaCostPercentage)); basepoints0 = CalculatePct(cost, auraSpellInfo->Effects[1].CalcValue()); trigger_spell_id = 20272; target = this; } break; } break; } case SPELLFAMILY_SHAMAN: { // Lightning Shield (overwrite non existing triggered spell call in spell.dbc if (auraSpellInfo->SpellFamilyFlags[0] & 0x400 && auraSpellInfo->HasAttribute(SPELL_ATTR1_NO_THREAT)) { // Do not proc off from self-casted items if (Spell const* spell = eventInfo.GetProcSpell()) { if (spell->m_castItemGUID && victim->GetGUID() == GetGUID()) { return false; } } trigger_spell_id = sSpellMgr->GetSpellWithRank(26364, auraSpellInfo->GetRank()); } // Nature's Guardian else if (auraSpellInfo->SpellIconID == 2013) { // Check health condition - should drop to less 30% (damage deal after this!) if (!HealthBelowPctDamaged(30, damage)) return false; if (victim && victim->IsAlive()) victim->GetThreatMgr().ModifyThreatByPercent(this, -10); basepoints0 = int32(CountPctFromMaxHealth(triggerAmount)); trigger_spell_id = 31616; target = this; } break; } case SPELLFAMILY_DEATHKNIGHT: { // Acclimation if (auraSpellInfo->SpellIconID == 1930) { if (!procSpell) return false; switch (GetFirstSchoolInMask(procSpell->GetSchoolMask())) { case SPELL_SCHOOL_NORMAL: return false; // ignore case SPELL_SCHOOL_HOLY: trigger_spell_id = 50490; break; case SPELL_SCHOOL_FIRE: trigger_spell_id = 50362; break; case SPELL_SCHOOL_NATURE: trigger_spell_id = 50488; break; case SPELL_SCHOOL_FROST: trigger_spell_id = 50485; break; case SPELL_SCHOOL_SHADOW: trigger_spell_id = 50489; break; case SPELL_SCHOOL_ARCANE: trigger_spell_id = 50486; break; default: return false; } } // Blood Presence (Improved) else if (auraSpellInfo->Id == 63611) { if (!IsPlayer()) return false; trigger_spell_id = 50475; basepoints0 = CalculatePct(int32(damage), triggerAmount); } break; } } } // All ok. Check current trigger spell SpellInfo const* triggerEntry = sSpellMgr->GetSpellInfo(trigger_spell_id); if (!triggerEntry) { // Don't cast unknown spell LOG_ERROR("entities.unit", "Unit::HandleProcTriggerSpell: Spell {} (effIndex: {}) has unknown TriggerSpell {}. Unhandled custom case?", auraSpellInfo->Id, triggeredByAura->GetEffIndex(), trigger_spell_id); return false; } // not allow proc extra attack spell at extra attack if (triggerEntry->HasEffect(SPELL_EFFECT_ADD_EXTRA_ATTACKS)) { uint32 lastExtraAttackSpell = eventInfo.GetActor()->GetLastExtraAttackSpell(); // Patch 1.12.0(?) extra attack abilities can no longer chain proc themselves if (lastExtraAttackSpell == trigger_spell_id) { return false; } // Patch 2.2.0 Sword Specialization (Warrior, Rogue) extra attack can no longer proc additional extra attacks // 3.3.5 Sword Specialization (Warrior), Hack and Slash (Rogue) if (lastExtraAttackSpell == SPELL_SWORD_SPECIALIZATION || lastExtraAttackSpell == SPELL_HACK_AND_SLASH) { return false; } } // Custom requirements (not listed in procEx) Warning! damage dealing after this // Custom triggered spells switch (auraSpellInfo->Id) { // Deep Wounds case 12834: case 12849: case 12867: { if (!IsPlayer()) return false; if (procFlags & PROC_FLAG_DONE_OFFHAND_ATTACK) basepoints0 = int32((GetFloatValue(UNIT_FIELD_MAXOFFHANDDAMAGE) + GetFloatValue(UNIT_FIELD_MINOFFHANDDAMAGE)) / 2.0f); else basepoints0 = int32((GetFloatValue(UNIT_FIELD_MAXDAMAGE) + GetFloatValue(UNIT_FIELD_MINDAMAGE)) / 2.0f); break; } // Persistent Shield (Scarab Brooch trinket) // This spell originally trigger 13567 - Dummy Trigger (vs dummy efect) case 26467: { basepoints0 = int32(CalculatePct(damage, 15)); target = victim; trigger_spell_id = 26470; break; } // Unyielding Knights (item exploit 29108\29109) case 38164: { if (!victim || victim->GetEntry() != 19457) // Proc only if your target is Grillok return false; break; } // Deflection case 52420: { if (!HealthBelowPct(35)) return false; break; } // Cheat Death case 28845: { // When your health drops below 20% if (HealthBelowPctDamaged(20, damage) || HealthBelowPct(20)) return false; break; } // Deadly Swiftness (Rank 1) case 31255: { // whenever you deal damage to a target who is below 20% health. if (!victim || !victim->IsAlive() || victim->HealthAbovePct(20)) return false; target = this; trigger_spell_id = 22588; [[fallthrough]]; /// @todo: Not sure whether the fallthrough was a mistake (forgetting a break) or intended. This should be double-checked. } // Bonus Healing (Crystal Spire of Karabor mace) case 40971: { // If your target is below $s1% health if (!victim || !victim->IsAlive() || victim->HealthAbovePct(triggerAmount)) return false; break; } // Rapid Recuperation case 53228: case 53232: { // This effect only from Rapid Fire (ability cast) if (!procSpell || !(procSpell->SpellFamilyFlags[0] & 0x20)) return false; break; } // Decimation case 63156: case 63158: // Can proc only if target has hp below 35% if (!victim || !victim->HasAuraState(AURA_STATE_HEALTHLESS_35_PERCENT, procSpell, this)) return false; break; // Ulduar, Hodir, Toasty Fire case 62821: if (!this->IsPlayer()) // spell has Attribute, but persistent area auras ignore it return false; break; case 15337: // Improved Spirit Tap (Rank 1) case 15338: // Improved Spirit Tap (Rank 2) { if (!procSpell) return false; if (procSpell->SpellFamilyFlags[0] & 0x800000) if ((procSpell->Id != 58381) || !roll_chance_i(50)) return false; target = victim; break; } // Professor Putricide - Ooze Spell Tank Protection case 71770: if (victim) victim->CastSpell(victim, trigger_spell_id, true); // EffectImplicitTarget is self return true; case 45057: // Evasive Maneuvers (Commendation of Kael`thas trinket) case 71634: // Item - Icecrown 25 Normal Tank Trinket 1 case 71640: // Item - Icecrown 25 Heroic Tank Trinket 1 case 75475: // Item - Chamber of Aspects 25 Normal Tank Trinket case 75481: // Item - Chamber of Aspects 25 Heroic Tank Trinket { // Procs only if damage takes health below $s1% if (!HealthBelowPctDamaged(triggerAmount, damage)) return false; break; } default: break; } if (auraSpellInfo->SpellFamilyName == SPELLFAMILY_DEATHKNIGHT) { // Xinef: keep this order, Aura 70656 has SpellIconID 85! // Item - Death Knight T10 Melee 4P Bonus if (auraSpellInfo->Id == 70656) { if (!IsPlayer() || !IsClass(CLASS_DEATH_KNIGHT, CLASS_CONTEXT_ABILITY)) return false; for (uint8 i = 0; i < MAX_RUNES; ++i) if (ToPlayer()->GetRuneCooldown(i) == 0) return false; } // Blade Barrier else if (auraSpellInfo->SpellIconID == 85) { Player* plr = ToPlayer(); if (!plr || !plr->IsClass(CLASS_DEATH_KNIGHT, CLASS_CONTEXT_ABILITY) || !procSpell) return false; if (!plr->IsBaseRuneSlotsOnCooldown(RUNE_BLOOD)) return false; } // Rime else if (auraSpellInfo->SpellIconID == 56) { if (!IsPlayer()) return false; // Howling Blast ToPlayer()->RemoveCategoryCooldown(1248); } } // Custom basepoints/target for exist spell // dummy basepoints or other customs switch (trigger_spell_id) { // Auras which should proc on area aura source (caster in this case): // Turn the Tables case 52914: case 52915: case 52910: // Honor Among Thieves case 51699: { target = triggeredByAura->GetBase()->GetCaster(); if (!target) return false; if (Player* pTarget = target->ToPlayer()) { if (cooldown) { if (pTarget->HasSpellCooldown(trigger_spell_id)) return false; pTarget->AddSpellCooldown(trigger_spell_id, 0, cooldown); } Unit* cptarget = nullptr; if (trigger_spell_id == 51699) { cptarget = pTarget->GetComboTarget(); if (!cptarget) { cptarget = pTarget->GetSelectedUnit(); } } else cptarget = target; if (cptarget) { target->CastSpell(cptarget, trigger_spell_id, true); return true; } } return false; } // Cast positive spell on enemy target case 20233: // Improved Lay on Hands (cast on target) { target = victim; break; } // Ruby Drake, Evasive Aura case 50241: { if (GetAura(50240)) return false; break; } // Combo points add triggers (need add combopoint only for main target, and after possible combopoints reset) case 15250: // Rogue Setup { // applied only for main target if (!victim || (IsPlayer() && victim != ToPlayer()->GetSelectedUnit())) return false; break; // continue normal case } // Finish movies that add combo case 14189: // Seal Fate (Netherblade set) case 14157: // Ruthlessness { victim = nullptr; // Need add combopoint AFTER finish movie (or they dropped in finish phase) break; } // Item - Druid T10 Balance 2P Bonus case 16870: { if (HasAura(70718)) CastSpell(this, 70721, true); RemoveAurasDueToSpell(trigger_spell_id); break; } // Shamanistic Rage triggered spell case 30824: { basepoints0 = int32(CalculatePct(GetTotalAttackPowerValue(BASE_ATTACK), triggerAmount)); break; } // Enlightenment (trigger only from mana cost spells) case 35095: { if (!procSpell || procSpell->PowerType != POWER_MANA || (procSpell->ManaCost == 0 && procSpell->ManaCostPercentage == 0 && procSpell->ManaCostPerlevel == 0)) return false; break; } case 46916: // Slam! (Bloodsurge proc) case 52437: // Sudden Death { // Item - Warrior T10 Melee 4P Bonus if (AuraEffect const* aurEff = GetAuraEffect(70847, 0)) { if (!roll_chance_i(aurEff->GetAmount())) { // Xinef: dont allow normal proc to override set one if (GetAura((trigger_spell_id == 46916) ? 71072 : 71069)) return false; // Xinef: just to be sure RemoveAurasDueToSpell(70849); break; } // Xinef: fully remove all auras and reapply once more RemoveAurasDueToSpell(70849); RemoveAurasDueToSpell(71072); RemoveAurasDueToSpell(71069); CastSpell(this, 70849, true, castItem, triggeredByAura); // Extra Charge! if (trigger_spell_id == 46916) CastSpell(this, 71072, true, castItem, triggeredByAura); // Slam GCD Reduced else CastSpell(this, 71069, true, castItem, triggeredByAura); // Execute GCD Reduced } break; } // Sword and Board case 50227: { // Remove cooldown on Shield Slam if (IsPlayer()) ToPlayer()->RemoveCategoryCooldown(1209); break; } // Maelstrom Weapon case 53817: { // have rank dependent proc chance, ignore too often cases // PPM = 2.5 * (rank of talent), uint32 rank = auraSpellInfo->GetRank(); // 5 rank -> 100% 4 rank -> 80% and etc from full rate if (!roll_chance_i(20 * rank)) return false; // Item - Shaman T10 Enhancement 4P Bonus if (AuraEffect const* aurEff = GetAuraEffect(70832, 0)) if (Aura const* maelstrom = GetAura(53817)) // xinef: we have 4 charges and all proc conditions are met - aura reaches 5 charges if ((maelstrom->GetStackAmount() == 4) && roll_chance_i(aurEff->GetAmount())) CastSpell(this, 70831, true, castItem, triggeredByAura); break; } // Astral Shift case 52179: { if (!procSpell || !(procEx & (PROC_EX_NORMAL_HIT | PROC_EX_CRITICAL_HIT)) || this == victim) return false; // Need stun, fear or silence mechanic if (!(procSpell->GetAllEffectsMechanicMask() & ((1 << MECHANIC_SILENCE) | (1 << MECHANIC_STUN) | (1 << MECHANIC_FEAR)))) return false; break; } // Glyph of Death's Embrace case 58679: { // Proc only from healing part of Death Coil. Check is essential as all Death Coil spells have 0x2000 mask in SpellFamilyFlags if (!procSpell || !(procSpell->SpellFamilyName == SPELLFAMILY_DEATHKNIGHT && procSpell->SpellFamilyFlags[0] == 0x80002000)) return false; break; } // Glyph of Death Grip case 58628: { // remove cooldown of Death Grip if (IsPlayer()) ToPlayer()->RemoveSpellCooldown(49576, true); return true; } // Savage Defense case 62606: { basepoints0 = CalculatePct(triggerAmount, GetTotalAttackPowerValue(BASE_ATTACK)); break; } // Body and Soul case 64128: case 65081: { // Proc only from PW:S cast if (!procSpell || !(procSpell->SpellFamilyFlags[0] & 0x00000001)) return false; break; } // Culling the Herd case 70893: { if (!procSpell) { return false; } // check if we're doing a critical hit if (!(procSpell->SpellFamilyFlags[1] & 0x10000000) && (procEx != PROC_EX_CRITICAL_HIT)) return false; // check if we're procced by Claw, Bite or Smack (need to use the spell icon ID to detect it) if (!(procSpell->SpellIconID == 262 || procSpell->SpellIconID == 1680 || procSpell->SpellIconID == 473)) return false; break; } // Fingers of Frost, synchronise with Frostbite case 44544: { if (procPhase == PROC_SPELL_PHASE_HIT) { // Find Frostbite if (AuraEffect* aurEff = this->GetAuraEffect(SPELL_AURA_ADD_TARGET_TRIGGER, SPELLFAMILY_MAGE, 119, EFFECT_0)) { if (!victim) return false; uint8 fofRank = sSpellMgr->GetSpellRank(triggeredByAura->GetId()); uint8 fbRank = sSpellMgr->GetSpellRank(aurEff->GetId()); uint8 chance = uint8(std::ceil(fofRank * fbRank * 16.6f)); if (roll_chance_i(chance)) CastSpell(victim, aurEff->GetSpellInfo()->Effects[EFFECT_0].TriggerSpell, true); } } break; } } // try detect target manually if not set if (!target) target = !(procFlags & (PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_POS | PROC_FLAG_DONE_SPELL_NONE_DMG_CLASS_POS)) && triggerEntry->IsPositive() ? this : victim; if (cooldown) { if (HasSpellCooldown(triggerEntry->Id)) return false; AddSpellCooldown(triggerEntry->Id, 0, cooldown); } if (basepoints0) CastCustomSpell(target, triggerEntry->Id, &basepoints0, nullptr, nullptr, true, castItem, triggeredByAura); else CastSpell(target, triggerEntry->Id, true, castItem, triggeredByAura); return true; } bool Unit::HandleOverrideClassScriptAuraProc(Unit* victim, uint32 /*damage*/, AuraEffect* triggeredByAura, SpellInfo const* procSpell, uint32 cooldown) { int32 scriptId = triggeredByAura->GetMiscValue(); if (!victim || !victim->IsAlive()) return false; Item* castItem = triggeredByAura->GetBase()->GetCastItemGUID() && IsPlayer() ? ToPlayer()->GetItemByGuid(triggeredByAura->GetBase()->GetCastItemGUID()) : nullptr; uint32 triggered_spell_id = 0; switch (scriptId) { case 836: // Improved Blizzard (Rank 1) { if (!procSpell || procSpell->SpellVisual[0] != 9487) return false; triggered_spell_id = 12484; break; } case 988: // Improved Blizzard (Rank 2) { if (!procSpell || procSpell->SpellVisual[0] != 9487) return false; triggered_spell_id = 12485; break; } case 989: // Improved Blizzard (Rank 3) { if (!procSpell || procSpell->SpellVisual[0] != 9487) return false; triggered_spell_id = 12486; break; } case 4533: // Dreamwalker Raiment 2 pieces bonus { // Chance 50% if (!roll_chance_i(50)) return false; switch (victim->getPowerType()) { case POWER_MANA: triggered_spell_id = 28722; break; case POWER_RAGE: triggered_spell_id = 28723; break; case POWER_ENERGY: triggered_spell_id = 28724; break; default: return false; } break; } case 4537: // Dreamwalker Raiment 6 pieces bonus triggered_spell_id = 28750; // Blessing of the Claw break; case 5497: // Improved Mana Gems triggered_spell_id = 37445; // Mana Surge break; case 7010: // Revitalize - can proc on full hp target case 7011: case 7012: { if (!roll_chance_i(triggeredByAura->GetAmount())) return false; switch (victim->getPowerType()) { case POWER_MANA: triggered_spell_id = 48542; break; case POWER_RAGE: triggered_spell_id = 48541; break; case POWER_ENERGY: triggered_spell_id = 48540; break; case POWER_RUNIC_POWER: triggered_spell_id = 48543; break; default: break; } break; } default: break; } // not processed if (!triggered_spell_id) return false; // standard non-dummy case SpellInfo const* triggerEntry = sSpellMgr->GetSpellInfo(triggered_spell_id); if (!triggerEntry) { LOG_ERROR("entities.unit", "Unit::HandleOverrideClassScriptAuraProc: Spell {} triggering for class script id {}", triggered_spell_id, scriptId); return false; } if (cooldown) { if (HasSpellCooldown(triggered_spell_id)) return false; AddSpellCooldown(triggered_spell_id, 0, cooldown); } CastSpell(victim, triggered_spell_id, true, castItem, triggeredByAura); return true; } void Unit::setPowerType(Powers new_powertype) { SetByteValue(UNIT_FIELD_BYTES_0, 3, new_powertype); if (IsPlayer()) { if (ToPlayer()->GetGroup()) ToPlayer()->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_POWER_TYPE); } else if (Pet* pet = ToCreature()->ToPet()) { if (pet->isControlled()) { Unit* owner = GetOwner(); if (owner && (owner->IsPlayer()) && owner->ToPlayer()->GetGroup()) owner->ToPlayer()->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_PET_POWER_TYPE); } } float powerMultiplier = 1.0f; if (!IsPet()) if (Creature* creature = ToCreature()) powerMultiplier = creature->GetCreatureTemplate()->ModMana; switch (new_powertype) { default: case POWER_MANA: break; case POWER_RAGE: SetMaxPower(POWER_RAGE, uint32(std::ceil(GetCreatePowers(POWER_RAGE) * powerMultiplier))); SetPower(POWER_RAGE, 0); break; case POWER_FOCUS: SetMaxPower(POWER_FOCUS, uint32(std::ceil(GetCreatePowers(POWER_FOCUS) * powerMultiplier))); SetPower(POWER_FOCUS, uint32(std::ceil(GetCreatePowers(POWER_FOCUS) * powerMultiplier))); break; case POWER_ENERGY: SetMaxPower(POWER_ENERGY, uint32(std::ceil(GetCreatePowers(POWER_ENERGY) * powerMultiplier))); break; case POWER_HAPPINESS: SetMaxPower(POWER_HAPPINESS, uint32(std::ceil(GetCreatePowers(POWER_HAPPINESS) * powerMultiplier))); SetPower(POWER_HAPPINESS, uint32(std::ceil(GetCreatePowers(POWER_HAPPINESS) * powerMultiplier))); break; } if (Player const* player = ToPlayer()) if (player->NeedSendSpectatorData()) { ArenaSpectator::SendCommand_UInt32Value(FindMap(), GetGUID(), "PWT", new_powertype); ArenaSpectator::SendCommand_UInt32Value(FindMap(), GetGUID(), "MPW", new_powertype == POWER_RAGE || new_powertype == POWER_RUNIC_POWER ? GetMaxPower(new_powertype) / 10 : GetMaxPower(new_powertype)); ArenaSpectator::SendCommand_UInt32Value(FindMap(), GetGUID(), "CPW", new_powertype == POWER_RAGE || new_powertype == POWER_RUNIC_POWER ? GetPower(new_powertype) / 10 : GetPower(new_powertype)); } } FactionTemplateEntry const* Unit::GetFactionTemplateEntry() const { FactionTemplateEntry const* entry = sFactionTemplateStore.LookupEntry(GetFaction()); if (!entry) { static ObjectGuid guid; // prevent repeating spam same faction problem if (GetGUID() != guid) { if (Player const* player = ToPlayer()) LOG_ERROR("entities.unit", "Player {} has invalid faction (faction template id) #{}", player->GetName(), GetFaction()); else if (Creature const* creature = ToCreature()) LOG_ERROR("entities.unit", "Creature (template id: {}) has invalid faction (faction template id) #{}", creature->GetCreatureTemplate()->Entry, GetFaction()); else LOG_ERROR("entities.unit", "Unit (name={}, type={}) has invalid faction (faction template id) #{}", GetName(), uint32(GetTypeId()), GetFaction()); guid = GetGUID(); } } return entry; } void Unit::SetFaction(uint32 faction) { SetUInt32Value(UNIT_FIELD_FACTIONTEMPLATE, faction); if (IsCreature()) ToCreature()->UpdateMoveInLineOfSightState(); } // function based on function Unit::UnitReaction from 13850 client ReputationRank Unit::GetReactionTo(Unit const* target, bool checkOriginalFaction /*= false*/) const { // always friendly to self if (this == target) return REP_FRIENDLY; // always friendly to charmer or owner if (GetCharmerOrOwnerOrSelf() == target->GetCharmerOrOwnerOrSelf()) return REP_FRIENDLY; Player const* selfPlayerOwner = GetAffectingPlayer(); Player const* targetPlayerOwner = target->GetAffectingPlayer(); // check forced reputation to support SPELL_AURA_FORCE_REACTION if (selfPlayerOwner) { if (FactionTemplateEntry const* targetFactionTemplateEntry = target->GetFactionTemplateEntry()) if (ReputationRank const* repRank = selfPlayerOwner->GetReputationMgr().GetForcedRankIfAny(targetFactionTemplateEntry)) return *repRank; } else if (targetPlayerOwner) { if (FactionTemplateEntry const* selfFactionTemplateEntry = GetFactionTemplateEntry()) if (ReputationRank const* repRank = targetPlayerOwner->GetReputationMgr().GetForcedRankIfAny(selfFactionTemplateEntry)) return *repRank; } if (HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED)) { if (target->HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED)) { if (selfPlayerOwner && targetPlayerOwner) { // always friendly to other unit controlled by player, or to the player himself if (selfPlayerOwner == targetPlayerOwner) return REP_FRIENDLY; // duel - always hostile to opponent if (selfPlayerOwner->duel && selfPlayerOwner->duel->Opponent == targetPlayerOwner && selfPlayerOwner->duel->State == DUEL_STATE_IN_PROGRESS) return REP_HOSTILE; // same group - checks dependant only on our faction - skip FFA_PVP for example if (selfPlayerOwner->IsInRaidWith(targetPlayerOwner)) return REP_FRIENDLY; // return true to allow config option AllowTwoSide.Interaction.Group to work // however client seems to allow mixed group parties, because in 13850 client it works like: // return GetFactionReactionTo(GetFactionTemplateEntry(), target); } // check FFA_PVP if (IsFFAPvP() && target->IsFFAPvP()) return REP_HOSTILE; if (selfPlayerOwner) { if (FactionTemplateEntry const* targetFactionTemplateEntry = target->GetFactionTemplateEntry()) { if (ReputationRank const* repRank = selfPlayerOwner->GetReputationMgr().GetForcedRankIfAny(targetFactionTemplateEntry)) return *repRank; if (!selfPlayerOwner->HasUnitFlag2(UNIT_FLAG2_IGNORE_REPUTATION)) { if (FactionEntry const* targetFactionEntry = sFactionStore.LookupEntry(targetFactionTemplateEntry->faction)) { if (targetFactionEntry->CanHaveReputation()) { // check contested flags if (targetFactionTemplateEntry->factionFlags & FACTION_TEMPLATE_FLAG_ATTACK_PVP_ACTIVE_PLAYERS && selfPlayerOwner->HasPlayerFlag(PLAYER_FLAGS_CONTESTED_PVP)) return REP_HOSTILE; // if faction has reputation, hostile state depends only from AtWar state if (selfPlayerOwner->GetReputationMgr().IsAtWar(targetFactionEntry)) return REP_HOSTILE; return REP_FRIENDLY; } } } } } } } ReputationRank repRank = REP_HATED; if (!sScriptMgr->IfNormalReaction(this, target, repRank)) { return ReputationRank(repRank); } FactionTemplateEntry const* factionTemplateEntry = nullptr; if (checkOriginalFaction) { if (IsPlayer()) { if (ChrRacesEntry const* rEntry = sChrRacesStore.LookupEntry(getRace())) { factionTemplateEntry = sFactionTemplateStore.LookupEntry(rEntry->FactionID); } } else { Unit* owner = GetOwner(); if (HasUnitTypeMask(UNIT_MASK_MINION) && owner) { factionTemplateEntry = sFactionTemplateStore.LookupEntry(owner->GetFaction()); } else if (CreatureTemplate const* cinfo = ToCreature()->GetCreatureTemplate()) { factionTemplateEntry = sFactionTemplateStore.LookupEntry(cinfo->faction); } } } if (!factionTemplateEntry) { factionTemplateEntry = GetFactionTemplateEntry(); } // do checks dependant only on our faction return GetFactionReactionTo(factionTemplateEntry, target); } ReputationRank Unit::GetFactionReactionTo(FactionTemplateEntry const* factionTemplateEntry, Unit const* target) const { // always neutral when no template entry found if (!factionTemplateEntry) return REP_NEUTRAL; FactionTemplateEntry const* targetFactionTemplateEntry = target->GetFactionTemplateEntry(); if (!targetFactionTemplateEntry) return REP_NEUTRAL; // xinef: check forced reputation for self also if (Player const* selfPlayerOwner = GetAffectingPlayer()) if (ReputationRank const* repRank = selfPlayerOwner->GetReputationMgr().GetForcedRankIfAny(target->GetFactionTemplateEntry())) return *repRank; if (Player const* targetPlayerOwner = target->GetAffectingPlayer()) { // check contested flags if (factionTemplateEntry->factionFlags & FACTION_TEMPLATE_FLAG_ATTACK_PVP_ACTIVE_PLAYERS && targetPlayerOwner->HasPlayerFlag(PLAYER_FLAGS_CONTESTED_PVP)) return REP_HOSTILE; if (ReputationRank const* repRank = targetPlayerOwner->GetReputationMgr().GetForcedRankIfAny(factionTemplateEntry)) return *repRank; if (!target->HasUnitFlag2(UNIT_FLAG2_IGNORE_REPUTATION)) { if (FactionEntry const* factionEntry = sFactionStore.LookupEntry(factionTemplateEntry->faction)) { if (factionEntry->CanHaveReputation()) { // CvP case - check reputation, don't allow state higher than neutral when at war ReputationRank repRank = targetPlayerOwner->GetReputationMgr().GetRank(factionEntry); if (targetPlayerOwner->GetReputationMgr().IsAtWar(factionEntry)) repRank = std::min(REP_NEUTRAL, repRank); return repRank; } } } } // common faction based check if (factionTemplateEntry->IsHostileTo(*targetFactionTemplateEntry)) return REP_HOSTILE; if (factionTemplateEntry->IsFriendlyTo(*targetFactionTemplateEntry)) return REP_FRIENDLY; if (targetFactionTemplateEntry->IsFriendlyTo(*factionTemplateEntry)) return REP_FRIENDLY; if (factionTemplateEntry->factionFlags & FACTION_TEMPLATE_FLAG_HATES_ALL_EXCEPT_FRIENDS) return REP_HOSTILE; // neutral by default return REP_NEUTRAL; } bool Unit::IsHostileTo(Unit const* unit) const { return GetReactionTo(unit) <= REP_HOSTILE; } bool Unit::IsFriendlyTo(Unit const* unit) const { return GetReactionTo(unit) >= REP_FRIENDLY; } bool Unit::IsHostileToPlayers() const { FactionTemplateEntry const* my_faction = GetFactionTemplateEntry(); if (!my_faction || !my_faction->faction) return false; FactionEntry const* raw_faction = sFactionStore.LookupEntry(my_faction->faction); if (raw_faction && raw_faction->reputationListID >= 0) return false; return my_faction->IsHostileToPlayers(); } bool Unit::IsNeutralToAll() const { FactionTemplateEntry const* my_faction = GetFactionTemplateEntry(); if (!my_faction || !my_faction->faction) return true; FactionEntry const* raw_faction = sFactionStore.LookupEntry(my_faction->faction); if (raw_faction && raw_faction->reputationListID >= 0) return false; return my_faction->IsNeutralToAll(); } bool Unit::Attack(Unit* victim, bool meleeAttack) { if (!victim || victim == this) return false; // dead units can neither attack nor be attacked if (!IsAlive() || !victim->IsAlive()) return false; // pussywizard: check map, world, phase >_> multithreading crash fix if (!IsInMap(victim) || !InSamePhase(victim)) return false; // player cannot attack in mount state if (IsPlayer() && IsMounted()) return false; // creatures cannot attack while evading Creature* creature = ToCreature(); if (creature && creature->IsInEvadeMode()) { return false; } // creatures should not try to attack the player during polymorph if (creature && creature->IsPolymorphed()) { return false; } //if (HasUnitFlag(UNIT_FLAG_PACIFIED)) // pussywizard: why having this flag prevents from entering combat? it should just prevent melee attack // return false; // nobody can attack GM in GM-mode if (victim->IsPlayer()) { if (victim->ToPlayer()->IsGameMaster()) return false; } else { if (victim->ToCreature()->IsEvadingAttacks()) return false; } // Unit with SPELL_AURA_SPIRIT_OF_REDEMPTION can not attack if (HasSpiritOfRedemptionAura()) return false; // remove SPELL_AURA_MOD_UNATTACKABLE at attack (in case non-interruptible spells stun aura applied also that not let attack) if (HasUnattackableAura()) 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, true, true, true); 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); Unit* owner = GetCharmerOrOwner(); Creature* ownerCreature = owner ? owner->ToCreature() : nullptr; Creature* controlledCreatureWithSameVictim = nullptr; if (creature && !m_Controlled.empty()) { for (ControlSet::iterator itr = m_Controlled.begin(); itr != m_Controlled.end(); ++itr) { if ((*itr)->ToCreature() && (*itr)->GetVictim() == victim) { controlledCreatureWithSameVictim = (*itr)->ToCreature(); break; } } } // Share leash timer with controlled unit if (controlledCreatureWithSameVictim) creature->SetLastLeashExtensionTimePtr(controlledCreatureWithSameVictim->GetLastLeashExtensionTimePtr()); // Share leash timer with owner else if (creature && ownerCreature && ownerCreature->GetVictim() == victim) creature->SetLastLeashExtensionTimePtr(ownerCreature->GetLastLeashExtensionTimePtr()); // Update leash timer when attacking creatures else if (victim->IsCreature()) victim->ToCreature()->UpdateLeashExtensionTime(); // set position before any AI calls/assistance //if (IsCreature()) // ToCreature()->SetCombatStartPosition(GetPositionX(), GetPositionY(), GetPositionZ()); if (creature && !(IsControllableGuardian() && IsControlledByPlayer())) { // should not let player enter combat by right clicking target - doesn't helps SetInCombatWith(victim); if (victim->IsPlayer()) victim->SetInCombatWith(this); AddThreat(victim, 0.0f); creature->SendAIReaction(AI_REACTION_HOSTILE); /// @todo: Implement aggro range, detection range and assistance range templates if (!(creature->HasFlagsExtra(CREATURE_FLAG_EXTRA_DONT_CALL_ASSISTANCE))) creature->CallAssistance(); creature->SetAssistanceTimer(sWorld->getIntConfig(CONFIG_CREATURE_FAMILY_ASSISTANCE_PERIOD)); SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_ONESHOT_NONE); } // delay offhand weapon attack by 50% of the base attack time if (HasOffhandWeaponForAttack() && isAttackReady(OFF_ATTACK)) setAttackTimer(OFF_ATTACK, std::max(getAttackTimer(OFF_ATTACK), getAttackTimer(BASE_ATTACK) + int32(CalculatePct(GetFloatValue(UNIT_FIELD_BASEATTACKTIME), 50)))); if (meleeAttack) SendMeleeAttackStart(victim); return true; } /** * @brief Force the unit to stop attacking. This will clear UNIT_STATE_MELEE_ATTACKING, * Interrupt current spell, AI assistance, and call SendMeleeAttackStop() to the client */ 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); if (creature->HasSearchedAssistance()) { creature->SetNoSearchAssistance(false); } } SendMeleeAttackStop(victim); return true; } void Unit::CombatStop(bool includingCast) { if (includingCast && IsNonMeleeSpellCast(false)) InterruptNonMeleeSpells(false); AttackStop(); RemoveAllAttackers(); if (IsPlayer()) ToPlayer()->SendAttackSwingCancelAttack(); // melee and ranged forced attack cancel if (Creature* pCreature = ToCreature()) pCreature->ClearLastLeashExtensionTimePtr(); ClearInCombat(); // xinef: just in case if (IsPetInCombat() && !IsPlayer()) ClearInPetCombat(); } void Unit::CombatStopWithPets(bool includingCast) { CombatStop(includingCast); for (ControlSet::const_iterator itr = m_Controlled.begin(); itr != m_Controlled.end(); ++itr) (*itr)->CombatStop(includingCast); } bool Unit::isAttackingPlayer() const { if (HasUnitState(UNIT_STATE_ATTACK_PLAYER)) return true; if (!m_Controlled.empty()) for (ControlSet::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]) if (Creature* summon = GetMap()->GetCreature(m_SummonSlot[i])) if (summon->isAttackingPlayer()) return true; return false; } /** * @brief Remove all units in m_attackers list and send them AttackStop() */ void Unit::RemoveAllAttackers() { while (!m_attackers.empty()) { AttackerSet::iterator iter = m_attackers.begin(); if (!(*iter)->AttackStop()) { 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) { if (apply) { if (!HasFlag(UNIT_FIELD_AURASTATE, 1 << (flag - 1))) { SetFlag(UNIT_FIELD_AURASTATE, 1 << (flag - 1)); Unit::AuraMap& tAuras = GetOwnedAuras(); for (Unit::AuraMap::iterator itr = tAuras.begin(); itr != tAuras.end(); ++itr) { if ((*itr).second->IsRemoved()) continue; if ((*itr).second->GetSpellInfo()->CasterAuraState == flag ) if (AuraApplication* aurApp = (*itr).second->GetApplicationOfTarget(GetGUID())) (*itr).second->HandleAllEffects(aurApp, AURA_EFFECT_HANDLE_REAL, true); } } } else { if (HasFlag(UNIT_FIELD_AURASTATE, 1 << (flag - 1))) { RemoveFlag(UNIT_FIELD_AURASTATE, 1 << (flag - 1)); if (flag != AURA_STATE_ENRAGE) // enrage aura state triggering continues auras { Unit::AuraMap& tAuras = GetOwnedAuras(); for (Unit::AuraMap::iterator itr = tAuras.begin(); itr != tAuras.end(); ++itr) { if ((*itr).second->GetSpellInfo()->CasterAuraState == flag ) if (AuraApplication* aurApp = (*itr).second->GetApplicationOfTarget(GetGUID())) (*itr).second->HandleAllEffects(aurApp, AURA_EFFECT_HANDLE_REAL, false); } } } } } uint32 Unit::BuildAuraStateUpdateForTarget(Unit* target) const { uint32 auraStates = GetUInt32Value(UNIT_FIELD_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) { AuraEffectList const& stateAuras = Caster->GetAuraEffectsByType(SPELL_AURA_ABILITY_IGNORE_AURASTATE); for (AuraEffectList::const_iterator j = stateAuras.begin(); j != stateAuras.end(); ++j) if ((*j)->IsAffectedOnSpell(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 HasFlag(UNIT_FIELD_AURASTATE, 1 << (flag - 1)); } void Unit::SetOwnerGUID(ObjectGuid owner) { if (GetOwnerGUID() == owner) return; SetGuidValue(UNIT_FIELD_SUMMONEDBY, owner); if (!owner) return; m_applyResilience = !IsVehicle() && owner.IsPlayer(); // 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; SetFieldNotifyFlag(UF_FLAG_OWNER); UpdateData udata; WorldPacket packet; BuildValuesUpdateBlockForPlayer(&udata, player); udata.BuildPacket(packet); player->SendDirectMessage(&packet); RemoveFieldNotifyFlag(UF_FLAG_OWNER); } Unit* Unit::GetOwner() const { if (ObjectGuid ownerGUID = GetOwnerGUID()) return ObjectAccessor::GetUnit(*this, ownerGUID); return nullptr; } Unit* Unit::GetCharmer() const { if (ObjectGuid charmerGUID = GetCharmerGUID()) return ObjectAccessor::GetUnit(*this, charmerGUID); return nullptr; } Player* Unit::GetCharmerOrOwnerPlayerOrPlayerItself() const { ObjectGuid guid = GetCharmerOrOwnerGUID(); if (guid.IsPlayer()) return ObjectAccessor::GetPlayer(*this, guid); return const_cast(this)->ToPlayer(); } Player* Unit::GetAffectingPlayer() const { if (!GetCharmerOrOwnerGUID()) return const_cast(this)->ToPlayer(); if (Unit* owner = GetCharmerOrOwner()) return owner->GetCharmerOrOwnerPlayerOrPlayerItself(); return nullptr; } Minion* Unit::GetFirstMinion() const { if (ObjectGuid pet_guid = GetMinionGUID()) { if (Creature* pet = ObjectAccessor::GetCreatureOrPetOrVehicle(*this, pet_guid)) if (pet->HasUnitTypeMask(UNIT_MASK_MINION)) return (Minion*)pet; LOG_ERROR("entities.unit", "Unit::GetFirstMinion: Minion {} not exist.", pet_guid.ToString()); const_cast(this)->SetMinionGUID(ObjectGuid::Empty); } return nullptr; } Guardian* Unit::GetGuardianPet() const { if (ObjectGuid pet_guid = GetPetGUID()) { if (Creature* pet = ObjectAccessor::GetCreatureOrPetOrVehicle(*this, pet_guid)) if (pet->HasUnitTypeMask(UNIT_MASK_GUARDIAN)) return (Guardian*)pet; LOG_FATAL("entities.unit", "Unit::GetGuardianPet: Guardian {} not exist.", pet_guid.ToString()); const_cast(this)->SetPetGUID(ObjectGuid::Empty); } return nullptr; } Creature* Unit::GetCompanionPet() const { return ObjectAccessor::GetCreature(*this, m_SummonSlot[SUMMON_SLOT_MINIPET]); } Unit* Unit::GetCharm() const { if (ObjectGuid charm_guid = GetCharmGUID()) { if (Unit* pet = ObjectAccessor::GetUnit(*this, charm_guid)) return pet; LOG_ERROR("entities.unit", "Unit::GetCharm: Charmed creature {} not exist.", charm_guid.ToString()); const_cast(this)->SetGuidValue(UNIT_FIELD_CHARM, ObjectGuid::Empty); } return nullptr; } void Unit::SetMinion(Minion* minion, bool apply) { LOG_DEBUG("entities.unit", "SetMinion {} for {}, apply {}", minion->GetEntry(), GetEntry(), apply); if (apply) { if (minion->GetOwnerGUID()) { LOG_FATAL("entities.unit", "SetMinion: Minion {} is not the minion of owner {}", minion->GetEntry(), GetEntry()); return; } minion->SetOwnerGUID(GetGUID()); m_Controlled.insert(minion); if (IsPlayer()) { 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_CONTROLLABLE_GUARDIAN)) { AddGuidValue(UNIT_FIELD_SUMMON, minion->GetGUID()); } if (minion->m_Properties && minion->m_Properties->Type == SUMMON_TYPE_MINIPET) { SetCritterGUID(minion->GetGUID()); } // PvP, FFAPvP minion->SetByteValue(UNIT_FIELD_BYTES_2, 1, GetByteValue(UNIT_FIELD_BYTES_2, 1)); // Ghoul pets have energy instead of mana (is anywhere better place for this code?) if (minion->IsPetGhoul() || minion->GetEntry() == 24207 /*ENTRY_ARMY_OF_THE_DEAD*/) minion->setPowerType(POWER_ENERGY); if (IsPlayer()) { // Send infinity cooldown - client does that automatically but after relog cooldown needs to be set again SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(minion->GetUInt32Value(UNIT_CREATED_BY_SPELL)); if (spellInfo && spellInfo->IsCooldownStartedOnEvent()) ToPlayer()->AddSpellAndCategoryCooldowns(spellInfo, 0, nullptr, true); } } else { if (minion->GetOwnerGUID() != GetGUID()) { LOG_FATAL("entities.unit", "SetMinion: Minion {} is not the minion of owner {}", minion->GetEntry(), GetEntry()); return; } m_Controlled.erase(minion); if (minion->m_Properties && minion->m_Properties->Type == SUMMON_TYPE_MINIPET) { 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())) { for (int i = 0; i < MAX_SPELL_EFFECTS; ++i) { if (spInfo->Effects[i].Effect != SPELL_EFFECT_SUMMON) continue; RemoveAllMinionsByEntry(spInfo->Effects[i].MiscValue); } } } if (IsPlayer()) { SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(minion->GetUInt32Value(UNIT_CREATED_BY_SPELL)); // Remove infinity cooldown if (spellInfo && spellInfo->IsCooldownStartedOnEvent()) ToPlayer()->SendCooldownEvent(spellInfo); // xinef: clear spell book if (m_Controlled.empty()) ToPlayer()->SendRemoveControlBar(); } //if (minion->HasUnitTypeMask(UNIT_MASK_GUARDIAN)) { if (RemoveGuidValue(UNIT_FIELD_SUMMON, minion->GetGUID())) { // Check if there is another minion for (ControlSet::iterator itr = m_Controlled.begin(); itr != m_Controlled.end(); ++itr) { // do not use this check, creature do not have charm guid //if (GetCharmGUID() == (*itr)->GetGUID()) if (GetGUID() == (*itr)->GetCharmerGUID()) continue; //ASSERT((*itr)->GetOwnerGUID() == GetGUID()); if ((*itr)->GetOwnerGUID() != GetGUID()) { OutDebugInfo(); (*itr)->OutDebugInfo(); ABORT(); } ASSERT((*itr)->IsCreature()); if (!(*itr)->HasUnitTypeMask(UNIT_MASK_CONTROLLABLE_GUARDIAN)) continue; if (AddGuidValue(UNIT_FIELD_SUMMON, (*itr)->GetGUID())) { // show another pet bar if there is no charm bar if (IsPlayer() && !GetCharmGUID()) { if ((*itr)->IsPet()) ToPlayer()->PetSpellInitialize(); else ToPlayer()->CharmSpellInitialize(); } } break; } } } } } void Unit::GetAllMinionsByEntry(std::list& Minions, uint32 entry) { for (Unit::ControlSet::iterator itr = m_Controlled.begin(); itr != m_Controlled.end();) { Unit* unit = *itr; ++itr; if (unit->GetEntry() == entry && unit->IsCreature() && unit->ToCreature()->IsSummon()) // minion, actually Minions.push_back(unit->ToCreature()); } } void Unit::RemoveAllMinionsByEntry(uint32 entry) { for (Unit::ControlSet::iterator itr = m_Controlled.begin(); itr != m_Controlled.end();) { Unit* unit = *itr; ++itr; if (unit->GetEntry() == entry && unit->IsCreature() && 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 (IsPlayer()) { if (!AddGuidValue(UNIT_FIELD_CHARM, charm->GetGUID())) LOG_FATAL("entities.unit", "Player {} is trying to charm unit {}, but it already has a charmed unit {}", GetName(), charm->GetEntry(), GetCharmGUID().ToString()); 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; if (!HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED)) charm->RemoveUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED); } // PvP, FFAPvP charm->SetByteValue(UNIT_FIELD_BYTES_2, 1, GetByteValue(UNIT_FIELD_BYTES_2, 1)); if (!charm->AddGuidValue(UNIT_FIELD_CHARMEDBY, GetGUID())) LOG_FATAL("entities.unit", "Unit {} is being charmed, but it already has a charmer {}", charm->GetEntry(), charm->GetCharmerGUID().ToString()); _isWalkingBeforeCharm = charm->IsWalking(); if (_isWalkingBeforeCharm) { charm->SetWalk(false); charm->SendMovementFlagUpdate(); } m_Controlled.insert(charm); } else { if (IsPlayer()) { if (!RemoveGuidValue(UNIT_FIELD_CHARM, charm->GetGUID())) LOG_FATAL("entities.unit", "Player {} is trying to uncharm unit {}, but it has another charmed unit {}", GetName(), charm->GetEntry(), GetCharmGUID().ToString()); } if (!charm->RemoveGuidValue(UNIT_FIELD_CHARMEDBY, GetGUID())) LOG_FATAL("entities.unit", "Unit {} is being uncharmed, but it has another charmer {}", charm->GetEntry(), charm->GetCharmerGUID().ToString()); if (charm->IsPlayer()) { 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->SetByteValue(UNIT_FIELD_BYTES_2, 1, player->GetByteValue(UNIT_FIELD_BYTES_2, 1)); // Xinef: skip controlled erase if charmed unit is owned by charmer if (charm->IsInWorld() && !charm->IsDuringRemoveFromWorld() && player->GetGUID() == this->GetGUID() && (charm->IsPet() || charm->HasUnitTypeMask(UNIT_MASK_MINION))) return; } else { charm->m_ControlledByPlayer = false; charm->RemoveUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED); charm->SetByteValue(UNIT_FIELD_BYTES_2, 1, 0); } if (charm->IsWalking() != _isWalkingBeforeCharm) { charm->SetWalk(_isWalkingBeforeCharm); charm->SendMovementFlagUpdate(true); // send packet to self, to update movement state on player. } m_Controlled.erase(charm); } } int32 Unit::DealHeal(Unit* healer, Unit* victim, uint32 addhealth) { int32 gain = 0; if (healer) { if (victim->IsAIEnabled) victim->GetAI()->HealReceived(healer, addhealth); if (healer->IsAIEnabled) healer->GetAI()->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->IsCreature() && healer->ToCreature()->IsTotem()) unit = healer->GetOwner(); if (!unit) return gain; if (Player* player = unit->ToPlayer()) { 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->InBattleground()) // pussywizard: InBattleground() optimization player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HEALING_DONE, gain, 0, victim); //player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HIGHEST_HEAL_CASTED, addhealth); // pussywizard: optimization } /*if (Player* player = victim->ToPlayer()) { //player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_TOTAL_HEALING_RECEIVED, gain); // pussywizard: optimization //player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HIGHEST_HEALING_RECEIVED, addhealth); // pussywizard: optimization }*/ return gain; } bool RedirectSpellEvent::Execute(uint64 /*e_time*/, uint32 /*p_time*/) { if (Unit* auraOwner = ObjectAccessor::GetUnit(_self, _auraOwnerGUID)) { // Xinef: already removed if (!auraOwner->HasSpellMagnetAura()) return true; Unit::AuraEffectList const& magnetAuras = auraOwner->GetAuraEffectsByType(SPELL_AURA_SPELL_MAGNET); for (Unit::AuraEffectList::const_iterator itr = magnetAuras.begin(); itr != magnetAuras.end(); ++itr) if (*itr == _auraEffect) { (*itr)->GetBase()->DropCharge(AURA_REMOVE_BY_DEFAULT); return true; } } return true; } Unit* Unit::GetMagicHitRedirectTarget(Unit* victim, SpellInfo const* spellInfo) { // Patch 1.2 notes: Spell Reflection no longer reflects abilities if (spellInfo->HasAttribute(SPELL_ATTR0_IS_ABILITY) || spellInfo->HasAttribute(SPELL_ATTR1_NO_REDIRECTION) || spellInfo->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES)) return victim; Unit::AuraEffectList const& magnetAuras = victim->GetAuraEffectsByType(SPELL_AURA_SPELL_MAGNET); for (Unit::AuraEffectList::const_iterator itr = magnetAuras.begin(); itr != magnetAuras.end(); ++itr) { if (Unit* magnet = (*itr)->GetBase()->GetUnitOwner()) if (spellInfo->CheckExplicitTarget(this, magnet) == SPELL_CAST_OK //&& spellInfo->CheckTarget(this, magnet, false) == SPELL_CAST_OK && _IsValidAttackTarget(magnet, spellInfo) /*&& IsWithinLOSInMap(magnet)*/) { // Xinef: We should choose minimum between flight time and queue time as in reflect, however we dont know flight time at this point, use arbitrary small number magnet->m_Events.AddEvent(new RedirectSpellEvent(*magnet, victim->GetGUID(), *itr), magnet->m_Events.CalculateQueueTime(100)); if (magnet->IsTotem()) { uint64 queueTime = magnet->m_Events.CalculateQueueTime(100); if (spellInfo->Speed > 0.0f) { float dist = GetDistance(magnet->GetPositionX(), magnet->GetPositionY(), magnet->GetPositionZ()); if (dist < 5.0f) dist = 5.0f; queueTime = magnet->m_Events.CalculateTime((uint64)std::floor(dist / spellInfo->Speed * 1000.0f)); } magnet->m_Events.AddEvent(new KillMagnetEvent(*magnet), queueTime); } return magnet; } } return victim; } Unit* Unit::GetMeleeHitRedirectTarget(Unit* victim, SpellInfo const* spellInfo) { AuraEffectList const& hitTriggerAuras = victim->GetAuraEffectsByType(SPELL_AURA_ADD_CASTER_HIT_TRIGGER); for (AuraEffectList::const_iterator i = hitTriggerAuras.begin(); i != hitTriggerAuras.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))) if (roll_chance_i((*i)->GetAmount())) { (*i)->GetBase()->DropCharge(AURA_REMOVE_BY_EXPIRE); return magnet; } } return victim; } Unit* Unit::GetFirstControlled() const { // Sequence: charmed, pet, other guardians Unit* unit = GetCharm(); if (!unit) if (ObjectGuid guid = GetMinionGUID()) unit = ObjectAccessor::GetUnit(*this, guid); return unit; } void Unit::RemoveAllControlled(bool onDeath /*= false*/) { // possessed pet and vehicle if (IsPlayer()) 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()) { if (!(onDeath && !IsPlayer() && target->IsGuardian())) { target->ToTempSummon()->UnSummon(); } } else { LOG_ERROR("entities.unit", "Unit {} is trying to release unit {} which is neither charmed nor owned by it", GetEntry(), target->GetEntry()); } } } Unit* Unit::GetNextRandomRaidMemberOrPet(float radius) { Player* player = nullptr; if (IsPlayer()) player = ToPlayer(); // Should we enable this also for charmed units? else if (IsCreature() && 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* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) if (Player* Target = itr->GetSource()) { if (Target != this && !IsWithinDistInMap(Target, radius)) continue; // IsHostileTo check duel and controlled by enemy if (Target != this && Target->IsAlive() && !IsHostileTo(Target)) nearMembers.push_back(Target); // Push player's pet to vector if (Unit* pet = Target->GetGuardianPet()) if (pet != this && pet->IsAlive() && IsWithinDistInMap(pet, radius) && !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); m_sharedVision.push_back(player); player->m_isInSharedVisionOf.insert(this); } // only called in Player::SetSeer void Unit::RemovePlayerFromVision(Player* player) { m_sharedVision.remove(player); player->m_isInSharedVisionOf.erase(this); /// @todo: This isn't right, if a previously active object was set to active with e.g. Mind Vision this will make them no longer active if (m_sharedVision.empty()) setActive(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(bool onDeath /*= false*/) { 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()) { if (!(onDeath && !IsPlayer() && OldTotem->IsGuardian())) { OldTotem->ToTempSummon()->UnSummon(); } } } } } void Unit::SendHealSpellLog(HealInfo const& healInfo, bool critical) { uint32 overheal = healInfo.GetHeal() - healInfo.GetEffectiveHeal(); // we guess size WorldPacket data(SMSG_SPELLHEALLOG, (8 + 8 + 4 + 4 + 4 + 4 + 1 + 1)); data << healInfo.GetTarget()->GetPackGUID(); data << GetPackGUID(); data << uint32(healInfo.GetSpellInfo()->Id); data << uint32(healInfo.GetHeal()); data << uint32(overheal); data << uint32(healInfo.GetAbsorb()); // Absorb amount data << uint8(critical ? 1 : 0); data << uint8(0); // unused SendMessageToSet(&data, true); } int32 Unit::HealBySpell(HealInfo& healInfo, bool critical) { uint32 heal = healInfo.GetHeal(); sScriptMgr->ModifyHealReceived(this, healInfo.GetTarget(), heal, healInfo.GetSpellInfo()); healInfo.SetHeal(heal); // calculate heal absorb and reduce healing CalcHealAbsorb(healInfo); int32 gain = Unit::DealHeal(healInfo.GetHealer(), healInfo.GetTarget(), healInfo.GetHeal()); healInfo.SetEffectiveHeal(gain); SendHealSpellLog(healInfo, critical); return gain; } void Unit::SendEnergizeSpellLog(Unit* victim, uint32 spellID, uint32 damage, Powers powerType) { WorldPacket data(SMSG_SPELLENERGIZELOG, (8 + 8 + 4 + 4 + 4 + 1)); data << victim->GetPackGUID(); data << GetPackGUID(); data << uint32(spellID); data << uint32(powerType); data << uint32(damage); SendMessageToSet(&data, true); } void Unit::EnergizeBySpell(Unit* victim, uint32 spellID, uint32 damage, Powers powerType) { int32 gainedPower = victim->ModifyPower(powerType, damage, false); if (powerType != POWER_HAPPINESS && gainedPower) { SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellID); victim->getHostileRefMgr().threatAssist(this, float(gainedPower) * 0.5f, spellInfo); } SendEnergizeSpellLog(victim, spellID, damage, powerType); } float Unit::SpellPctDamageModsDone(Unit* victim, SpellInfo const* spellProto, DamageEffectType damagetype) { 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; // For totems get damage bonus from owner if (IsCreature()) { if (IsTotem()) { if (Unit* owner = GetOwner()) return owner->SpellPctDamageModsDone(victim, spellProto, damagetype); } // Dancing Rune Weapon... else if (GetEntry() == 27893) { if (Unit* owner = GetOwner()) return owner->SpellPctDamageModsDone(victim, spellProto, damagetype); } } // Done total percent damage auras float DoneTotalMod = 1.0f; DoneTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_DAMAGE_PERCENT_DONE, [spellProto, this, damagetype](AuraEffect const* aurEff) { // prevent apply mods from weapon specific case to non weapon specific spells (Example: thunder clap and two-handed weapon specialization) if (spellProto->EquippedItemClass == -1 && aurEff->GetSpellInfo()->EquippedItemClass != -1 && !aurEff->GetSpellInfo()->HasAttribute(SPELL_ATTR5_AURA_AFFECTS_NOT_JUST_REQ_EQUIPPED_ITEM) && aurEff->GetMiscValue() == SPELL_SCHOOL_MASK_NORMAL) { return false; } if (!spellProto->ValidateAttribute6SpellDamageMods(this, aurEff, damagetype == DOT)) return false; if (aurEff->GetMiscValue() & spellProto->GetSchoolMask()) { if (aurEff->GetSpellInfo()->EquippedItemClass == -1) return true; else if (!aurEff->GetSpellInfo()->HasAttribute(SPELL_ATTR5_AURA_AFFECTS_NOT_JUST_REQ_EQUIPPED_ITEM) && (aurEff->GetSpellInfo()->EquippedItemSubClassMask == 0)) return true; else if (ToPlayer() && ToPlayer()->HasItemFitToSpellRequirements(aurEff->GetSpellInfo())) return true; } return false; }); uint32 creatureTypeMask = victim->GetCreatureTypeMask(); DoneTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_DAMAGE_DONE_VERSUS, [creatureTypeMask, spellProto, damagetype, this](AuraEffect const* aurEff) { return creatureTypeMask & aurEff->GetMiscValue() && spellProto->ValidateAttribute6SpellDamageMods(this, aurEff, damagetype == DOT); }); // bonus against aurastate DoneTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_DAMAGE_DONE_VERSUS_AURASTATE, [victim, spellProto, damagetype, this](AuraEffect const* aurEff) { return victim->HasAuraState(AuraStateType(aurEff->GetMiscValue())) && spellProto->ValidateAttribute6SpellDamageMods(this, aurEff, damagetype == DOT); }); // done scripted mod (take it from owner) Unit* owner = GetOwner() ? GetOwner() : this; AuraEffectList const& mOverrideClassScript = owner->GetAuraEffectsByType(SPELL_AURA_OVERRIDE_CLASS_SCRIPTS); for (AuraEffectList::const_iterator i = mOverrideClassScript.begin(); i != mOverrideClassScript.end(); ++i) { // Xinef: self cast is ommited (because of Rage of Rivendare) if (!spellProto->ValidateAttribute6SpellDamageMods(this, *i, damagetype == DOT)) continue; // xinef: Molten Fury should work on all spells switch ((*i)->GetMiscValue()) { case 4920: // Molten Fury case 4919: if (victim->HasAuraState(AURA_STATE_HEALTHLESS_35_PERCENT, spellProto, this)) AddPct(DoneTotalMod, (*i)->GetAmount()); break; } if (!(*i)->IsAffectedOnSpell(spellProto)) continue; switch ((*i)->GetMiscValue()) { case 6917: // Death's Embrace case 6926: case 6928: { if (victim->HasAuraState(AURA_STATE_HEALTHLESS_35_PERCENT, spellProto, this)) AddPct(DoneTotalMod, (*i)->GetAmount()); break; } // Soul Siphon case 4992: case 4993: { // effect 1 m_amount int32 maxPercent = (*i)->GetAmount(); // effect 0 m_amount int32 stepPercent = CalculateSpellDamage(this, (*i)->GetSpellInfo(), 0); // count affliction effects and calc additional damage in percentage int32 modPercent = 0; AuraApplicationMap const& victimAuras = victim->GetAppliedAuras(); for (AuraApplicationMap::const_iterator itr = victimAuras.begin(); itr != victimAuras.end(); ++itr) { Aura const* aura = itr->second->GetBase(); SpellInfo const* m_spell = aura->GetSpellInfo(); if (m_spell->SpellFamilyName != SPELLFAMILY_WARLOCK || !(m_spell->SpellFamilyFlags[1] & 0x0004071B || m_spell->SpellFamilyFlags[0] & 0x8044C402)) continue; modPercent += stepPercent * aura->GetStackAmount(); if (modPercent >= maxPercent) { modPercent = maxPercent; break; } } AddPct(DoneTotalMod, modPercent); break; } case 6916: // Death's Embrace case 6925: case 6927: if (HasAuraState(AURA_STATE_HEALTHLESS_20_PERCENT, spellProto, this)) AddPct(DoneTotalMod, (*i)->GetAmount()); break; case 5481: // Starfire Bonus { if (victim->GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_DRUID, 0x200002, 0, 0)) AddPct(DoneTotalMod, (*i)->GetAmount()); break; } // Tundra Stalker // Merciless Combat case 7277: { // Merciless Combat if ((*i)->GetSpellInfo()->SpellIconID == 2656) { if ((spellProto && spellProto->SpellFamilyFlags[0] & 0x2) || spellProto->SpellFamilyFlags[1] & 0x2 ) if (!victim->HealthAbovePct(35)) AddPct(DoneTotalMod, (*i)->GetAmount()); } // Tundra Stalker else { // Frost Fever (target debuff) if (victim->HasAura(55095)) AddPct(DoneTotalMod, (*i)->GetAmount()); break; } break; } // Rage of Rivendare case 7293: { if (victim->GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_DEATHKNIGHT, 0, 0x02000000, 0)) AddPct(DoneTotalMod, (*i)->GetSpellInfo()->GetRank() * 2.0f); break; } // Twisted Faith case 7377: { if (victim->GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_PRIEST, 0x8000, 0, 0, GetGUID())) AddPct(DoneTotalMod, (*i)->GetAmount()); break; } // Marked for Death case 7598: case 7599: case 7600: case 7601: case 7602: { if (victim->GetAuraEffect(SPELL_AURA_MOD_STALKED, SPELLFAMILY_HUNTER, 0x400, 0, 0)) AddPct(DoneTotalMod, (*i)->GetAmount()); break; } // Dirty Deeds case 6427: case 6428: case 6579: case 6580: { if (victim->HasAuraState(AURA_STATE_HEALTHLESS_35_PERCENT, spellProto, this)) { // effect 0 has expected value but in negative state int32 bonus = -(*i)->GetBase()->GetEffect(0)->GetAmount(); AddPct(DoneTotalMod, bonus); } break; } } } // Custom scripted damage switch (spellProto->SpellFamilyName) { case SPELLFAMILY_MAGE: // Ice Lance if (spellProto->SpellIconID == 186) { if (victim->HasAuraState(AURA_STATE_FROZEN, spellProto, this)) { // Glyph of Ice Lance if (owner->HasAura(56377) && victim->GetLevel() > owner->GetLevel()) DoneTotalMod *= 4.0f; else DoneTotalMod *= 3.0f; } } // Torment the weak if (spellProto->SpellFamilyFlags[0] & 0x20600021 || spellProto->SpellFamilyFlags[1] & 0x9000) if (victim->HasAuraWithMechanic((1 << MECHANIC_SNARE) | (1 << MECHANIC_SLOW_ATTACK))) if (AuraEffect* aurEff = GetAuraEffect(SPELL_AURA_DUMMY, SPELLFAMILY_GENERIC, 3263, EFFECT_0)) AddPct(DoneTotalMod, aurEff->GetAmount()); break; case SPELLFAMILY_PRIEST: // Mind Flay if (spellProto->SpellFamilyFlags[0] & 0x800000) { // Glyph of Shadow Word: Pain if (AuraEffect* aurEff = GetAuraEffect(55687, 0)) // Increase Mind Flay damage if Shadow Word: Pain present on target if (victim->GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_PRIEST, 0x8000, 0, 0, GetGUID())) AddPct(DoneTotalMod, aurEff->GetAmount()); // Twisted Faith - Mind Flay part if (AuraEffect* aurEff = GetAuraEffect(SPELL_AURA_OVERRIDE_CLASS_SCRIPTS, SPELLFAMILY_PRIEST, 2848, 1)) // Increase Mind Flay damage if Shadow Word: Pain present on target if (victim->GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_PRIEST, 0x8000, 0, 0, GetGUID())) AddPct(DoneTotalMod, aurEff->GetAmount()); } // Smite else if (spellProto->SpellFamilyFlags[0] & 0x80) { // Glyph of Smite if (AuraEffect* aurEff = GetAuraEffect(55692, 0)) if (victim->GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_PRIEST, 0x100000, 0, 0, GetGUID())) AddPct(DoneTotalMod, aurEff->GetAmount()); } // Shadow Word: Death else if (spellProto->SpellFamilyFlags[1] & 0x2) { // Glyph of Shadow Word: Death if (AuraEffect* aurEff = GetAuraEffect(55682, 1)) if (victim->HasAuraState(AURA_STATE_HEALTHLESS_35_PERCENT)) AddPct(DoneTotalMod, aurEff->GetAmount()); } break; case SPELLFAMILY_PALADIN: // Judgement of Vengeance/Judgement of Corruption if ((spellProto->SpellFamilyFlags[1] & 0x400000) && spellProto->SpellIconID == 2292) { // Get stack of Holy Vengeance/Blood Corruption on the target added by caster uint32 stacks = 0; Unit::AuraEffectList const& auras = victim->GetAuraEffectsByType(SPELL_AURA_PERIODIC_DAMAGE); for (Unit::AuraEffectList::const_iterator itr = auras.begin(); itr != auras.end(); ++itr) if (((*itr)->GetId() == 31803 || (*itr)->GetId() == 53742) && (*itr)->GetCasterGUID() == GetGUID()) { stacks = (*itr)->GetBase()->GetStackAmount(); break; } // + 10% for each application of Holy Vengeance/Blood Corruption on the target if (stacks) AddPct(DoneTotalMod, 10 * stacks); } break; case SPELLFAMILY_DRUID: // Thorns if (spellProto->SpellFamilyFlags[0] & 0x100) { // Brambles if (AuraEffect* aurEff = GetAuraEffectOfRankedSpell(16836, 0)) AddPct(DoneTotalMod, aurEff->GetAmount()); } break; case SPELLFAMILY_WARLOCK: // Fire and Brimstone if (spellProto->SpellFamilyFlags[1] & 0x00020040) if (victim->HasAuraState(AURA_STATE_CONFLAGRATE)) { AuraEffectList const& mDumyAuras = GetAuraEffectsByType(SPELL_AURA_DUMMY); for (AuraEffectList::const_iterator i = mDumyAuras.begin(); i != mDumyAuras.end(); ++i) if ((*i)->GetSpellInfo()->SpellIconID == 3173) { AddPct(DoneTotalMod, (*i)->GetAmount()); break; } } // Drain Soul - increased damage for targets under 25 % HP if (spellProto->SpellFamilyFlags[0] & 0x00004000) if (!victim->HealthAbovePct(25)) DoneTotalMod *= 4; // Shadow Bite (15% increase from each dot) if (spellProto->SpellFamilyFlags[1] & 0x00400000 && IsPet()) if (uint8 count = victim->GetDoTsByCaster(GetOwnerGUID())) AddPct(DoneTotalMod, 15 * count); break; case SPELLFAMILY_HUNTER: // Steady Shot if (spellProto->SpellFamilyFlags[1] & 0x1) if (AuraEffect* aurEff = GetAuraEffect(56826, 0)) // Glyph of Steady Shot if (victim->GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_HUNTER, 0x00004000, 0, 0, GetGUID())) AddPct(DoneTotalMod, aurEff->GetAmount()); break; case SPELLFAMILY_DEATHKNIGHT: // Improved Icy Touch if (spellProto->SpellFamilyFlags[0] & 0x2) if (AuraEffect* aurEff = GetDummyAuraEffect(SPELLFAMILY_DEATHKNIGHT, 2721, 0)) AddPct(DoneTotalMod, aurEff->GetAmount()); // Glacier Rot if (spellProto->SpellFamilyFlags[0] & 0x2 || spellProto->SpellFamilyFlags[1] & 0x6) if (AuraEffect* aurEff = GetDummyAuraEffect(SPELLFAMILY_DEATHKNIGHT, 196, 0)) if (victim->GetDiseasesByCaster(owner->GetGUID()) > 0) AddPct(DoneTotalMod, aurEff->GetAmount()); break; } return DoneTotalMod; } uint32 Unit::SpellDamageBonusDone(Unit* victim, SpellInfo const* spellProto, uint32 pdamage, DamageEffectType damagetype, uint8 effIndex, float TotalMod, uint32 stack) { if (!spellProto || !victim || damagetype == DIRECT_DAMAGE) return pdamage; // Some spells don't benefit from done mods if (spellProto->HasAttribute(SPELL_ATTR3_IGNORE_CASTER_MODIFIERS)) return pdamage; // For totems get damage bonus from owner if (IsCreature()) { if (IsTotem()) { if (Unit* owner = GetOwner()) return owner->SpellDamageBonusDone(victim, spellProto, pdamage, damagetype, effIndex, TotalMod, stack); } // Dancing Rune Weapon... else if (GetEntry() == 27893) { if (Unit* owner = GetOwner()) return owner->SpellDamageBonusDone(victim, spellProto, pdamage, damagetype, TotalMod, stack) / 2; } } // Done total percent damage auras float ApCoeffMod = 1.0f; int32 DoneTotal = 0; float DoneTotalMod = TotalMod ? TotalMod : SpellPctDamageModsDone(victim, spellProto, damagetype); // Config : RATE_CREATURE_X_SPELLDAMAGE & Do Not Modify Pet/Guardian/Mind Controlled Damage if (IsCreature() && (!ToCreature()->IsPet() || !ToCreature()->IsGuardian() || !ToCreature()->IsControlledByPlayer())) DoneTotalMod *= ToCreature()->GetSpellDamageMod(ToCreature()->GetCreatureTemplate()->rank); // Some spells don't benefit from pct done mods if (!spellProto->HasAttribute(SPELL_ATTR6_IGNORE_CASTER_DAMAGE_MODIFIERS)) { uint32 creatureTypeMask = victim->GetCreatureTypeMask(); // Add flat bonus from spell damage versus DoneTotal += GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_FLAT_SPELL_DAMAGE_VERSUS, creatureTypeMask); } // done scripted mod (take it from owner) Unit* owner = GetOwner() ? GetOwner() : this; int32 DoneAdvertisedBenefit = 0; AuraEffectList const& mOverrideClassScript = owner->GetAuraEffectsByType(SPELL_AURA_OVERRIDE_CLASS_SCRIPTS); for (AuraEffectList::const_iterator i = mOverrideClassScript.begin(); i != mOverrideClassScript.end(); ++i) { if (!(*i)->IsAffectedOnSpell(spellProto)) continue; switch ((*i)->GetMiscValue()) { case 4418: // Increased Shock Damage case 4554: // Increased Lightning Damage case 4555: // Improved Moonfire case 5142: // Increased Lightning Damage case 5147: // Improved Consecration / Libram of Resurgence case 5148: // Idol of the Shooting Star case 6008: // Increased Lightning Damage case 8627: // Totem of Hex { DoneAdvertisedBenefit += (*i)->GetAmount(); break; } } } // Custom scripted damage switch (spellProto->SpellFamilyName) { case SPELLFAMILY_DRUID: { // Insect Swarm vs Item - Druid T8 Balance Relic if (spellProto->SpellFamilyFlags[0] & 0x00200000) { if (AuraEffect const* relicAurEff = GetAuraEffect(64950, EFFECT_0)) { DoneAdvertisedBenefit += relicAurEff->GetAmount(); } } // Nourish vs Idol of the Flourishing Life if (spellProto->SpellFamilyFlags[1] & 0x02000000) { if (AuraEffect const* relicAurEff = GetAuraEffect(64949, EFFECT_0)) { DoneAdvertisedBenefit += relicAurEff->GetAmount(); } } break; } case SPELLFAMILY_DEATHKNIGHT: { // Sigil of the Vengeful Heart if (spellProto->SpellFamilyFlags[0] & 0x2000) { if (AuraEffect* aurEff = GetAuraEffect(64962, EFFECT_1)) { AddPct(DoneTotal, aurEff->GetAmount()); } } // Impurity if (AuraEffect* aurEff = GetDummyAuraEffect(SPELLFAMILY_DEATHKNIGHT, 1986, 0)) { AddPct(ApCoeffMod, aurEff->GetAmount()); } // Blood Boil - bonus for diseased targets if ((spellProto->SpellFamilyFlags[0] & 0x00040000) && victim->GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_DEATHKNIGHT, 0, 0, 0x00000002, GetGUID())) { DoneTotal += 95; ApCoeffMod = 1.5835f; } break; } default: break; } // Done fixed damage bonus auras DoneAdvertisedBenefit += SpellBaseDamageBonusDone(spellProto->GetSchoolMask()); // Check for table values float coeff = spellProto->Effects[effIndex].BonusMultiplier; SpellBonusEntry const* bonus = sSpellMgr->GetSpellBonusData(spellProto->Id); if (bonus) { if (damagetype == DOT) { coeff = bonus->dot_damage; if (bonus->ap_dot_bonus > 0) { WeaponAttackType 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(bonus->ap_dot_bonus * stack * ApCoeffMod * APbonus); } } else { coeff = bonus->direct_damage; if (bonus->ap_bonus > 0) { WeaponAttackType 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(bonus->ap_bonus * stack * ApCoeffMod * APbonus); } } } // Default calculation if (coeff && DoneAdvertisedBenefit) { float factorMod = CalculateLevelPenalty(spellProto) * stack; if (Player* modOwner = GetSpellModOwner()) { coeff *= 100.0f; modOwner->ApplySpellMod(spellProto->Id, SPELLMOD_BONUS_MULTIPLIER, coeff); coeff /= 100.0f; } DoneTotal += int32(DoneAdvertisedBenefit * coeff * factorMod); } float tmpDamage = (float(pdamage) + DoneTotal) * DoneTotalMod; // apply spellmod to Done damage (flat and pct) if (Player* modOwner = GetSpellModOwner()) modOwner->ApplySpellMod(spellProto->Id, damagetype == DOT ? SPELLMOD_DOT : SPELLMOD_DAMAGE, tmpDamage); return uint32(std::max(tmpDamage, 0.0f)); } uint32 Unit::SpellDamageBonusTaken(Unit* caster, SpellInfo const* spellProto, uint32 pdamage, DamageEffectType damagetype, uint32 stack) { if (!spellProto || damagetype == DIRECT_DAMAGE) return pdamage; int32 TakenTotal = 0; float TakenTotalMod = 1.0f; // 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 = processDummyAuras(TakenTotalMod); // From caster spells if (caster) { TakenTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_DAMAGE_FROM_CASTER, [caster, spellProto](AuraEffect const* aurEff) -> bool { if (aurEff->GetCasterGUID() == caster->GetGUID() && aurEff->IsAffectedOnSpell(spellProto)) return true; return false; }); } if (uint32 mechanicMask = spellProto->GetAllEffectsMechanicMask()) { int32 modifierMax = 0; int32 modifierMin = 0; AuraEffectList const& auraEffectList = GetAuraEffectsByType(SPELL_AURA_MOD_MECHANIC_DAMAGE_TAKEN_PERCENT); for (AuraEffectList::const_iterator i = auraEffectList.begin(); i != auraEffectList.end(); ++i) { if (!spellProto->ValidateAttribute6SpellDamageMods(caster, *i, damagetype == DOT)) continue; // Only death knight spell with this aura if ((*i)->GetSpellInfo()->SpellFamilyName == SPELLFAMILY_DEATHKNIGHT) if (!caster || caster->GetGUID() != (*i)->GetCasterGUID()) continue; if (mechanicMask & uint32(1 << (*i)->GetMiscValue())) { if ((*i)->GetAmount() > 0) { if ((*i)->GetAmount() > modifierMax) modifierMax = (*i)->GetAmount(); } else if ((*i)->GetAmount() < modifierMin) modifierMin = (*i)->GetAmount(); } } AddPct(TakenTotalMod, modifierMax); AddPct(TakenTotalMod, modifierMin); } int32 TakenAdvertisedBenefit = SpellBaseDamageBonusTaken(spellProto->GetSchoolMask(), damagetype == DOT); // Check for table values float coeff = 0; SpellBonusEntry const* bonus = sSpellMgr->GetSpellBonusData(spellProto->Id); if (bonus) coeff = (damagetype == DOT) ? bonus->dot_damage : bonus->direct_damage; // Default calculation if (TakenAdvertisedBenefit) { if (coeff <= 0.0f) { if (caster) coeff = caster->CalculateDefaultCoefficient(spellProto, damagetype) * int32(stack); else coeff = CalculateDefaultCoefficient(spellProto, damagetype) * int32(stack); } float factorMod = CalculateLevelPenalty(spellProto) * stack; TakenTotal += int32(TakenAdvertisedBenefit * coeff * factorMod); } // No positive taken bonus, custom attr if (spellProto->HasAttribute(SPELL_ATTR0_CU_NO_POSITIVE_TAKEN_BONUS) && TakenTotalMod > 1.0f) { TakenTotal = 0; TakenTotalMod = 1.0f; } // xinef: sanctified wrath talent if (caster && TakenTotalMod < 1.0f && caster->HasIgnoreTargetResistAura()) { float ignoreModifier = 1.0f - TakenTotalMod; bool addModifier = false; AuraEffectList const& ResIgnoreAuras = caster->GetAuraEffectsByType(SPELL_AURA_MOD_IGNORE_TARGET_RESIST); for (AuraEffectList::const_iterator j = ResIgnoreAuras.begin(); j != ResIgnoreAuras.end(); ++j) if ((*j)->GetMiscValue() & spellProto->SchoolMask) { ApplyPct(ignoreModifier, (*j)->GetAmount()); addModifier = true; } if (addModifier) TakenTotalMod += ignoreModifier; } float tmpDamage = (float(pdamage) + TakenTotal) * TakenTotalMod; return uint32(std::max(tmpDamage, 0.0f)); } float Unit::processDummyAuras(float TakenTotalMod) const { // note: old code coming from TC, just extracted here to remove the code duplication + solve potential crash // see: https://github.com/TrinityCore/TrinityCore/commit/c85710e148d75450baedf6632b9ca6fd40b4148e // .. taken pct: dummy auras auto const& mDummyAuras = GetAuraEffectsByType(SPELL_AURA_DUMMY); for (auto i = mDummyAuras.begin(); i != mDummyAuras.end(); ++i) { if (!(*i) || !(*i)->GetSpellInfo()) { continue; } if (auto spellIconId = (*i)->GetSpellInfo()->SpellIconID) { switch (spellIconId) { // Cheat Death case 2109: if ((*i)->GetMiscValue() & SPELL_SCHOOL_MASK_NORMAL) { // Patch 2.4.3: The resilience required to reach the 90% damage reduction cap // is 22.5% critical strike damage reduction, or 444 resilience. // To calculate for 90%, we multiply the 100% by 4 (22.5% * 4 = 90%) float mod = -1.0f * GetMeleeCritDamageReduction(400); AddPct(TakenTotalMod, std::max(mod, float((*i)->GetAmount()))); } break; } } } return TakenTotalMod; } int32 Unit::SpellBaseDamageBonusDone(SpellSchoolMask schoolMask) { int32 DoneAdvertisedBenefit = GetTotalAuraModifier(SPELL_AURA_MOD_DAMAGE_DONE, [schoolMask](AuraEffect const* aurEff) { return aurEff->GetMiscValue() & schoolMask && // -1 == any item class (not wand then) aurEff->GetSpellInfo()->EquippedItemClass == -1 && // 0 == any inventory type (not wand then) aurEff->GetSpellInfo()->EquippedItemInventoryTypeMask == 0; }); if (IsPlayer()) { // Base value DoneAdvertisedBenefit += ToPlayer()->GetBaseSpellPowerBonus(); DoneAdvertisedBenefit += ToPlayer()->GetBaseSpellDamageBonus(); // Damage bonus from stats AuraEffectList const& mDamageDoneOfStatPercent = GetAuraEffectsByType(SPELL_AURA_MOD_SPELL_DAMAGE_OF_STAT_PERCENT); for (AuraEffectList::const_iterator i = mDamageDoneOfStatPercent.begin(); i != mDamageDoneOfStatPercent.end(); ++i) { if ((*i)->GetMiscValue() & schoolMask) { // stat used stored in miscValueB for this aura Stats usedStat = Stats((*i)->GetMiscValueB()); DoneAdvertisedBenefit += int32(CalculatePct(GetStat(usedStat), (*i)->GetAmount())); } } // ... and attack power DoneAdvertisedBenefit += int32(CalculatePct(GetTotalAttackPowerValue(BASE_ATTACK), GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_SPELL_DAMAGE_OF_ATTACK_POWER, schoolMask))); } return DoneAdvertisedBenefit; } int32 Unit::SpellBaseDamageBonusTaken(SpellSchoolMask schoolMask, bool isDoT) { return GetTotalAuraModifier(SPELL_AURA_MOD_DAMAGE_TAKEN, [schoolMask, isDoT](AuraEffect const* aurEff) { return (aurEff->GetMiscValue() & schoolMask) != 0 // Xinef: if we have DoT damage type and aura has charges, check if it affects DoTs // Xinef: required for hemorrhage & rupture / garrote && !(isDoT && aurEff->GetBase()->IsUsingCharges() && aurEff->GetSpellInfo()->ProcFlags & PROC_FLAG_TAKEN_PERIODIC); }); } float Unit::SpellDoneCritChance(Unit const* /*victim*/, SpellInfo const* spellProto, SpellSchoolMask schoolMask, WeaponAttackType attackType, bool skipEffectCheck) const { // Mobs can't crit with spells. if (IsCreature() && !GetSpellModOwner()) return -100.0f; // not critting spell if (spellProto->HasAttribute(SPELL_ATTR2_CANT_CRIT)) return 0.0f; // Xinef: check if spell is capable of critting, auras requires special aura to crit so they can be skipped if (!skipEffectCheck && !spellProto->IsCritCapable()) return 0.0f; float crit_chance = 0.0f; switch (spellProto->DmgClass) { case SPELL_DAMAGE_CLASS_MAGIC: { if (schoolMask & SPELL_SCHOOL_MASK_NORMAL) crit_chance = 0.0f; // For other schools else if (IsPlayer()) crit_chance = GetFloatValue(static_cast(PLAYER_SPELL_CRIT_PERCENTAGE1) + GetFirstSchoolInMask(schoolMask)); else { crit_chance = (float)m_baseSpellCritChance; crit_chance += GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_SPELL_CRIT_CHANCE_SCHOOL, schoolMask); } break; } case SPELL_DAMAGE_CLASS_MELEE: case SPELL_DAMAGE_CLASS_RANGED: { if (IsPlayer()) { switch (attackType) { case BASE_ATTACK: crit_chance = GetFloatValue(PLAYER_CRIT_PERCENTAGE); break; case OFF_ATTACK: crit_chance = GetFloatValue(PLAYER_OFFHAND_CRIT_PERCENTAGE); break; case RANGED_ATTACK: crit_chance = GetFloatValue(PLAYER_RANGED_CRIT_PERCENTAGE); break; default: break; } } else { crit_chance = 5.0f; crit_chance += GetTotalAuraModifier(SPELL_AURA_MOD_WEAPON_CRIT_PERCENT); crit_chance += GetTotalAuraModifier(SPELL_AURA_MOD_CRIT_PCT); } crit_chance += GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_SPELL_CRIT_CHANCE_SCHOOL, schoolMask); break; } // values overridden in spellmgr for lifebloom and earth shield case SPELL_DAMAGE_CLASS_NONE: default: return 0.0f; } // percent done // only players use intelligence for critical chance computations if (Player* modOwner = GetSpellModOwner()) modOwner->ApplySpellMod(spellProto->Id, SPELLMOD_CRITICAL_CHANCE, crit_chance); // xinef: can be negative! return crit_chance; } float Unit::SpellTakenCritChance(Unit const* caster, SpellInfo const* spellProto, SpellSchoolMask schoolMask, float doneChance, WeaponAttackType attackType, bool skipEffectCheck) const { // not critting spell if (spellProto->HasAttribute(SPELL_ATTR2_CANT_CRIT)) return 0.0f; // Xinef: check if spell is capable of critting, auras requires special aura to crit so they can be skipped if (!skipEffectCheck && !spellProto->IsCritCapable()) return 0.0f; float crit_chance = doneChance; switch (spellProto->DmgClass) { case SPELL_DAMAGE_CLASS_MAGIC: { if (!spellProto->IsPositive()) { // Modify critical chance by victim SPELL_AURA_MOD_ATTACKER_SPELL_CRIT_CHANCE // xinef: apply max and min only if (HasAttackerSpellCritChanceAura()) { crit_chance += GetMaxNegativeAuraModifierByMiscMask(SPELL_AURA_MOD_ATTACKER_SPELL_CRIT_CHANCE, schoolMask); crit_chance += GetMaxPositiveAuraModifierByMiscMask(SPELL_AURA_MOD_ATTACKER_SPELL_CRIT_CHANCE, schoolMask); } Unit::ApplyResilience(this, &crit_chance, nullptr, false, CR_CRIT_TAKEN_SPELL); } // scripted (increase crit chance ... against ... target by x% if (caster) { AuraEffectList const& mOverrideClassScript = caster->GetAuraEffectsByType(SPELL_AURA_OVERRIDE_CLASS_SCRIPTS); for (AuraEffectList::const_iterator i = mOverrideClassScript.begin(); i != mOverrideClassScript.end(); ++i) { if (!((*i)->IsAffectedOnSpell(spellProto))) continue; int32 modChance = 0; switch ((*i)->GetMiscValue()) { // Shatter case 911: modChance += 16; [[fallthrough]]; case 910: modChance += 17; [[fallthrough]]; case 849: modChance += 17; if (!HasAuraState(AURA_STATE_FROZEN, spellProto, caster)) break; crit_chance += modChance; break; case 7917: // Glyph of Shadowburn if (HasAuraState(AURA_STATE_HEALTHLESS_35_PERCENT, spellProto, caster)) crit_chance += (*i)->GetAmount(); break; case 7997: // Renewed Hope case 7998: if (HasAura(6788)) crit_chance += (*i)->GetAmount(); break; default: break; } } // Custom crit by class switch (spellProto->SpellFamilyName) { case SPELLFAMILY_MAGE: // Glyph of Fire Blast if (spellProto->SpellFamilyFlags[0] == 0x2 && spellProto->SpellIconID == 12) if (HasAuraWithMechanic((1 << MECHANIC_STUN) | (1 << MECHANIC_KNOCKOUT))) if (AuraEffect const* aurEff = caster->GetAuraEffect(56369, EFFECT_0)) crit_chance += aurEff->GetAmount(); break; case SPELLFAMILY_DRUID: // Improved Faerie Fire if (HasAuraState(AURA_STATE_FAERIE_FIRE)) if (AuraEffect const* aurEff = caster->GetDummyAuraEffect(SPELLFAMILY_DRUID, 109, 0)) crit_chance += aurEff->GetAmount(); // cumulative effect - don't break // Starfire if (spellProto->SpellFamilyFlags[0] & 0x4 && spellProto->SpellIconID == 1485) { // Improved Insect Swarm if (AuraEffect const* aurEff = caster->GetDummyAuraEffect(SPELLFAMILY_DRUID, 1771, 0)) if (GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_DRUID, 0x00000002, 0, 0)) crit_chance += aurEff->GetAmount(); break; } break; case SPELLFAMILY_ROGUE: // Shiv-applied poisons can't crit if (caster->FindCurrentSpellBySpellId(5938)) crit_chance = 0.0f; break; case SPELLFAMILY_PALADIN: // Flash of light if (spellProto->SpellFamilyFlags[0] & 0x40000000) { // Sacred Shield if (AuraEffect const* aura = GetAuraEffect(58597, 1, GetGUID())) crit_chance += aura->GetAmount(); break; } // Exorcism else if (spellProto->GetCategory() == 19) { if (GetCreatureTypeMask() & CREATURE_TYPEMASK_DEMON_OR_UNDEAD) return 100.0f; break; } break; case SPELLFAMILY_SHAMAN: // Lava Burst if (spellProto->SpellFamilyFlags[1] & 0x00001000) { if (GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_SHAMAN, 0x10000000, 0, 0, caster->GetGUID())) if (GetTotalAuraModifier(SPELL_AURA_MOD_ATTACKER_SPELL_AND_WEAPON_CRIT_CHANCE) > -100) return 100.0f; break; } break; } } break; } case SPELL_DAMAGE_CLASS_MELEE: // Custom crit by class if (caster) { switch (spellProto->SpellFamilyName) { case SPELLFAMILY_DRUID: // Rend and Tear - bonus crit chance for Ferocious Bite on bleeding targets if (spellProto->SpellFamilyFlags[0] & 0x00800000 && spellProto->SpellIconID == 1680 && HasAuraState(AURA_STATE_BLEEDING)) { if (AuraEffect const* rendAndTear = caster->GetDummyAuraEffect(SPELLFAMILY_DRUID, 2859, 1)) crit_chance += rendAndTear->GetAmount(); break; } break; case SPELLFAMILY_WARRIOR: // Victory Rush if (spellProto->SpellFamilyFlags[1] & 0x100) { // Glyph of Victory Rush if (AuraEffect const* aurEff = caster->GetAuraEffect(58382, 0)) crit_chance += aurEff->GetAmount(); break; } break; } } // 100% critical chance against sitting target if (IsPlayer() && (IsSitState() || getStandState() == UNIT_STAND_STATE_SLEEP)) { return 100.0f; } [[fallthrough]]; /// @todo: Not sure whether the fallthrough was a mistake (forgetting a break) or intended. This should be double-checked. case SPELL_DAMAGE_CLASS_RANGED: { // flat aura mods if (attackType == RANGED_ATTACK) crit_chance += GetTotalAuraModifier(SPELL_AURA_MOD_ATTACKER_RANGED_CRIT_CHANCE); else crit_chance += GetTotalAuraModifier(SPELL_AURA_MOD_ATTACKER_MELEE_CRIT_CHANCE); // reduce crit chance from Rating for players if (attackType != RANGED_ATTACK) { // xinef: little hack, crit chance dont require caster to calculate, pass victim Unit::ApplyResilience(this, &crit_chance, nullptr, false, CR_CRIT_TAKEN_MELEE); } else Unit::ApplyResilience(this, &crit_chance, nullptr, false, CR_CRIT_TAKEN_RANGED); // Apply crit chance from defence skill if (caster) crit_chance += (int32(caster->GetMaxSkillValueForLevel(this)) - int32(GetDefenseSkillValue(caster))) * 0.04f; break; } // values overridden in spellmgr for lifebloom and earth shield case SPELL_DAMAGE_CLASS_NONE: default: return 0.0f; } if (caster) { crit_chance += GetTotalAuraModifier(SPELL_AURA_MOD_CRIT_CHANCE_FOR_CASTER, [caster](AuraEffect const* aurEff) { return caster->GetGUID() == aurEff->GetCasterGUID(); }); } // Modify critical chance by victim SPELL_AURA_MOD_ATTACKER_SPELL_AND_WEAPON_CRIT_CHANCE // xinef: should be calculated at the end if (!spellProto->IsPositive()) crit_chance += GetTotalAuraModifier(SPELL_AURA_MOD_ATTACKER_SPELL_AND_WEAPON_CRIT_CHANCE); // xinef: can be negative! return crit_chance; } uint32 Unit::SpellCriticalDamageBonus(Unit const* caster, SpellInfo const* spellProto, uint32 damage, Unit const* victim) { // Calculate critical bonus int32 crit_bonus = damage; float crit_mod = 0.0f; switch (spellProto->DmgClass) { case SPELL_DAMAGE_CLASS_MELEE: // for melee based spells is 100% case SPELL_DAMAGE_CLASS_RANGED: /// @todo: write here full calculation for melee/ranged spells crit_bonus += damage; break; default: crit_bonus += damage / 2; // for spells is 50% break; } if (caster) { crit_mod += caster->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_CRIT_DAMAGE_BONUS, spellProto->GetSchoolMask()); if (victim) crit_mod += caster->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_CRIT_PERCENT_VERSUS, victim->GetCreatureTypeMask()); if (crit_bonus != 0 && crit_mod != 0.0f) AddPct(crit_bonus, crit_mod); crit_bonus -= damage; // adds additional damage to critBonus (from talents) if (Player* modOwner = caster->GetSpellModOwner()) modOwner->ApplySpellMod(spellProto->Id, SPELLMOD_CRIT_DAMAGE_BONUS, crit_bonus); crit_bonus += damage; } return crit_bonus; } uint32 Unit::SpellCriticalHealingBonus(Unit const* caster, SpellInfo const* spellProto, uint32 damage, Unit const* victim) { // Calculate critical bonus int32 crit_bonus; switch (spellProto->DmgClass) { case SPELL_DAMAGE_CLASS_MELEE: // for melee based spells is 100% case SPELL_DAMAGE_CLASS_RANGED: /// @todo: write here full calculation for melee/ranged spells crit_bonus = damage; break; default: crit_bonus = damage / 2; // for spells is 50% break; } if (caster) { if (victim) { uint32 creatureTypeMask = victim->GetCreatureTypeMask(); crit_bonus = int32(crit_bonus * caster->GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_CRIT_PERCENT_VERSUS, creatureTypeMask)); } // adds additional damage to critBonus (from talents) // xinef: used for death knight death coil if (Player* modOwner = caster->GetSpellModOwner()) modOwner->ApplySpellMod(spellProto->Id, SPELLMOD_CRIT_DAMAGE_BONUS, crit_bonus); } if (crit_bonus > 0) damage += crit_bonus; if (caster) damage = int32(float(damage) * caster->GetTotalAuraMultiplier(SPELL_AURA_MOD_CRITICAL_HEALING_AMOUNT)); return damage; } float Unit::SpellPctHealingModsDone(Unit* victim, SpellInfo const* spellProto, DamageEffectType damagetype) { // For totems get healing bonus from owner (statue isn't totem in fact) if (IsCreature() && IsTotem()) if (Unit* owner = GetOwner()) return owner->SpellPctHealingModsDone(victim, spellProto, damagetype); // Some spells don't benefit from done mods if (spellProto->HasAttribute(SPELL_ATTR3_IGNORE_CASTER_MODIFIERS)) return 1.0f; // xinef: Some spells don't benefit from done mods if (spellProto->HasAttribute(SPELL_ATTR6_IGNORE_HEALTH_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 DoneTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_HEALING_DONE_PERCENT); // done scripted mod (take it from owner) Unit* owner = GetOwner() ? GetOwner() : this; AuraEffectList const& mOverrideClassScript = owner->GetAuraEffectsByType(SPELL_AURA_OVERRIDE_CLASS_SCRIPTS); for (AuraEffectList::const_iterator i = mOverrideClassScript.begin(); i != mOverrideClassScript.end(); ++i) { if (!(*i)->IsAffectedOnSpell(spellProto)) continue; switch ((*i)->GetMiscValue()) { case 21: // Test of Faith case 6935: case 6918: if (victim->HealthBelowPct(50)) AddPct(DoneTotalMod, (*i)->GetAmount()); break; case 7798: // Glyph of Regrowth { if (victim->GetAuraEffect(SPELL_AURA_PERIODIC_HEAL, SPELLFAMILY_DRUID, 0x40, 0, 0)) AddPct(DoneTotalMod, (*i)->GetAmount()); break; } case 7871: // Glyph of Lesser Healing Wave { // xinef: affected by any earth shield if (victim->GetAuraEffect(SPELL_AURA_DUMMY, SPELLFAMILY_SHAMAN, 0, 0x00000400, 0)) AddPct(DoneTotalMod, (*i)->GetAmount()); break; } default: break; } } switch (spellProto->SpellFamilyName) { case SPELLFAMILY_GENERIC: // Talents and glyphs for healing stream totem if (spellProto->Id == 52042) { // Glyph of Healing Stream Totem if (AuraEffect* dummy = owner->GetAuraEffect(55456, EFFECT_0)) AddPct(DoneTotalMod, dummy->GetAmount()); // Healing Stream totem - Restorative Totems if (AuraEffect* aurEff = GetDummyAuraEffect(SPELLFAMILY_SHAMAN, 338, 1)) AddPct(DoneTotalMod, aurEff->GetAmount()); } break; case SPELLFAMILY_PRIEST: // T9 HEALING 4P, empowered renew instant heal if (spellProto->Id == 63544) if (AuraEffect* aurEff = GetAuraEffect(67202, EFFECT_0)) AddPct(DoneTotalMod, aurEff->GetAmount()); break; } return DoneTotalMod; } uint32 Unit::SpellHealingBonusDone(Unit* victim, SpellInfo const* spellProto, uint32 healamount, DamageEffectType damagetype, uint8 effIndex, float TotalMod, uint32 stack) { // For totems get healing bonus from owner (statue isn't totem in fact) if (IsCreature() && IsTotem()) if (Unit* owner = GetOwner()) return owner->SpellHealingBonusDone(victim, spellProto, healamount, damagetype, effIndex, TotalMod, stack); // No bonus healing for potion spells if (spellProto->SpellFamilyName == SPELLFAMILY_POTION) return healamount; float ApCoeffMod = 1.0f; float DoneTotalMod = TotalMod ? TotalMod : SpellPctHealingModsDone(victim, spellProto, damagetype); int32 DoneTotal = 0; // done scripted mod (take it from owner) Unit* owner = GetOwner() ? GetOwner() : this; int32 DoneAdvertisedBenefit = 0; AuraEffectList const& mOverrideClassScript = owner->GetAuraEffectsByType(SPELL_AURA_OVERRIDE_CLASS_SCRIPTS); for (AuraEffectList::const_iterator i = mOverrideClassScript.begin(); i != mOverrideClassScript.end(); ++i) { if (!(*i)->IsAffectedOnSpell(spellProto)) continue; switch ((*i)->GetMiscValue()) { case 4415: // Increased Rejuvenation Healing case 4953: DoneAdvertisedBenefit += (*i)->GetAmount(); break; 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 DoneAdvertisedBenefit += (*i)->GetAmount(); break; } } switch (spellProto->SpellFamilyName) { case SPELLFAMILY_DEATHKNIGHT: { // Impurity if (AuraEffect* aurEff = GetDummyAuraEffect(SPELLFAMILY_DEATHKNIGHT, 1986, 0)) { AddPct(ApCoeffMod, aurEff->GetAmount()); } break; } default: break; } // Done fixed damage bonus auras DoneAdvertisedBenefit += SpellBaseHealingBonusDone(spellProto->GetSchoolMask()); float coeff = spellProto->Effects[effIndex].BonusMultiplier; // Check for table values SpellBonusEntry const* bonus = sSpellMgr->GetSpellBonusData(spellProto->Id); if (bonus) { if (damagetype == DOT) { coeff = bonus->dot_damage; if (bonus->ap_dot_bonus > 0) DoneTotal += int32(bonus->ap_dot_bonus * ApCoeffMod * stack * GetTotalAttackPowerValue( (spellProto->IsRangedWeaponSpell() && spellProto->DmgClass != SPELL_DAMAGE_CLASS_MELEE) ? RANGED_ATTACK : BASE_ATTACK)); } else { coeff = bonus->direct_damage; if (bonus->ap_bonus > 0) DoneTotal += int32(bonus->ap_bonus * ApCoeffMod * stack * GetTotalAttackPowerValue( (spellProto->IsRangedWeaponSpell() && spellProto->DmgClass != SPELL_DAMAGE_CLASS_MELEE) ? RANGED_ATTACK : BASE_ATTACK)); } } else { // No bonus healing for SPELL_DAMAGE_CLASS_NONE class spells by default if (spellProto->DmgClass == SPELL_DAMAGE_CLASS_NONE) return healamount; } // Default calculation if (DoneAdvertisedBenefit) { float factorMod = CalculateLevelPenalty(spellProto) * stack; if (Player* modOwner = GetSpellModOwner()) { coeff *= 100.0f; modOwner->ApplySpellMod(spellProto->Id, SPELLMOD_BONUS_MULTIPLIER, coeff); coeff /= 100.0f; } DoneTotal += int32(DoneAdvertisedBenefit * coeff * factorMod); } for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) { switch (spellProto->Effects[i].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 (spellProto->Effects[i].Effect == SPELL_EFFECT_HEALTH_LEECH) DoneTotal = 0; } // use float as more appropriate for negative values and percent applying float heal = float(int32(healamount) + DoneTotal) * DoneTotalMod; // apply spellmod to Done amount if (Player* modOwner = GetSpellModOwner()) modOwner->ApplySpellMod(spellProto->Id, damagetype == DOT ? SPELLMOD_DOT : SPELLMOD_DAMAGE, heal); return uint32(std::max(heal, 0.0f)); } uint32 Unit::SpellHealingBonusTaken(Unit* caster, SpellInfo const* spellProto, uint32 healamount, DamageEffectType damagetype, uint32 stack) { float TakenTotalMod = 1.0f; float minval = 0.0f; // Healing taken percent if (!sScriptMgr->OnSpellHealingBonusTakenNegativeModifiers(this, caster, spellProto, minval)) { minval = float(GetMaxNegativeAuraModifier(SPELL_AURA_MOD_HEALING_PCT)); } if (minval) AddPct(TakenTotalMod, minval); float maxval = float(GetMaxPositiveAuraModifier(SPELL_AURA_MOD_HEALING_PCT)); if (maxval) AddPct(TakenTotalMod, maxval); // Tenacity increase healing % taken if (AuraEffect const* Tenacity = GetAuraEffect(58549, 0)) AddPct(TakenTotalMod, Tenacity->GetAmount()); // Healing Done int32 TakenTotal = 0; // Taken fixed damage bonus auras int32 TakenAdvertisedBenefit = GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_HEALING, spellProto->GetSchoolMask()); // Nourish cast, glyph of nourish if (spellProto->SpellFamilyName == SPELLFAMILY_DRUID && spellProto->SpellFamilyFlags[1] & 0x2000000 && caster) { bool any = false; bool hasglyph = caster->GetAuraEffectDummy(62971); AuraEffectList const& auras = GetAuraEffectsByType(SPELL_AURA_PERIODIC_HEAL); for (AuraEffectList::const_iterator i = auras.begin(); i != auras.end(); ++i) { if (((*i)->GetCasterGUID() == caster->GetGUID())) { SpellInfo const* spell = (*i)->GetSpellInfo(); // Rejuvenation, Regrowth, Lifebloom, or Wild Growth if (!any && spell->SpellFamilyFlags.HasFlag(0x50, 0x4000010, 0)) { TakenTotalMod *= 1.2f; any = true; } if (hasglyph) TakenTotalMod += 0.06f; } } } 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); float maxval_hot = float(GetMaxPositiveAuraModifier(SPELL_AURA_MOD_HOT_PCT)); if (maxval_hot) AddPct(TakenTotalMod, maxval_hot); } // Check for table values SpellBonusEntry const* bonus = sSpellMgr->GetSpellBonusData(spellProto->Id); float coeff = 0; float factorMod = 1.0f; if (bonus) coeff = (damagetype == DOT) ? bonus->dot_damage : bonus->direct_damage; else { // No bonus healing for SPELL_DAMAGE_CLASS_NONE class spells by default if (spellProto->DmgClass == SPELL_DAMAGE_CLASS_NONE) { healamount = uint32(std::max((float(healamount) * TakenTotalMod), 0.0f)); return healamount; } } // Default calculation if (TakenAdvertisedBenefit) { float TakenCoeff = 0.0f; if (coeff <= 0) coeff = CalculateDefaultCoefficient(spellProto, damagetype) * int32(stack) * 1.88f; // As wowwiki says: C = (Cast Time / 3.5) * 1.88 (for healing spells) factorMod *= CalculateLevelPenalty(spellProto) * int32(stack); if (Player* modOwner = GetSpellModOwner()) { coeff *= 100.0f; modOwner->ApplySpellMod(spellProto->Id, SPELLMOD_BONUS_MULTIPLIER, coeff); coeff /= 100.0f; } TakenTotal += int32(TakenAdvertisedBenefit * (coeff > 0 ? coeff : TakenCoeff) * factorMod); } if (caster) { TakenTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_HEALING_RECEIVED, [caster, spellProto](AuraEffect const* aurEff) { return caster->GetGUID() == aurEff->GetCasterGUID() && aurEff->IsAffectedOnSpell(spellProto); }); } for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) { switch (spellProto->Effects[i].ApplyAuraName) { // Bonus healing does not apply to these spells case SPELL_AURA_PERIODIC_LEECH: case SPELL_AURA_PERIODIC_HEALTH_FUNNEL: TakenTotal = 0; break; default: break; } if (spellProto->Effects[i].Effect == SPELL_EFFECT_HEALTH_LEECH) TakenTotal = 0; } // No positive taken bonus, custom attr if ((spellProto->HasAttribute(SPELL_ATTR6_IGNORE_HEALTH_MODIFIERS) || spellProto->HasAttribute(SPELL_ATTR0_CU_NO_POSITIVE_TAKEN_BONUS)) && TakenTotalMod > 1.0f) { TakenTotal = 0; TakenTotalMod = 1.0f; } float heal = float(int32(healamount) + TakenTotal) * TakenTotalMod; return uint32(std::max(heal, 0.0f)); } int32 Unit::SpellBaseHealingBonusDone(SpellSchoolMask schoolMask) { int32 AdvertisedBenefit = 0; AdvertisedBenefit += GetTotalAuraModifier(SPELL_AURA_MOD_HEALING_DONE, [schoolMask](AuraEffect const* aurEff) { return !aurEff->GetMiscValue() || (aurEff->GetMiscValue() & schoolMask) != 0; }); // Healing bonus of spirit, intellect and strength if (IsPlayer()) { // Base value AdvertisedBenefit += ToPlayer()->GetBaseSpellPowerBonus(); AdvertisedBenefit += ToPlayer()->GetBaseSpellHealingBonus(); // 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)->GetSpellInfo()->Effects[(*i)->GetEffIndex()].MiscValue); AdvertisedBenefit += int32(CalculatePct(GetStat(usedStat), (*i)->GetAmount())); } // ... and attack power AdvertisedBenefit += int32(CalculatePct(GetTotalAttackPowerValue(BASE_ATTACK), GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_SPELL_HEALING_OF_ATTACK_POWER, schoolMask))); } return AdvertisedBenefit; } bool Unit::IsImmunedToDamage(SpellSchoolMask meleeSchoolMask) const { if (meleeSchoolMask == SPELL_SCHOOL_MASK_NONE) { return false; } // If m_immuneToDamage type contain magic, IMMUNE damage. SpellImmuneList const& damageList = m_spellImmune[IMMUNITY_DAMAGE]; for (SpellImmuneList::const_iterator itr = damageList.begin(); itr != damageList.end(); ++itr) if ((itr->type & meleeSchoolMask) == meleeSchoolMask) return true; return false; } bool Unit::IsImmunedToDamage(SpellInfo const* spellInfo) const { if (!spellInfo) { return false; } if (spellInfo->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES) && !HasSpiritOfRedemptionAura()) { return false; } if (spellInfo->HasAttribute(SPELL_ATTR1_IMMUNITY_TO_HOSTILE_AND_FRIENDLY_EFFECTS) || spellInfo->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES)) { return false; } uint32 schoolMask = spellInfo->GetSchoolMask(); if (schoolMask == SPELL_SCHOOL_MASK_NONE) { return false; } // If m_immuneToDamage type contain magic, IMMUNE damage. SpellImmuneList const& damageList = m_spellImmune[IMMUNITY_DAMAGE]; for (SpellImmuneList::const_iterator itr = damageList.begin(); itr != damageList.end(); ++itr) if ((itr->type & schoolMask) == schoolMask) return true; return false; } bool Unit::IsImmunedToDamage(Spell const* spell) const { SpellInfo const* spellInfo = spell->GetSpellInfo(); if (!spellInfo) { return false; } if (spellInfo->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES) && !HasSpiritOfRedemptionAura()) { return false; } if (spellInfo->HasAttribute(SPELL_ATTR1_IMMUNITY_TO_HOSTILE_AND_FRIENDLY_EFFECTS) || spellInfo->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES)) { return false; } uint32 schoolMask = spell->GetSpellSchoolMask(); if (schoolMask == SPELL_SCHOOL_MASK_NONE) { return false; } // If m_immuneToDamage type contain magic, IMMUNE damage. SpellImmuneList const& damageList = m_spellImmune[IMMUNITY_DAMAGE]; for (SpellImmuneList::const_iterator itr = damageList.begin(); itr != damageList.end(); ++itr) { if ((itr->type & schoolMask) == schoolMask) { return true; } } return false; } bool Unit::IsImmunedToSchool(SpellSchoolMask meleeSchoolMask) const { if (meleeSchoolMask == SPELL_SCHOOL_MASK_NONE) { return false; } // If m_immuneToSchool type contain this school type, IMMUNE damage. SpellImmuneList const& schoolList = m_spellImmune[IMMUNITY_SCHOOL]; for (SpellImmuneList::const_iterator itr = schoolList.begin(); itr != schoolList.end(); ++itr) if ((itr->type & meleeSchoolMask) == meleeSchoolMask) return true; return false; } bool Unit::IsImmunedToSchool(SpellInfo const* spellInfo) const { if (spellInfo->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES) && !HasSpiritOfRedemptionAura()) return false; uint32 schoolMask = spellInfo->GetSchoolMask(); if (schoolMask == SPELL_SCHOOL_MASK_NONE) { return false; } if (spellInfo->Id != 42292 && spellInfo->Id != 59752 && spellInfo->Id != 19574 && spellInfo->Id != 34471) { // If m_immuneToSchool type contain this school type, IMMUNE damage. SpellImmuneList const& schoolList = m_spellImmune[IMMUNITY_SCHOOL]; for (SpellImmuneList::const_iterator itr = schoolList.begin(); itr != schoolList.end(); ++itr) if ((itr->type & schoolMask) == schoolMask && !spellInfo->CanPierceImmuneAura(sSpellMgr->GetSpellInfo(itr->spellId))) return true; } return false; } bool Unit::IsImmunedToSchool(Spell const* spell) const { SpellInfo const* spellInfo = spell->GetSpellInfo(); if (spellInfo->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES) && !HasSpiritOfRedemptionAura()) { return false; } uint32 schoolMask = spell->GetSpellSchoolMask(); if (schoolMask == SPELL_SCHOOL_MASK_NONE) { return false; } if (spellInfo->Id != 42292 && spellInfo->Id != 59752 && spellInfo->Id != 19574 && spellInfo->Id != 34471) { // If m_immuneToSchool type contain this school type, IMMUNE damage. SpellImmuneList const& schoolList = m_spellImmune[IMMUNITY_SCHOOL]; for (SpellImmuneList::const_iterator itr = schoolList.begin(); itr != schoolList.end(); ++itr) { if ((itr->type & schoolMask) == schoolMask && !spellInfo->CanPierceImmuneAura(sSpellMgr->GetSpellInfo(itr->spellId))) { return true; } } } return false; } bool Unit::IsImmunedToDamageOrSchool(SpellSchoolMask meleeSchoolMask) const { if (meleeSchoolMask == SPELL_SCHOOL_MASK_NONE) { return false; } return IsImmunedToDamage(meleeSchoolMask) || IsImmunedToSchool(meleeSchoolMask); } bool Unit::IsImmunedToDamageOrSchool(SpellInfo const* spellInfo) const { return IsImmunedToDamage(spellInfo) || IsImmunedToSchool(spellInfo); } bool Unit::IsImmunedToSpell(SpellInfo const* spellInfo, Spell const* spell) { if (!spellInfo) return false; // Single spell immunity. SpellImmuneList const& idList = m_spellImmune[IMMUNITY_ID]; for (SpellImmuneList::const_iterator itr = idList.begin(); itr != idList.end(); ++itr) if (itr->type == spellInfo->Id) return true; // xinef: my special immunity, if spellid is not on this list it means npc is immune SpellImmuneList const& allowIdList = m_spellImmune[IMMUNITY_ALLOW_ID]; if (!allowIdList.empty()) { for (SpellImmuneList::const_iterator itr = allowIdList.begin(); itr != allowIdList.end(); ++itr) if (itr->type == spellInfo->Id) return false; return true; } if (spellInfo->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES) && !HasSpiritOfRedemptionAura()) return false; if (spellInfo->Dispel) { SpellImmuneList const& dispelList = m_spellImmune[IMMUNITY_DISPEL]; for (SpellImmuneList::const_iterator itr = dispelList.begin(); itr != dispelList.end(); ++itr) if (itr->type == spellInfo->Dispel) return true; } // Spells that don't have effectMechanics. if (spellInfo->Mechanic) { SpellImmuneList const& mechanicList = m_spellImmune[IMMUNITY_MECHANIC]; for (SpellImmuneList::const_iterator itr = mechanicList.begin(); itr != mechanicList.end(); ++itr) if (itr->type == spellInfo->Mechanic) return true; } bool immuneToAllEffects = true; for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) { // State/effect immunities applied by aura expect full spell immunity // Ignore effects with mechanic, they are supposed to be checked separately if (!spellInfo->Effects[i].IsEffect()) continue; // Xinef: if target is immune to one effect, and the spell has transform aura - it is immune to whole spell if (IsImmunedToSpellEffect(spellInfo, i)) { if (spellInfo->HasAura(SPELL_AURA_TRANSFORM)) return true; continue; } immuneToAllEffects = false; break; } if (immuneToAllEffects) //Return immune only if the target is immune to all spell effects. return true; if (spellInfo->Id != 42292 && spellInfo->Id != 59752 && spellInfo->Id != 19574 && spellInfo->Id != 34471) { SpellSchoolMask spellSchoolMask = spellInfo->GetSchoolMask(); if (spell) { spellSchoolMask = spell->GetSpellSchoolMask(); } if (spellSchoolMask != SPELL_SCHOOL_MASK_NONE) { SpellImmuneList const& schoolList = m_spellImmune[IMMUNITY_SCHOOL]; for (SpellImmuneList::const_iterator itr = schoolList.begin(); itr != schoolList.end(); ++itr) { SpellInfo const* immuneSpellInfo = sSpellMgr->GetSpellInfo(itr->spellId); if (((itr->type & spellSchoolMask) == spellSchoolMask) && (!immuneSpellInfo || immuneSpellInfo->IsPositive()) && !spellInfo->IsPositive() && !spellInfo->CanPierceImmuneAura(immuneSpellInfo)) { return true; } } } } return false; } bool Unit::IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index) const { if (!spellInfo || !spellInfo->Effects[index].IsEffect()) return false; // xinef: pet scaling auras if (spellInfo->HasAttribute(SPELL_ATTR4_OWNER_POWER_SCALING)) return false; if (spellInfo->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES) && !HasSpiritOfRedemptionAura()) return false; //If m_immuneToEffect type contain this effect type, IMMUNE effect. uint32 effect = spellInfo->Effects[index].Effect; SpellImmuneList const& effectList = m_spellImmune[IMMUNITY_EFFECT]; for (SpellImmuneList::const_iterator itr = effectList.begin(); itr != effectList.end(); ++itr) { if (itr->type == effect && (itr->spellId != 62692 || (spellInfo->Effects[index].MiscValue == POWER_MANA && !CanRestoreMana(spellInfo)))) { return true; } } if (uint32 mechanic = spellInfo->Effects[index].Mechanic) { SpellImmuneList const& mechanicList = m_spellImmune[IMMUNITY_MECHANIC]; for (SpellImmuneList::const_iterator itr = mechanicList.begin(); itr != mechanicList.end(); ++itr) if (itr->type == mechanic) return true; } if (uint32 aura = spellInfo->Effects[index].ApplyAuraName) { SpellImmuneList const& list = m_spellImmune[IMMUNITY_STATE]; for (SpellImmuneList::const_iterator itr = list.begin(); itr != list.end(); ++itr) { if (itr->type == aura && (itr->spellId != 64848 || (spellInfo->Effects[index].MiscValue == POWER_MANA && !CanRestoreMana(spellInfo)))) { if (!spellInfo->HasAttribute(SPELL_ATTR3_ALWAYS_HIT)) { if (itr->blockType == SPELL_BLOCK_TYPE_ALL || spellInfo->IsPositive()) // xinef: added for pet scaling { return true; } } } } if (!spellInfo->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES)) { // Check for immune to application of harmful magical effects AuraEffectList const& immuneAuraApply = GetAuraEffectsByType(SPELL_AURA_MOD_IMMUNE_AURA_APPLY_SCHOOL); for (AuraEffectList::const_iterator iter = immuneAuraApply.begin(); iter != immuneAuraApply.end(); ++iter) { if (/*(spellInfo->Dispel == DISPEL_MAGIC || spellInfo->Dispel == DISPEL_CURSE || spellInfo->Dispel == DISPEL_DISEASE) &&*/ // Magic debuff, xinef: all kinds? ((*iter)->GetMiscValue() & spellInfo->GetSchoolMask()) && // Check school !spellInfo->IsPositiveEffect(index) && // Harmful spellInfo->Effects[index].Effect != SPELL_EFFECT_PERSISTENT_AREA_AURA) // Not Persistent area auras { return true; } } } } return false; } uint32 Unit::MeleeDamageBonusDone(Unit* victim, uint32 pdamage, WeaponAttackType attType, SpellInfo const* spellProto, SpellSchoolMask damageSchoolMask /*= SPELL_SCHOOL_MASK_NORMAL*/) { if (!victim || pdamage == 0) return 0; if (IsCreature()) { // Dancing Rune Weapon... if (GetEntry() == 27893) { if (Unit* owner = GetOwner()) return owner->MeleeDamageBonusDone(victim, pdamage, attType, spellProto, damageSchoolMask) / 2; } } uint32 creatureTypeMask = victim->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 += victim->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 += victim->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 normalized = false; if (spellProto) for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) if (spellProto->Effects[i].Effect == SPELL_EFFECT_NORMALIZED_WEAPON_DMG) { normalized = true; break; } DoneFlatBenefit += int32(APbonus / 14.0f * GetAPMultiplier(attType, normalized)); } // Done total percent damage auras float DoneTotalMod = 1.0f; // mods for SPELL_SCHOOL_MASK_NORMAL are already factored in base melee damage calculation if (!(damageSchoolMask & SPELL_SCHOOL_MASK_NORMAL)) { // Some spells don't benefit from pct done mods DoneTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_DAMAGE_PERCENT_DONE, [spellProto, this, damageSchoolMask](AuraEffect const* aurEff) { if (!spellProto || (spellProto->ValidateAttribute6SpellDamageMods(this, aurEff, false))) { if ((aurEff->GetMiscValue() & damageSchoolMask)) { if (aurEff->GetSpellInfo()->EquippedItemClass == -1) return true; else if (!aurEff->GetSpellInfo()->HasAttribute(SPELL_ATTR5_AURA_AFFECTS_NOT_JUST_REQ_EQUIPPED_ITEM) && (aurEff->GetSpellInfo()->EquippedItemSubClassMask == 0)) return true; else if (ToPlayer() && ToPlayer()->HasItemFitToSpellRequirements(aurEff->GetSpellInfo())) return true; } } return false; }); } DoneTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_DAMAGE_DONE_VERSUS, [creatureTypeMask, spellProto, this](AuraEffect const* aurEff) { return (creatureTypeMask & aurEff->GetMiscValue() && (!spellProto || spellProto->ValidateAttribute6SpellDamageMods(this, aurEff, false))); }); // bonus against aurastate DoneTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_DAMAGE_DONE_VERSUS_AURASTATE, [victim, spellProto, this](AuraEffect const* aurEff) { return (victim->HasAuraState(AuraStateType(aurEff->GetMiscValue())) && (!spellProto || spellProto->ValidateAttribute6SpellDamageMods(this, aurEff, false))); }); // done scripted mod (take it from owner) Unit* owner = GetOwner() ? GetOwner() : this; AuraEffectList const& mOverrideClassScript = owner->GetAuraEffectsByType(SPELL_AURA_OVERRIDE_CLASS_SCRIPTS); for (AuraEffectList::const_iterator i = mOverrideClassScript.begin(); i != mOverrideClassScript.end(); ++i) { if (spellProto && !spellProto->ValidateAttribute6SpellDamageMods(this, *i, false)) continue; if (!(*i)->IsAffectedOnSpell(spellProto)) continue; switch ((*i)->GetMiscValue()) { // Tundra Stalker // Merciless Combat case 7277: { // Merciless Combat if ((*i)->GetSpellInfo()->SpellIconID == 2656) { if (!victim->HealthAbovePct(35)) AddPct(DoneTotalMod, (*i)->GetAmount()); } // Tundra Stalker else { // Frost Fever (target debuff) if (victim->HasAura(55095)) AddPct(DoneTotalMod, (*i)->GetAmount()); } break; } // Rage of Rivendare case 7293: { if (victim->GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_DEATHKNIGHT, 0, 0x02000000, 0)) AddPct(DoneTotalMod, (*i)->GetSpellInfo()->GetRank() * 2.0f); break; } // Marked for Death case 7598: case 7599: case 7600: case 7601: case 7602: { if (victim->GetAuraEffect(SPELL_AURA_MOD_STALKED, SPELLFAMILY_HUNTER, 0x400, 0, 0)) AddPct(DoneTotalMod, (*i)->GetAmount()); break; } // Dirty Deeds case 6427: case 6428: { if (victim->HasAuraState(AURA_STATE_HEALTHLESS_35_PERCENT, spellProto, this)) { // effect 0 has expected value but in negative state int32 bonus = -(*i)->GetBase()->GetEffect(0)->GetAmount(); AddPct(DoneTotalMod, bonus); } break; } } } // Custom scripted damage if (spellProto) switch (spellProto->SpellFamilyName) { case SPELLFAMILY_DEATHKNIGHT: // Glacier Rot if (spellProto->SpellFamilyFlags[0] & 0x2 || spellProto->SpellFamilyFlags[1] & 0x6) if (AuraEffect* aurEff = GetDummyAuraEffect(SPELLFAMILY_DEATHKNIGHT, 196, 0)) if (victim->GetDiseasesByCaster(owner->GetGUID()) > 0) AddPct(DoneTotalMod, aurEff->GetAmount()); break; } // Some spells don't benefit from done mods if (spellProto) if (spellProto->HasAttribute(SPELL_ATTR3_IGNORE_CASTER_MODIFIERS)) { DoneFlatBenefit = 0; DoneTotalMod = 1.0f; } float tmpDamage = float(int32(pdamage) + DoneFlatBenefit) * DoneTotalMod; // apply spellmod to Done damage if (spellProto) if (Player* modOwner = GetSpellModOwner()) modOwner->ApplySpellMod(spellProto->Id, SPELLMOD_DAMAGE, tmpDamage); // bonus result can be negative return uint32(std::max(tmpDamage, 0.0f)); } uint32 Unit::MeleeDamageBonusTaken(Unit* attacker, uint32 pdamage, WeaponAttackType attType, 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, damageSchoolMask); if (attType != RANGED_ATTACK) TakenFlatBenefit += GetTotalAuraModifier(SPELL_AURA_MOD_MELEE_DAMAGE_TAKEN); else TakenFlatBenefit += GetTotalAuraModifier(SPELL_AURA_MOD_RANGED_DAMAGE_TAKEN); // Taken total percent damage auras float TakenTotalMod = 1.0f; TakenTotalMod *= GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN, damageSchoolMask); // .. taken pct (special attacks) if (spellProto) { // From caster spells TakenTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_DAMAGE_FROM_CASTER, [attacker, spellProto](AuraEffect const* aurEff) { return attacker->GetGUID() == aurEff->GetCasterGUID() && aurEff->IsAffectedOnSpell(spellProto); }); // Mod damage from spell mechanic uint32 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 & uint32(1 << (aurEff->GetMiscValue()))) return true; return false; }); } } TakenTotalMod = processDummyAuras(TakenTotalMod); // .. taken pct: class scripts /*AuraEffectList const& mclassScritAuras = GetAuraEffectsByType(SPELL_AURA_OVERRIDE_CLASS_SCRIPTS); for (AuraEffectList::const_iterator i = mclassScritAuras.begin(); i != mclassScritAuras.end(); ++i) { switch ((*i)->GetMiscValue()) { } }*/ if (attType != RANGED_ATTACK) { TakenTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_MELEE_DAMAGE_TAKEN_PCT); } else { TakenTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_RANGED_DAMAGE_TAKEN_PCT); } // No positive taken bonus, custom attr if (spellProto) if (spellProto->HasAttribute(SPELL_ATTR0_CU_NO_POSITIVE_TAKEN_BONUS) && TakenTotalMod > 1.0f) { TakenFlatBenefit = 0; TakenTotalMod = 1.0f; } // xinef: sanctified wrath talent if (TakenTotalMod < 1.0f && attacker->HasIgnoreTargetResistAura()) { float ignoreModifier = 1.0f - TakenTotalMod; bool addModifier = false; AuraEffectList const& ResIgnoreAuras = attacker->GetAuraEffectsByType(SPELL_AURA_MOD_IGNORE_TARGET_RESIST); for (AuraEffectList::const_iterator j = ResIgnoreAuras.begin(); j != ResIgnoreAuras.end(); ++j) if ((*j)->GetMiscValue() & damageSchoolMask) { ApplyPct(ignoreModifier, (*j)->GetAmount()); addModifier = true; } if (addModifier) TakenTotalMod += ignoreModifier; } float tmpDamage = (float(pdamage) + TakenFlatBenefit) * TakenTotalMod; // bonus result can be negative return uint32(std::max(tmpDamage, 0.0f)); } class spellIdImmunityPredicate { public: spellIdImmunityPredicate(uint32 type) : _type(type) {} bool operator()(SpellImmune const& spellImmune) { return spellImmune.spellId == 0 && spellImmune.type == _type; } private: uint32 _type; }; void Unit::ApplySpellImmune(uint32 spellId, uint32 op, uint32 type, bool apply, SpellImmuneBlockType blockType) { if (apply) { // xinef: immunities with spellId 0 are intended to be applied only once (script purposes mosty) if (spellId == 0 && std::find_if(m_spellImmune[op].begin(), m_spellImmune[op].end(), spellIdImmunityPredicate(type)) != m_spellImmune[op].end()) return; SpellImmune immune; immune.spellId = spellId; immune.type = type; immune.blockType = blockType; m_spellImmune[op].push_back(std::move(immune)); } else { for (SpellImmuneList::iterator itr = m_spellImmune[op].begin(); itr != m_spellImmune[op].end(); ++itr) { if (itr->spellId == spellId && itr->type == type) { m_spellImmune[op].erase(itr); break; } } } } void Unit::ApplySpellDispelImmunity(SpellInfo const* spellProto, DispelType type, bool apply) { ApplySpellImmune(spellProto->Id, IMMUNITY_DISPEL, type, apply); if (apply && spellProto->HasAttribute(SPELL_ATTR1_IMMUNITY_PURGES_EFFECT)) { // Create dispel mask by dispel type uint32 dispelMask = SpellInfo::GetDispelMask(type); // Dispel all existing auras vs current dispel type AuraApplicationMap& auras = GetAppliedAuras(); for (AuraApplicationMap::iterator itr = auras.begin(); itr != auras.end();) { SpellInfo const* spell = itr->second->GetBase()->GetSpellInfo(); if (spell->GetDispelMask() & dispelMask) { // Dispel aura RemoveAura(itr); } else ++itr; } } } float Unit::GetWeaponProcChance() const { // normalized proc chance for weapon attack speed // (odd formula...) if (isAttackReady(BASE_ATTACK)) return (GetAttackTime(BASE_ATTACK) * 1.8f / 1000.0f); else if (HasOffhandWeaponForAttack() && isAttackReady(OFF_ATTACK)) return (GetAttackTime(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->Id, SPELLMOD_PROC_PER_MINUTE, PPM); return (WeaponSpeed * PPM) / 600.0f; // result is chance in percents (probability = Speed_in_sec * (PPM / 60)) } void Unit::Mount(uint32 mount, uint32 VehicleId, uint32 creatureEntry) { if (mount) SetUInt32Value(UNIT_FIELD_MOUNTDISPLAYID, mount); SetUnitFlag(UNIT_FLAG_MOUNT); if (Player* player = ToPlayer()) { sScriptMgr->AnticheatSetUnderACKmount(player); // mount as a vehicle if (VehicleId) { if (CreateVehicleKit(VehicleId, creatureEntry)) { GetVehicleKit()->Reset(); // Send others that we now have a vehicle WorldPacket data(SMSG_PLAYER_VEHICLE_DATA, GetPackGUID().size() + 4); data << GetPackGUID(); data << uint32(VehicleId); SendMessageToSet(&data, true); data.Initialize(SMSG_ON_CANCEL_EXPECTED_RIDE_VEHICLE_AURA, 0); player->SendDirectMessage(&data); // mounts can also have accessories GetVehicleKit()->InstallAllAccessories(false); } } // unsummon pet Pet* pet = player->GetPet(); if (pet) { Battleground* bg = ToPlayer()->GetBattleground(); // don't unsummon pet in arena but SetFlag UNIT_FLAG_STUNNED to disable pet's interface if (bg && bg->isArena()) pet->SetUnitFlag(UNIT_FLAG_STUNNED); else player->UnsummonPetTemporaryIfAny(); } // xinef: if we have charmed npc, stun him also if (Unit* charm = player->GetCharm()) if (charm->IsCreature()) charm->SetUnitFlag(UNIT_FLAG_STUNNED); WorldPacket data(SMSG_MOVE_SET_COLLISION_HGT, GetPackGUID().size() + 4 + 4); data << GetPackGUID(); data << player->GetSession()->GetOrderCounter(); // movement counter data << player->GetCollisionHeight(); player->SendDirectMessage(&data); player->GetSession()->IncrementOrderCounter(); } RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_MOUNT); } void Unit::Dismount() { if (!IsMounted()) return; SetUInt32Value(UNIT_FIELD_MOUNTDISPLAYID, 0); RemoveUnitFlag(UNIT_FLAG_MOUNT); if (Player* player = ToPlayer()) { WorldPacket data(SMSG_MOVE_SET_COLLISION_HGT, GetPackGUID().size() + 4 + 4); data << GetPackGUID(); data << player->GetSession()->GetOrderCounter(); // movement counter data << player->GetCollisionHeight(); player->SendDirectMessage(&data); player->GetSession()->IncrementOrderCounter(); } WorldPacket data(SMSG_DISMOUNT, 8); data << GetPackGUID(); SendMessageToSet(&data, true); // dismount as a vehicle if (IsPlayer() && GetVehicleKit()) { // Send other players that we are no longer a vehicle data.Initialize(SMSG_PLAYER_VEHICLE_DATA, 8 + 4); data << GetPackGUID(); data << uint32(0); ToPlayer()->SendMessageToSet(&data, true); // Remove vehicle from player RemoveVehicleKit(); } RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_NOT_MOUNTED); // 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()) { sScriptMgr->AnticheatSetUnderACKmount(player); if (Pet* pPet = player->GetPet()) { if (pPet->HasUnitFlag(UNIT_FLAG_STUNNED) && !pPet->HasUnitState(UNIT_STATE_STUNNED)) pPet->RemoveUnitFlag(UNIT_FLAG_STUNNED); } else player->ResummonPetTemporaryUnSummonedIfAny(); // xinef: if we have charmed npc, remove stun also if (Unit* charm = player->GetCharm()) if (charm->IsCreature() && !charm->HasUnitState(UNIT_STATE_STUNNED)) charm->RemoveUnitFlag(UNIT_FLAG_STUNNED); } } void Unit::SetInCombatWith(Unit* enemy, uint32 duration) { // Xinef: Dont allow to start combat with triggers if (enemy->IsCreature() && enemy->ToCreature()->IsTrigger()) return; Unit* eOwner = enemy->GetCharmerOrOwnerOrSelf(); if (eOwner->IsPvP() || eOwner->IsFFAPvP()) { SetInCombatState(true, enemy, duration); return; } // check for duel if (eOwner->IsPlayer() && eOwner->ToPlayer()->duel) { Unit const* myOwner = GetCharmerOrOwnerOrSelf(); if (((Player const*)eOwner)->duel->Opponent == myOwner) { SetInCombatState(true, enemy, duration); return; } } SetInCombatState(false, enemy, duration); } void Unit::SetImmuneToPC(bool apply, bool keepCombat) { (void)keepCombat; if (apply) SetUnitFlag(UNIT_FLAG_IMMUNE_TO_PC); else RemoveUnitFlag(UNIT_FLAG_IMMUNE_TO_PC); } void Unit::SetImmuneToNPC(bool apply, bool keepCombat) { (void)keepCombat; if (apply) SetUnitFlag(UNIT_FLAG_IMMUNE_TO_NPC); else RemoveUnitFlag(UNIT_FLAG_IMMUNE_TO_NPC); } void Unit::CombatStart(Unit* victim, bool initialAggro) { // Xinef: Dont allow to start combat with triggers if (victim->IsCreature() && victim->ToCreature()->IsTrigger()) return; if (initialAggro) { // Make player victim stand up automatically if (victim->getStandState() && victim->IsPlayer()) { victim->SetStandState(UNIT_STAND_STATE_STAND); } if (!victim->IsInCombat() && !victim->IsPlayer() && !victim->ToCreature()->HasReactState(REACT_PASSIVE) && victim->ToCreature()->IsAIEnabled) { if (victim->IsPet()) victim->ToCreature()->AI()->AttackedBy(this); // PetAI has special handler before AttackStart() else { victim->ToCreature()->AI()->AttackStart(this); // if the target is an NPC with a pet or minion, pet should react. if (Unit* victimControlledUnit = victim->GetFirstControlled()) { victimControlledUnit->SetInCombatWith(this); SetInCombatWith(victimControlledUnit); victimControlledUnit->AddThreat(this, 0.0f); } } // if unit has an owner, put owner in combat. if (Unit* victimOwner = victim->GetOwner()) { if (!(victimOwner->IsInCombatWith(this))) { /* warding off to not take over aggro for no reason Using only AddThreat causes delay in attack */ if (!victimOwner->IsInCombat() && victimOwner->IsAIEnabled) { victimOwner->ToCreature()->AI()->AttackStart(this); } victimOwner->SetInCombatWith(this); SetInCombatWith(victimOwner); victimOwner->AddThreat(this, 0.0f); } } } bool alreadyInCombat = IsInCombat(); SetInCombatWith(victim); victim->SetInCombatWith(this); // Update leash timer when attacking creatures if (victim->IsCreature() && this != victim) victim->ToCreature()->UpdateLeashExtensionTime(); // Xinef: If pet started combat - put owner in combat if (!alreadyInCombat && IsInCombat()) { if (Unit* owner = GetOwner()) { owner->SetInCombatWith(victim); victim->SetInCombatWith(owner); } } } Unit* who = victim->GetCharmerOrOwnerOrSelf(); if (who->IsPlayer()) SetContestedPvP(who->ToPlayer()); Player* player = GetCharmerOrOwnerPlayerOrPlayerItself(); if (player && who->IsPvP() && (!who->IsPlayer() || !player->duel || player->duel->Opponent != who)) { player->UpdatePvP(true); player->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_ENTER_PVP_COMBAT); } } void Unit::CombatStartOnCast(Unit* target, bool initialAggro, uint32 duration) { // Xinef: Dont allow to start combat with triggers if (target->IsCreature() && target->ToCreature()->IsTrigger()) return; if (initialAggro) { SetInCombatWith(target, duration); // Xinef: If pet started combat - put owner in combat if (Unit* owner = GetOwner()) owner->SetInCombatWith(target, duration); // Update leash timer when attacking creatures if (target->IsCreature()) target->ToCreature()->UpdateLeashExtensionTime(); else if (ToCreature()) // Reset leash if it is a spell caster, else it may evade inbetween casts ToCreature()->UpdateLeashExtensionTime(); } Unit* who = target->GetCharmerOrOwnerOrSelf(); if (who->IsPlayer()) SetContestedPvP(who->ToPlayer()); Player* player = GetCharmerOrOwnerPlayerOrPlayerItself(); if (player && who->IsPvP() && (!who->IsPlayer() || !player->duel || player->duel->Opponent != who)) { player->UpdatePvP(true); player->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_ENTER_PVP_COMBAT); } } void Unit::SetInCombatState(bool PvP, Unit* enemy, uint32 duration) { // only alive units can be in combat if (!IsAlive()) return; if (PvP) m_CombatTimer = std::max(GetCombatTimer(), std::max(5500, duration)); else if (duration) m_CombatTimer = std::max(GetCombatTimer(), duration); if (HasUnitState(UNIT_STATE_EVADE) || GetCreatureType() == CREATURE_TYPE_NON_COMBAT_PET) return; // xinef: if we somehow engage in combat (scripts, dunno) with player, remove this flag so he can fight back if (IsCreature() && enemy && IsImmuneToPC() && enemy->GetCharmerOrOwnerPlayerOrPlayerItself()) SetImmuneToPC(false); // unit has engaged in combat, remove immunity so players can fight back if (IsInCombat()) return; SetUnitFlag(UNIT_FLAG_IN_COMBAT); if (Creature* creature = ToCreature()) { // Set home position at place of engaging combat for escorted creatures if ((IsAIEnabled && creature->AI()->IsEscorted()) || GetMotionMaster()->GetCurrentMovementGeneratorType() == WAYPOINT_MOTION_TYPE || GetMotionMaster()->GetCurrentMovementGeneratorType() == ESCORT_MOTION_TYPE) creature->SetHomePosition(GetPositionX(), GetPositionY(), GetPositionZ(), GetOrientation()); if (enemy) { creature->UpdateLeashExtensionTime(); if (IsAIEnabled) creature->AI()->JustEngagedWith(enemy); if (creature->GetFormation()) creature->GetFormation()->MemberEngagingTarget(creature, enemy); sScriptMgr->OnUnitEnterCombat(creature, enemy); } creature->RefreshSwimmingFlag(); if (IsPet()) { UpdateSpeed(MOVE_RUN, true); UpdateSpeed(MOVE_SWIM, true); UpdateSpeed(MOVE_FLIGHT, true); } if (!(creature->GetCreatureTemplate()->type_flags & CREATURE_TYPE_FLAG_ALLOW_MOUNTED_COMBAT)) Dismount(); if (!IsStandState()) // pussywizard: already done in CombatStart(target, initialAggro) for the target, but when aggro'ing from MoveInLOS CombatStart is not called! SetStandState(UNIT_STAND_STATE_STAND); } for (Unit::ControlSet::iterator itr = m_Controlled.begin(); itr != m_Controlled.end();) { Unit* controlled = *itr; ++itr; // Xinef: Dont set combat for passive units, they will evade in next update... if (controlled->IsCreature() && controlled->ToCreature()->HasReactState(REACT_PASSIVE)) continue; controlled->SetInCombatState(PvP, enemy, duration); } if (Player* player = this->ToPlayer()) { sScriptMgr->OnPlayerEnterCombat(player, enemy); } } void Unit::ClearInCombat() { m_CombatTimer = 0; RemoveUnitFlag(UNIT_FLAG_IN_COMBAT); // Player's state will be cleared in Player::UpdateContestedPvP if (Creature* creature = ToCreature()) { if (creature->GetCreatureTemplate() && creature->GetCreatureTemplate()->unit_flags & UNIT_FLAG_IMMUNE_TO_PC) SetImmuneToPC(true); // set immunity state to the one from db on evade ClearUnitState(UNIT_STATE_ATTACK_PLAYER); if (HasDynamicFlag(UNIT_DYNFLAG_TAPPED)) ReplaceAllDynamicFlags(creature->GetCreatureTemplate()->dynamicflags); creature->SetAssistanceTimer(0); // Xinef: will be recalculated at follow movement generator initialization if (!IsPet() && !IsCharmed()) return; } else if (Player* player = ToPlayer()) { player->UpdatePotionCooldown(); if (player->IsClass(CLASS_DEATH_KNIGHT, CLASS_CONTEXT_ABILITY)) for (uint8 i = 0; i < MAX_RUNES; ++i) player->SetGracePeriod(i, 0); } if (Player* player = this->ToPlayer()) { sScriptMgr->OnPlayerLeaveCombat(player); } } void Unit::ClearInPetCombat() { RemoveUnitFlag(UNIT_FLAG_PET_IN_COMBAT); RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_LEAVE_COMBAT); if (Unit* owner = GetOwner()) { owner->RemoveUnitFlag(UNIT_FLAG_PET_IN_COMBAT); } } bool Unit::isTargetableForAttack(bool checkFakeDeath, Unit const* byWho) const { if (!IsAlive()) return false; if (HasUnitFlag(UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NOT_SELECTABLE)) return false; if (IsImmuneToPC() && byWho && byWho->GetCharmerOrOwnerPlayerOrPlayerItself()) return false; if (IsPlayer() && ToPlayer()->IsGameMaster()) return false; return !HasUnitState(UNIT_STATE_UNATTACKABLE) && (!checkFakeDeath || !HasUnitState(UNIT_STATE_DIED)); } bool Unit::IsValidAttackTarget(Unit const* target, SpellInfo const* bySpell) const { return _IsValidAttackTarget(target, bySpell); } // function based on function Unit::CanAttack from 13850 client bool Unit::_IsValidAttackTarget(Unit const* target, SpellInfo const* bySpell, WorldObject const* obj) const { ASSERT(target); // can't attack self if (this == target) return false; // can't attack unattackable units or GMs if (target->HasUnitState(UNIT_STATE_UNATTACKABLE) || (target->IsPlayer() && target->ToPlayer()->IsGameMaster())) return false; // can't attack own vehicle or passenger if (m_vehicle) if (IsOnVehicle(target) || m_vehicle->GetBase()->IsOnVehicle(target)) if (!IsHostileTo(target)) // pussywizard: actually can attack own vehicle or passenger if it's hostile to us - needed for snobold in Gormok encounter return false; // can't attack invisible (ignore stealth for aoe spells) also if the area being looked at is from a spell use the dynamic object created instead of the casting unit. //Ignore stealth if target is player and unit in combat with same player if ((!bySpell || !bySpell->HasAttribute(SPELL_ATTR6_IGNORE_PHASE_SHIFT)) && (obj ? !obj->CanSeeOrDetect(target, bySpell && bySpell->IsAffectingArea()) : !CanSeeOrDetect(target, (bySpell && bySpell->IsAffectingArea()) || (target->IsPlayer() && target->HasStealthAura() && target->IsInCombat() && IsInCombatWith(target))))) return false; // can't attack dead if ((!bySpell || !bySpell->IsAllowingDeadTarget()) && !target->IsAlive()) return false; // can't attack untargetable if ((!bySpell || !bySpell->HasAttribute(SPELL_ATTR6_CAN_TARGET_UNTARGETABLE)) && target->HasUnitFlag(UNIT_FLAG_NOT_SELECTABLE)) return false; if (Player const* playerAttacker = ToPlayer()) { if (playerAttacker->HasPlayerFlag(PLAYER_FLAGS_UBER) || playerAttacker->IsSpectator()) return false; } // check flags if (target->HasUnitFlag(UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_TAXI_FLIGHT | UNIT_FLAG_NOT_ATTACKABLE_1 | UNIT_FLAG_NON_ATTACKABLE_2) || (!HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED) && target->IsImmuneToNPC()) || (!target->HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED) && IsImmuneToNPC()) || (HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED) && target->IsImmuneToPC()) // check if this is a world trigger cast - GOs are using world triggers to cast their spells, so we need to ignore their immunity flag here, this is a temp workaround, needs removal when go cast is implemented properly || ((GetEntry() != WORLD_TRIGGER && (!obj || !obj->isType(TYPEMASK_GAMEOBJECT | TYPEMASK_DYNAMICOBJECT))) && target->HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED) && IsImmuneToPC())) return false; // CvC case - can attack each other only when one of them is hostile if (!HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED) && !target->HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED)) return GetReactionTo(target) <= REP_HOSTILE || target->GetReactionTo(this) <= REP_HOSTILE; // PvP, PvC, CvP case // can't attack friendly targets ReputationRank repThisToTarget = GetReactionTo(target); ReputationRank repTargetToThis; if (repThisToTarget > REP_NEUTRAL || (repTargetToThis = target->GetReactionTo(this)) > REP_NEUTRAL) { // contested guards can attack contested PvP players even though players may be friendly if (!target->IsControlledByPlayer()) return false; bool isContestedGuard = false; if (FactionTemplateEntry const* entry = GetFactionTemplateEntry()) isContestedGuard = entry->factionFlags & FACTION_TEMPLATE_FLAG_ATTACK_PVP_ACTIVE_PLAYERS; bool isContestedPvp = false; if (Player const* player = target->GetCharmerOrOwnerPlayerOrPlayerItself()) isContestedPvp = player->HasPlayerFlag(PLAYER_FLAGS_CONTESTED_PVP); if (!isContestedGuard && !isContestedPvp) return false; } // Not all neutral creatures can be attacked (even some unfriendly faction does not react aggresive to you, like Sporaggar) if (repThisToTarget == REP_NEUTRAL && repTargetToThis <= REP_NEUTRAL) { Player* owner = GetAffectingPlayer(); Unit const* const thisUnit = owner ? owner : this; if (!(target->IsPlayer() && thisUnit->IsPlayer()) && !(target->IsCreature() && thisUnit->IsCreature())) { Player const* player = target->IsPlayer() ? target->ToPlayer() : thisUnit->ToPlayer(); Unit const* creature = target->IsCreature() ? target : thisUnit; if (FactionTemplateEntry const* factionTemplate = creature->GetFactionTemplateEntry()) { if (!(player->GetReputationMgr().GetForcedRankIfAny(factionTemplate))) if (FactionEntry const* factionEntry = sFactionStore.LookupEntry(factionTemplate->faction)) if (FactionState const* repState = player->GetReputationMgr().GetState(factionEntry)) if (!(repState->Flags & FACTION_FLAG_AT_WAR)) return false; } } } Creature const* creatureAttacker = ToCreature(); if (creatureAttacker && creatureAttacker->GetCreatureTemplate()->type_flags & CREATURE_TYPE_FLAG_TREAT_AS_RAID_UNIT) return false; Player const* playerAffectingAttacker = HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED) ? GetAffectingPlayer() : nullptr; Player const* playerAffectingTarget = target->HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED) ? target->GetAffectingPlayer() : nullptr; // check duel - before sanctuary checks if (playerAffectingAttacker && playerAffectingTarget) if (playerAffectingAttacker->duel && playerAffectingAttacker->duel->Opponent == playerAffectingTarget && playerAffectingAttacker->duel->State == DUEL_STATE_IN_PROGRESS) return true; // PvP case - can't attack when attacker or target are in sanctuary // however, 13850 client doesn't allow to attack when one of the unit's has sanctuary flag and is pvp if (target->HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED) && HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED) && (target->IsInSanctuary() || IsInSanctuary())) return false; // additional checks - only PvP case if (playerAffectingAttacker && playerAffectingTarget) { if (!IsPvP() && bySpell && bySpell->IsAffectingArea() && !bySpell->HasAttribute(SPELL_ATTR5_IGNORE_AREA_EFFECT_PVP_CHECK)) return false; if (target->IsPvP()) return true; if (IsFFAPvP() && target->IsFFAPvP()) return true; return HasByteFlag(UNIT_FIELD_BYTES_2, 1, UNIT_BYTE2_FLAG_UNK1) || target->HasByteFlag(UNIT_FIELD_BYTES_2, 1, UNIT_BYTE2_FLAG_UNK1); } return true; } bool Unit::IsValidAssistTarget(Unit const* target) const { return _IsValidAssistTarget(target, nullptr); } // function based on function Unit::CanAssist from 13850 client bool Unit::_IsValidAssistTarget(Unit const* target, SpellInfo const* bySpell) const { ASSERT(target); // can assist to self if (this == target) return true; // can't assist unattackable units or GMs if (target->HasUnitState(UNIT_STATE_UNATTACKABLE) || (target->IsPlayer() && target->ToPlayer()->IsGameMaster())) return false; // can't assist own vehicle or passenger if (m_vehicle) if (IsOnVehicle(target) || m_vehicle->GetBase()->IsOnVehicle(target)) return false; // can't assist invisible if ((!bySpell || !bySpell->HasAttribute(SPELL_ATTR6_IGNORE_PHASE_SHIFT)) && !CanSeeOrDetect(target, bySpell && bySpell->IsAffectingArea())) return false; // can't assist dead if ((!bySpell || !bySpell->IsAllowingDeadTarget()) && !target->IsAlive()) return false; // can't assist untargetable if ((!bySpell || !bySpell->HasAttribute(SPELL_ATTR6_CAN_TARGET_UNTARGETABLE)) && target->HasUnitFlag(UNIT_FLAG_NOT_SELECTABLE)) return false; if (!bySpell || !bySpell->HasAttribute(SPELL_ATTR6_CAN_ASSIST_IMMUNE_PC)) { // xinef: do not allow to assist non attackable units if (target->HasUnitFlag(UNIT_FLAG_NON_ATTACKABLE)) return false; if (HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED)) { if (target->IsImmuneToPC()) return false; } else { if (target->IsImmuneToNPC()) return false; } } // can't assist non-friendly targets if (GetReactionTo(target) < REP_NEUTRAL && target->GetReactionTo(this) < REP_NEUTRAL && (!ToCreature() || !(ToCreature()->GetCreatureTemplate()->type_flags & CREATURE_TYPE_FLAG_TREAT_AS_RAID_UNIT))) return false; // PvP case if (target->HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED)) { Player const* targetPlayerOwner = target->GetAffectingPlayer(); if (HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED)) { Player const* selfPlayerOwner = GetAffectingPlayer(); if (selfPlayerOwner && targetPlayerOwner) { // can't assist player which is dueling someone if (selfPlayerOwner != targetPlayerOwner && targetPlayerOwner->duel) return false; } // can't assist player in ffa_pvp zone from outside if (target->IsFFAPvP() && !IsFFAPvP()) return false; // can't assist player out of sanctuary from sanctuary if has pvp enabled if (target->IsPvP()) if (IsInSanctuary() && !target->IsInSanctuary()) return false; } } // PvC case - player can assist creature only if has specific type flags // !target->HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED) && else if (HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED) && (!bySpell || !bySpell->HasAttribute(SPELL_ATTR6_CAN_ASSIST_IMMUNE_PC)) && !target->IsPvP()) { if (Creature const* creatureTarget = target->ToCreature()) return creatureTarget->GetCreatureTemplate()->type_flags & CREATURE_TYPE_FLAG_TREAT_AS_RAID_UNIT || creatureTarget->GetCreatureTemplate()->type_flags & CREATURE_TYPE_FLAG_CAN_ASSIST; } return true; } int32 Unit::ModifyHealth(int32 dVal) { int32 gain = 0; if (dVal == 0) return 0; int32 curHealth = (int32)GetHealth(); int32 val = dVal + curHealth; if (val <= 0) { SetHealth(0); return -curHealth; } int32 maxHealth = (int32)GetMaxHealth(); if (val < maxHealth) { SetHealth(val); gain = val - curHealth; } else if (curHealth != maxHealth) { SetHealth(maxHealth); gain = maxHealth - curHealth; } return gain; } int32 Unit::GetHealthGain(int32 dVal) { int32 gain = 0; if (dVal == 0) return 0; int32 curHealth = (int32)GetHealth(); int32 val = dVal + curHealth; if (val <= 0) { return -curHealth; } int32 maxHealth = (int32)GetMaxHealth(); if (val < maxHealth) gain = dVal; else if (curHealth != maxHealth) gain = maxHealth - curHealth; return gain; } // returns negative amount on power reduction int32 Unit::ModifyPower(Powers power, int32 dVal, bool withPowerUpdate /*= true*/) { if (dVal == 0) return 0; int32 gain = 0; int32 curPower = (int32)GetPower(power); int32 val = dVal + curPower; if (val <= 0) { SetPower(power, 0, withPowerUpdate); return -curPower; } int32 maxPower = (int32)GetMaxPower(power); if (val < maxPower) { SetPower(power, val, withPowerUpdate); gain = val - curPower; } else if (curPower != maxPower) { SetPower(power, maxPower, withPowerUpdate); gain = maxPower - curPower; } if (GetAI()) { GetAI()->OnPowerUpdate(power, gain, dVal, curPower); } return gain; } bool Unit::IsAlwaysVisibleFor(WorldObject const* seer) const { if (WorldObject::IsAlwaysVisibleFor(seer)) return true; // Always seen by owner if (ObjectGuid guid = GetCharmerOrOwnerGUID()) 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; if (Player* ownerPlayer = GetSpellModOwner()) if (Player const* seerPlayer = seer->ToPlayer()) { if (ownerPlayer->IsGroupVisibleFor(seerPlayer)) return true; } return false; } 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::SetModelVisible(bool on) { if (on) RemoveAurasDueToSpell(24401); else CastSpell(this, 24401, true); } void Unit::UpdateSpeed(UnitMoveType mtype, bool forced) { 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: case MOVE_WALK: break; 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: { // xinef: check for forced_speed_mod of sea turtle Unit::AuraEffectList const& swimAuras = GetAuraEffectsByType(SPELL_AURA_MOD_INCREASE_SWIM_SPEED); for (Unit::AuraEffectList::const_iterator itr = swimAuras.begin(); itr != swimAuras.end(); ++itr) { // xinef: sea turtle only, it is not affected by any increasing / decreasing effects if ((*itr)->GetId() == 64731 /*SPELL_SEA_TURTLE*/) { SetSpeed(mtype, AddPct(non_stack_bonus, (*itr)->GetAmount()), forced); return; } else if ( // case: increase speed ((*itr)->GetAmount() > 0 && (*itr)->GetAmount() > main_speed_mod) || // case: decrease speed ((*itr)->GetAmount() < 0 && (*itr)->GetAmount() < main_speed_mod) ) { main_speed_mod = (*itr)->GetAmount(); } } break; } case MOVE_FLIGHT: { if (IsCreature() && IsControlledByPlayer()) // not sure if good for pet { main_speed_mod = GetMaxPositiveAuraModifier(SPELL_AURA_MOD_INCREASE_FLIGHT_SPEED); stack_bonus = GetTotalAuraMultiplier(SPELL_AURA_MOD_FLIGHT_SPEED_NOT_STACKING); // 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_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); non_stack_bonus += GetMaxPositiveAuraModifier(SPELL_AURA_MOD_FLIGHT_SPEED_MOUNTED_NOT_STACKING) / 100.0f; } else // Use not mount (shapeshift for example) auras (should stack) { main_speed_mod = GetMaxPositiveAuraModifier(SPELL_AURA_MOD_INCREASE_FLIGHT_SPEED); stack_bonus = GetTotalAuraModifier(SPELL_AURA_MOD_FLIGHT_SPEED_ALWAYS); non_stack_bonus += GetMaxPositiveAuraModifier(SPELL_AURA_MOD_FLIGHT_SPEED_NOT_STACKING) / 100.0f; } // Update speed for vehicle if available if (IsPlayer() && GetVehicle()) GetVehicleBase()->UpdateSpeed(MOVE_FLIGHT, true); break; } default: 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 (IsCreature()) { if (IsPet() && ToPet()->isControlled() && IsControlledByPlayer()) { // contant value for player pets speed *= 1.15f; } else { 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()) { uint32 immuneMask = creature->GetCreatureTemplate()->MechanicImmuneMask; if (immuneMask & (1 << (MECHANIC_SNARE - 1)) || immuneMask & (1 << (MECHANIC_DAZE - 1))) break; } // Use speed from aura float max_speed = normalization / (IsControlledByPlayer() ? playerBaseMoveSpeed[mtype] : baseMoveSpeed[mtype]); if (speed > max_speed) speed = max_speed; } break; } default: break; } int32 slowFromHealth = 0; Creature* creature = ToCreature(); // ignore pets, player owned vehicles, and mobs immune to snare if (creature && !IsPet() && !(IsControlledByPlayer() && IsVehicle()) && !(creature->HasMechanicTemplateImmunity(MECHANIC_SNARE)) && !(creature->IsDungeonBoss())) { // 1.6% for each % under 30. // use min(0, health-30) so that we don't boost mobs above 30. slowFromHealth = (int32) std::min(0.0f, (1.66f * (GetHealthPct() - 30.0f))); } if (slowFromHealth) { AddPct(speed, slowFromHealth); } // 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 base_speed = (IsCreature() ? ToCreature()->GetCreatureTemplate()->speed_run : 1.0f); float min_speed = base_speed * (minSpeedMod / 100.0f); if (speed < min_speed) speed = min_speed; } SetSpeed(mtype, speed, forced); } float Unit::GetSpeed(UnitMoveType mtype) const { return m_speed_rate[mtype] * (IsControlledByPlayer() ? playerBaseMoveSpeed[mtype] : baseMoveSpeed[mtype]); } void Unit::SetSpeed(UnitMoveType mtype, float rate, bool forced) { if (rate < 0) rate = 0.0f; // Update speed only on change if (m_speed_rate[mtype] == rate) return; m_speed_rate[mtype] = rate; propagateSpeedChange(); SpeedOpcodePair const& speedOpcodes = SetSpeed2Opc_table[mtype]; if (forced && IsClientControlled()) { Player* player = const_cast(GetClientControlling()); uint32 const counter = player->GetSession()->GetOrderCounter(); // register forced speed changes for WorldSession::HandleForceSpeedChangeAck // and do it only for real sent packets and use run for run/mounted as client expected ++player->m_forced_speed_changes[mtype]; WorldPacket data(speedOpcodes[static_cast(SpeedOpcodeIndex::PC)], 18); data << GetPackGUID(); data << counter; if (mtype == MOVE_RUN) data << uint8(0); // new 2.1.0 data << GetSpeed(mtype); player->GetSession()->SendPacket(&data); player->GetSession()->IncrementOrderCounter(); } else if (forced) { WorldPacket data(speedOpcodes[static_cast(SpeedOpcodeIndex::NPC)], 12); data << GetPackGUID(); data << float(GetSpeed(mtype)); SendMessageToSet(&data, true); } if (IsPlayer()) { // Xinef: update speed of pet also if (!IsInCombat()) { Unit* pet = ToPlayer()->GetPet(); if (!pet) pet = GetCharm(); // xinef: do not affect vehicles and possesed pets if (pet && (pet->HasUnitFlag(UNIT_FLAG_POSSESSED) || pet->IsVehicle())) pet = nullptr; if (pet && pet->IsCreature() && !pet->IsInCombat() && pet->GetMotionMaster()->GetCurrentMovementGeneratorType() == FOLLOW_MOTION_TYPE) pet->UpdateSpeed(mtype, forced); if (Unit* critter = ObjectAccessor::GetUnit(*this, GetCritterGUID())) critter->UpdateSpeed(mtype, forced); } ToPlayer()->SetCanTeleport(true); } } void Unit::setDeathState(DeathState s, bool despawn) { // death state needs to be updated before RemoveAllAurasOnDeath() calls HandleChannelDeathItem(..) so that // it can be used to check creation of death items (such as soul shards). m_deathState = s; if (s != DeathState::Alive && s != DeathState::JustRespawned) { CombatStop(); GetThreatMgr().ClearAllThreat(); getHostileRefMgr().deleteReferences(); ClearComboPointHolders(); // any combo points pointed to unit lost at it death if (IsNonMeleeSpellCast(false)) InterruptNonMeleeSpells(false); UnsummonAllTotems(true); RemoveAllControlled(true); RemoveAllAurasOnDeath(); } if (s == DeathState::JustDied) { // remove aurastates allowing special moves ClearAllReactives(); ClearDiminishings(); GetMotionMaster()->Clear(false); GetMotionMaster()->MoveIdle(); // Xinef: Remove Hover so the corpse can fall to the ground SetHover(false); if (despawn) DisableSpline(); else StopMoving(); // 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); // Stop emote on death SetUInt32Value(UNIT_NPC_EMOTESTATE, 0); // players in instance don't have ZoneScript, but they have InstanceScript if (ZoneScript* zoneScript = GetZoneScript() ? GetZoneScript() : (ZoneScript*)GetInstanceScript()) zoneScript->OnUnitDeath(this); } else if (s == DeathState::JustRespawned) { RemoveFlag (UNIT_FIELD_FLAGS, UNIT_FLAG_SKINNABLE); // clear skinnable for creature and player (at battleground) } } /*######################################## ######## ######## ######## AGGRO SYSTEM ######## ######## ######## ########################################*/ bool Unit::CanHaveThreatList(bool skipAliveCheck) const { // only creatures can have threat list if (!IsCreature()) return false; // only alive units can have threat list if (!skipAliveCheck && !IsAlive()) return false; // totems can not have threat list if (ToCreature()->IsTotem()) return false; // vehicles can not have threat list if (ToCreature()->IsVehicle() && GetMap()->IsBattlegroundOrArena()) return false; // summons can not have a threat list, unless they are controlled by a creature if (HasUnitTypeMask(UNIT_MASK_MINION | UNIT_MASK_GUARDIAN | UNIT_MASK_CONTROLLABLE_GUARDIAN) && ((Pet*)this)->GetOwnerGUID().IsPlayer()) return false; return true; } //====================================================================== float Unit::ApplyTotalThreatModifier(float fThreat, SpellSchoolMask schoolMask) { if (!HasThreatAura() || fThreat < 0) return fThreat; SpellSchools school = GetFirstSchoolInMask(schoolMask); return fThreat * m_threatModifier[school]; } //====================================================================== void Unit::AddThreat(Unit* victim, float fThreat, SpellSchoolMask schoolMask, SpellInfo const* threatSpell) { // Only mobs can manage threat lists if (CanHaveThreatList() && !HasUnitState(UNIT_STATE_EVADE)) { m_ThreatMgr.AddThreat(victim, fThreat, schoolMask, threatSpell); } } //====================================================================== void Unit::TauntApply(Unit* taunter) { ASSERT(IsCreature()); if (!taunter || (taunter->IsPlayer() && taunter->ToPlayer()->IsGameMaster())) return; if (!CanHaveThreatList()) return; Creature* creature = ToCreature(); if (creature->HasReactState(REACT_PASSIVE)) return; Unit* target = GetVictim(); if (target && target == taunter) return; SetInFront(taunter); SetGuidValue(UNIT_FIELD_TARGET, taunter->GetGUID()); if (creature->IsAIEnabled) creature->AI()->AttackStart(taunter); //m_ThreatMgr.tauntApply(taunter); } //====================================================================== void Unit::TauntFadeOut(Unit* taunter) { ASSERT(IsCreature()); if (!taunter || (taunter->IsPlayer() && taunter->ToPlayer()->IsGameMaster())) return; if (!CanHaveThreatList()) return; Creature* creature = ToCreature(); if (creature->HasReactState(REACT_PASSIVE)) return; Unit* target = GetVictim(); if (!target || target != taunter) return; if (m_ThreatMgr.isThreatListEmpty()) { if (creature->IsAIEnabled) creature->AI()->EnterEvadeMode(CreatureAI::EVADE_REASON_NO_HOSTILES); return; } target = creature->SelectVictim(); // might have more taunt auras remaining if (target && target != taunter) { SetGuidValue(UNIT_FIELD_TARGET, target->GetGUID()); SetInFront(target); if (creature->IsAIEnabled) creature->AI()->AttackStart(target); } } //====================================================================== Unit* Creature::SelectVictim() { // function provides main threat functionality // next-victim-selection algorithm and evade mode are called // threat list sorting etc. Unit* target = nullptr; // First checking if we have some taunt on us AuraEffectList const& tauntAuras = GetAuraEffectsByType(SPELL_AURA_MOD_TAUNT); if (!tauntAuras.empty()) for (Unit::AuraEffectList::const_reverse_iterator itr = tauntAuras.rbegin(); itr != tauntAuras.rend(); ++itr) if (Unit* caster = (*itr)->GetCaster()) if (CanCreatureAttack(caster) && !caster->HasAuraTypeWithCaster(SPELL_AURA_IGNORED, GetGUID())) { target = caster; break; } if (CanHaveThreatList()) { if (!target && !m_ThreatMgr.isThreatListEmpty()) target = m_ThreatMgr.getHostileTarget(); } else if (!HasReactState(REACT_PASSIVE)) { // we have player pet probably target = getAttackerForHelper(); if (!target && IsSummon()) if (Unit* owner = ToTempSummon()->GetOwner()) { if (owner->IsInCombat()) target = owner->getAttackerForHelper(); if (!target) for (ControlSet::const_iterator itr = owner->m_Controlled.begin(); itr != owner->m_Controlled.end(); ++itr) if ((*itr)->IsInCombat()) { target = (*itr)->getAttackerForHelper(); if (target) break; } } } else return nullptr; if (target && CanCreatureAttack(target)) { SetInFront(target); return target; } // last case when creature must not go to evade mode: // it in combat but attacker not make any damage and not enter to aggro radius to have record in threat list // Note: creature does not have targeted movement generator but has attacker in this case for (AttackerSet::const_iterator itr = m_attackers.begin(); itr != m_attackers.end(); ++itr) if ((*itr) && CanCreatureAttack(*itr) && !(*itr)->IsPlayer() && !(*itr)->ToCreature()->HasUnitTypeMask(UNIT_MASK_CONTROLLABLE_GUARDIAN)) return nullptr; if (GetVehicle()) return nullptr; // pussywizard: not sure why it's here // pussywizard: can't evade when having invisibility aura with duration? o_O Unit::AuraEffectList const& iAuras = GetAuraEffectsByType(SPELL_AURA_MOD_INVISIBILITY); if (!iAuras.empty()) { for (Unit::AuraEffectList::const_iterator itr = iAuras.begin(); itr != iAuras.end(); ++itr) if ((*itr)->GetBase()->IsPermanent()) { AI()->EnterEvadeMode(CreatureAI::EVADE_REASON_NO_HOSTILES); break; } return nullptr; } // Last chance: creature group if (CreatureGroup* group = GetFormation()) { if (Unit* groupTarget = group->GetNewTargetForMember(this)) { SetInFront(groupTarget); return groupTarget; } } // enter in evade mode in other case AI()->EnterEvadeMode(); return nullptr; } //====================================================================== //====================================================================== //====================================================================== float Unit::ApplyEffectModifiers(SpellInfo const* spellProto, uint8 effect_index, float value) const { if (Player* modOwner = GetSpellModOwner()) { modOwner->ApplySpellMod(spellProto->Id, SPELLMOD_ALL_EFFECTS, value); switch (effect_index) { case 0: modOwner->ApplySpellMod(spellProto->Id, SPELLMOD_EFFECT1, value); break; case 1: modOwner->ApplySpellMod(spellProto->Id, SPELLMOD_EFFECT2, value); break; case 2: modOwner->ApplySpellMod(spellProto->Id, SPELLMOD_EFFECT3, value); break; } } return value; } // function uses real base points (typically value - 1) int32 Unit::CalculateSpellDamage(Unit const* target, SpellInfo const* spellProto, uint8 effect_index, int32 const* basePoints) const { return spellProto->Effects[effect_index].CalcValue(this, basePoints, target); } int32 Unit::CalcSpellDuration(SpellInfo const* spellProto) { uint8 comboPoints = GetComboPoints(); int32 minduration = spellProto->GetDuration(); int32 maxduration = spellProto->GetMaxDuration(); int32 duration; if (comboPoints && minduration != -1 && minduration != maxduration) duration = minduration + int32((maxduration - minduration) * comboPoints / 5); else duration = minduration; return duration; } int32 Unit::ModSpellDuration(SpellInfo const* spellProto, Unit const* target, int32 duration, bool positive, uint32 effectMask) { // don't mod permanent auras duration if (duration < 0) return duration; // some auras are not affected by duration modifiers if (spellProto->HasAttribute(SPELL_ATTR7_NO_TARGET_DURATION_MOD)) return duration; // cut duration only of negative effects // xinef: also calculate self casts, spell can be reflected for example if (!positive) { int32 mechanic = spellProto->GetSpellMechanicMaskByEffectMask(effectMask); int32 durationMod; int32 durationMod_always = 0; int32 durationMod_not_stack = 0; for (uint8 i = 1; i <= MECHANIC_ENRAGED; ++i) { if (!(mechanic & 1 << i)) continue; // Xinef: spells affecting movement imparing effects should not reduce duration if disoriented mechanic is present if (i == MECHANIC_SNARE && (mechanic & (1 << MECHANIC_DISORIENTED))) continue; // Find total mod value (negative bonus) int32 new_durationMod_always = target->GetTotalAuraModifierByMiscValue(SPELL_AURA_MECHANIC_DURATION_MOD, i); // Find max mod (negative bonus) int32 new_durationMod_not_stack = target->GetMaxNegativeAuraModifierByMiscValue(SPELL_AURA_MECHANIC_DURATION_MOD_NOT_STACK, i); // Check if mods applied before were weaker if (new_durationMod_always < durationMod_always) durationMod_always = new_durationMod_always; if (new_durationMod_not_stack < durationMod_not_stack) durationMod_not_stack = new_durationMod_not_stack; } // Select strongest negative mod if (durationMod_always > durationMod_not_stack) durationMod = durationMod_not_stack; else durationMod = durationMod_always; if (durationMod != 0) AddPct(duration, durationMod); // there are only negative mods currently durationMod_always = target->GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_AURA_DURATION_BY_DISPEL, spellProto->Dispel); durationMod_not_stack = target->GetMaxNegativeAuraModifierByMiscValue(SPELL_AURA_MOD_AURA_DURATION_BY_DISPEL_NOT_STACK, spellProto->Dispel); durationMod = 0; if (durationMod_always > durationMod_not_stack) durationMod += durationMod_not_stack; else durationMod += durationMod_always; if (durationMod != 0) AddPct(duration, durationMod); } else { // else positive mods here, there are no currently // when there will be, change GetTotalAuraModifierByMiscValue to GetTotalPositiveAuraModifierByMiscValue } // Glyphs which increase duration of selfcasted buffs if (target == this) { switch (spellProto->SpellFamilyName) { case SPELLFAMILY_DRUID: if (spellProto->SpellFamilyFlags[0] & 0x100) { // Glyph of Thorns if (AuraEffect* aurEff = GetAuraEffect(57862, 0)) duration += aurEff->GetAmount() * MINUTE * IN_MILLISECONDS; } break; case SPELLFAMILY_PALADIN: if ((spellProto->SpellFamilyFlags[0] & 0x00000002) && spellProto->SpellIconID == 298) { // Glyph of Blessing of Might if (AuraEffect* aurEff = GetAuraEffect(57958, 0)) duration += aurEff->GetAmount() * MINUTE * IN_MILLISECONDS; } else if ((spellProto->SpellFamilyFlags[0] & 0x00010000) && spellProto->SpellIconID == 306) { // Glyph of Blessing of Wisdom if (AuraEffect* aurEff = GetAuraEffect(57979, 0)) duration += aurEff->GetAmount() * MINUTE * IN_MILLISECONDS; } break; } } return std::max(duration, 0); } void Unit::ModSpellCastTime(SpellInfo const* spellInfo, int32& castTime, Spell* spell) { if (!spellInfo || castTime < 0) return; if (spellInfo->IsChanneled() && spellInfo->HasAura(SPELL_AURA_MOUNTED)) return; // called from caster if (Player* modOwner = GetSpellModOwner()) /// @todo:(MadAgos) Eventually check and delete the bool argument modOwner->ApplySpellMod(spellInfo->Id, SPELLMOD_CASTING_TIME, castTime, spell, bool(modOwner != this && !IsPet())); switch (spellInfo->DmgClass) { case SPELL_DAMAGE_CLASS_NONE: if (spellInfo->AttributesEx5 & SPELL_ATTR5_SPELL_HASTE_AFFECTS_PERIODIC) // required double check castTime = int32(float(castTime) * GetFloatValue(UNIT_MOD_CAST_SPEED)); else if (spellInfo->SpellVisual[0] == 3881 && HasAura(67556)) // cooking with Chef Hat. castTime = 500; break; case SPELL_DAMAGE_CLASS_MELEE: break; // no known cases case SPELL_DAMAGE_CLASS_MAGIC: castTime = CanInstantCast() ? 0 : int32(float(castTime) * GetFloatValue(UNIT_MOD_CAST_SPEED)); break; case SPELL_DAMAGE_CLASS_RANGED: castTime = int32(float(castTime) * m_modAttackSpeedPct[RANGED_ATTACK]); break; default: break; } } DiminishingLevels Unit::GetDiminishing(DiminishingGroup group) { for (Diminishing::iterator i = m_Diminishing.begin(); i != m_Diminishing.end(); ++i) { if (i->DRGroup != group) continue; if (!i->hitCount) return DIMINISHING_LEVEL_1; if (!i->hitTime) return DIMINISHING_LEVEL_1; // If last spell was casted more than 15 seconds ago - reset the count. if (i->stack == 0 && getMSTimeDiff(i->hitTime, GameTime::GetGameTimeMS().count()) > 15000) { i->hitCount = DIMINISHING_LEVEL_1; return DIMINISHING_LEVEL_1; } // or else increase the count. else return DiminishingLevels(i->hitCount); } return DIMINISHING_LEVEL_1; } void Unit::IncrDiminishing(DiminishingGroup group) { // Checking for existing in the table for (Diminishing::iterator i = m_Diminishing.begin(); i != m_Diminishing.end(); ++i) { if (i->DRGroup != group) continue; if (int32(i->hitCount) < GetDiminishingReturnsMaxLevel(group)) i->hitCount += 1; return; } m_Diminishing.push_back(DiminishingReturn(group, GameTime::GetGameTimeMS().count(), DIMINISHING_LEVEL_2)); } float Unit::ApplyDiminishingToDuration(DiminishingGroup group, int32& duration, Unit* caster, DiminishingLevels Level, int32 limitduration) { // xinef: dont apply diminish to self casts if (duration == -1 || group == DIMINISHING_NONE) return 1.0f; // test pet/charm masters instead pets/charmeds Unit const* targetOwner = GetOwner(); Unit const* casterOwner = caster->GetOwner(); // Duration of crowd control abilities on pvp target is limited by 10 sec. (2.2.0) if (limitduration > 0 && duration > limitduration) { Unit const* target = targetOwner ? targetOwner : this; Unit const* source = casterOwner ? casterOwner : caster; if ((target->IsPlayer() || target->ToCreature()->HasFlagsExtra(CREATURE_FLAG_EXTRA_ALL_DIMINISH)) && source->IsPlayer()) duration = limitduration; } float mod = 1.0f; if (group == DIMINISHING_TAUNT) { if (IsCreature() && (ToCreature()->HasFlagsExtra(CREATURE_FLAG_EXTRA_OBEYS_TAUNT_DIMINISHING_RETURNS))) { DiminishingLevels diminish = Level; 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; } } } // Some diminishings applies to mobs too (for example, Stun) else if ((GetDiminishingReturnsGroupType(group) == DRTYPE_PLAYER && ((targetOwner ? (targetOwner->IsPlayer()) : (IsPlayer())) || (IsCreature() && ToCreature()->HasFlagsExtra(CREATURE_FLAG_EXTRA_ALL_DIMINISH)))) || GetDiminishingReturnsGroupType(group) == DRTYPE_ALL) { DiminishingLevels diminish = Level; 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; } } duration = int32(duration * mod); return mod; } void Unit::ApplyDiminishingAura(DiminishingGroup group, bool apply) { // Checking for existing in the table for (Diminishing::iterator i = m_Diminishing.begin(); i != m_Diminishing.end(); ++i) { if (i->DRGroup != group) continue; if (apply) i->stack += 1; else if (i->stack) { i->stack -= 1; // Remember time after last aura from group removed if (i->stack == 0) i->hitTime = GameTime::GetGameTimeMS().count(); } break; } } float Unit::GetSpellMaxRangeForTarget(Unit const* target, SpellInfo const* spellInfo) const { if (!spellInfo->RangeEntry) { return 0; } if (spellInfo->RangeEntry->RangeMax[1] == spellInfo->RangeEntry->RangeMax[0]) { return spellInfo->GetMaxRange(); } if (!target) { return spellInfo->GetMaxRange(true); } return spellInfo->GetMaxRange(!IsHostileTo(target)); } float Unit::GetSpellMinRangeForTarget(Unit const* target, SpellInfo const* spellInfo) const { if (!spellInfo->RangeEntry) { return 0; } if (spellInfo->RangeEntry->RangeMin[1] == spellInfo->RangeEntry->RangeMin[0]) { return spellInfo->GetMinRange(); } return spellInfo->GetMinRange(!IsHostileTo(target)); } void Unit::SetAnimTier(AnimTier animTier) { SetByteValue(UNIT_FIELD_BYTES_1, UNIT_BYTES_1_OFFSET_ANIM_TIER, uint8(animTier)); } uint32 Unit::GetCreatureType() const { if (IsPlayer()) { ShapeshiftForm form = GetShapeshiftForm(); SpellShapeshiftFormEntry const* ssEntry = sSpellShapeshiftFormStore.LookupEntry(form); if (ssEntry && ssEntry->creatureType > 0) return ssEntry->creatureType; else return CREATURE_TYPE_HUMANOID; } else return ToCreature()->GetCreatureTemplate()->type; } /*####################################### ######## ######## ######## STAT SYSTEM ######## ######## ######## #######################################*/ bool Unit::HandleStatFlatModifier(UnitMods unitMod, UnitModifierFlatType modifierType, float amount, bool apply) { if (unitMod >= UNIT_MOD_END || modifierType >= MODIFIER_TYPE_FLAT_END) { LOG_ERROR("entities.unit", "ERROR in HandleStatModifier(): non-existing UnitMods or wrong UnitModifierType!"); return false; } if (!amount) return false; switch (modifierType) { case BASE_VALUE: case TOTAL_VALUE: m_auraFlatModifiersGroup[unitMod][modifierType] += apply ? amount : -amount; break; default: break; } UpdateUnitMod(unitMod); return true; } // Usage outside of AuraEffect Handlers is discouraged as the value will be lost when auras change. Use an Aura instead. void Unit::ApplyStatPctModifier(UnitMods unitMod, UnitModifierPctType modifierType, float pct) { if (unitMod >= UNIT_MOD_END || modifierType >= MODIFIER_TYPE_PCT_END) { 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) { 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) { 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: case UNIT_MOD_STAT_SPIRIT: 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_HAPPINESS: case UNIT_MOD_RUNE: case UNIT_MOD_RUNIC_POWER: UpdateMaxPower(GetPowerTypeByAuraGroup(unitMod)); 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: 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, std::bind(&Unit::CheckAttackFitToAuraRequirement, this, attackType, std::placeholders::_1)); 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, float additionalValue) const { UnitMods unitMod = UnitMods(static_cast(UNIT_MOD_STAT_START) + stat); if (GetPctModifierValue(unitMod, TOTAL_PCT) <= 0.0f) return 0.0f; // value = ((base_value * base_pct) + total_value) * total_pct float value = GetFlatModifierValue(unitMod, BASE_VALUE) + GetCreateStat(stat); value *= GetPctModifierValue(unitMod, BASE_PCT); value += GetFlatModifierValue(unitMod, TOTAL_VALUE) + additionalValue; value *= GetPctModifierValue(unitMod, TOTAL_PCT); return value; } float Unit::GetTotalAuraModValue(UnitMods unitMod) const { if (unitMod >= UNIT_MOD_END) { LOG_ERROR("entities.unit", "attempt to access non-existing UnitMods in GetTotalAuraModValue()!"); return 0.0f; } if (GetPctModifierValue(unitMod, TOTAL_PCT) <= 0.0f) return 0.0f; float value = GetFlatModifierValue(unitMod, BASE_VALUE); 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; case UNIT_MOD_STAT_SPIRIT: stat = STAT_SPIRIT; break; default: break; } return stat; } Powers Unit::GetPowerTypeByAuraGroup(UnitMods unitMod) const { switch (unitMod) { case UNIT_MOD_RAGE: return POWER_RAGE; case UNIT_MOD_FOCUS: return POWER_FOCUS; case UNIT_MOD_ENERGY: return POWER_ENERGY; case UNIT_MOD_HAPPINESS: return POWER_HAPPINESS; case UNIT_MOD_RUNE: return POWER_RUNE; case UNIT_MOD_RUNIC_POWER: return POWER_RUNIC_POWER; default: case UNIT_MOD_MANA: return POWER_MANA; } } float Unit::GetTotalAttackPowerValue(WeaponAttackType attType, Unit* victim) const { if (attType == RANGED_ATTACK) { int32 ap = GetInt32Value(UNIT_FIELD_RANGED_ATTACK_POWER) + GetInt32Value(UNIT_FIELD_RANGED_ATTACK_POWER_MODS); if (victim) ap += victim->GetTotalAuraModifier(SPELL_AURA_RANGED_ATTACK_POWER_ATTACKER_BONUS); if (ap < 0) return 0.0f; return ap * (1.0f + GetFloatValue(UNIT_FIELD_RANGED_ATTACK_POWER_MULTIPLIER)); } else { int32 ap = GetInt32Value(UNIT_FIELD_ATTACK_POWER) + GetInt32Value(UNIT_FIELD_ATTACK_POWER_MODS); if (victim) ap += victim->GetTotalAuraModifier(SPELL_AURA_MELEE_ATTACK_POWER_ATTACKER_BONUS); if (ap < 0) return 0.0f; return ap * (1.0f + GetFloatValue(UNIT_FIELD_ATTACK_POWER_MULTIPLIER)); } } float Unit::GetWeaponDamageRange(WeaponAttackType attType, WeaponDamageRange type, uint8 damageIndex /*= 0*/) const { if (attType == OFF_ATTACK && !HasOffhandWeaponForAttack()) return 0.0f; return m_weaponDamage[attType][type][damageIndex]; } void Unit::SetLevel(uint8 lvl, bool showLevelChange) { SetUInt32Value(UNIT_FIELD_LEVEL, lvl); // Xinef: unmark field bit update if (!showLevelChange) _changesMask.UnsetBit(UNIT_FIELD_LEVEL); // group update if (IsPlayer() && ToPlayer()->GetGroup()) ToPlayer()->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_LEVEL); if (IsPlayer()) { sCharacterCache->UpdateCharacterLevel(GetGUID(), lvl); } } void Unit::SetHealth(uint32 val) { if (getDeathState() == DeathState::JustDied) val = 0; else if (IsPlayer() && getDeathState() == DeathState::Dead) val = 1; else { uint32 maxHealth = GetMaxHealth(); if (maxHealth < val) val = maxHealth; } float prevHealthPct = GetHealthPct(); SetUInt32Value(UNIT_FIELD_HEALTH, val); // mobs that are now or were below 30% need to update their speed if (IsCreature() && !(IsPet() && ToPet()->isControlled() && IsControlledByPlayer()) && (prevHealthPct < 30.0 || HealthBelowPct(30))) { UpdateSpeed(MOVE_RUN, false); } // group update if (IsPlayer()) { Player* player = ToPlayer(); if (player->NeedSendSpectatorData()) ArenaSpectator::SendCommand_UInt32Value(FindMap(), GetGUID(), "CHP", val); if (player->GetGroup()) player->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_CUR_HP); } else if (Pet* pet = ToCreature()->ToPet()) { if (pet->isControlled()) { if (Unit* owner = GetOwner()) if (Player* player = owner->ToPlayer()) { if (player->NeedSendSpectatorData() && pet->GetCreatureTemplate()->family) ArenaSpectator::SendCommand_UInt32Value(player->FindMap(), player->GetGUID(), "PHP", (uint32)pet->GetHealthPct()); if (player->GetGroup()) player->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_PET_CUR_HP); } } } } void Unit::SetMaxHealth(uint32 val) { if (!val) val = 1; uint32 health = GetHealth(); SetUInt32Value(UNIT_FIELD_MAXHEALTH, val); // group update if (IsPlayer()) { Player* player = ToPlayer(); if (player->NeedSendSpectatorData()) ArenaSpectator::SendCommand_UInt32Value(FindMap(), GetGUID(), "MHP", val); if (player->GetGroup()) player->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_MAX_HP); } else if (Pet* pet = ToCreature()->ToPet()) { if (pet->isControlled()) { if (Unit* owner = GetOwner()) if (Player* player = owner->ToPlayer()) { if (player->NeedSendSpectatorData() && pet->GetCreatureTemplate()->family) ArenaSpectator::SendCommand_UInt32Value(player->FindMap(), player->GetGUID(), "PHP", (uint32)pet->GetHealthPct()); if (player->GetGroup()) player->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_PET_MAX_HP); } } } if (val < health) SetHealth(val); } void Unit::SetPower(Powers power, uint32 val, bool withPowerUpdate /*= true*/, bool fromRegenerate /* = false */) { if (!fromRegenerate && GetPower(power) == val) return; uint32 maxPower = GetMaxPower(power); if (maxPower < val) val = maxPower; if (fromRegenerate) { UpdateUInt32Value(UNIT_FIELD_POWER1 + AsUnderlyingType(power), val); AddToObjectUpdateIfNeeded(); } else SetStatInt32Value(UNIT_FIELD_POWER1 + AsUnderlyingType(power), val); if (withPowerUpdate) { WorldPacket data(SMSG_POWER_UPDATE, 8 + 1 + 4); data << GetPackGUID(); data << uint8(power); data << uint32(val); SendMessageToSet(&data, IsPlayer()); } // group update if (IsPlayer()) { Player* player = ToPlayer(); if (getPowerType() == power && player->NeedSendSpectatorData()) ArenaSpectator::SendCommand_UInt32Value(FindMap(), GetGUID(), "CPW", power == POWER_RAGE || power == POWER_RUNIC_POWER ? val / 10 : val); if (player->GetGroup()) player->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_CUR_POWER); } else if (Pet* pet = ToCreature()->ToPet()) { if (pet->isControlled()) { Unit* owner = GetOwner(); if (owner && (owner->IsPlayer()) && owner->ToPlayer()->GetGroup()) owner->ToPlayer()->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_PET_CUR_POWER); } // Update the pet's character sheet with happiness damage bonus if (pet->getPetType() == HUNTER_PET && power == POWER_HAPPINESS) pet->UpdateDamagePhysical(BASE_ATTACK); } } void Unit::SetMaxPower(Powers power, uint32 val) { uint32 cur_power = GetPower(power); SetStatInt32Value(static_cast(UNIT_FIELD_MAXPOWER1) + power, val); // group update if (IsPlayer()) { Player* player = ToPlayer(); if (getPowerType() == power && player->NeedSendSpectatorData()) ArenaSpectator::SendCommand_UInt32Value(FindMap(), GetGUID(), "MPW", power == POWER_RAGE || power == POWER_RUNIC_POWER ? val / 10 : val); if (player->GetGroup()) player->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_MAX_POWER); } else if (Pet* pet = ToCreature()->ToPet()) { if (pet->isControlled()) { Unit* owner = GetOwner(); if (owner && (owner->IsPlayer()) && owner->ToPlayer()->GetGroup()) owner->ToPlayer()->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_PET_MAX_POWER); } } if (val < cur_power) SetPower(power, val); } uint32 Unit::GetCreatePowers(Powers power) const { // Only hunter pets have POWER_FOCUS and POWER_HAPPINESS switch (power) { case POWER_MANA: return GetCreateMana(); case POWER_RAGE: return 1000; case POWER_FOCUS: return (IsPlayer() || !((Creature const*)this)->IsPet() || ((Pet const*)this)->getPetType() != HUNTER_PET ? 0 : 100); case POWER_ENERGY: return 100; case POWER_HAPPINESS: return (IsPlayer() || !((Creature const*)this)->IsPet() || ((Pet const*)this)->getPetType() != HUNTER_PET ? 0 : 1050000); case POWER_RUNIC_POWER: return 1000; case POWER_RUNE: return 0; case POWER_HEALTH: return 0; default: break; } return 0; } void Unit::AddToWorld() { if (!IsInWorld()) { WorldObject::AddToWorld(); } } void Unit::RemoveFromWorld() { // cleanup ASSERT(GetGUID()); if (IsInWorld()) { m_duringRemoveFromWorld = true; if (IsVehicle()) RemoveVehicleKit(); RemoveCharmAuras(); RemoveBindSightAuras(); RemoveNotOwnSingleTargetAuras(); RemoveAllGameObjects(); RemoveAllDynObjects(); ExitVehicle(); // Remove applied auras with SPELL_AURA_CONTROL_VEHICLE UnsummonAllTotems(); RemoveAllControlled(); RemoveAreaAurasDueToLeaveWorld(); if (GetCharmerGUID()) { LOG_FATAL("entities.unit", "Unit {} has charmer guid when removed from world", GetEntry()); ABORT(); } if (Unit* owner = GetOwner()) { if (owner->m_Controlled.find(this) != owner->m_Controlled.end()) { if (HasUnitTypeMask(UNIT_MASK_MINION | UNIT_MASK_GUARDIAN)) owner->SetMinion((Minion*)this, false); LOG_INFO("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) { if (IsDuringRemoveFromWorld()) return; // This needs to be before RemoveFromWorld to make GetCaster() return a valid pointer on aura removal InterruptNonMeleeSpells(true); if (IsInWorld()) // not in world and not being removed atm RemoveFromWorld(); 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(); ClearComboPoints(); ClearComboPointHolders(); GetThreatMgr().ClearAllThreat(); getHostileRefMgr().deleteReferences(); GetMotionMaster()->Clear(false); // remove different non-standard movement generators. } void Unit::CleanupsBeforeDelete(bool finalCleanup) { if (GetTransport()) { GetTransport()->RemovePassenger(this); SetTransport(nullptr); m_movementInfo.transport.Reset(); m_movementInfo.RemoveMovementFlag(MOVEMENTFLAG_ONTRANSPORT); } CleanupBeforeRemoveFromMap(finalCleanup); } void Unit::UpdateCharmAI() { if (IsPlayer()) return; if (i_disabledAI) // disabled AI must be primary AI { if (!IsCharmed()) { delete i_AI; i_AI = i_disabledAI; i_disabledAI = nullptr; } } else { if (IsCharmed()) { i_disabledAI = i_AI; if (isPossessed() || IsVehicle()) i_AI = new PossessedAI(ToCreature()); else i_AI = new PetAI(ToCreature()); } } } CharmInfo* Unit::InitCharmInfo() { if (!m_charmInfo) m_charmInfo = new CharmInfo(this); return m_charmInfo; } void Unit::DeleteCharmInfo() { if (!m_charmInfo) return; m_charmInfo->RestoreState(); delete m_charmInfo; m_charmInfo = nullptr; } bool Unit::isFrozen() const { return HasAuraState(AURA_STATE_FROZEN); } struct ProcTriggeredData { ProcTriggeredData(Aura* _aura) : aura(_aura) { effMask = 0; spellProcEvent = nullptr; triggerSpelId.fill(0); } SpellProcEventEntry const* spellProcEvent; Aura* aura; uint32 effMask; std::array triggerSpelId; bool operator==(const uint32 spellId) const { return aura->GetId() == spellId; } }; typedef std::list< ProcTriggeredData > ProcTriggeredList; // List of auras that CAN be trigger but may not exist in spell_proc_event // in most case need for drop charges // in some types of aura need do additional check // for example SPELL_AURA_MECHANIC_IMMUNITY - need check for mechanic bool InitTriggerAuraData() { for (uint16 i = 0; i < TOTAL_AURAS; ++i) { isTriggerAura[i] = false; isNonTriggerAura[i] = false; isAlwaysTriggeredAura[i] = false; } isTriggerAura[SPELL_AURA_DUMMY] = true; isTriggerAura[SPELL_AURA_MOD_CONFUSE] = true; isTriggerAura[SPELL_AURA_MOD_THREAT] = true; isTriggerAura[SPELL_AURA_MOD_STUN] = true; // Aura does not have charges but needs to be removed on trigger isTriggerAura[SPELL_AURA_MOD_DAMAGE_DONE] = true; isTriggerAura[SPELL_AURA_MOD_DAMAGE_TAKEN] = true; isTriggerAura[SPELL_AURA_MOD_RESISTANCE] = true; isTriggerAura[SPELL_AURA_MOD_STEALTH] = true; isTriggerAura[SPELL_AURA_MOD_FEAR] = true; // Aura does not have charges but needs to be removed on trigger isTriggerAura[SPELL_AURA_MOD_ROOT] = true; isTriggerAura[SPELL_AURA_TRANSFORM] = true; isTriggerAura[SPELL_AURA_REFLECT_SPELLS] = true; isTriggerAura[SPELL_AURA_DAMAGE_IMMUNITY] = true; isTriggerAura[SPELL_AURA_PROC_TRIGGER_SPELL] = true; isTriggerAura[SPELL_AURA_PROC_TRIGGER_DAMAGE] = true; isTriggerAura[SPELL_AURA_MOD_CASTING_SPEED_NOT_STACK] = true; isTriggerAura[SPELL_AURA_SCHOOL_ABSORB] = true; // Savage Defense untested isTriggerAura[SPELL_AURA_MOD_POWER_COST_SCHOOL_PCT] = true; isTriggerAura[SPELL_AURA_MOD_POWER_COST_SCHOOL] = true; isTriggerAura[SPELL_AURA_REFLECT_SPELLS_SCHOOL] = true; isTriggerAura[SPELL_AURA_MECHANIC_IMMUNITY] = true; isTriggerAura[SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN] = true; isTriggerAura[SPELL_AURA_SPELL_MAGNET] = true; isTriggerAura[SPELL_AURA_MOD_ATTACK_POWER] = true; isTriggerAura[SPELL_AURA_ADD_CASTER_HIT_TRIGGER] = true; isTriggerAura[SPELL_AURA_OVERRIDE_CLASS_SCRIPTS] = true; isTriggerAura[SPELL_AURA_MOD_MECHANIC_RESISTANCE] = true; isTriggerAura[SPELL_AURA_RANGED_ATTACK_POWER_ATTACKER_BONUS] = true; isTriggerAura[SPELL_AURA_MOD_MELEE_HASTE] = true; isTriggerAura[SPELL_AURA_MOD_ATTACKER_MELEE_HIT_CHANCE] = true; isTriggerAura[SPELL_AURA_RAID_PROC_FROM_CHARGE] = true; isTriggerAura[SPELL_AURA_RAID_PROC_FROM_CHARGE_WITH_VALUE] = true; isTriggerAura[SPELL_AURA_PROC_TRIGGER_SPELL_WITH_VALUE] = true; isTriggerAura[SPELL_AURA_MOD_DAMAGE_FROM_CASTER] = true; isTriggerAura[SPELL_AURA_MOD_SPELL_CRIT_CHANCE] = true; isTriggerAura[SPELL_AURA_ABILITY_IGNORE_AURASTATE] = true; isNonTriggerAura[SPELL_AURA_MOD_POWER_REGEN] = true; isNonTriggerAura[SPELL_AURA_REDUCE_PUSHBACK] = true; isAlwaysTriggeredAura[SPELL_AURA_OVERRIDE_CLASS_SCRIPTS] = true; isAlwaysTriggeredAura[SPELL_AURA_MOD_FEAR] = true; isAlwaysTriggeredAura[SPELL_AURA_MOD_ROOT] = true; isAlwaysTriggeredAura[SPELL_AURA_MOD_STUN] = true; isAlwaysTriggeredAura[SPELL_AURA_TRANSFORM] = true; isAlwaysTriggeredAura[SPELL_AURA_SPELL_MAGNET] = true; isAlwaysTriggeredAura[SPELL_AURA_SCHOOL_ABSORB] = true; isAlwaysTriggeredAura[SPELL_AURA_MOD_STEALTH] = true; return true; } void createProcFlags(SpellInfo const* spellInfo, WeaponAttackType attackType, bool positive, uint32& procAttacker, uint32& procVictim) { if (spellInfo) { switch (spellInfo->DmgClass) { case SPELL_DAMAGE_CLASS_MAGIC: if (positive) { procAttacker |= PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_POS; procVictim |= PROC_FLAG_TAKEN_SPELL_MAGIC_DMG_CLASS_POS; } else { procAttacker |= PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG; procVictim |= PROC_FLAG_TAKEN_SPELL_MAGIC_DMG_CLASS_NEG; } break; case SPELL_DAMAGE_CLASS_NONE: if (positive) { procAttacker |= PROC_FLAG_DONE_SPELL_NONE_DMG_CLASS_POS; procVictim |= PROC_FLAG_TAKEN_SPELL_NONE_DMG_CLASS_POS; } else { procAttacker |= PROC_FLAG_DONE_SPELL_NONE_DMG_CLASS_NEG; procVictim |= PROC_FLAG_TAKEN_SPELL_NONE_DMG_CLASS_NEG; } break; case SPELL_DAMAGE_CLASS_MELEE: procAttacker = PROC_FLAG_DONE_SPELL_MELEE_DMG_CLASS; if (attackType == OFF_ATTACK) procAttacker |= PROC_FLAG_DONE_OFFHAND_ATTACK; else procAttacker |= PROC_FLAG_DONE_MAINHAND_ATTACK; procVictim = PROC_FLAG_TAKEN_SPELL_MELEE_DMG_CLASS; break; case SPELL_DAMAGE_CLASS_RANGED: // Auto attack if (spellInfo->HasAttribute(SPELL_ATTR2_AUTO_REPEAT)) { procAttacker = PROC_FLAG_DONE_RANGED_AUTO_ATTACK; procVictim = PROC_FLAG_TAKEN_RANGED_AUTO_ATTACK; } else // Ranged spell attack { procAttacker = PROC_FLAG_DONE_SPELL_RANGED_DMG_CLASS; procVictim = PROC_FLAG_TAKEN_SPELL_RANGED_DMG_CLASS; } break; default: if (spellInfo->EquippedItemClass == ITEM_CLASS_WEAPON && spellInfo->EquippedItemSubClassMask & (1 << ITEM_SUBCLASS_WEAPON_WAND) && spellInfo->HasAttribute(SPELL_ATTR2_AUTO_REPEAT)) // Wands auto attack { procAttacker = PROC_FLAG_DONE_RANGED_AUTO_ATTACK; procVictim = PROC_FLAG_TAKEN_RANGED_AUTO_ATTACK; } break; } } else if (attackType == BASE_ATTACK) { procAttacker = PROC_FLAG_DONE_MELEE_AUTO_ATTACK | PROC_FLAG_DONE_MAINHAND_ATTACK; procVictim = PROC_FLAG_TAKEN_MELEE_AUTO_ATTACK; } else if (attackType == OFF_ATTACK) { procAttacker = PROC_FLAG_DONE_MELEE_AUTO_ATTACK | PROC_FLAG_DONE_OFFHAND_ATTACK; procVictim = PROC_FLAG_TAKEN_MELEE_AUTO_ATTACK; } } uint32 createProcExtendMask(SpellNonMeleeDamage* damageInfo, SpellMissInfo missCondition) { uint32 procEx = PROC_EX_NONE; // Check victim state if (missCondition != SPELL_MISS_NONE) switch (missCondition) { case SPELL_MISS_MISS: procEx |= PROC_EX_MISS; break; case SPELL_MISS_RESIST: procEx |= PROC_EX_RESIST; break; case SPELL_MISS_DODGE: procEx |= PROC_EX_DODGE; break; case SPELL_MISS_PARRY: procEx |= PROC_EX_PARRY; break; case SPELL_MISS_BLOCK: procEx |= PROC_EX_BLOCK; break; case SPELL_MISS_EVADE: procEx |= PROC_EX_EVADE; break; case SPELL_MISS_IMMUNE: procEx |= PROC_EX_IMMUNE; break; case SPELL_MISS_IMMUNE2: procEx |= PROC_EX_IMMUNE; break; case SPELL_MISS_DEFLECT: procEx |= PROC_EX_DEFLECT; break; case SPELL_MISS_ABSORB: procEx |= PROC_EX_ABSORB; break; case SPELL_MISS_REFLECT: procEx |= PROC_EX_REFLECT; break; default: break; } else { // On block if (damageInfo->blocked) procEx |= PROC_EX_BLOCK; // On absorb if (damageInfo->absorb) procEx |= PROC_EX_ABSORB; // On crit if (damageInfo->HitInfo & SPELL_HIT_TYPE_CRIT) procEx |= PROC_EX_CRITICAL_HIT; else procEx |= PROC_EX_NORMAL_HIT; } return procEx; } void Unit::ProcDamageAndSpellFor(bool isVictim, Unit* target, uint32 procFlag, uint32 procExtra, WeaponAttackType attType, SpellInfo const* procSpellInfo, uint32 damage, SpellInfo const* procAura, int8 procAuraEffectIndex, Spell const* procSpell, DamageInfo* damageInfo, HealInfo* healInfo, uint32 procPhase) { // Player is loaded now - do not allow passive spell casts to proc if (IsPlayer() && ToPlayer()->GetSession()->PlayerLoading()) return; // For melee/ranged based attack need update skills and set some Aura states if victim present if (procFlag & MELEE_BASED_TRIGGER_MASK && target && procPhase == PROC_SPELL_PHASE_HIT) { // Xinef: Shaman in ghost wolf form cant proc anything melee based if (!isVictim && GetShapeshiftForm() == FORM_GHOSTWOLF) return; // Update skills here for players // only when you are not fighting other players or their pets/totems (pvp) if (IsPlayer() && !target->IsCharmedOwnedByPlayerOrPlayer()) { // On melee based hit/miss/resist/parry/dodge need to update skill (for victim and attacker) if (procExtra & (PROC_EX_NORMAL_HIT | PROC_EX_MISS | PROC_EX_RESIST | PROC_EX_PARRY | PROC_EX_DODGE)) { ToPlayer()->UpdateCombatSkills(target, attType, isVictim, procSpell ? procSpell->m_weaponItem : nullptr); } // Update defence if player is victim and we block - TODO: confirm that blocked attacks only have a chance to increase defence skill else if (isVictim && procExtra & (PROC_EX_BLOCK)) { ToPlayer()->UpdateCombatSkills(target, attType, true); } } // If exist crit/parry/dodge/block need update aura state (for victim and attacker) if (procExtra & (PROC_EX_CRITICAL_HIT | PROC_EX_PARRY | PROC_EX_DODGE | PROC_EX_BLOCK)) { // for victim if (isVictim) { // if victim and dodge attack if (procExtra & PROC_EX_DODGE) { // Update AURA_STATE on dodge if (!IsClass(CLASS_ROGUE, CLASS_CONTEXT_ABILITY_REACTIVE)) // skip Rogue Riposte { ModifyAuraState(AURA_STATE_DEFENSE, true); StartReactiveTimer(REACTIVE_DEFENSE); } } // if victim and parry attack if (procExtra & PROC_EX_PARRY) { // For Hunters only Counterattack (skip Mongoose bite) if (IsClass(CLASS_HUNTER, CLASS_CONTEXT_ABILITY_REACTIVE)) { ModifyAuraState(AURA_STATE_HUNTER_PARRY, true); StartReactiveTimer(REACTIVE_HUNTER_PARRY); } else { ModifyAuraState(AURA_STATE_DEFENSE, true); StartReactiveTimer(REACTIVE_DEFENSE); } } // if and victim block attack if (procExtra & PROC_EX_BLOCK) { ModifyAuraState(AURA_STATE_DEFENSE, true); StartReactiveTimer(REACTIVE_DEFENSE); } } else // For attacker { // Overpower on victim dodge if (procExtra & PROC_EX_DODGE) { if (IsClass(CLASS_WARRIOR, CLASS_CONTEXT_ABILITY_REACTIVE)) { AddComboPoints(target, 1); StartReactiveTimer(REACTIVE_OVERPOWER); } } // Wolverine Bite if ((procExtra & PROC_HIT_CRITICAL) && IsHunterPet()) { AddComboPoints(target, 1); StartReactiveTimer(REACTIVE_WOLVERINE_BITE); } } } } Unit* actor = isVictim ? target : this; Unit* actionTarget = !isVictim ? target : this; ProcEventInfo eventInfo = ProcEventInfo(actor, actionTarget, target, procFlag, 0, procPhase, procExtra, procSpell, damageInfo, healInfo, procAura, procAuraEffectIndex); ProcTriggeredList procTriggered; // Fill procTriggered list for (AuraApplicationMap::const_iterator itr = GetAppliedAuras().begin(); itr != GetAppliedAuras().end(); ++itr) { // Do not allow auras to proc from effect triggered by itself if (procAura && procAura->Id == itr->first) continue; // Xinef: Generic Item Equipment cooldown, -1 is a special marker if (itr->second->GetBase()->GetCastItemGUID() && HasSpellItemCooldown(itr->first, uint32(-1))) continue; ProcTriggeredData triggerData(itr->second->GetBase()); // Defensive procs are active on absorbs (so absorption effects are not a hindrance) bool active = damage || (procExtra & PROC_EX_BLOCK && isVictim); if (isVictim) procExtra &= ~PROC_EX_INTERNAL_REQ_FAMILY; SpellInfo const* spellProto = itr->second->GetBase()->GetSpellInfo(); // only auras that have trigger spell should proc from fully absorbed damage if (procExtra & PROC_EX_ABSORB && isVictim) if (damage || spellProto->Effects[EFFECT_0].TriggerSpell || spellProto->Effects[EFFECT_1].TriggerSpell || spellProto->Effects[EFFECT_2].TriggerSpell) active = true; // xinef: fix spell procing from damaging / healing casts if spell has DoT / HoT effect only // only player spells are taken into account if (!active && !isVictim && !(procFlag & PROC_FLAG_DONE_PERIODIC) && procSpellInfo && procSpellInfo->SpellFamilyName && (procSpellInfo->HasAura(SPELL_AURA_PERIODIC_DAMAGE) || procSpellInfo->HasAura(SPELL_AURA_PERIODIC_HEAL))) active = true; // AuraScript Hook if (!triggerData.aura->CallScriptCheckProcHandlers(itr->second, eventInfo)) { continue; } bool isTriggeredAtSpellProcEvent = IsTriggeredAtSpellProcEvent(target, triggerData.aura, attType, isVictim, active, triggerData.spellProcEvent, eventInfo); // AuraScript Hook if (!triggerData.aura->CallScriptAfterCheckProcHandlers(itr->second, eventInfo, isTriggeredAtSpellProcEvent)) { continue; } // do checks using conditions table ConditionList conditions = sConditionMgr->GetConditionsForNotGroupedEntry(CONDITION_SOURCE_TYPE_SPELL_PROC, spellProto->Id); ConditionSourceInfo condInfo = ConditionSourceInfo(eventInfo.GetActor(), eventInfo.GetActionTarget()); if (!sConditionMgr->IsObjectMeetToConditions(condInfo, conditions)) { continue; } bool hasTriggeredProc = false; for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) { if (itr->second->HasEffect(i)) { AuraEffect* aurEff = itr->second->GetBase()->GetEffect(i); // Skip this auras if (isNonTriggerAura[aurEff->GetAuraType()]) continue; // If not trigger by default and spellProcEvent == nullptr - skip if (!isTriggerAura[aurEff->GetAuraType()] && !triggerData.spellProcEvent) continue; switch (aurEff->GetAuraType()) { case SPELL_AURA_PROC_TRIGGER_SPELL: case SPELL_AURA_MANA_SHIELD: case SPELL_AURA_DUMMY: case SPELL_AURA_PROC_TRIGGER_SPELL_WITH_VALUE: if (uint32 triggerSpellId = aurEff->GetSpellInfo()->Effects[i].TriggerSpell) { triggerData.triggerSpelId[i] = triggerSpellId; hasTriggeredProc = true; } break; default: break; } // Some spells must always trigger //if (isAlwaysTriggeredAura[aurEff->GetAuraType()]) triggerData.effMask |= 1 << i; } } if (triggerData.effMask) { // If there is aura that triggers another proc aura, make sure that the triggered one is going to be proccessed on top of it if (hasTriggeredProc) { bool proccessed = false; for (uint8 i = 0; i < EFFECT_ALL; ++i) { if (uint32 triggeredSpellId = triggerData.triggerSpelId[i]) { auto iter = std::find(procTriggered.begin(), procTriggered.end(), triggeredSpellId); if (iter != procTriggered.end()) { std::advance(iter, 1); procTriggered.insert(iter, triggerData); proccessed = true; break; } } } if (!proccessed) { procTriggered.push_front(triggerData); } } else { procTriggered.push_front(triggerData); } } } // Nothing found if (procTriggered.empty()) return; // Note: must SetCantProc(false) before return if (procExtra & (PROC_EX_INTERNAL_TRIGGERED | PROC_EX_INTERNAL_CANT_PROC)) SetCantProc(true); // Handle effects proceed this time for (ProcTriggeredList::const_iterator i = procTriggered.begin(); i != procTriggered.end(); ++i) { // look for aura in auras list, it may be removed while proc event processing if (i->aura->IsRemoved()) continue; bool useCharges = i->aura->IsUsingCharges(); // no more charges to use, prevent proc if (useCharges && !i->aura->GetCharges()) continue; bool takeCharges = false; SpellInfo const* spellInfo = i->aura->GetSpellInfo(); AuraApplication* aurApp = i->aura->GetApplicationOfTarget(GetGUID()); bool prepare = i->aura->CallScriptPrepareProcHandlers(aurApp, eventInfo); // For players set spell cooldown if need uint32 cooldown = 0; if (prepare && i->spellProcEvent && i->spellProcEvent->cooldown) cooldown = i->spellProcEvent->cooldown; // Xinef: set cooldown for actual proc eventInfo.SetProcCooldown(cooldown); // Note: must SetCantProc(false) before return if (spellInfo->HasAttribute(SPELL_ATTR3_INSTANT_TARGET_PROCS)) SetCantProc(true); bool handled = i->aura->CallScriptProcHandlers(aurApp, eventInfo); // "handled" is needed as long as proc can be handled in multiple places if (!handled && HandleAuraProc(target, damage, i->aura, procSpellInfo, procFlag, procExtra, cooldown, &handled)) { uint32 Id = i->aura->GetId(); LOG_DEBUG("spells.aura", "ProcDamageAndSpell: casting spell {} (triggered with value by {} aura of spell {})", spellInfo->Id, (isVictim ? "a victim's" : "an attacker's"), Id); takeCharges = true; } if (!handled) for (uint8 effIndex = 0; effIndex < MAX_SPELL_EFFECTS; ++effIndex) { if (!(i->effMask & (1 << effIndex))) continue; AuraEffect* triggeredByAura = i->aura->GetEffect(effIndex); ASSERT(triggeredByAura); bool prevented = i->aura->CallScriptEffectProcHandlers(triggeredByAura, aurApp, eventInfo); if (prevented) { takeCharges = true; continue; } switch (triggeredByAura->GetAuraType()) { case SPELL_AURA_PROC_TRIGGER_SPELL: { LOG_DEBUG("spells.aura", "ProcDamageAndSpell: casting spell {} (triggered by {} aura of spell {})", spellInfo->Id, (isVictim ? "a victim's" : "an attacker's"), triggeredByAura->GetId()); // Don`t drop charge or add cooldown for not started trigger if (HandleProcTriggerSpell(target, damage, triggeredByAura, procSpellInfo, procFlag, procExtra, cooldown, procPhase, eventInfo)) takeCharges = true; break; } case SPELL_AURA_PROC_TRIGGER_DAMAGE: { // target has to be valid if (!eventInfo.GetProcTarget()) break; triggeredByAura->HandleProcTriggerDamageAuraProc(aurApp, eventInfo); // this function is part of the new proc system takeCharges = true; break; } case SPELL_AURA_MANA_SHIELD: case SPELL_AURA_DUMMY: { LOG_DEBUG("spells.aura", "ProcDamageAndSpell: casting spell id {} (triggered by {} dummy aura of spell {})", spellInfo->Id, (isVictim ? "a victim's" : "an attacker's"), triggeredByAura->GetId()); if (HandleDummyAuraProc(target, damage, triggeredByAura, procSpellInfo, procFlag, procExtra, cooldown, eventInfo)) takeCharges = true; break; } case SPELL_AURA_OBS_MOD_POWER: case SPELL_AURA_MOD_SPELL_CRIT_CHANCE: case SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN: case SPELL_AURA_MOD_MELEE_HASTE: LOG_DEBUG("spells.aura", "ProcDamageAndSpell: casting spell id {} (triggered by {} aura of spell {})", spellInfo->Id, isVictim ? "a victim's" : "an attacker's", triggeredByAura->GetId()); takeCharges = true; break; case SPELL_AURA_OVERRIDE_CLASS_SCRIPTS: { LOG_DEBUG("spells.aura", "ProcDamageAndSpell: casting spell id {} (triggered by {} aura of spell {})", spellInfo->Id, (isVictim ? "a victim's" : "an attacker's"), triggeredByAura->GetId()); if (HandleOverrideClassScriptAuraProc(target, damage, triggeredByAura, procSpellInfo, cooldown)) takeCharges = true; break; } case SPELL_AURA_RAID_PROC_FROM_CHARGE_WITH_VALUE: { LOG_DEBUG("spells.aura", "ProcDamageAndSpell: casting mending (triggered by {} dummy aura of spell {})", (isVictim ? "a victim's" : "an attacker's"), triggeredByAura->GetId()); if (damage > 0) { HandleAuraRaidProcFromChargeWithValue(triggeredByAura); takeCharges = true; } break; } case SPELL_AURA_RAID_PROC_FROM_CHARGE: { LOG_DEBUG("spells.aura", "ProcDamageAndSpell: casting mending (triggered by {} dummy aura of spell {})", (isVictim ? "a victim's" : "an attacker's"), triggeredByAura->GetId()); HandleAuraRaidProcFromCharge(triggeredByAura); takeCharges = true; break; } case SPELL_AURA_PROC_TRIGGER_SPELL_WITH_VALUE: { LOG_DEBUG("spells.aura", "ProcDamageAndSpell: casting spell {} (triggered with value by {} aura of spell {})", spellInfo->Id, (isVictim ? "a victim's" : "an attacker's"), triggeredByAura->GetId()); if (HandleProcTriggerSpell(target, damage, triggeredByAura, procSpellInfo, procFlag, procExtra, cooldown, procPhase, eventInfo)) takeCharges = true; break; } case SPELL_AURA_MOD_CASTING_SPEED_NOT_STACK: // Skip melee hits or instant cast spells // xinef: check channeled spells which are affected by haste also if (procSpellInfo && (procSpellInfo->SpellFamilyName || !IsPlayer()) && (procSpellInfo->CalcCastTime() > 0 /*|| (procSpell->IsChanneled() && procSpell->GetDuration() > 0 && (HasAuraTypeWithAffectMask(SPELL_AURA_PERIODIC_HASTE, procSpell) || procSpell->HasAttribute(SPELL_ATTR5_SPELL_HASTE_AFFECTS_PERIODIC)))*/)) takeCharges = true; break; case SPELL_AURA_REFLECT_SPELLS_SCHOOL: // Skip Melee hits and spells ws wrong school if (procSpellInfo && (triggeredByAura->GetMiscValue() & procSpellInfo->SchoolMask)) // School check takeCharges = true; break; case SPELL_AURA_SPELL_MAGNET: // Skip Melee hits and targets with magnet aura if (procSpellInfo && (triggeredByAura->GetBase()->GetUnitOwner()->ToUnit() == ToUnit())) // Magnet takeCharges = true; break; case SPELL_AURA_MOD_POWER_COST_SCHOOL_PCT: case SPELL_AURA_MOD_POWER_COST_SCHOOL: // Skip melee hits and spells ws wrong school or zero cost if (procSpellInfo && (procSpellInfo->ManaCost != 0 || procSpellInfo->ManaCostPercentage != 0 || (procSpellInfo->SpellFamilyFlags[1] & 0x2)) && // Cost check, mutilate include (triggeredByAura->GetMiscValue() & procSpellInfo->SchoolMask)) // School check takeCharges = true; break; case SPELL_AURA_MECHANIC_IMMUNITY: case SPELL_AURA_MOD_MECHANIC_RESISTANCE: // Compare mechanic if (procSpellInfo && procSpellInfo->Mechanic == uint32(triggeredByAura->GetMiscValue())) takeCharges = true; break; case SPELL_AURA_MOD_DAMAGE_FROM_CASTER: // Compare casters if (target && triggeredByAura->GetCasterGUID() == target->GetGUID()) takeCharges = true; break; // CC Auras which use their amount amount to drop // Are there any more auras which need this? case SPELL_AURA_MOD_CONFUSE: case SPELL_AURA_MOD_FEAR: case SPELL_AURA_MOD_STUN: case SPELL_AURA_MOD_ROOT: case SPELL_AURA_TRANSFORM: { // Spell own direct damage at apply wont break the CC // Xinef: Or when the aura is at full duration (assume that such auras should be added at the end, skipping all damage procs etc.) if (procSpellInfo) if ((!i->aura->IsPermanent() && i->aura->GetDuration() == i->aura->GetMaxDuration()) || procSpellInfo->Id == triggeredByAura->GetId() || procSpellInfo->HasAttribute(SPELL_ATTR4_REACTIVE_DAMAGE_PROC)) break; // chargeable mods are breaking on hit if (useCharges) takeCharges = true; else if (triggeredByAura->GetAmount()) // aura must have amount { int32 damageLeft = triggeredByAura->GetAmount(); // No damage left if (damageLeft < int32(damage)) i->aura->Remove(); else triggeredByAura->SetAmount(damageLeft - damage); } break; } case SPELL_AURA_ABILITY_IGNORE_AURASTATE: if (procSpellInfo && procSpellInfo->Id == 20647) // hack for warriors execute, both dummy and damage spell are affected by ignore aurastate aura break; takeCharges = true; break; case SPELL_AURA_ADD_FLAT_MODIFIER: case SPELL_AURA_ADD_PCT_MODIFIER: { if (triggeredByAura->GetSpellModifier()) { // Do proc if mod is consumed by spell if (!procSpell || procSpell->m_appliedMods.find(i->aura) != procSpell->m_appliedMods.end()) { takeCharges = true; } } break; } default: takeCharges = true; break; } i->aura->CallScriptAfterEffectProcHandlers(triggeredByAura, aurApp, eventInfo); } // Remove charge (aura can be removed by triggers) // xinef: take into account attribute6 of proc spell if (prepare && useCharges && takeCharges) if (!procSpellInfo || isVictim || !procSpellInfo->HasAttribute(SPELL_ATTR6_DO_NOT_CONSUME_RESOURCES)) i->aura->DropCharge(); i->aura->CallScriptAfterProcHandlers(aurApp, eventInfo); if (spellInfo->HasAttribute(SPELL_ATTR3_INSTANT_TARGET_PROCS)) SetCantProc(false); } // Cleanup proc requirements if (procExtra & (PROC_EX_INTERNAL_TRIGGERED | PROC_EX_INTERNAL_CANT_PROC)) SetCantProc(false); } void Unit::GetProcAurasTriggeredOnEvent(std::list& aurasTriggeringProc, std::list* procAuras, ProcEventInfo eventInfo) { // use provided list of auras which can proc if (procAuras) { for (std::list::iterator itr = procAuras->begin(); itr != procAuras->end(); ++itr) { ASSERT((*itr)->GetTarget() == this); if (!(*itr)->GetRemoveMode()) if ((*itr)->GetBase()->IsProcTriggeredOnEvent(*itr, eventInfo)) { (*itr)->GetBase()->PrepareProcToTrigger(*itr, eventInfo); aurasTriggeringProc.push_back(*itr); } } } // or generate one on our own else { for (AuraApplicationMap::iterator itr = GetAppliedAuras().begin(); itr != GetAppliedAuras().end(); ++itr) { if (itr->second->GetBase()->IsProcTriggeredOnEvent(itr->second, eventInfo)) { itr->second->GetBase()->PrepareProcToTrigger(itr->second, eventInfo); aurasTriggeringProc.push_back(itr->second); } } } } void Unit::TriggerAurasProcOnEvent(CalcDamageInfo& damageInfo) { DamageInfo dmgInfo = DamageInfo(damageInfo); TriggerAurasProcOnEvent(nullptr, nullptr, damageInfo.target, damageInfo.procAttacker, damageInfo.procVictim, 0, 0, damageInfo.procEx, nullptr, &dmgInfo, nullptr); } void Unit::TriggerAurasProcOnEvent(std::list* myProcAuras, std::list* targetProcAuras, Unit* actionTarget, uint32 typeMaskActor, uint32 typeMaskActionTarget, uint32 spellTypeMask, uint32 spellPhaseMask, uint32 hitMask, Spell* spell, DamageInfo* damageInfo, HealInfo* healInfo) { // prepare data for self trigger ProcEventInfo myProcEventInfo = ProcEventInfo(this, actionTarget, actionTarget, typeMaskActor, spellTypeMask, spellPhaseMask, hitMask, spell, damageInfo, healInfo); std::list myAurasTriggeringProc; GetProcAurasTriggeredOnEvent(myAurasTriggeringProc, myProcAuras, myProcEventInfo); // prepare data for target trigger ProcEventInfo targetProcEventInfo = ProcEventInfo(this, actionTarget, this, typeMaskActionTarget, spellTypeMask, spellPhaseMask, hitMask, spell, damageInfo, healInfo); std::list targetAurasTriggeringProc; if (typeMaskActionTarget) GetProcAurasTriggeredOnEvent(targetAurasTriggeringProc, targetProcAuras, targetProcEventInfo); TriggerAurasProcOnEvent(myProcEventInfo, myAurasTriggeringProc); if (typeMaskActionTarget) TriggerAurasProcOnEvent(targetProcEventInfo, targetAurasTriggeringProc); } void Unit::TriggerAurasProcOnEvent(ProcEventInfo& eventInfo, std::list& aurasTriggeringProc) { for (std::list::iterator itr = aurasTriggeringProc.begin(); itr != aurasTriggeringProc.end(); ++itr) { if (!(*itr)->GetRemoveMode()) (*itr)->GetBase()->TriggerProcOnEvent(*itr, eventInfo); } } Player* Unit::GetSpellModOwner() const { if (Player* player = const_cast(this)->ToPlayer()) { return player; } if (Unit* owner = GetOwner()) { if (Player* player = owner->ToPlayer()) { return player; } } // Special handling for Eye of Kilrogg if (GetEntry() == NPC_EYE_OF_KILROGG) { if (TempSummon const* tempSummon = ToTempSummon()) { if (Unit* summoner = tempSummon->GetSummonerUnit()) { return summoner->ToPlayer(); } } } return nullptr; } ///----------Pet responses methods----------------- void Unit::SendPetActionFeedback(uint8 msg) const { Unit* owner = GetOwner(); if (!owner || !owner->IsPlayer()) return; WorldPacket data(SMSG_PET_ACTION_FEEDBACK, 1); data << uint8(msg); owner->ToPlayer()->SendDirectMessage(&data); } void Unit::SendPetActionSound(PetAction action) const { SendMessageToSet(WorldPackets::Pet::PetActionSound(GetGUID(), static_cast(action)).Write(), false); } void Unit::SendPetDismissSound() const { if (CreatureDisplayInfoEntry const* displayInfo = sCreatureDisplayInfoStore.LookupEntry(GetNativeDisplayId())) SendMessageToSet(WorldPackets::Pet::PetDismissSound(static_cast(displayInfo->ModelId), GetPosition()).Write(), false); } void Unit::SendPetAIReaction(ObjectGuid guid) const { Unit* owner = GetOwner(); if (!owner || !owner->IsPlayer()) return; WorldPacket data(SMSG_AI_REACTION, 8 + 4); data << guid; data << uint32(AI_REACTION_HOSTILE); owner->ToPlayer()->SendDirectMessage(&data); } ///----------End of Pet responses methods---------- 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()) return; if (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*/) { if (slot >= MAX_MOTION_SLOT) return; if (MovementGenerator* movementGenerator = GetMotionMaster()->GetMotionSlot(slot)) movementGenerator->Pause(timer); StopMoving(); } void Unit::ResumeMovement(uint32 timer /* = 0*/, uint8 slot /* = 0*/) { if (slot >= MAX_MOTION_SLOT) return; if (MovementGenerator* movementGenerator = GetMotionMaster()->GetMotionSlot(slot)) movementGenerator->Resume(timer); } void Unit::StopMovingOnCurrentPos() { ClearUnitState(UNIT_STATE_MOVING); // not need send any packets if not in world if (!IsInWorld()) return; DisableSpline(); // pussywizard: required so Launch() won't recalculate position from previous spline Movement::MoveSplineInit init(this); init.MoveTo(GetPositionX(), GetPositionY(), GetPositionZ()); init.SetFacing(GetOrientation()); init.Launch(); } void Unit::SendMovementFlagUpdate(bool self /* = false */) { if (IsRooted()) { // each case where this occurs has to be examined and reported and dealt with. LOG_ERROR("Unit", "Attempted sending heartbeat with root flag for guid {}", GetGUID().ToString()); return; } WorldPacket data; BuildHeartBeatMsg(&data); SendMessageToSet(&data, self); } bool Unit::IsSitState() const { uint8 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 { uint8 s = getStandState(); return !IsSitState() && s != UNIT_STAND_STATE_SLEEP && s != UNIT_STAND_STATE_KNEEL; } bool Unit::IsStandUpOnMovementState() const { uint8 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 || s == UNIT_STAND_STATE_SLEEP; } void Unit::SetStandState(uint8 state) { SetByteValue(UNIT_FIELD_BYTES_1, UNIT_BYTES_1_OFFSET_STAND_STATE, state); if (IsStandState()) RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_NOT_SEATED); if (IsPlayer()) { WorldPacket data(SMSG_STANDSTATE_UPDATE, 1); data << (uint8)state; ToPlayer()->SendDirectMessage(&data); } } bool Unit::IsPolymorphed() const { uint32 transformId = getTransForm(); if (!transformId) return false; SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(transformId); 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 = IsPlayer() ? 0.1f : 0.01f; SetObjectScale(std::max(scale, scaleMin)); } void Unit::SetDisplayId(uint32 modelId, float displayScale /*=1.f*/) { SetUInt32Value(UNIT_FIELD_DISPLAYID, modelId); // Set Gender by modelId if (CreatureModelInfo const* minfo = sObjectMgr->GetCreatureModelInfo(modelId)) SetByteValue(UNIT_FIELD_BYTES_0, 2, minfo->gender); SetObjectScale(displayScale); sScriptMgr->OnDisplayIdChange(this, modelId); } void Unit::RestoreDisplayId() { AuraEffect* handledAura = nullptr; AuraEffect* handledAuraForced = 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.rbegin(); i != transforms.rend(); ++i) { if (AuraApplication const* aurApp = (*i)->GetBase()->GetApplicationOfTarget(GetGUID())) { if (!handledAura) handledAura = (*i); // xinef: prefer negative/forced auras if ((*i)->GetSpellInfo()->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES) || !aurApp->IsPositive()) { handledAuraForced = (*i); break; } } } } // Xinef: include clone auras (eg mirror images) if (!handledAuraForced && !handledAura) { Unit::AuraEffectList const& cloneAuras = GetAuraEffectsByType(SPELL_AURA_CLONE_CASTER); if (!cloneAuras.empty()) for (Unit::AuraEffectList::const_iterator i = cloneAuras.begin(); i != cloneAuras.end(); ++i) handledAura = *i; } AuraEffectList const& shapeshiftAura = GetAuraEffectsByType(SPELL_AURA_MOD_SHAPESHIFT); // xinef: order of execution is important! // first forced transform auras, then shapeshifts, then normal transform // transform aura was found if (handledAuraForced) { handledAuraForced->HandleEffect(this, AURA_EFFECT_HANDLE_SEND_FOR_CLIENT, true); return; } else if (!shapeshiftAura.empty()) // we've found shapeshift { // only one such aura possible at a time if (uint32 modelId = GetModelForForm(GetShapeshiftForm(), shapeshiftAura.front()->GetId())) { SetDisplayId(modelId); return; } } else if (handledAura) { handledAura->HandleEffect(this, AURA_EFFECT_HANDLE_SEND_FOR_CLIENT, true); return; } // no auras found - set modelid to default SetDisplayId(GetNativeDisplayId()); } void Unit::AddComboPoints(Unit* target, int8 count) { if (!count) { return; } if (target && target != m_comboTarget) { if (m_comboTarget) { m_comboTarget->RemoveComboPointHolder(this); } m_comboTarget = target; m_comboPoints = count; target->AddComboPointHolder(this); } else { m_comboPoints = std::max(std::min(m_comboPoints + count, 5), 0); } SendComboPoints(); } void Unit::ClearComboPoints() { if (!m_comboTarget) { return; } // remove Premed-like effects // (NB: this Aura retains the CP while it's active - now that CP have reset, it shouldn't be there anymore) RemoveAurasByType(SPELL_AURA_RETAIN_COMBO_POINTS); m_comboPoints = 0; SendComboPoints(); m_comboTarget->RemoveComboPointHolder(this); m_comboTarget = nullptr; } void Unit::SendComboPoints() { if (m_cleanupDone) { return; } PackedGuid const packGUID = m_comboTarget ? m_comboTarget->GetPackGUID() : PackedGuid(); if (Player* playerMe = ToPlayer()) { WorldPacket data(SMSG_UPDATE_COMBO_POINTS, packGUID.size() + 1); data << packGUID; data << uint8(m_comboPoints); playerMe->SendDirectMessage(&data); } ObjectGuid ownerGuid = GetCharmerOrOwnerGUID(); Player* owner = nullptr; if (ownerGuid.IsPlayer()) { owner = ObjectAccessor::GetPlayer(*this, ownerGuid); } if (m_movedByPlayer || owner) { WorldPacket data(SMSG_PET_UPDATE_COMBO_POINTS, GetPackGUID().size() + packGUID.size() + 1); data << GetPackGUID(); data << packGUID; data << uint8(m_comboPoints); if (m_movedByPlayer) m_movedByPlayer->ToPlayer()->SendDirectMessage(&data); if (owner && owner != m_movedByPlayer) owner->SendDirectMessage(&data); } } void Unit::ClearComboPointHolders() { while (!m_ComboPointHolders.empty()) { (*m_ComboPointHolders.begin())->ClearComboPoints(); // this also removes it from m_comboPointHolders } } void Unit::ClearAllReactives() { for (uint8 i = 0; i < MAX_REACTIVE; ++i) m_reactiveTimer[i] = 0; if (HasAuraState(AURA_STATE_DEFENSE)) ModifyAuraState(AURA_STATE_DEFENSE, false); if (IsClass(CLASS_HUNTER, CLASS_CONTEXT_ABILITY_REACTIVE) && HasAuraState(AURA_STATE_HUNTER_PARRY)) ModifyAuraState(AURA_STATE_HUNTER_PARRY, false); if (IsClass(CLASS_WARRIOR, CLASS_CONTEXT_ABILITY_REACTIVE) && IsPlayer()) ClearComboPoints(); } 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_DEFENSE)) ModifyAuraState(AURA_STATE_DEFENSE, false); break; case REACTIVE_HUNTER_PARRY: if (IsClass(CLASS_HUNTER, CLASS_CONTEXT_ABILITY_REACTIVE) && HasAuraState(AURA_STATE_HUNTER_PARRY)) ModifyAuraState(AURA_STATE_HUNTER_PARRY, false); break; case REACTIVE_OVERPOWER: if (IsClass(CLASS_WARRIOR, CLASS_CONTEXT_ABILITY_REACTIVE)) { ClearComboPoints(); } break; case REACTIVE_WOLVERINE_BITE: if (IsHunterPet()) ClearComboPoints(); break; default: break; } } else { m_reactiveTimer[reactive] -= p_time; } } } Unit* Unit::SelectNearbyTarget(Unit* exclude, float dist) const { std::list targets; Acore::AnyUnfriendlyUnitInObjectRangeCheck u_check(this, this, dist); Acore::UnitListSearcher searcher(this, targets, u_check); Cell::VisitObjects(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) || !IsValidAttackTarget(*tIter)) { std::list::iterator tIter2 = tIter; ++tIter; targets.erase(tIter2); } else ++tIter; } // no appropriate targets if (targets.empty()) return nullptr; // select random return Acore::Containers::SelectRandomContainerElement(targets); } Unit* Unit::SelectNearbyNoTotemTarget(Unit* exclude, float dist) const { std::list targets; Acore::AnyUnfriendlyNoTotemUnitInObjectRangeCheck u_check(this, this, dist); Acore::UnitListSearcher searcher(this, targets, u_check); Cell::VisitObjects(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) || !IsValidAttackTarget(*tIter)) { std::list::iterator tIter2 = tIter; ++tIter; targets.erase(tIter2); } else ++tIter; } // no appropriate targets if (targets.empty()) return nullptr; // select random return Acore::Containers::SelectRandomContainerElement(targets); } 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 amount = GetFloatValue(UNIT_FIELD_BASEATTACKTIME + AsUnderlyingType(att)); float remainingTimePct = std::max((float)m_attackTimer[att], 0.0f) / (GetAttackTime(att) * m_modAttackSpeedPct[att]); if (val > 0.f) { ApplyPercentModFloatVar(m_modAttackSpeedPct[att], val, !apply); ApplyPercentModFloatVar(amount, val, !apply); } else { ApplyPercentModFloatVar(m_modAttackSpeedPct[att], -val, apply); ApplyPercentModFloatVar(amount, -val, apply); } SetFloatValue(UNIT_FIELD_BASEATTACKTIME + AsUnderlyingType(att), amount); m_attackTimer[att] = uint32(GetAttackTime(att) * m_modAttackSpeedPct[att] * remainingTimePct); } void Unit::ApplyCastTimePercentMod(float val, bool apply) { float amount = GetFloatValue(UNIT_MOD_CAST_SPEED); if (val > 0.f) ApplyPercentModFloatVar(amount, val, !apply); else ApplyPercentModFloatVar(amount, -val, apply); SetFloatValue(UNIT_MOD_CAST_SPEED, amount); } uint32 Unit::GetCastingTimeForBonus(SpellInfo const* spellProto, DamageEffectType damagetype, uint32 CastingTime) const { // Not apply this to creature casted spells with casttime == 0 if (CastingTime == 0 && IsCreature() && !IsPet()) return 3500; if (CastingTime > 7000) CastingTime = 7000; if (CastingTime < 1500) CastingTime = 1500; if (damagetype == DOT && !spellProto->IsChanneled()) CastingTime = 3500; int32 overTime = 0; uint8 effects = 0; bool DirectDamage = false; bool AreaEffect = false; for (uint32 i = 0; i < MAX_SPELL_EFFECTS; i++) { switch (spellProto->Effects[i].Effect) { case SPELL_EFFECT_SCHOOL_DAMAGE: case SPELL_EFFECT_POWER_DRAIN: case SPELL_EFFECT_HEALTH_LEECH: case SPELL_EFFECT_ENVIRONMENTAL_DAMAGE: case SPELL_EFFECT_POWER_BURN: case SPELL_EFFECT_HEAL: DirectDamage = true; break; case SPELL_EFFECT_APPLY_AURA: switch (spellProto->Effects[i].ApplyAuraName) { case SPELL_AURA_PERIODIC_DAMAGE: case SPELL_AURA_PERIODIC_HEAL: case SPELL_AURA_PERIODIC_LEECH: if (spellProto->GetDuration()) overTime = spellProto->GetDuration(); break; default: // -5% per additional effect ++effects; break; } default: break; } if (spellProto->Effects[i].IsTargetingArea()) AreaEffect = true; } // Combined Spells with Both Over Time and Direct Damage if (overTime > 0 && DirectDamage) { // mainly for DoTs which are 3500 here otherwise uint32 OriginalCastTime = spellProto->CalcCastTime(); if (OriginalCastTime > 7000) OriginalCastTime = 7000; if (OriginalCastTime < 1500) OriginalCastTime = 1500; // Portion to Over Time float PtOT = (overTime / 15000.0f) / ((overTime / 15000.0f) + (OriginalCastTime / 3500.0f)); if (damagetype == DOT) CastingTime = uint32(CastingTime * PtOT); else if (PtOT < 1.0f) CastingTime = uint32(CastingTime * (1 - PtOT)); else CastingTime = 0; } // Area Effect Spells receive only half of bonus if (AreaEffect) CastingTime /= 2; // 50% for damage and healing spells for leech spells from damage bonus and 0% from healing for (uint8 j = 0; j < MAX_SPELL_EFFECTS; ++j) { if (spellProto->Effects[j].Effect == SPELL_EFFECT_HEALTH_LEECH || (spellProto->Effects[j].Effect == SPELL_EFFECT_APPLY_AURA && spellProto->Effects[j].ApplyAuraName == SPELL_AURA_PERIODIC_LEECH)) { CastingTime /= 2; break; } } // -5% of total per any additional effect for (uint8 i = 0; i < effects; ++i) CastingTime *= 0.95f; return CastingTime; } void Unit::UpdateAuraForGroup(uint8 slot) { if (slot >= MAX_AURAS) // slot not found, return return; if (Player* player = ToPlayer()) { if (player->GetGroup()) { player->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_AURAS); player->SetAuraUpdateMaskForRaid(slot); } } else if (IsCreature() && IsPet()) { Pet* pet = ((Pet*)this); if (pet->isControlled()) { Unit* owner = GetOwner(); if (owner && (owner->IsPlayer()) && owner->ToPlayer()->GetGroup()) { owner->ToPlayer()->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_PET_AURAS); pet->SetAuraUpdateMaskForRaid(slot); } } } } float Unit::CalculateDefaultCoefficient(SpellInfo const* spellInfo, DamageEffectType damagetype) const { // Damage over Time spells bonus calculation float DotFactor = 1.0f; if (damagetype == DOT) { int32 DotDuration = spellInfo->GetDuration(); if (!spellInfo->IsChanneled() && DotDuration > 0) DotFactor = DotDuration / 15000.0f; if (uint32 DotTicks = spellInfo->GetMaxTicks()) DotFactor /= DotTicks; } int32 CastingTime = spellInfo->IsChanneled() ? spellInfo->GetDuration() : spellInfo->CalcCastTime(); // Distribute Damage over multiple effects, reduce by AoE CastingTime = GetCastingTimeForBonus(spellInfo, damagetype, CastingTime); // As wowwiki says: C = (Cast Time / 3.5) return (CastingTime / 3500.0f) * DotFactor; } float Unit::GetAPMultiplier(WeaponAttackType attType, bool normalized) { if (!normalized || !IsPlayer()) return float(GetAttackTime(attType)) / 1000.0f; Item* Weapon = ToPlayer()->GetWeaponForAttack(attType, true); if (!Weapon) return 2.4f; // fist attack switch (Weapon->GetTemplate()->InventoryType) { case INVTYPE_2HWEAPON: return 3.3f; case INVTYPE_RANGED: case INVTYPE_RANGEDRIGHT: case INVTYPE_THROWN: return 2.8f; case INVTYPE_WEAPON: case INVTYPE_WEAPONMAINHAND: case INVTYPE_WEAPONOFFHAND: default: return Weapon->GetTemplate()->SubClass == ITEM_SUBCLASS_WEAPON_DAGGER ? 1.7f : 2.4f; } } bool Unit::IsUnderLastManaUseEffect() const { return getMSTimeDiff(m_lastManaUse, GameTime::GetGameTimeMS().count()) < 5000; } void Unit::SetContestedPvP(Player* attackedPlayer, bool lookForNearContestedGuards) { Player* player = GetCharmerOrOwnerPlayerOrPlayerItself(); if (!player || ((attackedPlayer && (attackedPlayer == player || (player->duel && player->duel->Opponent == attackedPlayer))) || player->InBattleground())) return; // check if there any guards that should care about the contested flag on player if (lookForNearContestedGuards) { std::list targets; Acore::NearestVisibleDetectableContestedGuardUnitCheck u_check(this); Acore::UnitListSearcher searcher(this, targets, u_check); Cell::VisitObjects(this, searcher, MAX_AGGRO_RADIUS); // return if there are no contested guards found if (!targets.size()) { return; } } player->SetContestedPvPTimer(30000); if (!player->HasUnitState(UNIT_STATE_ATTACK_PLAYER)) { player->AddUnitState(UNIT_STATE_ATTACK_PLAYER); player->SetPlayerFlag(PLAYER_FLAGS_CONTESTED_PVP); // call MoveInLineOfSight for nearby contested guards AddToNotify(NOTIFY_AI_RELOCATION); } if (!HasUnitState(UNIT_STATE_ATTACK_PLAYER)) { AddUnitState(UNIT_STATE_ATTACK_PLAYER); // call MoveInLineOfSight for nearby contested guards AddToNotify(NOTIFY_AI_RELOCATION); } } void Unit::SetCantProc(bool apply) { if (apply) ++m_procDeep; else { ASSERT(m_procDeep); --m_procDeep; } } void Unit::AddPetAura(PetAura const* petSpell) { if (!IsPlayer()) return; m_petAuras.insert(petSpell); if (Pet* pet = ToPlayer()->GetPet()) pet->CastPetAura(petSpell); else if (Unit* charm = GetCharm()) charm->CastPetAura(petSpell); } void Unit::RemovePetAura(PetAura const* petSpell) { if (!IsPlayer()) return; m_petAuras.erase(petSpell); if (Pet* pet = ToPlayer()->GetPet()) pet->RemoveAurasDueToSpell(petSpell->GetAura(pet->GetEntry())); if (Unit* charm = GetCharm()) charm->RemoveAurasDueToSpell(petSpell->GetAura(charm->GetEntry())); } void Unit::CastPetAura(PetAura const* aura) { uint32 auraId = aura->GetAura(GetEntry()); if (!auraId) return; if (auraId == 35696) // Demonic Knowledge { int32 basePoints = aura->GetDamage(); CastCustomSpell(this, auraId, &basePoints, nullptr, nullptr, true); } else CastSpell(this, auraId, true); } bool Unit::IsPetAura(Aura const* aura) { Unit* owner = GetOwner(); if (!owner || !owner->IsPlayer()) return false; // if the owner has that pet aura, return true for (PetAura const* petAura : owner->m_petAuras) if (petAura->GetAura(GetEntry()) == aura->GetId()) return true; return false; } Pet* Unit::CreateTamedPetFrom(Creature* creatureTarget, uint32 spell_id) { if (!IsPlayer()) return nullptr; Pet* pet = new Pet(ToPlayer(), HUNTER_PET); if (!pet->CreateBaseAtCreature(creatureTarget)) { delete pet; return nullptr; } uint8 level = creatureTarget->GetLevel() + 5 < GetLevel() ? (GetLevel() - 5) : creatureTarget->GetLevel(); if (!InitTamedPet(pet, level, spell_id)) { delete pet; return nullptr; } return pet; } Pet* Unit::CreateTamedPetFrom(uint32 creatureEntry, uint32 spell_id) { if (!IsPlayer()) 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(); if (petStable.CurrentPet || petStable.GetUnslottedHunterPet()) return false; pet->SetCreatorGUID(GetGUID()); pet->SetFaction(GetFaction()); pet->SetUInt32Value(UNIT_CREATED_BY_SPELL, spell_id); if (IsPlayer()) pet->ReplaceAllUnitFlags(UNIT_FLAG_PLAYER_CONTROLLED); if (!pet->InitStatsForLevel(level)) { LOG_ERROR("entities.unit", "Pet::InitStatsForLevel() failed for creature (Entry: {})!", pet->GetEntry()); return false; } pet->GetCharmInfo()->SetPetNumber(sObjectMgr->GeneratePetNumber(), true); // this enables pet details window (Shift+P) pet->InitPetCreateSpells(); pet->SetFullHealth(); pet->FillPetInfo(&petStable.CurrentPet.emplace()); return true; } bool Unit::IsTriggeredAtSpellProcEvent(Unit* victim, Aura* aura, WeaponAttackType attType, bool isVictim, bool active, SpellProcEventEntry const*& spellProcEvent, ProcEventInfo const& eventInfo) { SpellInfo const* spellProto = aura->GetSpellInfo(); SpellInfo const* procSpell = eventInfo.GetSpellInfo(); // let the aura be handled by new proc system if it has new entry if (sSpellMgr->GetSpellProcEntry(spellProto->Id)) return false; // Get proc Event Entry spellProcEvent = sSpellMgr->GetSpellProcEvent(spellProto->Id); // Get EventProcFlag uint32 EventProcFlag; if (spellProcEvent && spellProcEvent->procFlags) // if exist get custom spellProcEvent->procFlags EventProcFlag = spellProcEvent->procFlags; else EventProcFlag = spellProto->ProcFlags; // else get from spell proto // Continue if no trigger exist if (!EventProcFlag) return false; // Additional checks for triggered spells (ignore trap casts) if (eventInfo.GetHitMask() & PROC_EX_INTERNAL_TRIGGERED && !(EventProcFlag & PROC_FLAG_DONE_TRAP_ACTIVATION)) { if (!spellProto->HasAttribute(SPELL_ATTR3_CAN_PROC_FROM_PROCS)) return false; } // Xinef: additional check for player auras - only player spells can trigger player proc auras // Xinef: skip victim auras // Excluded player shoot spells // Excluded player item spells if (!isVictim && IsPlayer() && !(EventProcFlag & (PROC_FLAG_KILL | PROC_FLAG_DEATH))) { if (procSpell && procSpell->SpellFamilyName == SPELLFAMILY_GENERIC && procSpell->GetCategory() != 76 && (!eventInfo.GetProcSpell() || !eventInfo.GetProcSpell()->m_CastItem) && (!eventInfo.GetTriggerAuraSpell() || eventInfo.GetTriggerAuraSpell()->SpellFamilyName == SPELLFAMILY_GENERIC)) { return false; } } // Check spellProcEvent data requirements if (!sSpellMgr->IsSpellProcEventCanTriggeredBy(spellProto, spellProcEvent, EventProcFlag, eventInfo, active)) return false; // In most cases req get honor or XP from kill if (EventProcFlag & PROC_FLAG_KILL && IsPlayer()) { bool allow = false; if (victim) allow = ToPlayer()->isHonorOrXPTarget(victim); // Shadow Word: Death - can trigger from every kill if (aura->GetId() == 32409 || aura->GetId() == 18372 || aura->GetId() == 18213) allow = true; if (!allow) return false; } // Aura added by spell can`t trigger from self (prevent drop charges/do triggers) // But except periodic and kill triggers (can triggered from self) if (procSpell && procSpell->Id == spellProto->Id && !(spellProto->ProcFlags & (PROC_FLAG_TAKEN_PERIODIC | PROC_FLAG_KILL))) return false; // Check if current equipment allows aura to proc if (!isVictim && IsPlayer() && !spellProto->HasAttribute(SPELL_ATTR3_NO_PROC_EQUIP_REQUIREMENT)) { Player* player = ToPlayer(); if (spellProto->EquippedItemClass == ITEM_CLASS_WEAPON) { Item* item = nullptr; if (attType == BASE_ATTACK) item = player->GetUseableItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND); else if (attType == OFF_ATTACK) item = player->GetUseableItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND); else item = player->GetUseableItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_RANGED); if (player->IsInFeralForm()) return false; if (!item || item->IsBroken() || item->GetTemplate()->Class != ITEM_CLASS_WEAPON || !((1 << item->GetTemplate()->SubClass) & spellProto->EquippedItemSubClassMask)) return false; } else if (spellProto->EquippedItemClass == ITEM_CLASS_ARMOR) { // Check if player is wearing shield Item* item = player->GetUseableItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND); if (!item || item->IsBroken() || item->GetTemplate()->Class != ITEM_CLASS_ARMOR || !((1 << item->GetTemplate()->SubClass) & spellProto->EquippedItemSubClassMask)) return false; } } // Get chance from spell float chance = float(spellProto->ProcChance); // If in spellProcEvent exist custom chance, chance = spellProcEvent->customChance; if (spellProcEvent && spellProcEvent->customChance) chance = spellProcEvent->customChance; // If PPM exist calculate chance from PPM if (spellProcEvent && spellProcEvent->ppmRate != 0) { uint32 attackSpeed = 0; Unit* attacker = nullptr; if (!isVictim) attacker = this; else if (victim) attacker = victim; if (attacker) { if (!procSpell || procSpell->DmgClass == SPELL_DAMAGE_CLASS_MELEE || procSpell->IsRangedWeaponSpell()) { attackSpeed = attacker->GetAttackTime(attType); } else //spells user their casttime for ppm calculations { if (procSpell->CastTimeEntry) attackSpeed = procSpell->CastTimeEntry->CastTime; //instants and fast spells use 1.5s castspeed if (attackSpeed < 1500) attackSpeed = 1500; } } chance = GetPPMProcChance(attackSpeed, spellProcEvent->ppmRate, spellProto); } // Custom chances switch (spellProto->SpellFamilyName) { case SPELLFAMILY_WARRIOR: { // Recklessness, allow to proc only once for whirlwind if (spellProto->Id == 1719 && procSpell && procSpell->Id == 44949) return false; } } if (eventInfo.GetProcChance()) { chance = *eventInfo.GetProcChance(); } // Apply chance modifer aura if (Player* modOwner = GetSpellModOwner()) { modOwner->ApplySpellMod(spellProto->Id, SPELLMOD_CHANCE_OF_SUCCESS, chance); } return roll_chance_f(chance); } bool Unit::HandleAuraRaidProcFromChargeWithValue(AuraEffect* triggeredByAura) { // aura can be deleted at casts SpellInfo const* spellProto = triggeredByAura->GetSpellInfo(); int32 heal = triggeredByAura->GetAmount(); ObjectGuid caster_guid = triggeredByAura->GetCasterGUID(); // Currently only Prayer of Mending if (!(spellProto->SpellFamilyName == SPELLFAMILY_PRIEST && spellProto->SpellFamilyFlags[1] & 0x20)) { LOG_DEBUG("spells.aura", "Unit::HandleAuraRaidProcFromChargeWithValue, received not handled spell: {}", spellProto->Id); return false; } // jumps int32 jumps = triggeredByAura->GetBase()->GetCharges() - 1; // current aura expire triggeredByAura->GetBase()->SetCharges(1); // will removed at next charges decrease // next target selection if (jumps > 0) { if (Unit* caster = triggeredByAura->GetCaster()) { // smart healing float radius = triggeredByAura->GetSpellInfo()->Effects[triggeredByAura->GetEffIndex()].CalcRadius(caster); std::list nearMembers; Player* player = nullptr; if (IsPlayer()) player = ToPlayer(); else if (GetOwner()) player = GetOwner()->ToPlayer(); if (player) { Group* group = player->GetGroup(); if (!group) { if (player != this) { if (IsWithinDistInMap(player, radius)) nearMembers.push_back(player); } else if (Unit* pet = GetGuardianPet()) { if (IsWithinDistInMap(pet, radius)) nearMembers.push_back(pet); } } else { for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) if (Player* Target = itr->GetSource()) { if (Target != this && !IsWithinDistInMap(Target, radius)) continue; // IsHostileTo check duel and controlled by enemy if (Target != this && Target->IsAlive() && !IsHostileTo(Target)) nearMembers.push_back(Target); // Push player's pet to vector if (Unit* pet = Target->GetGuardianPet()) if (pet != this && pet->IsAlive() && IsWithinDistInMap(pet, radius) && !IsHostileTo(pet)) nearMembers.push_back(pet); } } if (!nearMembers.empty()) { nearMembers.sort(Acore::HealthPctOrderPred()); if (Unit* target = nearMembers.front()) { CastSpell(target, 41637 /*Dummy visual effect triggered by main spell cast*/, true); CastCustomSpell(target, spellProto->Id, &heal, nullptr, nullptr, true, nullptr, triggeredByAura, caster_guid); if (Aura* aura = target->GetAura(spellProto->Id, caster->GetGUID())) aura->SetCharges(jumps); } } } } } // heal CastCustomSpell(this, 33110, &heal, nullptr, nullptr, true, nullptr, nullptr, caster_guid); return true; } bool Unit::HandleAuraRaidProcFromCharge(AuraEffect* triggeredByAura) { // aura can be deleted at casts SpellInfo const* spellProto = triggeredByAura->GetSpellInfo(); uint32 damageSpellId; switch (spellProto->Id) { case 57949: // shiver damageSpellId = 57952; //animationSpellId = 57951; dummy effects for jump spell have unknown use (see also 41637) break; case 59978: // shiver damageSpellId = 59979; break; case 43593: // Cold Stare damageSpellId = 43594; break; default: LOG_ERROR("entities.unit", "Unit::HandleAuraRaidProcFromCharge, received unhandled spell: {}", spellProto->Id); return false; } ObjectGuid caster_guid = triggeredByAura->GetCasterGUID(); // jumps int32 jumps = triggeredByAura->GetBase()->GetCharges() - 1; // current aura expire triggeredByAura->GetBase()->SetCharges(1); // will removed at next charges decrease // next target selection if (jumps > 0) { if (Unit* caster = triggeredByAura->GetCaster()) { float radius = triggeredByAura->GetSpellInfo()->Effects[triggeredByAura->GetEffIndex()].CalcRadius(caster); if (Unit* target = GetNextRandomRaidMemberOrPet(radius)) { CastSpell(target, spellProto, true, nullptr, triggeredByAura, caster_guid); if (Aura* aura = target->GetAura(spellProto->Id, caster->GetGUID())) aura->SetCharges(jumps); } } } CastSpell(this, damageSpellId, true, nullptr, triggeredByAura, caster_guid); return true; } void Unit::Kill(Unit* killer, Unit* victim, bool durabilityLoss, WeaponAttackType attackType, SpellInfo const* spellProto, Spell const* spell /*= nullptr*/) { // Prevent killing unit twice (and giving reward from kill twice) if (!victim->GetHealth()) return; if (killer && !killer->IsInMap(victim)) killer = nullptr; // find player: owner of controlled `this` or `this` itself maybe Player* player = killer ? killer->GetCharmerOrOwnerPlayerOrPlayerItself() : nullptr; Creature* creature = victim->ToCreature(); bool isRewardAllowed = true; if (creature) { isRewardAllowed = (creature->IsDamageEnoughForLootingAndReward() && !creature->IsLootRewardDisabled()); if (!isRewardAllowed) creature->SetLootRecipient(nullptr); } // pussywizard: remade this if section (player is on the same map if (isRewardAllowed && creature) { Player* lr = creature->GetLootRecipient(); if (lr && lr->IsInMap(creature)) player = creature->GetLootRecipient(); else if (Group* lrg = creature->GetLootRecipientGroup()) for (GroupReference* itr = lrg->GetFirstMember(); itr != nullptr; itr = itr->next()) if (Player* member = itr->GetSource()) if (member->IsAtLootRewardDistance(creature)) { player = member; break; } } // 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 && player && player != victim) { WorldPacket data(SMSG_PARTYKILLLOG, (8 + 8)); // send event PARTY_KILL data << player->GetGUID(); // player with killing blow data << victim->GetGUID(); // victim Player* looter = player; Group* group = player->GetGroup(); bool hasLooterGuid = false; if (group) { group->BroadcastPacket(&data, group->GetMemberGroup(player->GetGUID())); if (creature) { group->UpdateLooterGuid(creature, true); if (group->GetLooterGuid() && group->GetLootMethod() != FREE_FOR_ALL) { looter = ObjectAccessor::FindPlayer(group->GetLooterGuid()); if (looter) { hasLooterGuid = true; creature->SetLootRecipient(looter); // update creature loot recipient to the allowed looter. } } } } else { player->SendDirectMessage(&data); if (creature) { WorldPacket data2(SMSG_LOOT_LIST, 8 + 1 + 1); data2 << creature->GetGUID(); data2 << uint8(0); // unk1 data2 << uint8(0); // no group looter player->SendMessageToSet(&data2, true); } } // Generate loot before updating looter if (creature) { Loot* loot = &creature->loot; loot->clear(); if (uint32 lootid = creature->GetCreatureTemplate()->lootid) loot->FillLoot(lootid, LootTemplates_Creature, looter, false, false, creature->GetLootMode(), creature); if (creature->GetLootMode()) loot->generateMoneyLoot(creature->GetCreatureTemplate()->mingold, creature->GetCreatureTemplate()->maxgold); if (group) { if (hasLooterGuid) group->SendLooter(creature, looter); else group->SendLooter(creature, nullptr); // Update round robin looter only if the creature had loot if (!creature->loot.empty()) group->UpdateLooterGuid(creature); } } player->RewardPlayerAndGroupAtKill(victim, false); } // 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 (killer && (killer->IsPet() || killer->IsTotem())) if (Unit* owner = killer->GetOwner()) { Unit::ProcDamageAndSpell(owner, victim, PROC_FLAG_KILL, PROC_FLAG_NONE, PROC_EX_NONE, 0, attackType, spellProto, nullptr, -1, spell); sScriptMgr->OnPlayerCreatureKilledByPet( killer->GetCharmerOrOwnerPlayerOrPlayerItself(), victim->ToCreature()); } if (killer != victim) { Unit::ProcDamageAndSpell(killer, victim, killer ? PROC_FLAG_KILL : 0, PROC_FLAG_KILLED, PROC_EX_NONE, 0, attackType, spellProto, nullptr, -1, spell); } // Proc auras on death - must be before aura/combat remove Unit::ProcDamageAndSpell(victim, nullptr, PROC_FLAG_DEATH, PROC_FLAG_NONE, PROC_EX_NONE, 0, attackType, spellProto, nullptr, -1, spell); // 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 (killer) if (Player* killerPlayer = killer->GetCharmerOrOwnerPlayerOrPlayerItself()) killerPlayer->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_GET_KILLING_BLOWS, 1, 0, victim); // Spirit of Redemption // if talent known but not triggered (check priest class for speedup check) bool spiritOfRedemption = false; if (victim->IsPlayer() && victim->IsClass(CLASS_PRIEST, CLASS_CONTEXT_ABILITY) && !victim->ToPlayer()->HasPlayerFlag(PLAYER_FLAGS_IS_OUT_OF_BOUNDS)) { if (AuraEffect* aurEff = victim->GetAuraEffectDummy(20711)) { // Xinef: aura_spirit_of_redemption is triggered by 27827 shapeshift if (victim->HasSpiritOfRedemptionAura() || victim->HasAura(27827)) { /*LOG_INFO("misc", "Player ({}) died with spirit of redemption. Killer (Entry: {}, Name: {}), Map: {}, x: {}, y: {}, z: {}", victim->GetGUID().ToString(), killer ? killer->GetEntry() : 1, killer ? killer->GetName() : "", victim->GetMapId(), victim->GetPositionX(), victim->GetPositionY(), victim->GetPositionZ()); ACE_Stack_Trace trace(0, 50); LOG_INFO("misc", "TRACE: {}\n\n", trace);*/ } else { // save value before aura remove uint32 ressSpellId = victim->GetUInt32Value(PLAYER_SELF_RES_SPELL); if (!ressSpellId) ressSpellId = victim->ToPlayer()->GetResurrectionSpellId(); //Remove all expected to remove at death auras (most important negative case like DoT or periodic triggers) victim->RemoveAllAurasOnDeath(); // Stop attacks victim->CombatStop(); victim->getHostileRefMgr().deleteReferences(); // restore for use at real death victim->SetUInt32Value(PLAYER_SELF_RES_SPELL, ressSpellId); // FORM_SPIRITOFREDEMPTION and related auras victim->CastSpell(victim, 27827, true, nullptr, aurEff); spiritOfRedemption = true; } } } if (!spiritOfRedemption) { LOG_DEBUG("entities.unit", "SET DeathState::JustDied"); victim->setDeathState(DeathState::JustDied); } // Inform pets (if any) when player kills target) // MUST come after victim->setDeathState(DeathState::JustDied); or pet next target // selection will get stuck on same target and break pet react state if (player) { Pet* pet = player->GetPet(); if (pet && pet->IsAlive() && pet->isControlled()) pet->AI()->KilledUnit(victim); } // 10% durability loss on death // clean InHateListOf 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 && !plrVictim->InBattleground()) || (player && sWorld->getBoolConfig(CONFIG_DURABILITY_LOSS_IN_PVP))) { LOG_DEBUG("entities.unit", "We are dead, losing {} percent durability", sWorld->getRate(RATE_DURABILITY_LOSS_ON_DEATH) / 100.0f); plrVictim->DurabilityLossAll(sWorld->getRate(RATE_DURABILITY_LOSS_ON_DEATH) / 100.0f, false); // durability lost message plrVictim->SendDurabilityLoss(); } // Call KilledUnit for creatures if (killer && killer->IsCreature() && killer->IsAIEnabled) killer->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 { LOG_DEBUG("entities.unit", "DealDamageNotPlayer"); if (!creature->IsPet() && creature->GetLootMode() > 0) { creature->GetThreatMgr().ClearAllThreat(); // must be after setDeathState which resets dynamic flags if (!creature->loot.isLooted()) { creature->SetDynamicFlag(UNIT_DYNFLAG_LOOTABLE); } else { creature->AllLootRemovedFromCorpse(); } } // Call KilledUnit for creatures, this needs to be called after the lootable flag is set if (killer && killer->IsCreature() && killer->IsAIEnabled) killer->ToCreature()->AI()->KilledUnit(victim); // Call creature just died function if (CreatureAI* ai = creature->AI()) { ai->JustDied(killer); } if (TempSummon* summon = creature->ToTempSummon()) { if (WorldObject* summoner = summon->GetSummoner()) { if (summoner->ToCreature() && summoner->ToCreature()->IsAIEnabled) { summoner->ToCreature()->AI()->SummonedCreatureDies(creature, killer); } else if (summoner->ToGameObject() && summoner->ToGameObject()->AI()) { summoner->ToGameObject()->AI()->SummonedCreatureDies(creature, killer); } } } // Dungeon specific stuff, only applies to players killing creatures if (creature->GetInstanceId()) { Map* instanceMap = creature->GetMap(); //Player* creditedPlayer = GetCharmerOrOwnerPlayerOrPlayerItself(); /// @todo: do instance binding anyway if the charmer/owner is offline if (instanceMap->IsDungeon() && player) if (instanceMap->IsRaidOrHeroicDungeon()) if (creature->HasFlagsExtra(CREATURE_FLAG_EXTRA_INSTANCE_BIND)) instanceMap->ToInstanceMap()->PermBindAllPlayers(); } } // 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 && killer != victim) { if (OutdoorPvP* pvp = player->GetOutdoorPvP()) pvp->HandleKill(player, victim); if (Battlefield* bf = sBattlefieldMgr->GetBattlefieldToZoneId(player->GetZoneId())) bf->HandleKill(player, victim); } //if (victim->IsPlayer()) // 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 (player) if (Battleground* bg = player->GetBattleground()) { if (victim->IsPlayer()) bg->HandleKillPlayer(victim->ToPlayer(), player); else bg->HandleKillUnit(victim->ToCreature(), player); } // achievement stuff if (killer && victim->IsPlayer()) { if (killer->IsCreature()) victim->ToPlayer()->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_KILLED_BY_CREATURE, killer->GetEntry()); else if (victim != killer && killer->IsPlayer()) victim->ToPlayer()->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_KILLED_BY_PLAYER, 1, killer->ToPlayer()->GetTeamId()); } // Hook for OnPVPKill Event if (killer) { if (Player* killerPlr = killer->ToPlayer()) { if (Player* killedPlr = victim->ToPlayer()) sScriptMgr->OnPlayerPVPKill(killerPlr, killedPlr); else if (Creature* killedCre = victim->ToCreature()) sScriptMgr->OnPlayerCreatureKill(killerPlr, killedCre); } else if (Creature* killerCre = killer->ToCreature()) { if (Player* killed = victim->ToPlayer()) sScriptMgr->OnPlayerKilledByCreature(killerCre, killed); } } sScriptMgr->OnUnitDeath(victim, killer); } void Unit::SetControlled(bool apply, UnitState state, Unit* source /*= nullptr*/, bool isFear /*= false*/) { if (apply) { if (HasUnitState(state)) return; 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)) { ClearUnitState(UNIT_STATE_MELEE_ATTACKING); SendMeleeAttackStop(); // SendAutoRepeatCancel ? SetConfused(true); CastStop(0, false); } break; case UNIT_STATE_FLEEING: if (!HasUnitState(UNIT_STATE_STUNNED | UNIT_STATE_CONFUSED)) { ClearUnitState(UNIT_STATE_MELEE_ATTACKING); SendMeleeAttackStop(); // SendAutoRepeatCancel ? SetFeared(true, source, isFear); CastStop(0, false); } break; default: break; } if (IsPlayer()) { sScriptMgr->AnticheatSetRootACKUpd(ToPlayer()); } } else { // xinef: moved from below, checked all SetX functions, no calls to currently modified state // xinef: added to each case because of return //ClearUnitState(state); switch (state) { case UNIT_STATE_STUNNED: if (HasStunAura()) return; ClearUnitState(state); SetStunned(false); break; case UNIT_STATE_ROOT: // Prevent creature_template_movement rooted flag from being removed on aura expiration. if (IsCreature()) { if (ToCreature()->GetCreatureTemplate()->Movement.Rooted) { return; } } if (HasRootAura() || GetVehicle()) return; ClearUnitState(state); SetRooted(false); break; case UNIT_STATE_CONFUSED: if (HasConfuseAura()) return; ClearUnitState(state); SetConfused(false); break; case UNIT_STATE_FLEEING: if (HasFearAura()) return; ClearUnitState(state); SetFeared(false); break; default: return; } //ClearUnitState(state); if (HasUnitState(UNIT_STATE_STUNNED) || HasStunAura()) SetStunned(true); else { if (HasUnitState(UNIT_STATE_ROOT) || HasRootAura()) SetRooted(true); if (HasUnitState(UNIT_STATE_CONFUSED) || HasConfuseAura()) SetConfused(true); else if (HasUnitState(UNIT_STATE_FLEEING) || HasFearAura()) { bool isFear = false; if (HasFearAura()) { isFear = true; source = ObjectAccessor::GetUnit(*this, GetAuraEffectsByType(SPELL_AURA_MOD_FEAR).front()->GetCasterGUID()); } if (!source) { source = getAttackerForHelper(); } SetFeared(true, source, isFear); } } } } void Unit::SetStunned(bool apply) { if (IsInFlight()) return; if (apply) { SetTarget(); SetUnitFlag(UNIT_FLAG_STUNNED); if (IsPlayer()) { SetStandState(UNIT_STAND_STATE_STAND); } SetRooted(true, true); CastStop(); } else { if (IsAlive() && GetVictim()) SetTarget(GetVictim()->GetGUID()); if (IsCreature()) { // don't remove UNIT_FLAG_STUNNED for pet when owner is mounted (disabled pet's interface) Unit* owner = GetOwner(); if (!owner || !owner->IsPlayer() || !owner->ToPlayer()->IsMounted()) RemoveUnitFlag(UNIT_FLAG_STUNNED); // Xinef: same for charmed npcs owner = GetCharmer(); if (!owner || !owner->IsPlayer() || !owner->ToPlayer()->IsMounted()) RemoveUnitFlag(UNIT_FLAG_STUNNED); } else RemoveUnitFlag(UNIT_FLAG_STUNNED); if (!HasUnitState(UNIT_STATE_ROOT)) // prevent moving if it also has root effect { SetRooted(false, true); } } } void Unit::SetRooted(bool apply, bool stun, bool logout) { const uint32 state = (stun ? (logout ? UNIT_STATE_LOGOUT_TIMER : UNIT_STATE_STUNNED) : UNIT_STATE_ROOT); if (apply) { AddUnitState(state); SendMoveRoot(true); } else { ClearUnitState(state); // Prevent giving ability to move if more immobilizers are active if (!IsImmobilizedState()) SendMoveRoot(false); } } void Unit::SendMoveRoot(bool apply) { const Player* client = GetClientControlling(); // Apply flags in-place when unit currently is not controlled by a player if (!client) { if (apply) { m_movementInfo.RemoveMovementFlag(MOVEMENTFLAG_MASK_MOVING_FLY); m_movementInfo.AddMovementFlag(MOVEMENTFLAG_ROOT); if (!client) StopMoving(); } else m_movementInfo.RemoveMovementFlag(MOVEMENTFLAG_ROOT); } if (!IsInWorld()) return; const PackedGuid& guid = GetPackGUID(); // Wrath+ spline root: when unit is currently not controlled by a player if (!client) { WorldPacket data(apply ? SMSG_SPLINE_MOVE_ROOT : SMSG_SPLINE_MOVE_UNROOT, guid.size()); data << guid; SendMessageToSet(&data, true); } // Wrath+ force root: when unit is controlled by a player else { uint32 const counter = client->GetSession()->GetOrderCounter(); WorldPacket data(apply ? SMSG_FORCE_MOVE_ROOT : SMSG_FORCE_MOVE_UNROOT, guid.size() + 4); data << guid; data << counter; client->GetSession()->SendPacket(&data); client->GetSession()->IncrementOrderCounter(); } } void Unit::DisableRotate(bool apply) { if (!IsCreature()) return; if (apply) SetUnitFlag(UNIT_FLAG_POSSESSED); else if (!HasUnitState(UNIT_STATE_POSSESSED)) RemoveUnitFlag(UNIT_FLAG_POSSESSED); } void Unit::SetFeared(bool apply, Unit* fearedBy /*= nullptr*/, bool isFear /*= false*/) { if (apply) { SetTarget(); GetMotionMaster()->MoveFleeing(fearedBy, isFear ? 0 : sWorld->getIntConfig(CONFIG_CREATURE_FAMILY_FLEE_DELAY)); } else { if (IsAlive()) { if (GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_CONTROLLED) == FLEEING_MOTION_TYPE) { GetMotionMaster()->MovementExpired(); StopMoving(); } if (GetVictim()) SetTarget(GetVictim()->GetGUID()); } } // xinef: block / allow control to real mover (eg. charmer) if (IsPlayer()) { if (m_movedByPlayer) m_movedByPlayer->ToPlayer()->SetClientControl(this, !apply); // verified //else // ToPlayer()->SetClientControl(this, !apply); } } void Unit::SetConfused(bool apply) { if (apply) { SetTarget(); GetMotionMaster()->MoveConfused(); } else { if (IsAlive()) { if (GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_CONTROLLED) == CONFUSED_MOTION_TYPE) { GetMotionMaster()->MovementExpired(); StopMoving(); } if (GetVictim()) SetTarget(GetVictim()->GetGUID()); } } // xinef: block / allow control to real mover (eg. charmer) if (IsPlayer()) { if (m_movedByPlayer) m_movedByPlayer->ToPlayer()->SetClientControl(this, !apply); // verified //else // ToPlayer()->SetClientControl(this, !apply); } } bool Unit::SetCharmedBy(Unit* charmer, CharmType type, AuraApplication const* aurApp) { if (!charmer) return false; if (!charmer->IsInWorld() || charmer->IsDuringRemoveFromWorld()) { return false; } // dismount players when charmed if (IsPlayer() && type != CHARM_TYPE_POSSESS) RemoveAurasByType(SPELL_AURA_MOUNTED); if (charmer->IsPlayer()) charmer->RemoveAurasByType(SPELL_AURA_MOUNTED); ASSERT(type != CHARM_TYPE_POSSESS || charmer->IsPlayer()); if (type == CHARM_TYPE_VEHICLE && !IsVehicle()) // pussywizard throw 1; ASSERT((type == CHARM_TYPE_VEHICLE) == IsVehicle()); LOG_DEBUG("entities.unit", "SetCharmedBy: charmer {} ({}), charmed {} ({}), type {}.", charmer->GetEntry(), charmer->GetGUID().ToString(), GetEntry(), GetGUID().ToString(), uint32(type)); if (this == charmer) { LOG_FATAL("entities.unit", "Unit::SetCharmedBy: Unit {} ({}) is trying to charm itself!", GetEntry(), GetGUID().ToString()); return false; } //if (HasUnitState(UNIT_STATE_UNATTACKABLE)) // return false; if (IsPlayer() && ToPlayer()->GetTransport()) { LOG_FATAL("entities.unit", "Unit::SetCharmedBy: Player on transport is trying to charm {} ({})", GetEntry(), GetGUID().ToString()); return false; } // Already charmed if (GetCharmerGUID()) { LOG_FATAL("entities.unit", "Unit::SetCharmedBy: {} ({}) has already been charmed but {} ({}) is trying to charm it!", GetEntry(), GetGUID().ToString(), charmer->GetEntry(), charmer->GetGUID().ToString()); return false; } CastStop(); AttackStop(); // Xinef: dont reset threat and combat, put them on offline list, moved down after faction changes // CombatStop(); /// @todo: CombatStop(true) may cause crash (interrupt spells) // DeleteThreatList(); Player* playerCharmer = charmer->ToPlayer(); // Charmer stop charming if (playerCharmer) { playerCharmer->StopCastingCharm(aurApp ? aurApp->GetBase() : nullptr); playerCharmer->StopCastingBindSight(aurApp ? aurApp->GetBase() : nullptr); } // Charmed stop charming if (IsPlayer()) { ToPlayer()->StopCastingCharm(aurApp ? aurApp->GetBase() : nullptr); ToPlayer()->StopCastingBindSight(aurApp ? aurApp->GetBase() : nullptr); } // StopCastingCharm may remove a possessed pet? if (!IsInWorld()) { LOG_FATAL("entities.unit", "Unit::SetCharmedBy: {} ({}) is not in world but {} ({}) is trying to charm it!", GetEntry(), GetGUID().ToString(), charmer->GetEntry(), 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()); // Set charmed charmer->SetCharm(this, true); StopAttackingInvalidTarget(); if (IsCreature()) { GetMotionMaster()->Clear(false); GetMotionMaster()->MoveIdle(); StopMoving(); if (charmer->IsPlayer() && charmer->IsClass(CLASS_WARLOCK, CLASS_CONTEXT_PET_CHARM) && ToCreature()->GetCreatureTemplate()->type == CREATURE_TYPE_DEMON) { // Disable CreatureAI/SmartAI and switch to CharmAI when charmed by warlock Creature* charmed = ToCreature(); charmed->NeedChangeAI = true; charmed->IsAIEnabled = false; } else { ToCreature()->AI()->OnCharmed(true); } // Xinef: If creature can fly, add normal player flying flag (fixes speed) if (charmer->IsPlayer() && ToCreature()->CanFly()) AddUnitMovementFlag(MOVEMENTFLAG_FLYING); } else { Player* player = ToPlayer(); if (player->isAFK()) player->ToggleAFK(); player->SetClientControl(this, false); // verified } // 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; // Pets already have a properly initialized CharmInfo, don't overwrite it. // Xinef: I need charmInfo for vehicle if (/*type != CHARM_TYPE_VEHICLE &&*/ !GetCharmInfo()) { InitCharmInfo(); if (type == CHARM_TYPE_POSSESS) GetCharmInfo()->InitPossessCreateSpells(); else if (type != CHARM_TYPE_VEHICLE) { GetCharmInfo()->InitCharmCreateSpells(); // Xinef: convert charm npcs dont have pet bar so initialize them as defensive helpers if (type == CHARM_TYPE_CONVERT && IsCreature()) ToCreature()->SetReactState(REACT_DEFENSIVE); } } if (playerCharmer) { switch (type) { case CHARM_TYPE_VEHICLE: SetUnitFlag(UNIT_FLAG_POSSESSED); AddUnitState(UNIT_STATE_NO_ENVIRONMENT_UPD); playerCharmer->SetClientControl(this, true); // verified playerCharmer->VehicleSpellInitialize(); break; case CHARM_TYPE_POSSESS: AddUnitState(UNIT_STATE_POSSESSED); AddUnitState(UNIT_STATE_NO_ENVIRONMENT_UPD); SetUnitFlag(UNIT_FLAG_POSSESSED); charmer->SetUnitFlag(UNIT_FLAG_DISABLE_MOVE); playerCharmer->SetClientControl(this, true); // verified playerCharmer->PossessSpellInitialize(); break; case CHARM_TYPE_CHARM: if (IsCreature() && charmer->IsClass(CLASS_WARLOCK, CLASS_CONTEXT_PET_CHARM)) { CreatureTemplate const* cinfo = ToCreature()->GetCreatureTemplate(); if (cinfo && cinfo->type == CREATURE_TYPE_DEMON) { // to prevent client crash SetByteValue(UNIT_FIELD_BYTES_0, 1, (uint8)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 SetUInt32Value(UNIT_FIELD_PET_NAME_TIMESTAMP, uint32(GameTime::GetGameTime().count())); // cast can't be helped } } if (playerCharmer->m_seer != this) { GetMotionMaster()->MoveFollow(charmer, PET_FOLLOW_DIST, GetFollowAngle()); playerCharmer->CharmSpellInitialize(); } break; default: break; } } else if (IsPlayer()) RemoveAurasByType(SPELL_AURA_MOD_SHAPESHIFT); if (Creature* creature = ToCreature()) creature->RefreshSwimmingFlag(); if (IsPlayer()) sScriptMgr->OnPlayerBeingCharmed(ToPlayer(), charmer, _oldFactionId, charmer->GetFaction()); return true; } void Unit::RemoveCharmedBy(Unit* charmer) { if (!IsCharmed()) return; if (!charmer) charmer = GetCharmer(); if (charmer != GetCharmer()) // one aura overrides another? { // LOG_FATAL("entities.unit", "Unit::RemoveCharmedBy: this: {} true charmer: {} false charmer: {}", // GetGUID().ToString(), GetCharmerGUID().ToString(), charmer->GetGUID().ToString()); // ABORT(); return; } CharmType type; if (HasUnitState(UNIT_STATE_POSSESSED)) type = CHARM_TYPE_POSSESS; else if (charmer && charmer->IsOnVehicle(this)) type = CHARM_TYPE_VEHICLE; else type = CHARM_TYPE_CHARM; if (_oldFactionId) { SetFaction(_oldFactionId); _oldFactionId = 0; } else RestoreFaction(); CastStop(); AttackStop(); // xinef: update speed after charming UpdateSpeed(MOVE_RUN, false); // xinef: do not break any controlled motion slot if (GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_CONTROLLED) == NULL_MOTION_TYPE) { StopMoving(); GetMotionMaster()->MovementExpired(); } // xinef: if we have any controlled movement, clear active and idle only else GetMotionMaster()->MovementExpiredOnSlot(MOTION_SLOT_ACTIVE, false); GetMotionMaster()->InitDefault(); // xinef: remove stunned flag if owner was mounted if (IsCreature() && !HasUnitState(UNIT_STATE_STUNNED)) RemoveUnitFlag(UNIT_FLAG_STUNNED); // If charmer still exists if (!charmer) return; ASSERT(type != CHARM_TYPE_POSSESS || charmer->IsPlayer()); ASSERT(type != CHARM_TYPE_VEHICLE || (IsCreature() && IsVehicle())); charmer->SetCharm(this, false); StopAttackingInvalidTarget(); Player* playerCharmer = charmer->ToPlayer(); if (playerCharmer) { switch (type) { case CHARM_TYPE_VEHICLE: playerCharmer->SetClientControl(this, false); playerCharmer->SetClientControl(charmer, true); // verified RemoveUnitFlag(UNIT_FLAG_POSSESSED); ClearUnitState(UNIT_STATE_NO_ENVIRONMENT_UPD); break; case CHARM_TYPE_POSSESS: playerCharmer->SetClientControl(this, false); playerCharmer->SetClientControl(charmer, true); // verified charmer->RemoveUnitFlag(UNIT_FLAG_DISABLE_MOVE); RemoveUnitFlag(UNIT_FLAG_POSSESSED); ClearUnitState(UNIT_STATE_POSSESSED); ClearUnitState(UNIT_STATE_NO_ENVIRONMENT_UPD); break; case CHARM_TYPE_CHARM: if (IsCreature() && charmer->IsClass(CLASS_WARLOCK, CLASS_CONTEXT_PET_CHARM)) { CreatureTemplate const* cinfo = ToCreature()->GetCreatureTemplate(); if (cinfo && cinfo->type == CREATURE_TYPE_DEMON) { SetByteValue(UNIT_FIELD_BYTES_0, 1, uint8(cinfo->unit_class)); if (GetCharmInfo()) GetCharmInfo()->SetPetNumber(0, true); else LOG_ERROR("entities.unit", "Aura::HandleModCharm: target={} has a charm aura but no charm info!", GetGUID().ToString()); } } break; default: break; } } if (Player* player = ToPlayer()) { sScriptMgr->AnticheatSetUnderACKmount(player); } // xinef: restore threat for (CharmThreatMap::const_iterator itr = _charmThreatInfo.begin(); itr != _charmThreatInfo.end(); ++itr) { if (Unit* target = ObjectAccessor::GetUnit(*this, itr->first)) if (!IsFriendlyTo(target)) AddThreat(target, itr->second); } _charmThreatInfo.clear(); if (Creature* creature = ToCreature()) { // Vehicle should not attack its passenger after he exists the seat if (type != CHARM_TYPE_VEHICLE && charmer->IsAlive() && !charmer->IsFriendlyTo(creature)) if (Attack(charmer, true)) GetMotionMaster()->MoveChase(charmer); // Creature will restore its old AI on next update if (creature->AI()) creature->AI()->OnCharmed(false); // Xinef: Remove movement flag flying RemoveUnitMovementFlag(MOVEMENTFLAG_FLYING); } else ToPlayer()->SetClientControl(this, true); // verified // a guardian should always have charminfo if (playerCharmer && this != charmer->GetFirstControlled()) playerCharmer->SendRemoveControlBar(); // xinef: Always delete charm info (restores react state) if (IsPlayer() || (IsCreature() && !ToCreature()->IsGuardian())) DeleteCharmInfo(); } void Unit::RestoreFaction() { if (IsPlayer()) 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) { VehicleEntry const* vehInfo = sVehicleStore.LookupEntry(id); if (!vehInfo) return false; m_vehicleKit = new Vehicle(this, vehInfo, creatureEntry); m_updateFlag |= UPDATEFLAG_VEHICLE; m_unitTypeMask |= UNIT_MASK_VEHICLE; return true; } void Unit::RemoveVehicleKit() { if (!m_vehicleKit) return; m_vehicleKit->Uninstall(); delete m_vehicleKit; m_vehicleKit = nullptr; m_updateFlag &= ~UPDATEFLAG_VEHICLE; m_unitTypeMask &= ~UNIT_MASK_VEHICLE; RemoveNpcFlag(UNIT_NPC_FLAG_SPELLCLICK | UNIT_NPC_FLAG_PLAYER_VEHICLE); } Unit* Unit::GetVehicleBase() const { return m_vehicle ? m_vehicle->GetBase() : nullptr; } 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()->GetGUID(); 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->IsPlayer() && u2->IsPlayer()) return u1->ToPlayer()->IsInSameGroupWith(u2->ToPlayer()); // Xinef: we assume that npcs with the same faction are in party else if (u1->IsCreature() && u2->IsCreature() && !u1->IsControlledByPlayer() && !u2->IsControlledByPlayer()) return u1->GetFaction() == u2->GetFaction(); // Xinef: creature type_flag should work for party check only if player group is not a raid else if ((u2->IsPlayer() && u1->IsCreature() && (u1->ToCreature()->GetCreatureTemplate()->type_flags & CREATURE_TYPE_FLAG_TREAT_AS_RAID_UNIT) && u2->ToPlayer()->GetGroup() && !u2->ToPlayer()->GetGroup()->isRaidGroup()) || (u1->IsPlayer() && u2->IsCreature() && (u2->ToCreature()->GetCreatureTemplate()->type_flags & CREATURE_TYPE_FLAG_TREAT_AS_RAID_UNIT) && u1->ToPlayer()->GetGroup() && !u1->ToPlayer()->GetGroup()->isRaidGroup())) return true; else return false; } 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->IsPlayer() && u2->IsPlayer()) return u1->ToPlayer()->IsInSameRaidWith(u2->ToPlayer()); // Xinef: we assume that npcs with the same faction are in party else if (u1->IsCreature() && u2->IsCreature() && !u1->IsControlledByPlayer() && !u2->IsControlledByPlayer()) return u1->GetFaction() == u2->GetFaction(); else if ((u2->IsPlayer() && u1->IsCreature() && u1->ToCreature()->GetCreatureTemplate()->type_flags & CREATURE_TYPE_FLAG_TREAT_AS_RAID_UNIT) || (u1->IsPlayer() && u2->IsCreature() && u2->ToCreature()->GetCreatureTemplate()->type_flags & CREATURE_TYPE_FLAG_TREAT_AS_RAID_UNIT)) return true; else return false; } void Unit::GetPartyMembers(std::list& TagUnitMap) { Unit* owner = GetCharmerOrOwnerOrSelf(); Group* group = nullptr; if (owner->IsPlayer()) group = owner->ToPlayer()->GetGroup(); if (group) { uint8 subgroup = owner->ToPlayer()->GetSubGroup(); for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) { Player* Target = itr->GetSource(); // IsHostileTo check duel and controlled by enemy if (Target && Target->IsInMap(owner) && Target->GetSubGroup() == subgroup && !IsHostileTo(Target)) { if (Target->IsAlive()) TagUnitMap.push_back(Target); for (Unit::ControlSet::iterator iterator = Target->m_Controlled.begin(); iterator != Target->m_Controlled.end(); ++iterator) { if (Unit* pet = *iterator) if (pet->IsGuardian() && pet->IsAlive()) TagUnitMap.push_back(pet); } } } } else { if (owner->IsAlive()) TagUnitMap.push_back(owner); for (Unit::ControlSet::iterator itr = owner->m_Controlled.begin(); itr != owner->m_Controlled.end(); ++itr) { if (Unit* pet = *itr) if (pet->IsGuardian() && pet->IsAlive()) TagUnitMap.push_back(pet); } } } Aura* Unit::AddAura(uint32 spellId, Unit* target) { if (!target) return nullptr; SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); if (!spellInfo) return nullptr; if (!target->IsAlive() && !spellInfo->HasAttribute(SPELL_ATTR0_PASSIVE) && !spellInfo->HasAttribute(SPELL_ATTR2_ALLOW_DEAD_TARGET)) return nullptr; return AddAura(spellInfo, MAX_EFFECT_MASK, target); } Aura* Unit::AddAura(SpellInfo const* spellInfo, uint8 effMask, Unit* target) { if (!spellInfo) return nullptr; if (target->IsImmunedToSpell(spellInfo)) return nullptr; for (uint32 i = 0; i < MAX_SPELL_EFFECTS; ++i) { if (!(effMask & (1 << i))) continue; if (target->IsImmunedToSpellEffect(spellInfo, i)) effMask &= ~(1 << i); } if (Aura* aura = Aura::TryRefreshStackOrCreate(spellInfo, effMask, target, this)) { 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(uint32 id) { WorldPacket data(SMSG_PLAY_SPELL_VISUAL, 8 + 4); data << GetGUID(); data << uint32(id); // SpellVisualKit.dbc index SendMessageToSet(&data, true); } void Unit::SendPlaySpellImpact(ObjectGuid guid, uint32 id) { WorldPacket data(SMSG_PLAY_SPELL_IMPACT, 8 + 4); data << guid; // target data << uint32(id); // SpellVisualKit.dbc index SendMessageToSet(&data, true); } void Unit::ApplyResilience(Unit const* victim, float* crit, int32* damage, bool isCrit, CombatRating type) { // player mounted on multi-passenger mount is also classified as vehicle if (victim->IsVehicle() && !victim->IsPlayer()) return; Unit const* target = nullptr; if (victim->IsPlayer()) target = victim; else if (victim->IsCreature()) { if (Unit* owner = victim->GetOwner()) if (owner->IsPlayer()) target = owner; } if (!target) return; switch (type) { case CR_CRIT_TAKEN_MELEE: // Crit chance reduction works against nonpets if (crit) *crit -= target->GetMeleeCritChanceReduction(); if (damage) { if (isCrit) *damage -= target->GetMeleeCritDamageReduction(*damage); *damage -= target->GetMeleeDamageReduction(*damage); } break; case CR_CRIT_TAKEN_RANGED: // Crit chance reduction works against nonpets if (crit) *crit -= target->GetRangedCritChanceReduction(); if (damage) { if (isCrit) *damage -= target->GetRangedCritDamageReduction(*damage); *damage -= target->GetRangedDamageReduction(*damage); } break; case CR_CRIT_TAKEN_SPELL: // Crit chance reduction works against nonpets if (crit) *crit -= target->GetSpellCritChanceReduction(); if (damage) { if (isCrit) *damage -= target->GetSpellCritDamageReduction(*damage); *damage -= target->GetSpellDamageReduction(*damage); } break; default: break; } } // 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, int32 skillDiff, uint32 spellId) const { SpellInfo const* spellInfo = spellId ? sSpellMgr->GetSpellInfo(spellId) : nullptr; if (spellInfo && spellInfo->HasAttribute(SPELL_ATTR7_NO_ATTACK_MISS)) { return 0.0f; } //calculate miss chance float missChance = victim->GetUnitMissChance(attType); // Check if dual wielding, add additional miss penalty - when mainhand has on next swing spell, offhand doesnt suffer penalty if (!spellId && (attType != RANGED_ATTACK) && HasOffhandWeaponForAttack() && (!m_currentSpells[CURRENT_MELEE_SPELL] || !m_currentSpells[CURRENT_MELEE_SPELL]->IsNextMeleeSwingSpell())) { missChance += 19; } // bonus from skills is 0.04% //miss_chance -= skillDiff * 0.04f; int32 diff = -skillDiff; if (victim->IsPlayer()) missChance += diff > 0 ? diff * 0.04f : diff * 0.02f; else missChance += diff > 10 ? 1 + (diff - 10) * 0.4f : diff * 0.1f; // Calculate hit chance float hitChance = 100.0f; // Spellmod from SPELLMOD_RESIST_MISS_CHANCE if (spellId) { if (Player* modOwner = GetSpellModOwner()) modOwner->ApplySpellMod(spellId, SPELLMOD_RESIST_MISS_CHANCE, hitChance); } missChance -= hitChance - 100.0f; if (attType == RANGED_ATTACK) missChance -= m_modRangedHitChance; else missChance -= m_modMeleeHitChance; // Limit miss chance from 0 to 60% if (missChance < 0.0f) return 0.0f; if (missChance > 60.0f) return 60.0f; return missChance; } uint32 Unit::GetPhaseByAuras() const { uint32 currentPhase = 0; AuraEffectList const& phases = GetAuraEffectsByType(SPELL_AURA_PHASE); if (!phases.empty()) for (AuraEffectList::const_iterator itr = phases.begin(); itr != phases.end(); ++itr) currentPhase |= (*itr)->GetMiscValue(); return currentPhase; } void Unit::SetPhaseMask(uint32 newPhaseMask, bool update) { if (newPhaseMask == GetPhaseMask()) return; if (IsInWorld()) { // xinef: to comment, bellow line should be removed // pussywizard: goign to other phase (valithria, algalon) should not remove such auras //RemoveNotOwnSingleTargetAuras(newPhaseMask, true); // we can lost access to caster or target if (!sScriptMgr->CanSetPhaseMask(this, newPhaseMask, update)) return; // modify hostile references for new phasemask, some special cases deal with hostile references themselves if (IsCreature() || (!ToPlayer()->IsGameMaster() && !ToPlayer()->GetSession()->PlayerLogout())) { HostileRefMgr& refMgr = getHostileRefMgr(); HostileReference* ref = refMgr.getFirst(); while (ref) { if (Unit* unit = ref->GetSource()->GetOwner()) if (Creature* creature = unit->ToCreature()) refMgr.setOnlineOfflineState(creature, creature->InSamePhase(newPhaseMask)); ref = ref->next(); } // modify threat lists for new phasemask if (!IsPlayer()) { ThreatContainer::StorageType threatList = GetThreatMgr().GetThreatList(); ThreatContainer::StorageType offlineThreatList = GetThreatMgr().GetOfflineThreatList(); // merge expects sorted lists threatList.sort(); offlineThreatList.sort(); threatList.merge(offlineThreatList); for (ThreatContainer::StorageType::const_iterator itr = threatList.begin(); itr != threatList.end(); ++itr) if (Unit* unit = (*itr)->getTarget()) unit->getHostileRefMgr().setOnlineOfflineState(ToCreature(), unit->InSamePhase(newPhaseMask)); } } } WorldObject::SetPhaseMask(newPhaseMask, false); if (!IsInWorld()) { return; } for (ControlSet::const_iterator itr = m_Controlled.begin(); itr != m_Controlled.end(); ) { Unit* controlled = *itr; ++itr; if (controlled->IsCreature()) { controlled->SetPhaseMask(newPhaseMask, true); } } for (uint8 i = 0; i < MAX_SUMMON_SLOT; ++i) { if (m_SummonSlot[i]) { if (Creature* summon = GetMap()->GetCreature(m_SummonSlot[i])) { summon->SetPhaseMask(newPhaseMask, true); } } } if (update) { UpdateObjectVisibility(); } } void Unit::UpdateObjectVisibility(bool forced, bool /*fromUpdate*/) { if (!forced) AddToNotify(NOTIFY_VISIBILITY_CHANGED); else { WorldObject::UpdateObjectVisibility(true); Acore::AIRelocationNotifier notifier(*this); float radius = 60.0f; Cell::VisitObjects(this, notifier, radius); } } void Unit::KnockbackFrom(float x, float y, float speedXY, float speedZ) { Player* player = ToPlayer(); if (!player) { if (Unit* charmer = GetCharmer()) { player = charmer->ToPlayer(); if (player && player->m_mover != this) player = nullptr; } } if (!player) { GetMotionMaster()->MoveKnockbackFrom(x, y, speedXY, speedZ); } else { float vcos, vsin; GetSinCos(x, y, vsin, vcos); WorldPacket data(SMSG_MOVE_KNOCK_BACK, (8 + 4 + 4 + 4 + 4 + 4)); data << GetPackGUID(); data << player->GetSession()->GetOrderCounter(); // movement counter data << float(vcos); // x direction data << float(vsin); // y direction data << float(speedXY); // Horizontal speed data << float(-speedZ); // Z Movement speed (vertical) player->SendDirectMessage(&data); player->GetSession()->IncrementOrderCounter(); player->SetCanKnockback(true); } } 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) { // Hardcoded cases switch (spellId) { case 7090: // Bear form return 29414; case 35200: // Roc form return 4877; default: break; } if (IsPlayer()) { if (uint32 ModelId = sObjectMgr->GetModelForShapeshift(form, ToPlayer())) return ModelId; } uint32 modelid = 0; SpellShapeshiftFormEntry const* formEntry = sSpellShapeshiftFormStore.LookupEntry(form); if (formEntry && formEntry->modelID_A) { // Take the alliance modelid as default if (!IsPlayer()) return formEntry->modelID_A; else { if (Player::TeamIdForRace(getRace()) == TEAM_ALLIANCE) modelid = formEntry->modelID_A; else modelid = formEntry->modelID_H; // If the player is horde but there are no values for the horde modelid - take the alliance modelid if (!modelid && Player::TeamIdForRace(getRace()) == TEAM_HORDE) modelid = formEntry->modelID_A; } } return modelid; } Unit* Unit::GetRedirectThreatTarget() const { return _redirectThreatInfo.GetTargetGUID() ? ObjectAccessor::GetUnit(*this, _redirectThreatInfo.GetTargetGUID()) : nullptr; } bool Unit::IsAttackSpeedOverridenShapeShift() const { // Mirroring clientside gameplay logic if (ShapeshiftForm form = GetShapeshiftForm()) if (SpellShapeshiftFormEntry const* entry = sSpellShapeshiftFormStore.LookupEntry(form)) return entry->attackSpeed > 0; return false; } void Unit::JumpTo(float speedXY, float speedZ, bool forward) { float angle = forward ? 0 : M_PI; if (IsCreature()) GetMotionMaster()->MoveJumpTo(angle, speedXY, speedZ); else { float vcos = cos(angle + GetOrientation()); float vsin = std::sin(angle + GetOrientation()); WorldPacket data(SMSG_MOVE_KNOCK_BACK, (8 + 4 + 4 + 4 + 4 + 4)); data << GetPackGUID(); data << uint32(0); // Sequence data << float(vcos); // x direction data << float(vsin); // y direction data << float(speedXY); // Horizontal speed data << float(-speedZ); // Z Movement speed (vertical) ToPlayer()->SendDirectMessage(&data); } } void Unit::JumpTo(WorldObject* obj, float speedZ) { float x, y, z; obj->GetContactPoint(this, x, y, z); float speedXY = GetExactDist2d(x, y) * 10.0f / speedZ; GetMotionMaster()->MoveJump(x, y, z, speedXY, speedZ); } bool Unit::HandleSpellClick(Unit* clicker, int8 seatId) { Creature* creature = ToCreature(); if (creature && creature->IsAIEnabled) { if (!creature->AI()->BeforeSpellClick(clicker)) { return false; } } bool result = false; uint32 spellClickEntry = GetVehicleKit() ? GetVehicleKit()->GetCreatureEntry() : GetEntry(); SpellClickInfoMapBounds clickPair = sObjectMgr->GetSpellClickInfoMapBounds(spellClickEntry); for (SpellClickInfoContainer::const_iterator itr = clickPair.first; itr != clickPair.second; ++itr) { //! First check simple relations from clicker to clickee if (!itr->second.IsFitToRequirements(clicker, this)) continue; //! Check database conditions ConditionList conds = sConditionMgr->GetConditionsForSpellClickEvent(spellClickEntry, itr->second.spellId); ConditionSourceInfo info = ConditionSourceInfo(clicker, this); if (!sConditionMgr->IsObjectMeetToConditions(info, conds)) continue; Unit* caster = (itr->second.castFlags & NPC_CLICK_CAST_CASTER_CLICKER) ? clicker : this; Unit* target = (itr->second.castFlags & NPC_CLICK_CAST_TARGET_CLICKER) ? clicker : this; ObjectGuid origCasterGUID = (itr->second.castFlags & NPC_CLICK_CAST_ORIG_CASTER_OWNER) ? GetOwnerGUID() : clicker->GetGUID(); SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(itr->second.spellId); // xinef: dont allow players to enter vehicles on arena if (spellInfo->HasAura(SPELL_AURA_CONTROL_VEHICLE) && caster->IsPlayer() && caster->FindMap() && caster->FindMap()->IsBattleArena()) continue; if (seatId > -1) { uint8 i = 0; bool valid = false; while (i < MAX_SPELL_EFFECTS) { if (spellInfo->Effects[i].ApplyAuraName == SPELL_AURA_CONTROL_VEHICLE) { valid = true; break; } ++i; } if (!valid) { LOG_ERROR("sql.sql", "Spell {} specified in npc_spellclick_spells is not a valid vehicle enter aura!", itr->second.spellId); continue; } if (IsInMap(caster)) caster->CastCustomSpell(itr->second.spellId, SpellValueMod(SPELLVALUE_BASE_POINT0 + i), seatId + 1, target, GetVehicleKit() ? TRIGGERED_IGNORE_CASTER_MOUNTED_OR_ON_VEHICLE : TRIGGERED_NONE, nullptr, nullptr, origCasterGUID); else // This can happen during Player::_LoadAuras { int32 bp0[MAX_SPELL_EFFECTS]; for (uint32 j = 0; j < MAX_SPELL_EFFECTS; ++j) bp0[j] = spellInfo->Effects[j].BasePoints; bp0[i] = seatId; Aura::TryRefreshStackOrCreate(spellInfo, MAX_EFFECT_MASK, this, clicker, bp0, nullptr, origCasterGUID); } } else { if (IsInMap(caster)) caster->CastSpell(target, spellInfo, GetVehicleKit() ? TRIGGERED_IGNORE_CASTER_MOUNTED_OR_ON_VEHICLE : TRIGGERED_NONE, nullptr, nullptr, origCasterGUID); else Aura::TryRefreshStackOrCreate(spellInfo, MAX_EFFECT_MASK, this, clicker, nullptr, nullptr, origCasterGUID); } result = true; } if (creature && creature->IsAIEnabled) creature->AI()->OnSpellClick(clicker, result); return result; } void Unit::EnterVehicle(Unit* base, int8 seatId) { CastCustomSpell(VEHICLE_SPELL_RIDE_HARDCODED, SPELLVALUE_BASE_POINT0, seatId + 1, base, TRIGGERED_IGNORE_CASTER_MOUNTED_OR_ON_VEHICLE); if (Player* player = ToPlayer()) { sScriptMgr->AnticheatSetUnderACKmount(player); } } void Unit::EnterVehicleUnattackable(Unit* base, int8 seatId) { CastCustomSpell(67830, SPELLVALUE_BASE_POINT0, seatId + 1, base, true); } void Unit::_EnterVehicle(Vehicle* vehicle, int8 seatId, AuraApplication const* aurApp) { // Must be called only from aura handler if (!IsAlive() || GetVehicleKit() == vehicle || vehicle->GetBase()->IsOnVehicle(this)) return; if (m_vehicle) { if (m_vehicle == vehicle) { if (seatId >= 0 && seatId != GetTransSeat()) { LOG_DEBUG("vehicles", "EnterVehicle: {} leave vehicle {} seat {} and enter {}.", GetEntry(), m_vehicle->GetBase()->GetEntry(), GetTransSeat(), seatId); ChangeSeat(seatId); } return; } else { LOG_DEBUG("vehicles", "EnterVehicle: {} exit {} and enter {}.", GetEntry(), m_vehicle->GetBase()->GetEntry(), vehicle->GetBase()->GetEntry()); ExitVehicle(); } } if (!aurApp || aurApp->GetRemoveMode()) return; if (Player* player = ToPlayer()) { if (vehicle->GetBase()->IsPlayer() && player->IsInCombat()) return; sScriptMgr->AnticheatSetUnderACKmount(player); InterruptNonMeleeSpells(false); player->StopCastingCharm(); player->StopCastingBindSight(); Dismount(); RemoveAurasByType(SPELL_AURA_MOUNTED); // drop flag at invisible in bg if (Battleground* bg = player->GetBattleground()) bg->EventPlayerDroppedFlag(player); WorldPacket data(SMSG_ON_CANCEL_EXPECTED_RIDE_VEHICLE_AURA, 0); player->SendDirectMessage(&data); } ASSERT(!m_vehicle); m_vehicle = vehicle; if (!m_vehicle->AddPassenger(this, seatId)) { m_vehicle = nullptr; return; } // Xinef: remove movement auras when entering vehicle (food buffs etc) RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_TURNING | AURA_INTERRUPT_FLAG_MOVE); } void Unit::ChangeSeat(int8 seatId, bool next) { if (!m_vehicle) return; if (seatId < 0) { seatId = m_vehicle->GetNextEmptySeat(GetTransSeat(), next); if (seatId < 0) return; } else if (seatId == GetTransSeat() || !m_vehicle->HasEmptySeat(seatId)) return; m_vehicle->RemovePassenger(this); if (!m_vehicle->AddPassenger(this, seatId)) ABORT(); } 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()); if (Player* player = ToPlayer()) { player->SetCanTeleport(true); } //! 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) if (Player* player = ToPlayer()) { sScriptMgr->AnticheatSetUnderACKmount(player); } } bool VehicleDespawnEvent::Execute(uint64 /*e_time*/, uint32 /*p_time*/) { Position pos = _self; _self.MovePositionToFirstCollision(pos, 20.0f, M_PI); _self.GetMotionMaster()->MovePoint(0, pos); _self.ToCreature()->DespawnOrUnsummon(_duration); return true; } void Unit::_ExitVehicle(Position const* exitPosition) { if (!m_vehicle) return; // This should be done before dismiss, because there may be some aura removal VehicleSeatAddon const* seatAddon = m_vehicle->GetSeatAddonForSeatOfPassenger(this); m_vehicle->RemovePassenger(this); Player* player = ToPlayer(); // If player is on mouted duel and exits the mount should immediatly lose the duel if (player && player->duel && player->duel->IsMounted) player->DuelComplete(DUEL_FLED); Vehicle* vehicle = m_vehicle; Unit* vehicleBase = m_vehicle->GetBase(); m_vehicle = nullptr; if (!vehicleBase) return; if (IsPlayer()) ToPlayer()->SetExpectingChangeTransport(true); SetControlled(false, UNIT_STATE_ROOT); // SMSG_MOVE_FORCE_UNROOT, ~MOVEMENTFLAG_ROOT 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 = vehicleBase->GetPosition(); // This should use passenger's current position, leaving it as it is now pos.SetOrientation(vehicleBase->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 }); } } AddUnitState(UNIT_STATE_MOVE); if (player) { player->SetFallInformation(GameTime::GetGameTime().count(), GetPositionZ()); sScriptMgr->AnticheatSetUnderACKmount(player); } // xinef: hack for flameleviathan seat vehicle VehicleEntry const* vehicleInfo = vehicle->GetVehicleInfo(); if (!vehicleInfo || vehicleInfo->m_ID != 341) { Movement::MoveSplineInit init(this); init.MoveTo(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ()); init.SetFacing(vehicleBase->GetOrientation()); init.SetTransportExit(); init.Launch(); } else { float o = pos.GetAngle(this); Movement::MoveSplineInit init(this); init.MoveTo(pos.GetPositionX() + 8 * cos(o), pos.GetPositionY() + 8 * std::sin(o), pos.GetPositionZ() + 16.0f); init.SetFacing(GetOrientation()); init.SetTransportExit(); init.Launch(); DisableSpline(); KnockbackFrom(pos.GetPositionX(), pos.GetPositionY(), 10.0f, 20.0f); CastSpell(this, VEHICLE_SPELL_PARACHUTE, true); } // xinef: move fall, should we support all creatures that exited vehicle in air? Currently Quest Drag and Drop only, Air Assault quest if (IsCreature() && !CanFly() && vehicleInfo && (vehicleInfo->m_ID == 113 || vehicleInfo->m_ID == 8 || vehicleInfo->m_ID == 290 || vehicleInfo->m_ID == 298)) { GetMotionMaster()->MoveFall(); } if ((!player || !(player->GetDelayedOperations() & DELAYED_VEHICLE_TELEPORT)) && vehicle->GetBase()->HasUnitTypeMask(UNIT_MASK_MINION)) if (((Minion*)vehicleBase)->GetOwner() == this) { if (!vehicleInfo || vehicleInfo->m_ID != 349) { vehicle->Dismiss(); } else if (vehicleBase->IsCreature()) { vehicle->Uninstall(); vehicleBase->m_Events.AddEventAtOffset(new VehicleDespawnEvent(*vehicleBase, 2s), 2s); } // xinef: ugly hack, no appripriate hook later to cast spell if (player) { if (vehicleBase->GetEntry() == NPC_EIDOLON_WATCHER) player->CastSpell(player, VEHICLE_SPELL_SHADE_CONTROL_END, true); else if (vehicleBase->GetEntry() == NPC_LITHE_STALKER) player->CastSpell(player, VEHICLE_SPELL_GEIST_CONTROL_END, true); } } if (HasUnitTypeMask(UNIT_MASK_ACCESSORY)) { // Vehicle just died, we die too if (vehicleBase->getDeathState() == DeathState::JustDied) setDeathState(DeathState::JustDied); // If for other reason we as minion are exiting the vehicle (ejected, master dismounted) - unsummon else ToTempSummon()->UnSummon(2s); // Approximation } if (player) { player->ResummonPetTemporaryUnSummonedIfAny(); player->SetCanTeleport(true); } } void Unit::BuildMovementPacket(ByteBuffer* data) const { *data << uint32(GetUnitMovementFlags()); // movement flags *data << uint16(GetExtraUnitMovementFlags()); // 2.3.0 *data << uint32(GameTime::GetGameTimeMS().count()); // time / counter *data << GetPositionX(); *data << GetPositionY(); *data << GetPositionZ(); *data << GetOrientation(); // 0x00000200 if (GetUnitMovementFlags() & MOVEMENTFLAG_ONTRANSPORT) { if (m_vehicle) *data << m_vehicle->GetBase()->GetPackGUID(); else if (GetTransport()) *data << GetTransport()->GetPackGUID(); else *data << (uint8)0; *data << float (GetTransOffsetX()); *data << float (GetTransOffsetY()); *data << float (GetTransOffsetZ()); *data << float (GetTransOffsetO()); *data << uint32(GetTransTime()); *data << uint8 (GetTransSeat()); if (GetExtraUnitMovementFlags() & MOVEMENTFLAG2_INTERPOLATED_MOVEMENT) *data << uint32(m_movementInfo.transport.time2); } // 0x02200000 if ((GetUnitMovementFlags() & (MOVEMENTFLAG_SWIMMING | MOVEMENTFLAG_FLYING)) || (m_movementInfo.flags2 & MOVEMENTFLAG2_ALWAYS_ALLOW_PITCHING)) *data << (float)m_movementInfo.pitch; *data << (uint32)m_movementInfo.fallTime; // 0x00001000 if (GetUnitMovementFlags() & MOVEMENTFLAG_FALLING) { *data << (float)m_movementInfo.jump.zspeed; *data << (float)m_movementInfo.jump.sinAngle; *data << (float)m_movementInfo.jump.cosAngle; *data << (float)m_movementInfo.jump.xyspeed; } // 0x04000000 if (GetUnitMovementFlags() & MOVEMENTFLAG_SPLINE_ELEVATION) *data << (float)m_movementInfo.splineElevation; } bool Unit::IsFalling() const { return m_movementInfo.HasMovementFlag(MOVEMENTFLAG_FALLING | MOVEMENTFLAG_FALLING_FAR) || (!movespline->Finalized() && movespline->Initialized() && movespline->isFalling()); } /** * @brief this method checks the current flag of a unit * * These flags can be set within the database or dynamically changed at runtime * UNIT_FLAG_SWIMMING must be updated when a unit enters a swimmable area * */ 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_CANNOT_SWIM)) return false; if (HasUnitFlag(UNIT_FLAG_POSSESSED) || HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED)) // is player return true; if (HasUnitFlag2(UNIT_FLAG2_UNUSED_6)) return false; if (HasUnitFlag(UNIT_FLAG_PET_IN_COMBAT)) return true; return HasUnitFlag(UNIT_FLAG_RENAME | UNIT_FLAG_SWIMMING); } void Unit::NearTeleportTo(Position& pos, bool casting /*= false*/, bool vehicleTeleport /*= false*/, bool withPet /*= false*/, bool removeTransport /*= false*/) { NearTeleportTo(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), pos.GetOrientation(), casting, vehicleTeleport, withPet, removeTransport); } void Unit::NearTeleportTo(float x, float y, float z, float orientation, bool casting /*= false*/, bool vehicleTeleport /*= false*/, bool withPet /*= false*/, bool removeTransport /*= false*/) { DisableSpline(); if (IsPlayer()) ToPlayer()->TeleportTo(GetMapId(), x, y, z, orientation, TELE_TO_NOT_LEAVE_COMBAT | (removeTransport ? 0 : TELE_TO_NOT_LEAVE_TRANSPORT) | TELE_TO_NOT_UNSUMMON_PET | (casting ? TELE_TO_SPELL : 0) | (vehicleTeleport ? TELE_TO_NOT_LEAVE_VEHICLE : 0) | (withPet ? TELE_TO_WITH_PET : 0)); else { Position pos = {x, y, z, orientation}; SendTeleportPacket(pos); UpdatePosition(x, y, z, orientation, true); UpdateObjectVisibility(); GetMotionMaster()->ReinitializeMovement(); } } void Unit::SendTameFailure(uint8 result) { WorldPacket data(SMSG_PET_TAME_FAILURE, 1); data << uint8(result); ToPlayer()->SendDirectMessage(&data); } void Unit::SendTeleportPacket(Position& pos) { Position oldPos = { GetPositionX(), GetPositionY(), GetPositionZ(), GetOrientation() }; if (IsCreature()) Relocate(&pos); if (IsPlayer()) { ToPlayer()->SetCanTeleport(true); } WorldPacket data2(MSG_MOVE_TELEPORT, 38); data2 << GetPackGUID(); BuildMovementPacket(&data2); if (IsCreature()) Relocate(&oldPos); if (IsPlayer()) Relocate(&pos); SendMessageToSet(&data2, false); } bool Unit::UpdatePosition(float x, float y, float z, float orientation, bool teleport) { if (!Acore::IsValidMapCoord(x, y, z, orientation)) return false; float old_orientation = GetOrientation(); float current_z = GetPositionZ(); bool turn = (old_orientation != orientation); bool relocated = (teleport || GetPositionX() != x || GetPositionY() != y || current_z != z); if (!GetVehicle()) { uint32 mask = 0; if (turn) mask |= AURA_INTERRUPT_FLAG_TURNING; if (relocated) mask |= AURA_INTERRUPT_FLAG_MOVE; if (mask) RemoveAurasWithInterruptFlags(mask); } if (relocated) { if (IsPlayer()) GetMap()->PlayerRelocation(ToPlayer(), x, y, z, orientation); else GetMap()->CreatureRelocation(ToCreature(), x, y, z, orientation); } else if (turn) { UpdateOrientation(orientation); if (IsPlayer() && ToPlayer()->GetFarSightDistance()) UpdateObjectVisibility(false); } return (relocated || turn); } //! 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(); } void Unit::SendThreatListUpdate() { if (!GetThreatMgr().isThreatListEmpty()) { uint32 count = GetThreatMgr().GetThreatList().size(); //LOG_DEBUG("entities.unit", "WORLD: Send SMSG_THREAT_UPDATE Message"); WorldPacket data(SMSG_THREAT_UPDATE, 8 + count * 8); data << GetPackGUID(); data << uint32(count); ThreatContainer::StorageType const& tlist = GetThreatMgr().GetThreatList(); for (ThreatContainer::StorageType::const_iterator itr = tlist.begin(); itr != tlist.end(); ++itr) { data << (*itr)->getUnitGuid().WriteAsPacked(); data << uint32((*itr)->GetThreat() * 100); } SendMessageToSet(&data, false); } } void Unit::SendChangeCurrentVictimOpcode(HostileReference* pHostileReference) { if (!GetThreatMgr().isThreatListEmpty()) { uint32 count = GetThreatMgr().GetThreatList().size(); LOG_DEBUG("entities.unit", "WORLD: Send SMSG_HIGHEST_THREAT_UPDATE Message"); WorldPacket data(SMSG_HIGHEST_THREAT_UPDATE, 8 + 8 + count * 8); data << GetPackGUID(); data << pHostileReference->getUnitGuid().WriteAsPacked(); data << uint32(count); ThreatContainer::StorageType const& tlist = GetThreatMgr().GetThreatList(); for (ThreatContainer::StorageType::const_iterator itr = tlist.begin(); itr != tlist.end(); ++itr) { data << (*itr)->getUnitGuid().WriteAsPacked(); data << uint32((*itr)->GetThreat() * 100); } SendMessageToSet(&data, false); } } void Unit::SendClearThreatListOpcode() { LOG_DEBUG("entities.unit", "WORLD: Send SMSG_THREAT_CLEAR Message"); WorldPacket data(SMSG_THREAT_CLEAR, 8); data << GetPackGUID(); SendMessageToSet(&data, false); } void Unit::SendRemoveFromThreatListOpcode(HostileReference* pHostileReference) { LOG_DEBUG("entities.unit", "WORLD: Send SMSG_THREAT_REMOVE Message"); WorldPacket data(SMSG_THREAT_REMOVE, 8 + 8); data << GetPackGUID(); data << pHostileReference->getUnitGuid().WriteAsPacked(); SendMessageToSet(&data, false); } void Unit::RewardRage(uint32 damage, uint32 weaponSpeedHitFactor, bool attacker) { // Rage formulae https://wowwiki-archive.fandom.com/wiki/Rage#Formulae float addRage; float rageconversion = ((0.0091107836f * GetLevel() * GetLevel()) + 3.225598133f * GetLevel()) + 4.2652911f; // Unknown if correct, but lineary adjust rage conversion above level 70 if (GetLevel() > 70) rageconversion += 13.27f * (GetLevel() - 70); if (attacker) { // see Bornak's bluepost explanation (05/29/2009) float rageFromDamageDealt = damage / rageconversion * 7.5f; addRage = (rageFromDamageDealt + weaponSpeedHitFactor) / 2.0f; addRage = std::min(addRage, rageFromDamageDealt * 2.0f); AddPct(addRage, GetTotalAuraModifier(SPELL_AURA_MOD_RAGE_FROM_DAMAGE_DEALT)); } else { addRage = damage / rageconversion * 2.5f; // Berserker Rage effect if (HasAura(18499)) addRage *= 3.0f; } addRage *= sWorld->getRate(RATE_POWER_RAGE_INCOME); ModifyPower(POWER_RAGE, uint32(addRage * 10)); } 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 (IsPlayer()) 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; } getHostileRefMgr().deleteReferencesForFaction(faction_id); for (ControlSet::const_iterator itr = m_Controlled.begin(); itr != m_Controlled.end(); ++itr) (*itr)->StopAttackFaction(faction_id); } void Unit::StopAttackingInvalidTarget() { AttackerSet const& attackers = getAttackers(); for (AttackerSet::const_iterator itr = attackers.begin(); itr != attackers.end();) { Unit* attacker = (*itr); if (!attacker->IsValidAttackTarget(this)) { attacker->AttackStop(); if (attacker->IsPlayer()) { attacker->ToPlayer()->SendAttackSwingCancelAttack(); } for (Unit* controlled : attacker->m_Controlled) { if (controlled->GetVictim() == this && !controlled->IsValidAttackTarget(this)) { controlled->AttackStop(); } } itr = attackers.begin(); } else { ++itr; } } } void Unit::OutDebugInfo() const { LOG_ERROR("entities.unit", "Unit::OutDebugInfo"); LOG_INFO("entities.unit", "GUID {}, name {}", GetGUID().ToString(), GetName()); LOG_INFO("entities.unit", "OwnerGUID {}, MinionGUID {}, CharmerGUID {}, CharmedGUID {}", GetOwnerGUID().ToString(), GetMinionGUID().ToString(), GetCharmerGUID().ToString(), GetCharmGUID().ToString()); LOG_INFO("entities.unit", "In world {}, unit type mask {}", (uint32)(IsInWorld() ? 1 : 0), m_unitTypeMask); if (IsInWorld()) LOG_INFO("entities.unit", "Mapid {}", GetMapId()); LOG_INFO("entities.unit", "Summon Slot: "); for (uint32 i = 0; i < MAX_SUMMON_SLOT; ++i) LOG_INFO("entities.unit", "{}, ", m_SummonSlot[i].ToString()); LOG_INFO("server.loading", " "); LOG_INFO("entities.unit", "Controlled List: "); for (ControlSet::const_iterator itr = m_Controlled.begin(); itr != m_Controlled.end(); ++itr) LOG_INFO("entities.unit", "{}, ", (*itr)->GetGUID().ToString()); LOG_INFO("server.loading", " "); LOG_INFO("entities.unit", "Aura List: "); for (AuraApplicationMap::const_iterator itr = m_appliedAuras.begin(); itr != m_appliedAuras.end(); ++itr) LOG_INFO("entities.unit", "{}, ", itr->first); LOG_INFO("server.loading", " "); if (IsVehicle()) { LOG_INFO("entities.unit", "Passenger List: "); for (SeatMap::iterator itr = GetVehicleKit()->Seats.begin(); itr != GetVehicleKit()->Seats.end(); ++itr) if (Unit* passenger = ObjectAccessor::GetUnit(*GetVehicleBase(), itr->second.Passenger.Guid)) LOG_INFO("entities.unit", "{}, ", passenger->GetGUID().ToString()); LOG_INFO("server.loading", " "); } if (GetVehicle()) LOG_INFO("entities.unit", "On vehicle {}.", GetVehicleBase()->GetEntry()); } class AuraMunchingQueue : public BasicEvent { public: AuraMunchingQueue(Unit& owner, ObjectGuid targetGUID, int32 basePoints, uint32 spellId, AuraEffect* aurEff, AuraType auraType) : _owner(owner), _targetGUID(targetGUID), _basePoints(basePoints), _spellId(spellId), _aurEff(aurEff), _auraType(auraType) { } bool Execute(uint64 /*e_time*/, uint32 /*p_time*/) override { if (_owner.IsInWorld() && _owner.FindMap()) if (Unit* target = ObjectAccessor::GetUnit(_owner, _targetGUID)) { bool auraFound = false; // Used to ensure _aurEff exists to avoid wild pointer access/crash Unit::AuraEffectList const& auraEffects = target->GetAuraEffectsByType(_auraType); for (Unit::AuraEffectList::const_iterator j = auraEffects.begin(); j != auraEffects.end(); ++j) if ((*j) == _aurEff) auraFound = true; if (!auraFound) _aurEff = nullptr; _owner.CastCustomSpell(_spellId, SPELLVALUE_BASE_POINT0, _basePoints, target, TriggerCastFlags(TRIGGERED_FULL_MASK & ~TRIGGERED_NO_PERIODIC_RESET), nullptr, _aurEff, _owner.GetGUID()); } return true; } private: Unit& _owner; ObjectGuid _targetGUID; int32 _basePoints; uint32 _spellId; AuraEffect* _aurEff; AuraType _auraType; }; void Unit::CastDelayedSpellWithPeriodicAmount(Unit* caster, uint32 spellId, AuraType auraType, int32 addAmount, uint8 effectIndex) { AuraEffect* aurEff = nullptr; for (AuraEffectList::iterator i = m_modAuras[auraType].begin(); i != m_modAuras[auraType].end(); ++i) { aurEff = *i; if (aurEff->GetCasterGUID() != caster->GetGUID() || aurEff->GetId() != spellId || aurEff->GetEffIndex() != effectIndex || !aurEff->GetTotalTicks()) continue; addAmount += ((aurEff->GetOldAmount() * std::max(aurEff->GetTotalTicks() - int32(aurEff->GetTickNumber()), 0)) / aurEff->GetTotalTicks()); break; } // xinef: delay only for casting on different unit if (this == caster || !sWorld->getBoolConfig(CONFIG_MUNCHING_BLIZZLIKE)) caster->CastCustomSpell(spellId, SPELLVALUE_BASE_POINT0, addAmount, this, TriggerCastFlags(TRIGGERED_FULL_MASK & ~TRIGGERED_NO_PERIODIC_RESET), nullptr, aurEff, caster->GetGUID()); else caster->m_Events.AddEvent(new AuraMunchingQueue(*caster, GetGUID(), addAmount, spellId, aurEff, auraType), caster->m_Events.CalculateQueueTime(400)); } void Unit::SendClearTarget() { WorldPacket data(SMSG_BREAK_TARGET, GetPackGUID().size()); data << GetPackGUID(); SendMessageToSet(&data, false); } uint32 Unit::GetResistance(SpellSchoolMask mask) const { int32 resist = -1; for (int i = SPELL_SCHOOL_NORMAL; i < MAX_SPELL_SCHOOL; ++i) if (mask & (1 << i) && (resist < 0 || resist > int32(GetResistance(SpellSchools(i))))) resist = int32(GetResistance(SpellSchools(i))); // resist value will never be negative here return uint32(resist); } void Unit::PetSpellFail(SpellInfo const* spellInfo, Unit* target, uint32 result) { CharmInfo* charmInfo = GetCharmInfo(); if (!charmInfo || !IsCreature()) return; if ((sDisableMgr->IsPathfindingEnabled(GetMap()) || result != SPELL_FAILED_LINE_OF_SIGHT) && target) { if ((result == SPELL_FAILED_LINE_OF_SIGHT || result == SPELL_FAILED_OUT_OF_RANGE) || !ToCreature()->HasReactState(REACT_PASSIVE)) if (Unit* owner = GetOwner()) { if (spellInfo->IsPositive() && IsFriendlyTo(target)) { AttackStop(); charmInfo->SetIsAtStay(false); charmInfo->SetIsCommandAttack(true); charmInfo->SetIsReturning(false); charmInfo->SetIsFollowing(false); GetMotionMaster()->MoveFollow(target, PET_FOLLOW_DIST, rand_norm() * 2 * M_PI); } else if (owner->IsValidAttackTarget(target)) { AttackStop(); charmInfo->SetIsAtStay(false); charmInfo->SetIsCommandAttack(true); charmInfo->SetIsReturning(false); charmInfo->SetIsFollowing(false); if (!ToCreature()->HasReactState(REACT_PASSIVE)) ToCreature()->AI()->AttackStart(target); else GetMotionMaster()->MoveChase(target); } } // can be extended in future if (result == SPELL_FAILED_LINE_OF_SIGHT || result == SPELL_FAILED_OUT_OF_RANGE) { charmInfo->SetForcedSpell(spellInfo->IsPositive() ? -int32(spellInfo->Id) : spellInfo->Id); charmInfo->SetForcedTargetGUID(target->GetGUID()); } else { charmInfo->SetForcedSpell(0); charmInfo->SetForcedTargetGUID(ObjectGuid::Empty); } } } int32 Unit::CalculateAOEDamageReduction(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; } void Unit::ExecuteDelayedUnitRelocationEvent() { this->RemoveFromNotify(NOTIFY_VISIBILITY_CHANGED); if (!this->IsInWorld() || this->IsDuringRemoveFromWorld()) return; if (this->HasSharedVision()) for (SharedVisionList::const_iterator itr = this->GetSharedVisionList().begin(); itr != this->GetSharedVisionList().end(); ++itr) if (Player* player = (*itr)) { if (player->IsOnVehicle(this) || !player->IsInWorld() || player->IsDuringRemoveFromWorld()) // players on vehicles have their own event executed (due to passenger relocation) continue; WorldObject* viewPoint = player; if (player->m_seer && player->m_seer->IsInWorld()) viewPoint = player->m_seer; if (!viewPoint->IsPositionValid() || !player->IsPositionValid()) continue; if (Unit* active = viewPoint->ToUnit()) { //if (active->IsVehicle()) // always check original unit here, last notify position is not relocated // active = player; float dx = active->m_last_notify_position.GetPositionX() - active->GetPositionX(); float dy = active->m_last_notify_position.GetPositionY() - active->GetPositionY(); float dz = active->m_last_notify_position.GetPositionZ() - active->GetPositionZ(); float distsq = dx * dx + dy * dy + dz * dz; float mindistsq = DynamicVisibilityMgr::GetReqMoveDistSq(active->FindMap()->GetEntry()->map_type); if (distsq < mindistsq) continue; // this will be relocated below sharedvision! //active->m_last_notify_position.Relocate(active->GetPositionX(), active->GetPositionY(), active->GetPositionZ()); } Acore::PlayerRelocationNotifier notifier(*player); Cell::VisitObjects(viewPoint, notifier, player->GetSightRange()); Cell::VisitFarVisibleObjects(viewPoint, notifier, VISIBILITY_DISTANCE_GIGANTIC); notifier.SendToSelf(); } if (Player* player = this->ToPlayer()) { WorldObject* viewPoint = player; if (player->m_seer && player->m_seer->IsInWorld()) viewPoint = player->m_seer; if (viewPoint->GetMapId() != player->GetMapId() || !viewPoint->IsPositionValid() || !player->IsPositionValid()) return; if (Unit* active = viewPoint->ToUnit()) { if (active->IsVehicle()) active = player; if (!player->GetFarSightDistance()) { float dx = active->m_last_notify_position.GetPositionX() - active->GetPositionX(); float dy = active->m_last_notify_position.GetPositionY() - active->GetPositionY(); float dz = active->m_last_notify_position.GetPositionZ() - active->GetPositionZ(); float distsq = dx * dx + dy * dy + dz * dz; float mindistsq = DynamicVisibilityMgr::GetReqMoveDistSq(active->FindMap()->GetEntry()->map_type); if (distsq < mindistsq) return; active->m_last_notify_position.Relocate(active->GetPositionX(), active->GetPositionY(), active->GetPositionZ()); } } GetMap()->LoadGridsInRange(*player, MAX_VISIBILITY_DISTANCE); Acore::PlayerRelocationNotifier notifier(*player); Cell::VisitObjects(viewPoint, notifier, player->GetSightRange()); Cell::VisitFarVisibleObjects(viewPoint, notifier, VISIBILITY_DISTANCE_GIGANTIC); notifier.SendToSelf(); this->AddToNotify(NOTIFY_AI_RELOCATION); } else if (Creature* unit = this->ToCreature()) { if (!unit->IsPositionValid()) return; float dx = unit->m_last_notify_position.GetPositionX() - unit->GetPositionX(); float dy = unit->m_last_notify_position.GetPositionY() - unit->GetPositionY(); float dz = unit->m_last_notify_position.GetPositionZ() - unit->GetPositionZ(); float distsq = dx * dx + dy * dy + dz * dz; float mindistsq = DynamicVisibilityMgr::GetReqMoveDistSq(unit->FindMap()->GetEntry()->map_type); if (distsq < mindistsq) return; unit->m_last_notify_position.Relocate(unit->GetPositionX(), unit->GetPositionY(), unit->GetPositionZ()); Acore::CreatureRelocationNotifier relocate(*unit); Cell::VisitObjects(unit, relocate, unit->GetVisibilityRange()); this->AddToNotify(NOTIFY_AI_RELOCATION); } } void Unit::ExecuteDelayedUnitAINotifyEvent() { this->RemoveFromNotify(NOTIFY_AI_RELOCATION); if (!this->IsInWorld() || this->IsDuringRemoveFromWorld()) return; Acore::AIRelocationNotifier notifier(*this); float radius = 60.0f; Cell::VisitObjects(this, notifier, radius); } void Unit::SetInFront(WorldObject const* target) { if (!HasUnitState(UNIT_STATE_CANNOT_TURN)) SetOrientation(GetAngle(target)); } void Unit::SetFacingTo(float ori) { Movement::MoveSplineInit init(this); init.MoveTo(GetPositionX(), GetPositionY(), GetPositionZ(), false); if (HasUnitMovementFlag(MOVEMENTFLAG_ONTRANSPORT) && GetTransGUID()) init.DisableTransportPathTransformations(); // It makes no sense to target global orientation init.SetFacing(ori); init.Launch(); } void Unit::SetFacingToObject(WorldObject* object, Milliseconds timed /*= 0ms*/) { // never face when already moving if (!IsStopped()) 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()); init.SetFacing(GetAngle(object)); // when on transport, GetAngle will still return global coordinates (and angle) that needs transforming init.Launch(); if (timed > 0ms) { if (Creature* c = ToCreature()) { c->m_Events.AddEventAtOffset([c]() { if (c->IsInWorld() && c->FindMap() && c->IsAlive() && !c->IsInCombat()) c->SetFacingTo(c->GetHomePosition().GetOrientation()); }, timed); } else LOG_ERROR("entities.unit", "Unit::SetFacingToObject called on non-creature unit {}. This should never happen.", GetEntry()); } } bool Unit::SetWalk(bool enable) { if (enable == IsWalking()) return false; if (enable) AddUnitMovementFlag(MOVEMENTFLAG_WALKING); else RemoveUnitMovementFlag(MOVEMENTFLAG_WALKING); propagateSpeedChange(); return true; } void Unit::SetDisableGravity(bool enable) { bool isClientControlled = IsClientControlled(); if (!isClientControlled) { if (enable) m_movementInfo.AddMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY); else m_movementInfo.RemoveMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY); } if (!IsInWorld()) // is sent on add to map return; if (isClientControlled) { if (Player const* player = GetClientControlling()) { uint32 const counter = player->GetSession()->GetOrderCounter(); WorldPacket data(enable ? SMSG_MOVE_GRAVITY_DISABLE : SMSG_MOVE_GRAVITY_ENABLE, GetPackGUID().size() + 4); data << GetPackGUID(); data << counter; player->GetSession()->SendPacket(&data); player->GetSession()->IncrementOrderCounter(); return; } } WorldPacket data(enable ? SMSG_SPLINE_MOVE_GRAVITY_DISABLE : SMSG_SPLINE_MOVE_GRAVITY_ENABLE, 9); data << GetPackGUID(); SendMessageToSet(&data, true); } bool Unit::SetSwim(bool enable) { if (enable == HasUnitMovementFlag(MOVEMENTFLAG_SWIMMING)) return false; if (enable) { AddUnitMovementFlag(MOVEMENTFLAG_SWIMMING); SetUnitFlag(UNIT_FLAG_SWIMMING); } else { RemoveUnitMovementFlag(MOVEMENTFLAG_SWIMMING); RemoveUnitFlag(UNIT_FLAG_SWIMMING); } return true; } /** * @brief Add the movement flag: MOVEMENTFLAGCAN_FLY. Generaly only use by players, allowing * them to fly by pressing space for example. For creatures, please look for DisableGravity(). * * Doesn't inform the client. */ void Unit::SetCanFly(bool enable) { bool isClientControlled = IsClientControlled(); if (!isClientControlled) { if (enable) m_movementInfo.AddMovementFlag(MOVEMENTFLAG_CAN_FLY); else m_movementInfo.RemoveMovementFlag(MOVEMENTFLAG_CAN_FLY); } if (!IsInWorld()) // is sent on add to map return; if (isClientControlled) { if (Player const* player = GetClientControlling()) { uint32 const counter = player->GetSession()->GetOrderCounter(); const_cast(player)->SetPendingFlightChange(counter); WorldPacket data(enable ? SMSG_MOVE_SET_CAN_FLY : SMSG_MOVE_UNSET_CAN_FLY, GetPackGUID().size() + 4); data << GetPackGUID(); data << counter; player->SendDirectMessage(&data); player->GetSession()->IncrementOrderCounter(); return; } } WorldPacket data(enable ? SMSG_SPLINE_MOVE_SET_FLYING : SMSG_SPLINE_MOVE_UNSET_FLYING, 9); data << GetPackGUID(); SendMessageToSet(&data, true); } void Unit::SetFeatherFall(bool enable) { bool isClientControlled = IsClientControlled(); if (!isClientControlled) { if (enable) m_movementInfo.AddMovementFlag(MOVEMENTFLAG_FALLING_SLOW); else m_movementInfo.RemoveMovementFlag(MOVEMENTFLAG_FALLING_SLOW); } if (!IsInWorld()) // is sent on add to map return; if (isClientControlled) { if (Player const* player = GetClientControlling()) { uint32 const counter = player->GetSession()->GetOrderCounter(); WorldPacket data(enable ? SMSG_MOVE_FEATHER_FALL : SMSG_MOVE_NORMAL_FALL, GetPackGUID().size() + 4); data << GetPackGUID(); data << counter; player->SendDirectMessage(&data); player->GetSession()->IncrementOrderCounter(); // start fall from current height if (!enable) const_cast(player)->SetFallInformation(0, GetPositionZ()); return; } } WorldPacket data(enable ? SMSG_SPLINE_MOVE_FEATHER_FALL : SMSG_SPLINE_MOVE_NORMAL_FALL); data << GetPackGUID(); SendMessageToSet(&data, true); } void Unit::SetHover(bool enable) { bool isClientControlled = IsClientControlled(); if (!isClientControlled) { if (enable) m_movementInfo.AddMovementFlag(MOVEMENTFLAG_HOVER); else m_movementInfo.RemoveMovementFlag(MOVEMENTFLAG_HOVER); } float hoverHeight = GetHoverHeight(); if (enable) { if (hoverHeight && GetPositionZ() - GetMap()->GetHeight(GetPhaseMask(), GetPositionX(), GetPositionY(), GetPositionZ()) < hoverHeight) Relocate(GetPositionX(), GetPositionY(), GetPositionZ() + hoverHeight); } else { if (IsAlive() || !IsUnit()) { float newZ = std::max(GetMap()->GetHeight(GetPhaseMask(), GetPositionX(), GetPositionY(), GetPositionZ()), GetPositionZ() - hoverHeight); UpdateAllowedPositionZ(GetPositionX(), GetPositionY(), newZ); Relocate(GetPositionX(), GetPositionY(), newZ); } } if (!IsInWorld()) // is sent on add to map return; if (isClientControlled) { if (Player const* player = GetClientControlling()) { WorldPacket data(enable ? SMSG_MOVE_SET_HOVER : SMSG_MOVE_UNSET_HOVER, GetPackGUID().size() + 4); uint32 const counter = player->GetSession()->GetOrderCounter(); data << GetPackGUID(); data << counter; player->SendDirectMessage(&data); player->GetSession()->IncrementOrderCounter(); return; } } WorldPacket data(enable ? SMSG_SPLINE_MOVE_SET_HOVER : SMSG_SPLINE_MOVE_UNSET_HOVER, 9); data << GetPackGUID(); SendMessageToSet(&data, true); } void Unit::SetWaterWalking(bool enable) { bool isClientControlled = IsClientControlled(); if (!isClientControlled) { if (enable) m_movementInfo.AddMovementFlag(MOVEMENTFLAG_WATERWALKING); else m_movementInfo.RemoveMovementFlag(MOVEMENTFLAG_WATERWALKING); } if (!IsInWorld()) // is sent on add to map return; if (isClientControlled) { if (Player const* player = GetClientControlling()) { uint32 const counter = player->GetSession()->GetOrderCounter(); WorldPacket data(enable ? SMSG_MOVE_WATER_WALK : SMSG_MOVE_LAND_WALK, GetPackGUID().size() + 4); data << GetPackGUID(); data << counter; player->SendDirectMessage(&data); player->GetSession()->IncrementOrderCounter(); return; } } WorldPacket data(enable ? SMSG_SPLINE_MOVE_WATER_WALK : SMSG_SPLINE_MOVE_LAND_WALK, 9); data << GetPackGUID(); SendMessageToSet(&data, true); } void Unit::SendMovementWaterWalking(Player* sendTo) { if (!movespline->Initialized()) return; WorldPacket data(SMSG_SPLINE_MOVE_WATER_WALK, 9); data << GetPackGUID(); sendTo->SendDirectMessage(&data); } void Unit::SendMovementFeatherFall(Player* sendTo) { if (!movespline->Initialized()) return; WorldPacket data(SMSG_SPLINE_MOVE_FEATHER_FALL, 9); data << GetPackGUID(); sendTo->SendDirectMessage(&data); } void Unit::SendMovementHover(Player* sendTo) { if (!movespline->Initialized()) return; WorldPacket data(SMSG_SPLINE_MOVE_SET_HOVER, 9); data << GetPackGUID(); sendTo->SendDirectMessage(&data); } void Unit::BuildValuesUpdate(uint8 updateType, ByteBuffer* data, Player* target) { if (!target) return; uint32* flags = UnitUpdateFieldFlags; uint32 visibleFlag = UF_FLAG_PUBLIC; if (target == this) visibleFlag |= UF_FLAG_PRIVATE; Player* plr = GetCharmerOrOwnerPlayerOrPlayerItself(); if (GetOwnerGUID() == target->GetGUID()) visibleFlag |= UF_FLAG_OWNER; if (HasDynamicFlag(UNIT_DYNFLAG_SPECIALINFO)) if (HasAuraTypeWithCaster(SPELL_AURA_EMPATHY, target->GetGUID())) visibleFlag |= UF_FLAG_SPECIAL_INFO; if (plr && plr->IsInSameRaidWith(target)) visibleFlag |= UF_FLAG_PARTY_MEMBER; uint64 cacheKey = static_cast(visibleFlag) << 8 | updateType; auto cacheIt = _valuesUpdateCache.find(cacheKey); if (cacheIt != _valuesUpdateCache.end()) { int32 cachePos = static_cast(data->wpos()); data->append(cacheIt->second.buffer); BuildValuesCachePosPointers dataAdjustedPos = cacheIt->second.posPointers; if (cachePos) dataAdjustedPos.ApplyOffset(cachePos); PatchValuesUpdate(*data, dataAdjustedPos, target); return; } BuildValuesCachedBuffer cacheValue(500); ByteBuffer fieldBuffer(400); UpdateMask updateMask; updateMask.SetCount(m_valuesCount); for (uint16 index = 0; index < m_valuesCount; ++index) { if (_fieldNotifyFlags & flags[index] || ((flags[index] & visibleFlag) & UF_FLAG_SPECIAL_INFO) || ((updateType == UPDATETYPE_VALUES ? _changesMask.GetBit(index) : m_uint32Values[index]) && (flags[index] & visibleFlag)) || (index == UNIT_FIELD_AURASTATE && HasFlag(UNIT_FIELD_AURASTATE, PER_CASTER_AURA_STATE_MASK))) { updateMask.SetBit(index); if (index == UNIT_NPC_FLAGS) { cacheValue.posPointers.UnitNPCFlagsPos = int32(fieldBuffer.wpos()); fieldBuffer << m_uint32Values[UNIT_NPC_FLAGS]; } else if (index == UNIT_FIELD_AURASTATE) { cacheValue.posPointers.UnitFieldAuraStatePos = int32(fieldBuffer.wpos()); fieldBuffer << uint32(0); // Fill in later. } // FIXME: Some values at server stored in float format but must be sent to client in uint32 format else if (index >= UNIT_FIELD_BASEATTACKTIME && index <= UNIT_FIELD_RANGEDATTACKTIME) { // convert from float to uint32 and send fieldBuffer << uint32(m_floatValues[index] < 0 ? 0 : m_floatValues[index]); } // there are some float values which may be negative or can't get negative due to other checks else if ((index >= UNIT_FIELD_NEGSTAT0 && index <= UNIT_FIELD_NEGSTAT4) || (index >= UNIT_FIELD_RESISTANCEBUFFMODSPOSITIVE && index <= (UNIT_FIELD_RESISTANCEBUFFMODSPOSITIVE + 6)) || (index >= UNIT_FIELD_RESISTANCEBUFFMODSNEGATIVE && index <= (UNIT_FIELD_RESISTANCEBUFFMODSNEGATIVE + 6)) || (index >= UNIT_FIELD_POSSTAT0 && index <= UNIT_FIELD_POSSTAT4)) { fieldBuffer << uint32(m_floatValues[index]); } // Gamemasters should be always able to select units - remove not selectable flag else if (index == UNIT_FIELD_FLAGS) { cacheValue.posPointers.UnitFieldFlagsPos = int32(fieldBuffer.wpos()); fieldBuffer << m_uint32Values[UNIT_FIELD_FLAGS]; } // use modelid_a if not gm, _h if gm for CREATURE_FLAG_EXTRA_TRIGGER creatures else if (index == UNIT_FIELD_DISPLAYID) { cacheValue.posPointers.UnitFieldDisplayPos = int32(fieldBuffer.wpos()); fieldBuffer << m_uint32Values[UNIT_FIELD_DISPLAYID]; } else if (index == UNIT_DYNAMIC_FLAGS) { cacheValue.posPointers.UnitDynamicFlagsPos = int32(fieldBuffer.wpos()); uint32 dynamicFlags = m_uint32Values[UNIT_DYNAMIC_FLAGS] & ~(UNIT_DYNFLAG_TAPPED | UNIT_DYNFLAG_TAPPED_BY_PLAYER); fieldBuffer << dynamicFlags; } else if (index == UNIT_FIELD_BYTES_2) { cacheValue.posPointers.UnitFieldBytes2Pos = int32(fieldBuffer.wpos()); fieldBuffer << m_uint32Values[index]; } else if (index == UNIT_FIELD_FACTIONTEMPLATE) { cacheValue.posPointers.UnitFieldFactionTemplatePos = int32(fieldBuffer.wpos()); fieldBuffer << m_uint32Values[index]; } else { if (sScriptMgr->ShouldTrackValuesUpdatePosByIndex(this, updateType, index)) cacheValue.posPointers.other[index] = static_cast(fieldBuffer.wpos()); // send in current format (float as float, uint32 as uint32) fieldBuffer << m_uint32Values[index]; } } } cacheValue.buffer << uint8(updateMask.GetBlockCount()); updateMask.AppendToPacket(&cacheValue.buffer); int32 fieldBufferPos = static_cast(cacheValue.buffer.wpos()); cacheValue.buffer.append(fieldBuffer); cacheValue.posPointers.ApplyOffset(fieldBufferPos); int32 cachePos = static_cast(data->wpos()); data->append(cacheValue.buffer); BuildValuesCachePosPointers dataAdjustedPos = cacheValue.posPointers; if (cachePos) dataAdjustedPos.ApplyOffset(cachePos); PatchValuesUpdate(*data, dataAdjustedPos, target); _valuesUpdateCache.insert(std::pair(cacheKey, std::move(cacheValue))); } void Unit::PatchValuesUpdate(ByteBuffer& valuesUpdateBuf, BuildValuesCachePosPointers& posPointers, Player* target) { Creature const* creature = ToCreature(); // UNIT_NPC_FLAGS if (creature && posPointers.UnitNPCFlagsPos >= 0) { uint32 appendValue = m_uint32Values[UNIT_NPC_FLAGS]; if (sWorld->getIntConfig(CONFIG_INSTANT_TAXI) == 2 && appendValue & UNIT_NPC_FLAG_FLIGHTMASTER) appendValue |= UNIT_NPC_FLAG_GOSSIP; // flight masters need NPC gossip flag to show instant flight toggle option if (!target->CanSeeSpellClickOn(creature)) appendValue &= ~UNIT_NPC_FLAG_SPELLCLICK; if (!target->CanSeeVendor(creature)) { appendValue &= ~UNIT_NPC_FLAG_REPAIR; appendValue &= ~UNIT_NPC_FLAG_VENDOR_MASK; } if (!creature->IsValidTrainerForPlayer(target, &appendValue)) appendValue &= ~UNIT_NPC_FLAG_TRAINER; valuesUpdateBuf.put(posPointers.UnitNPCFlagsPos, appendValue); } // UNIT_FIELD_AURASTATE if (posPointers.UnitFieldAuraStatePos >= 0) valuesUpdateBuf.put(posPointers.UnitFieldAuraStatePos, uint32(BuildAuraStateUpdateForTarget(target))); // UNIT_FIELD_FLAGS if (posPointers.UnitFieldFlagsPos >= 0) { uint32 appendValue = m_uint32Values[UNIT_FIELD_FLAGS]; if (target->IsGameMaster() && target->GetSession()->IsGMAccount()) appendValue &= ~UNIT_FLAG_NOT_SELECTABLE; valuesUpdateBuf.put(posPointers.UnitFieldFlagsPos, appendValue); } // UNIT_FIELD_DISPLAYID // Use modelid_a if not gm, _h if gm for CREATURE_FLAG_EXTRA_TRIGGER creatures. if (posPointers.UnitFieldDisplayPos >= 0) { uint32 displayId = m_uint32Values[UNIT_FIELD_DISPLAYID]; if (creature) { CreatureTemplate const* cinfo = creature->GetCreatureTemplate(); // this also applies for transform auras if (SpellInfo const* transform = sSpellMgr->GetSpellInfo(getTransForm())) for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) if (transform->Effects[i].IsAura(SPELL_AURA_TRANSFORM)) if (CreatureTemplate const* transformInfo = sObjectMgr->GetCreatureTemplate(transform->Effects[i].MiscValue)) { cinfo = transformInfo; break; } if (cinfo->HasFlagsExtra(CREATURE_FLAG_EXTRA_TRIGGER)) { if (target->IsGameMaster() && target->GetSession()->IsGMAccount()) displayId = cinfo->GetFirstVisibleModel()->CreatureDisplayID; else displayId = cinfo->GetFirstInvisibleModel()->CreatureDisplayID; } } valuesUpdateBuf.put(posPointers.UnitFieldDisplayPos, uint32(displayId)); } // UNIT_DYNAMIC_FLAGS // Hide lootable animation for unallowed players. if (posPointers.UnitDynamicFlagsPos >= 0) { uint32 dynamicFlags = m_uint32Values[UNIT_DYNAMIC_FLAGS] & ~(UNIT_DYNFLAG_TAPPED | UNIT_DYNFLAG_TAPPED_BY_PLAYER); if (creature) { if (creature->hasLootRecipient()) { dynamicFlags |= UNIT_DYNFLAG_TAPPED; if (creature->isTappedBy(target)) dynamicFlags |= UNIT_DYNFLAG_TAPPED_BY_PLAYER; } if (!target->isAllowedToLoot(creature)) dynamicFlags &= ~UNIT_DYNFLAG_LOOTABLE; } // unit UNIT_DYNFLAG_TRACK_UNIT should only be sent to caster of SPELL_AURA_MOD_STALKED auras if (dynamicFlags & UNIT_DYNFLAG_TRACK_UNIT) if (!HasAuraTypeWithCaster(SPELL_AURA_MOD_STALKED, target->GetGUID())) dynamicFlags &= ~UNIT_DYNFLAG_TRACK_UNIT; valuesUpdateBuf.put(posPointers.UnitDynamicFlagsPos, dynamicFlags); } // UNIT_FIELD_BYTES_2 if (posPointers.UnitFieldBytes2Pos >= 0) { if (IsControlledByPlayer() && target != this && sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_GROUP) && IsInRaidWith(target)) { FactionTemplateEntry const* ft1 = GetFactionTemplateEntry(); FactionTemplateEntry const* ft2 = target->GetFactionTemplateEntry(); if (ft1 && ft2 && !ft1->IsFriendlyTo(*ft2)) // Allow targetting opposite faction in party when enabled in config valuesUpdateBuf.put(posPointers.UnitFieldBytes2Pos, (m_uint32Values[UNIT_FIELD_BYTES_2] & ((UNIT_BYTE2_FLAG_SANCTUARY /*| UNIT_BYTE2_FLAG_AURAS | UNIT_BYTE2_FLAG_UNK5*/) << 8))); // this flag is at uint8 offset 1 !! }// pussywizard / Callmephil else if (target->IsSpectator() && target->FindMap() && target->FindMap()->IsBattleArena() && (this->IsPlayer() || this->IsCreature() || this->IsDynamicObject())) { valuesUpdateBuf.put(posPointers.UnitFieldBytes2Pos, (m_uint32Values[UNIT_FIELD_BYTES_2] & 0xFFFFF2FF)); // clear UNIT_BYTE2_FLAG_PVP, UNIT_BYTE2_FLAG_FFA_PVP, UNIT_BYTE2_FLAG_SANCTUARY } } // UNIT_FIELD_FACTIONTEMPLATE if (posPointers.UnitFieldFactionTemplatePos >= 0) { if (IsControlledByPlayer() && target != this && sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_GROUP) && IsInRaidWith(target)) { FactionTemplateEntry const* ft1 = GetFactionTemplateEntry(); FactionTemplateEntry const* ft2 = target->GetFactionTemplateEntry(); if (ft1 && ft2 && !ft1->IsFriendlyTo(*ft2)) // pretend that all other HOSTILE players have own faction, to allow follow, heal, rezz (trade wont work) valuesUpdateBuf.put(posPointers.UnitFieldFactionTemplatePos, uint32(target->GetFaction())); }// pussywizard / Callmephil else if (target->IsSpectator() && target->FindMap() && target->FindMap()->IsBattleArena() && (this->IsPlayer() || this->IsCreature() || this->IsDynamicObject())) { valuesUpdateBuf.put(posPointers.UnitFieldFactionTemplatePos, uint32(target->GetFaction())); } else if (target->IsGMSpectator() && IsControlledByPlayer()) { valuesUpdateBuf.put(posPointers.UnitFieldFactionTemplatePos, uint32(target->GetFaction())); } } sScriptMgr->OnPatchValuesUpdate(this, valuesUpdateBuf, posPointers, target); } void Unit::BuildCooldownPacket(WorldPacket& data, uint8 flags, uint32 spellId, uint32 cooldown) { data.Initialize(SMSG_SPELL_COOLDOWN, 8 + 1 + 4 + 4); data << GetGUID(); data << uint8(flags); data << uint32(spellId); data << uint32(cooldown); } void Unit::BuildCooldownPacket(WorldPacket& data, uint8 flags, PacketCooldowns const& cooldowns) { data.Initialize(SMSG_SPELL_COOLDOWN, 8 + 1 + (4 + 4) * cooldowns.size()); data << GetGUID(); data << uint8(flags); for (std::unordered_map::const_iterator itr = cooldowns.begin(); itr != cooldowns.end(); ++itr) { data << uint32(itr->first); data << uint32(itr->second); } } uint8 Unit::getRace(bool original) const { if (IsPlayer()) { if (original) return m_realRace; else return m_race; } return GetByteValue(UNIT_FIELD_BYTES_0, 0); } void Unit::setRace(uint8 race) { if (IsPlayer()) m_race = race; } DisplayRace Unit::GetDisplayRaceFromModelId(uint32 modelId) const { if (CreatureDisplayInfoEntry const* display = sCreatureDisplayInfoStore.LookupEntry(modelId)) { if (CreatureDisplayInfoExtraEntry const* displayExtra = sCreatureDisplayInfoExtraStore.LookupEntry(display->ExtendedDisplayInfoID)) { return DisplayRace(displayExtra->DisplayRaceID); } } return DisplayRace::None; } // Check if unit in combat with specific unit bool Unit::IsInCombatWith(Unit const* who) const { // Check target exists if (!who) return false; // Search in threat list ObjectGuid guid = who->GetGUID(); for (ThreatContainer::StorageType::const_iterator i = m_ThreatMgr.GetThreatList().begin(); i != m_ThreatMgr.GetThreatList().end(); ++i) { HostileReference* ref = (*i); // Return true if the unit matches if (ref && ref->getUnitGuid() == guid) return true; } // Nothing found, false. return false; } /** * @brief this method gets the diameter of a Unit by DB if any value is defined, otherwise it gets the value by the DBC * * If the player is mounted the diameter also takes in consideration the mount size * * @return float The diameter of a unit */ float Unit::GetCollisionWidth() const { if (IsPlayer()) return GetObjectSize(); float scaleMod = GetObjectScale(); // 99% sure about this float objectSize = GetObjectSize(); float defaultSize = DEFAULT_WORLD_OBJECT_SIZE * scaleMod; //! Dismounting case - use basic default model data CreatureDisplayInfoEntry const* displayInfo = sCreatureDisplayInfoStore.AssertEntry(GetNativeDisplayId()); CreatureModelDataEntry const* modelData = sCreatureModelDataStore.AssertEntry(displayInfo->ModelId); if (IsMounted()) { if (CreatureDisplayInfoEntry const* mountDisplayInfo = sCreatureDisplayInfoStore.LookupEntry(GetUInt32Value(UNIT_FIELD_MOUNTDISPLAYID))) { if (CreatureModelDataEntry const* mountModelData = sCreatureModelDataStore.LookupEntry(mountDisplayInfo->ModelId)) { if (G3D::fuzzyGt(mountModelData->CollisionWidth, modelData->CollisionWidth)) modelData = mountModelData; } } } float collisionWidth = scaleMod * modelData->CollisionWidth * modelData->Scale * displayInfo->scale * 2; // if the objectSize is the default value or the creature is mounted and we have a DBC value, then we can retrieve DBC value instead return G3D::fuzzyGt(collisionWidth, 0.0f) && (G3D::fuzzyEq(objectSize,defaultSize) || IsMounted()) ? collisionWidth : objectSize; } /** * @brief this method gets the radius of a Unit by DB if any value is defined, otherwise it gets the value by the DBC * * If the player is mounted the radius also takes in consideration the mount size * * @return float The radius of a unit */ float Unit::GetCollisionRadius() const { return GetCollisionWidth() / 2; } //! Return collision height sent to client float Unit::GetCollisionHeight() const { float scaleMod = GetObjectScale(); // 99% sure about this float defaultHeight = DEFAULT_COLLISION_HEIGHT * scaleMod; CreatureDisplayInfoEntry const* displayInfo = sCreatureDisplayInfoStore.AssertEntry(GetNativeDisplayId()); CreatureModelDataEntry const* modelData = sCreatureModelDataStore.AssertEntry(displayInfo->ModelId); float collisionHeight = 0.0f; if (IsMounted()) { if (CreatureDisplayInfoEntry const* mountDisplayInfo = sCreatureDisplayInfoStore.LookupEntry(GetUInt32Value(UNIT_FIELD_MOUNTDISPLAYID))) { if (CreatureModelDataEntry const* mountModelData = sCreatureModelDataStore.LookupEntry(mountDisplayInfo->ModelId)) { collisionHeight = scaleMod * (mountModelData->MountHeight + modelData->CollisionHeight * modelData->Scale * displayInfo->scale * 0.5f); } } } else collisionHeight = scaleMod * modelData->CollisionHeight * modelData->Scale * displayInfo->scale; return collisionHeight == 0.0f ? defaultHeight : collisionHeight; } void Unit::Talk(std::string_view text, ChatMsg msgType, Language language, float textRange, WorldObject const* target) { Acore::CustomChatTextBuilder builder(this, msgType, text, language, target); Acore::LocalizedPacketDo localizer(builder); Acore::PlayerDistWorker > worker(this, textRange, localizer); Cell::VisitObjects(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(); WorldPacket data; ChatHandler::BuildChatPacket(data, isBossWhisper ? CHAT_MSG_RAID_BOSS_WHISPER : CHAT_MSG_MONSTER_WHISPER, language, this, target, text, 0, "", locale); target->SendDirectMessage(&data); } uint32 Unit::GetVirtualItemId(uint32 slot) const { if (slot >= MAX_EQUIPMENT_ITEMS) return 0; return GetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + slot); } void Unit::SetVirtualItem(uint32 slot, uint32 itemId) { if (slot >= MAX_EQUIPMENT_ITEMS) return; SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + slot, itemId); } void Unit::Talk(uint32 textId, ChatMsg msgType, float textRange, WorldObject const* target) { if (!sObjectMgr->GetBroadcastText(textId)) { LOG_ERROR("entities.unit", "Unit::Talk: `broadcast_text` (ID: {}) was not found", textId); return; } Acore::BroadcastTextBuilder builder(this, msgType, textId, getGender(), target); Acore::LocalizedPacketDo localizer(builder); Acore::PlayerDistWorker > worker(this, textRange, localizer); Cell::VisitObjects(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; } BroadcastText const* bct = sObjectMgr->GetBroadcastText(textId); if (!bct) { LOG_ERROR("entities.unit", "Unit::Whisper: `broadcast_text` was not {} found", textId); return; } LocaleConstant locale = target->GetSession()->GetSessionDbLocaleIndex(); WorldPacket data; ChatHandler::BuildChatPacket(data, isBossWhisper ? CHAT_MSG_RAID_BOSS_WHISPER : CHAT_MSG_MONSTER_WHISPER, LANG_UNIVERSAL, this, target, bct->GetText(locale, getGender()), 0, "", locale); target->SendDirectMessage(&data); } bool Unit::CanRestoreMana(SpellInfo const* spellInfo) const { // Aura of Despair exceptions switch (spellInfo->Id) { case 16666: // Demonic Rune case 27869: // Dark Rune case 30824: // Shamanistic Rage case 31786: // Spiritual Attunement case 31930: // Judgements of the Wise case 34075: // Aspect of the Viper case 34720: // Thrill of the hunt case 47755: // Rapture case 54425: // Improved Felhunter case 57319: // Blessing of Sanctuary case 63337: // Saronite Vapors (regenerate mana) case 63375: // Improved stormstrike case 64372: // Lifebloom case 68285: // Improved Leader of the Pack return true; case 54428: // Divine Plea - only with talent Guarded by the Light return HasSpell(53583); default: break; } return false; } void Unit::SetShapeshiftForm(ShapeshiftForm form) { SetByteValue(UNIT_FIELD_BYTES_2, 3, form); sScriptMgr->OnUnitSetShapeshiftForm((Unit*)this, form); } bool Unit::IsInDisallowedMountForm() const { if (SpellInfo const* transformSpellInfo = sSpellMgr->GetSpellInfo(getTransForm())) { if (transformSpellInfo->HasAttribute(SPELL_ATTR0_ALLOW_WHILE_MOUNTED)) { return false; } } if (ShapeshiftForm form = GetShapeshiftForm()) { SpellShapeshiftFormEntry const* shapeshift = sSpellShapeshiftFormStore.LookupEntry(form); if (!shapeshift) { return true; } if (!(shapeshift->flags1 & SHAPESHIFT_FLAG_STANCE)) { return true; } } if (GetDisplayId() == GetNativeDisplayId()) { return false; } CreatureDisplayInfoEntry const* display = sCreatureDisplayInfoStore.LookupEntry(GetDisplayId()); 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->HasFlag(CREATURE_MODEL_DATA_FLAGS_CAN_MOUNT))) { if (race && !(race->HasFlag(CHRRACES_FLAGS_CAN_MOUNT))) { return true; } } return false; } void Unit::SetUInt32Value(uint16 index, uint32 value) { Object::SetUInt32Value(index, value); switch (index) { // Invalidating the cache on health change should fix an issue where the client sees dead NPCs when they are not. // We might also need to invalidate the cache for some other fields as well. case UNIT_FIELD_HEALTH: InvalidateValuesUpdateCache(); break; } } std::string Unit::GetDebugInfo() const { std::stringstream sstr; sstr << WorldObject::GetDebugInfo() << "\n" << std::boolalpha << "AliveState: " << IsAlive() << " UnitMovementFlags: " << GetUnitMovementFlags() << " ExtraUnitMovementFlags: " << GetExtraUnitMovementFlags() << " Class: " << std::to_string(getClass()); return sstr.str(); } bool Unit::IsClientControlled(Player const* exactClient /*= nullptr*/) const { // Severvide method to check if unit is client controlled (optionally check for specific client in control) // Applies only to player controlled units if (!HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED)) return false; // These flags are meant to be used when server controls this unit, client control is taken away if (HasFlag(UNIT_FIELD_FLAGS, (UNIT_FLAG_DISABLE_MOVE | UNIT_FLAG_CONFUSED | UNIT_FLAG_FLEEING))) return false; // If unit is possessed, it has lost original control... if (ObjectGuid const& guid = GetCharmerGUID()) { // ... but if it is a possessing charm, then we have to check if some other player controls it if (HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_POSSESSED) && guid.IsPlayer()) return (exactClient ? (exactClient->GetGUID() == guid) : true); return false; } // By default: players have client control over themselves if (IsPlayer()) return (exactClient ? (exactClient == this) : true); return false; } Player const* Unit::GetClientControlling() const { // Serverside reverse "mover" deduction logic at controlled unit // Applies only to player controlled units if (HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED)) { // Charm always removes control from original client... if (GetCharmerGUID()) { // ... but if it is a possessing charm, some other client may have control if (HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_POSSESSED)) { Unit const* charmer = GetCharmer(); if (charmer && charmer->IsPlayer()) return static_cast(charmer); } } else if (IsPlayer()) { // Check if anything prevents original client from controlling if (IsClientControlled(static_cast(this))) return static_cast(this); } } return nullptr; }