diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index 411082eb3e..37ac95aaca 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -11522,6 +11522,100 @@ TransportBase* Unit::GetDirectTransport() const return GetTransport(); } + +// VIANO_GROUP_CONTEXT_V1 +namespace +{ + class VianoGroupBotInRangeCheck + { + public: + VianoGroupBotInRangeCheck(Unit const* source, Player const* groupOwner, float range) + : _source(source), _groupOwner(groupOwner), _range(range) { } + + bool operator()(Unit* unit) const + { + if (!unit || !unit->IsAlive() || !_source || !_groupOwner) + return false; + + if (!_source->IsWithinDistInMap(unit, _range)) + return false; + + Creature* creature = unit->ToCreature(); + if (!creature || !creature->IsVianoAdventurer()) + return false; + + Player* botOwner = creature->GetOwner() ? creature->GetOwner()->ToPlayer() : nullptr; + if (!botOwner || !botOwner->IsInMap(_groupOwner)) + return false; + + Group const* group = _groupOwner->GetGroup(); + if (!group) + return botOwner->GetGUID() == _groupOwner->GetGUID(); + + return botOwner->GetGroup() == group + && botOwner->GetSubGroup() == _groupOwner->GetSubGroup(); + } + + private: + Unit const* _source; + Player const* _groupOwner; + float _range; + }; + + void AppendNearbyVianoGroupBots(Unit* source, Player* groupOwner, std::list& units) + { + if (!source || !groupOwner || !source->IsInWorld()) + return; + + float const range = source->GetVisibilityRange(); + std::list nearby; + VianoGroupBotInRangeCheck check(source, groupOwner, range); + Trinity::UnitListSearcher searcher(source, nearby, check); + Cell::VisitAllObjects(source, searcher, range); + + for (Unit* unit : nearby) + if (unit && std::find(units.begin(), units.end(), unit) == units.end()) + units.push_back(unit); + } +} + +bool Unit::IsVianoAdventurer() const +{ + Creature const* creature = ToCreature(); + if (!creature) + return false; + + CreatureTemplate const* cinfo = creature->GetCreatureTemplate(); + if (!cinfo) + return false; + + std::string const scriptName = sObjectMgr->GetScriptName(cinfo->ScriptID); + return scriptName.rfind("npc_horde_adventurer_", 0) == 0 + || scriptName.rfind("npc_alliance_adventurer_", 0) == 0; +} + +uint32 Unit::GetVianoEffectiveGroupSize() const +{ + std::list units; + const_cast(this)->GetPartyMembers(units); + + uint32 count = 0; + std::vector seen; + for (Unit* unit : units) + { + if (!unit || (!unit->IsPlayer() && !unit->IsVianoAdventurer())) + continue; + + ObjectGuid guid = unit->GetGUID(); + if (std::find(seen.begin(), seen.end(), guid) != seen.end()) + continue; + + seen.push_back(guid); + ++count; + } + return count; +} + bool Unit::IsInPartyWith(Unit const* unit) const { if (this == unit) @@ -11564,18 +11658,22 @@ void Unit::GetPartyMembers(std::list &TagUnitMap) { Unit* owner = GetCharmerOrOwnerOrSelf(); Group* group = nullptr; + Player* groupOwner = nullptr; + if (owner->GetTypeId() == TYPEID_PLAYER) - group = owner->ToPlayer()->GetGroup(); + { + groupOwner = owner->ToPlayer(); + group = groupOwner->GetGroup(); + } if (group) { - uint8 subgroup = owner->ToPlayer()->GetSubGroup(); + uint8 subgroup = groupOwner->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()) @@ -11595,6 +11693,9 @@ void Unit::GetPartyMembers(std::list &TagUnitMap) if ((pet == this || IsInMap(pet)) && pet->IsAlive()) TagUnitMap.push_back(pet); } + + if (groupOwner) + AppendNearbyVianoGroupBots(this, groupOwner, TagUnitMap); } bool Unit::IsContestedGuard() const @@ -14047,6 +14148,7 @@ void Unit::ProcessPendingSpellCastRequest() // Process packet provided targets SpellCastTargets targets(caster, _pendingSpellCastRequest->CastRequest); + // The spell cast has been requested by using an item. Handle the cast accordingly. if (_pendingSpellCastRequest->CastItemData.has_value()) { diff --git a/src/server/game/Entities/Unit/Unit.cpp.bak-remove-viano-heal-debug-20261001-012939 b/src/server/game/Entities/Unit/Unit.cpp.bak-remove-viano-heal-debug-20261001-012939 new file mode 100644 index 0000000000..d29f388497 --- /dev/null +++ b/src/server/game/Entities/Unit/Unit.cpp.bak-remove-viano-heal-debug-20261001-012939 @@ -0,0 +1,14298 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include "Unit.h" +#include "AbstractPursuer.h" +#include "Archaeology.h" +#include "Battlefield.h" +#include "BattlefieldMgr.h" +#include "Battleground.h" +#include "BattlegroundScore.h" +#include "CellImpl.h" +#include "CharacterCache.h" +#include "CharmInfo.h" +#include "Chat.h" +#include "ChatTextBuilder.h" +#include "ChatPackets.h" +#include "CombatLogPackets.h" +#include "CombatPackets.h" +#include "Common.h" +#include "ConditionMgr.h" +#include "Containers.h" +#include "CreatureAI.h" +#include "CreatureAIImpl.h" +#include "CreatureGroups.h" +#include "Formulas.h" +#include "GameClient.h" +#include "GameTime.h" +#include "GridNotifiersImpl.h" +#include "Group.h" +#include "InstanceSaveMgr.h" +#include "InstanceScript.h" +#include "Item.h" +#include "ListUtils.h" +#include "Log.h" +#include "LootMgr.h" +#include "LootPackets.h" +#include "MiscPackets.h" +#include "MotionMaster.h" +#include "MovementGenerator.h" +#include "MovementPackets.h" +#include "MovementPacketBuilder.h" +#include "MovementStructures.h" +#include "MoveSpline.h" +#include "MoveSplineInit.h" +#include "MovementPacketSender.h" +#include "ObjectAccessor.h" +#include "ObjectMgr.h" +#include "Opcodes.h" +#include "OutdoorPvP.h" +#include "PassiveAI.h" +#include "Pet.h" +#include "PetPackets.h" +#include "PetAI.h" +#include "PhasingHandler.h" +#include "Player.h" +#include "PlayerAI.h" +#include "QuestDef.h" +#include "ReputationMgr.h" +#include "ScheduledChangeAI.h" +#include "Spell.h" +#include "SpellAuraEffects.h" +#include "SpellAuras.h" +#include "SpellCastRequest.h" +#include "SpellHistory.h" +#include "SpellInfo.h" +#include "SpellMgr.h" +#include "SpellPackets.h" +#include "Stats.h" +#include "TemporarySummon.h" +#include "Totem.h" +#include "Transport.h" +#include "UnitAI.h" +#include "UpdateFieldFlags.h" +#include "Util.h" +#include "Vehicle.h" +#include "VehiclePackets.h" +#include "World.h" +#include "WorldPacket.h" +#include "WorldSession.h" +#include +#include + +float baseMoveSpeed[MAX_MOVE_TYPE] = +{ + 2.5f, // MOVE_WALK + 7.0f, // MOVE_RUN + 4.5f, // MOVE_RUN_BACK + 4.722222f, // MOVE_SWIM + 2.5f, // MOVE_SWIM_BACK + 3.141594f, // MOVE_TURN_RATE + 7.0f, // MOVE_FLIGHT + 4.5f, // MOVE_FLIGHT_BACK + 3.14f // MOVE_PITCH_RATE +}; + +float playerBaseMoveSpeed[MAX_MOVE_TYPE] = +{ + 2.5f, // MOVE_WALK + 7.0f, // MOVE_RUN + 4.5f, // MOVE_RUN_BACK + 4.722222f, // MOVE_SWIM + 2.5f, // MOVE_SWIM_BACK + 3.141594f, // MOVE_TURN_RATE + 7.0f, // MOVE_FLIGHT + 4.5f, // MOVE_FLIGHT_BACK + 3.14f // MOVE_PITCH_RATE +}; + +DamageInfo::DamageInfo(Unit* attacker, Unit* victim, uint32 damage, SpellInfo const* spellInfo, SpellSchoolMask schoolMask, DamageEffectType damageType, WeaponAttackType attackType) + : m_attacker(attacker), m_victim(victim), m_damage(damage), m_spellInfo(spellInfo), m_schoolMask(schoolMask), m_damageType(damageType), m_attackType(attackType), + m_absorb(0), m_resist(0), m_block(0), m_hitMask(PROC_HIT_NONE) +{ +} + +DamageInfo::DamageInfo(CalcDamageInfo const& dmgInfo) + : m_attacker(dmgInfo.Attacker), m_victim(dmgInfo.Target), m_damage(dmgInfo.Damage), m_spellInfo(nullptr), m_schoolMask(SpellSchoolMask(dmgInfo.DamageSchoolMask)), + m_damageType(DIRECT_DAMAGE), m_attackType(dmgInfo.AttackType), m_absorb(dmgInfo.Absorb), m_resist(dmgInfo.Resist), m_block(dmgInfo.Blocked), m_hitMask(PROC_HIT_NONE) +{ + switch (dmgInfo.TargetState) + { + case VICTIMSTATE_IS_IMMUNE: + m_hitMask |= PROC_HIT_IMMUNE; + break; + case VICTIMSTATE_BLOCKS: + m_hitMask |= PROC_HIT_FULL_BLOCK; + break; + } + + if (dmgInfo.HitInfo & (HITINFO_PARTIAL_ABSORB | HITINFO_FULL_ABSORB)) + m_hitMask |= PROC_HIT_ABSORB; + + if (dmgInfo.HitInfo & HITINFO_FULL_RESIST) + m_hitMask |= PROC_HIT_FULL_RESIST; + + if (m_block) + m_hitMask |= PROC_HIT_BLOCK; + + bool const damageNullified = (dmgInfo.HitInfo & (HITINFO_FULL_ABSORB | HITINFO_FULL_RESIST)) != 0 || + (m_hitMask & (PROC_HIT_IMMUNE | PROC_HIT_FULL_BLOCK)) != 0; + switch (dmgInfo.HitOutCome) + { + case MELEE_HIT_MISS: + m_hitMask |= PROC_HIT_MISS; + break; + case MELEE_HIT_DODGE: + m_hitMask |= PROC_HIT_DODGE; + break; + case MELEE_HIT_PARRY: + m_hitMask |= PROC_HIT_PARRY; + break; + case MELEE_HIT_EVADE: + m_hitMask |= PROC_HIT_EVADE; + break; + case MELEE_HIT_BLOCK: + case MELEE_HIT_CRUSHING: + case MELEE_HIT_GLANCING: + case MELEE_HIT_NORMAL: + if (!damageNullified) + m_hitMask |= PROC_HIT_NORMAL; + break; + case MELEE_HIT_CRIT: + if (!damageNullified) + m_hitMask |= PROC_HIT_CRITICAL; + break; + } +} + +DamageInfo::DamageInfo(SpellNonMeleeDamage const& spellNonMeleeDamage, DamageEffectType damageType, WeaponAttackType attackType, ProcFlagsHit hitMask) + : m_attacker(spellNonMeleeDamage.attacker), m_victim(spellNonMeleeDamage.target), m_damage(spellNonMeleeDamage.damage), + m_spellInfo(sSpellMgr->GetSpellInfo(spellNonMeleeDamage.SpellID)), m_schoolMask(SpellSchoolMask(spellNonMeleeDamage.schoolMask)), m_damageType(damageType), + m_attackType(attackType), m_absorb(spellNonMeleeDamage.absorb), m_resist(spellNonMeleeDamage.resist), m_block(spellNonMeleeDamage.blocked), m_hitMask(hitMask) +{ + if (spellNonMeleeDamage.blocked) + m_hitMask |= PROC_HIT_BLOCK; + if (spellNonMeleeDamage.absorb) + m_hitMask |= PROC_HIT_ABSORB; +} + +void DamageInfo::ModifyDamage(int32 amount) +{ + amount = std::max(amount, -static_cast(GetDamage())); + m_damage += amount; +} + +void DamageInfo::AbsorbDamage(uint32 amount) +{ + amount = std::min(amount, GetDamage()); + m_absorb += amount; + m_damage -= amount; + m_hitMask |= PROC_HIT_ABSORB; +} + +void DamageInfo::ResistDamage(uint32 amount) +{ + amount = std::min(amount, GetDamage()); + m_resist += amount; + m_damage -= amount; + if (!m_damage) + { + m_hitMask |= PROC_HIT_FULL_RESIST; + m_hitMask &= ~(PROC_HIT_NORMAL | PROC_HIT_CRITICAL); + } +} + +void DamageInfo::BlockDamage(uint32 amount) +{ + amount = std::min(amount, GetDamage()); + m_block += amount; + m_damage -= amount; + m_hitMask |= PROC_HIT_BLOCK; + if (!m_damage) + { + m_hitMask |= PROC_HIT_FULL_BLOCK; + m_hitMask &= ~(PROC_HIT_NORMAL | PROC_HIT_CRITICAL); + } +} + +ProcFlagsHit DamageInfo::GetHitMask() const +{ + return m_hitMask; +} + +HealInfo::HealInfo(Unit* healer, Unit* target, uint32 heal, SpellInfo const* spellInfo, SpellSchoolMask schoolMask) + : _healer(healer), _target(target), _heal(heal), _effectiveHeal(0), _absorb(0), _spellInfo(spellInfo), _schoolMask(schoolMask), _hitMask(0) +{ +} + +void HealInfo::AbsorbHeal(uint32 amount) +{ + amount = std::min(amount, GetHeal()); + _absorb += amount; + _heal -= amount; + amount = std::min(amount, GetEffectiveHeal()); + _effectiveHeal -= amount; + _hitMask |= PROC_HIT_ABSORB; +} + +uint32 HealInfo::GetHitMask() const +{ + return _hitMask; +} + +ProcEventInfo::ProcEventInfo(Unit* actor, Unit* actionTarget, Unit* procTarget, + ProcFlags typeMask, ProcFlagsSpellType spellTypeMask, + ProcFlagsSpellPhase spellPhaseMask, ProcFlagsHit hitMask, + Spell* spell, DamageInfo* damageInfo, + HealInfo* healInfo) : + _actor(actor), _actionTarget(actionTarget), _procTarget(procTarget), + _typeMask(typeMask), _spellTypeMask(spellTypeMask), + _spellPhaseMask(spellPhaseMask), _hitMask(hitMask), _spell(spell), + _damageInfo(damageInfo), _healInfo(healInfo) +{ } + +SpellInfo const* ProcEventInfo::GetSpellInfo() const +{ + if (_spell) + return _spell->GetSpellInfo(); + if (_damageInfo) + return _damageInfo->GetSpellInfo(); + if (_healInfo) + return _healInfo->GetSpellInfo(); + return nullptr; +} + +SpellSchoolMask ProcEventInfo::GetSchoolMask() const +{ + if (_spell) + return _spell->GetSpellInfo()->GetSchoolMask(); + if (_damageInfo) + return _damageInfo->GetSchoolMask(); + if (_healInfo) + return _healInfo->GetSchoolMask(); + return SPELL_SCHOOL_MASK_NONE; +} + +DispelableAura::DispelableAura(Aura* aura, int32 dispelChance, uint8 dispelCharges) : + _aura(aura), _chance(dispelChance), _charges(dispelCharges) +{ +} + +DispelableAura::~DispelableAura() = default; + +bool DispelableAura::RollDispel() const +{ + return roll_chance_i(_chance); +} + +Unit::Unit(bool isWorldObject) : + WorldObject(isWorldObject), m_lastSanctuaryTime(0), + LastCharmerGUID(), m_ControlledByPlayer(false), movespline(std::make_unique()), + m_procDeep(0), m_procChainLength(0), m_removedAurasCount(0), + m_interruptMask(SpellAuraInterruptFlags::None), m_interruptMask2(SpellAuraInterruptFlags2::None), + m_charmer(nullptr), m_charmed(nullptr), + i_motionMaster(std::make_unique(this)), m_vehicle(nullptr), + m_unitTypeMask(UNIT_MASK_NONE), m_isEngaged(false), m_combatManager(this), m_threatManager(this), + i_AI(nullptr), m_aiLocked(false), m_spellHistory(std::make_unique(this)), + _stats(std::make_unique(this)) +{ + m_objectType |= TYPEMASK_UNIT; + m_objectTypeId = TYPEID_UNIT; + + m_updateFlag.MovementUpdate = true; + + m_baseAttackSpeed = { }; + m_attackTimer = { }; + m_modAttackSpeedPct.fill(1.0f); + + m_extraAttacks = 0; + m_canDualWield = false; + + m_movementCounter = 0; + + m_state = 0; + m_deathState = ALIVE; + + m_currentSpells.fill(nullptr); + + m_auraUpdateIterator = m_ownedAuras.end(); + + m_transform = 0; + m_canModifyStats = false; + + 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] = BASE_MINDAMAGE; + m_weaponDamage[i][MAXDAMAGE] = BASE_MAXDAMAGE; + } + + m_attacking = nullptr; + m_modMeleeHitChance = 0.0f; + m_modRangedHitChance = 0.0f; + m_modSpellHitChance = 0.0f; + m_baseSpellCritChance = 5; + + m_speed_rate.fill(1.0f); + _gameClientMovingMe = nullptr; + + // remove aurastates allowing special moves + m_reactiveTimer = { }; + m_cleanupDone = false; + m_duringRemoveFromWorld = false; + + m_serverSideVisibility.SetValue(SERVERSIDE_VISIBILITY_GHOST, GHOST_VISIBILITY_ALIVE); + + _lastLiquid = nullptr; + + _powerBarId = 0; + + _powerUpdateTimer = GetPowerUpdateInterval(); + _healthRegenerationTimer = UNIT_HEALTH_REGENERATION_INTERVAL; + + _powerFraction.fill(0.0f); + _usedPowerTypes.fill(MAX_POWERS); + + _oldFactionId = 0; + _isWalkingBeforeCharm = false; +} + +//////////////////////////////////////////////////////////// +// Methods of class Unit +Unit::~Unit() +{ + // set current spells as deletable + for (size_t i = 0; i < m_currentSpells.size(); ++i) + { + if (m_currentSpells[i]) + { + m_currentSpells[i]->SetReferencedFromCurrent(false); + m_currentSpells[i] = nullptr; + } + } + + m_Events.KillAllEvents(true); + + _DeleteRemovedAuras(); + + ASSERT(!m_duringRemoveFromWorld); + ASSERT(!m_attacking); + ASSERT(m_attackers.empty()); + ASSERT(m_sharedVision.empty()); + ASSERT(m_Controlled.empty()); + ASSERT(m_appliedAuras.empty()); + ASSERT(m_ownedAuras.empty()); + ASSERT(m_removedAuras.empty()); + ASSERT(m_gameObj.empty()); + ASSERT(m_dynObj.empty()); + ASSERT(!_gameClientMovingMe || _gameClientMovingMe->GetBasePlayer() == this); +} + +void Unit::Update(uint32 p_time) +{ + // WARNING! Order of execution here is important, do not change. + // Spells must be processed with event system BEFORE they go to _UpdateSpells. + // Or else we may have some SPELL_STATE_FINISHED spells stalled in pointers, that is bad. + WorldObject::Update(p_time); + + CheckPendingMovementAcks(); + + if (!IsInWorld()) + return; + + // Update server controlled facing for standing units + if (movespline->Finalized() && !isMoving()) + { + // Prioritize tracking channeled spells above unit targets if they should both be present at the same time. + Spell const* channeledSpell = GetCurrentSpell(CURRENT_CHANNELED_SPELL); + if (channeledSpell && channeledSpell->GetSpellInfo()->HasAttribute(SPELL_ATTR1_TRACK_TARGET_IN_CHANNEL)) + { + ObjectGuid const channelGuid = GetChannelObjectGuid(); + if (!channelGuid.IsEmpty() && channelGuid != GetGUID()) + if (WorldObject const* objectTarget = ObjectAccessor::GetWorldObject(*this, channelGuid)) + SetOrientationTowards(objectTarget); + } + else if (IsCreature() && !HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_POSSESSED)) + { + ObjectGuid const targetGuid = GetTarget(); + if (!targetGuid.IsEmpty() && targetGuid != GetGUID()) + if (WorldObject const* objectTarget = ObjectAccessor::GetWorldObject(*this, targetGuid)) + SetOrientationTowards(objectTarget); + } + } + + _UpdateSpells(p_time); + ProcessPendingSpellCastRequest(); + + // If this is set during update SetCantProc(false) call is missing somewhere in the code + // Having this would prevent spells from being proced, so let's crash + ASSERT(!m_procDeep); + + m_combatManager.Update(p_time); + + auto spellPausesCombatTimer = [&](CurrentSpellTypes type) + { + return GetCurrentSpell(type) && GetCurrentSpell(type)->GetSpellInfo()->HasAttribute(SPELL_ATTR6_DELAY_COMBAT_TIMER_DURING_CAST); + }; + + if (!spellPausesCombatTimer(CURRENT_GENERIC_SPELL) && !spellPausesCombatTimer(CURRENT_CHANNELED_SPELL)) + { + if (uint32 base_att = getAttackTimer(BASE_ATTACK)) + setAttackTimer(BASE_ATTACK, (p_time >= base_att ? 0 : base_att - p_time)); + if (uint32 ranged_att = getAttackTimer(RANGED_ATTACK)) + setAttackTimer(RANGED_ATTACK, (p_time >= ranged_att ? 0 : ranged_att - p_time)); + if (uint32 off_att = getAttackTimer(OFF_ATTACK)) + setAttackTimer(OFF_ATTACK, (p_time >= off_att ? 0 : off_att - p_time)); + } + + // update abilities available only for fraction of time + UpdateReactives(p_time); + + if (IsAlive()) + { + ModifyAuraState(AURA_STATE_HEALTHLESS_20_PERCENT, HealthBelowPct(20)); + ModifyAuraState(AURA_STATE_HEALTHLESS_35_PERCENT, HealthBelowPct(35)); + ModifyAuraState(AURA_STATE_HEALTH_ABOVE_75_PERCENT, HealthAbovePct(75)); + } + + UpdateSplineMovement(p_time); + i_motionMaster->UpdateMotion(p_time); + + if (!i_AI && (GetTypeId() != TYPEID_PLAYER || IsCharmed())) + UpdateCharmAI(); + + // Wait with the aura interrupts until we have updated our movement generators and position6 + if (IsPlayer()) + InterruptMovementBasedAuras(); + else if (!movespline->Finalized()) + InterruptMovementBasedAuras(); + + // All position info based actions have been executed, reset info + _positionUpdateInfo.Reset(); + + // Update Health Regeneration + _healthRegenerationTimer -= p_time; + if (_healthRegenerationTimer <= 0) + { + RegenerateHealth(); + _healthRegenerationTimer += UNIT_HEALTH_REGENERATION_INTERVAL; + } + + if (HasScheduledAIChange() && (GetTypeId() != TYPEID_PLAYER || (IsCharmed() && GetCharmerGUID().IsCreature()))) + UpdateCharmAI(); + RefreshAI(); +} + +void Unit::Heartbeat() +{ + WorldObject::Heartbeat(); + + // SMSG_FLIGHT_SPLINE_SYNC for cyclic splines + SendFlightSplineSyncUpdate(); + + // Trigger heartbeat procs and generic aura behavior such as food emotes and invoking aura script hooks + TriggerAuraHeartbeat(); +} + +void Unit::TriggerAuraHeartbeat() +{ + for (auto const& [_, auraApplication] : m_appliedAuras) + auraApplication->GetBase()->Heartbeat(); + + Unit::ProcSkillsAndAuras(this, nullptr, PROC_FLAG_HEARTBEAT, PROC_FLAG_NONE, PROC_SPELL_TYPE_MASK_ALL, PROC_SPELL_PHASE_NONE, PROC_HIT_NONE, nullptr, nullptr, nullptr); +} + +bool Unit::haveOffhandWeapon() const +{ + if (Player const* player = ToPlayer()) + return player->GetWeaponForAttack(OFF_ATTACK, true) != nullptr; + + return CanDualWield(); +} + +void Unit::MonsterMoveWithSpeed(float x, float y, float z, float speed, bool generatePath, bool forceDestination) +{ + Movement::MoveSplineInit init(this); + init.MoveTo(x, y, z, generatePath, forceDestination); + init.SetVelocity(speed); + GetMotionMaster()->LaunchMoveSpline(std::move(init), 0, MOTION_SLOT_ACTIVE, POINT_MOTION_TYPE); +} + +void Unit::UpdateSplineMovement(uint32 t_diff) +{ + if (movespline->Finalized()) + return; + + movespline->updateState(t_diff); + bool arrived = movespline->Finalized(); + + if (arrived) + { + DisableSpline(); + if (Optional animTier = movespline->GetAnimation()) + SetAnimTier(*animTier); + } + + UpdateSplinePosition(); +} + +void Unit::UpdateSplinePosition() +{ + Movement::Location loc = movespline->ComputePosition(); + + if (movespline->onTransport) + { + Position& pos = m_movementInfo.transport.pos; + pos.m_positionX = loc.x; + pos.m_positionY = loc.y; + pos.m_positionZ = loc.z; + pos.SetOrientation(loc.orientation); + + if (TransportBase* transport = GetDirectTransport()) + transport->CalculatePassengerPosition(loc.x, loc.y, loc.z, &loc.orientation); + else + return; + } + + if (HasUnitState(UNIT_STATE_CANNOT_TURN)) + loc.orientation = GetOrientation(); + + UpdatePosition(loc.x, loc.y, loc.z, loc.orientation); +} + +void Unit::SendFlightSplineSyncUpdate() +{ + if (!movespline->isCyclic() || movespline->Finalized()) + return; + + WorldPackets::Movement::FlightSplineSync flightSplineSync; + flightSplineSync.Guid = GetGUID(); + flightSplineSync.SplineDist = float(movespline->timePassed()) / movespline->Duration(); + SendMessageToSet(flightSplineSync.Write(), true); +} + +void Unit::InterruptMovementBasedAuras() +{ + // TODO: Check if orientation transport offset changed instead of only global orientation + if (_positionUpdateInfo.Turned) + RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Turning); + + if (_positionUpdateInfo.Relocated && !GetVehicle()) + RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Moving); +} + +void Unit::DisableSpline() +{ + m_movementInfo.RemoveMovementFlag(MOVEMENTFLAG_FORWARD); + movespline->_Interrupt(); +} + +void Unit::resetAttackTimer(WeaponAttackType type) +{ + m_attackTimer[type] = uint32(GetBaseAttackTime(type) * m_modAttackSpeedPct[type]); +} + +bool Unit::IsWithinCombatRange(Unit const* obj, float dist2compare) const +{ + if (!obj || !IsInMap(obj) || !IsInPhase(obj)) + return false; + + float dx = GetPositionX() - obj->GetPositionX(); + float dy = GetPositionY() - obj->GetPositionY(); + float dz = GetPositionZ() - obj->GetPositionZ(); + float distsq = dx * dx + dy * dy + dz * dz; + + float sizefactor = GetCombatReach() + obj->GetCombatReach(); + float maxdist = dist2compare + sizefactor; + + return distsq < maxdist * maxdist; +} + +bool Unit::IsWithinMeleeRangeAt(Position const& pos, Unit const* obj) const +{ + if (!obj || !IsInMap(obj) || !IsInPhase(obj)) + return false; + + float dx = pos.GetPositionX() - obj->GetPositionX(); + float dy = pos.GetPositionY() - obj->GetPositionY(); + float dz = GetPositionZ() - obj->GetPositionZ(); + + float distsq = dx*dx + dy*dy + dz*dz; + + float maxdist = GetMeleeRange(obj); + + return distsq <= maxdist * maxdist; +} + +float Unit::GetMeleeRange(Unit const* target) const +{ + float range = GetCombatReach() + target->GetCombatReach() + 1.3333334f; + return std::max(range, NOMINAL_MELEE_RANGE); +} + +bool Unit::IsWithinBoundaryRadius(const Unit* obj) const +{ + if (!obj || !IsInMap(obj) || !IsInPhase(obj)) + return false; + + float objBoundaryRadius = std::max(obj->GetBoundaryRadius(), MIN_MELEE_REACH); + + return IsInDist(obj, objBoundaryRadius); +} + +AuraApplication* Unit::GetVisibleAura(uint8 slot) const +{ + VisibleAuraMap::const_iterator itr = m_visibleAuras.find(slot); + if (itr != m_visibleAuras.end()) + return itr->second; + return 0; +} + +void Unit::SetVisibleAura(uint8 slot, AuraApplication * aur) +{ + m_visibleAuras[slot]=aur; + UpdateAuraForGroup(slot); +} + +void Unit::RemoveVisibleAura(uint8 slot) +{ + m_visibleAuras.erase(slot); + UpdateAuraForGroup(slot); +} + +void Unit::UpdateInterruptMask() +{ + m_interruptMask = SpellAuraInterruptFlags::None; + m_interruptMask2 = SpellAuraInterruptFlags2::None; + for (AuraApplication const* aurApp : m_interruptableAuras) + { + m_interruptMask |= aurApp->GetBase()->GetSpellInfo()->AuraInterruptFlags; + m_interruptMask2 |= aurApp->GetBase()->GetSpellInfo()->AuraInterruptFlags2; + } + + if (Spell* spell = m_currentSpells[CURRENT_CHANNELED_SPELL]) + { + if (spell->getState() == SPELL_STATE_CHANNELING) + { + m_interruptMask |= spell->m_spellInfo->ChannelInterruptFlags; + m_interruptMask2 |= spell->m_spellInfo->ChannelInterruptFlags2; + } + } +} + +bool Unit::HasAuraTypeWithFamilyFlags(AuraType auraType, uint32 familyName, 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()->HasAuraInterruptFlag(SpellAuraInterruptFlags::Damage)) + return true; + return false; +} + +bool Unit::HasBreakableByDamageCrowdControlAura(Unit* excludeCasterChannel) const +{ + uint32 excludeAura = 0; + if (Spell* currentChanneledSpell = excludeCasterChannel ? excludeCasterChannel->GetCurrentSpell(CURRENT_CHANNELED_SPELL) : nullptr) + excludeAura = currentChanneledSpell->GetSpellInfo()->Id; //Avoid self interrupt of channeled Crowd Control spells like Seduction + + return ( HasBreakableByDamageAuraType(SPELL_AURA_MOD_CONFUSE, excludeAura) + || HasBreakableByDamageAuraType(SPELL_AURA_MOD_FEAR, excludeAura) + || HasBreakableByDamageAuraType(SPELL_AURA_MOD_STUN, excludeAura) + || HasBreakableByDamageAuraType(SPELL_AURA_MOD_ROOT, excludeAura) + || HasBreakableByDamageAuraType(SPELL_AURA_TRANSFORM, excludeAura)); +} + +/*static*/ void Unit::DealDamageMods(Unit const* victim, uint32& damage, uint32* absorb) +{ + if (!victim || !victim->IsAlive() || victim->HasUnitState(UNIT_STATE_IN_FLIGHT) || (victim->GetTypeId() == TYPEID_UNIT && victim->ToCreature()->IsEvadingAttacks())) + { + if (absorb) + *absorb += damage; + damage = 0; + } +} + +/*static*/ uint32 Unit::DealDamage(Unit* attacker, Unit* victim, uint32 damage, uint32 unmitigatedDamage, DamageEffectType damagetype, SpellSchoolMask damageSchoolMask, SpellInfo const* spellProto, bool durabilityLoss) +{ + // Sparring Checks + if (Creature* target = victim->ToCreature()) + { + if (attacker && attacker->IsCreature() && !attacker->IsCharmedOwnedByPlayerOrPlayer()) + { + if (target->GetNoNpcDamageBelowPctHealthValue() != 0.0f) + { + if (damage >= target->GetHealth()) // First check: if we have a sparring limit we will never allow creatures to kill the sparring victim + damage = target->GetHealth() - 1; + else if (target->GetHealthPct() <= target->GetNoNpcDamageBelowPctHealthValue()) // Second check: stop incomming damage when we have surpassed the health limit + damage = 0; + } + } + } + + if (UnitAI* victimAI = victim->GetAI()) + victimAI->DamageTaken(attacker, damage); + + if (UnitAI* attackerAI = attacker ? attacker->GetAI() : nullptr) + attackerAI->DamageDealt(victim, damage, damagetype); + + // Hook for OnDamage Event + sScriptMgr->OnDamage(attacker, victim, damage); + + if (victim->GetTypeId() == TYPEID_PLAYER) + { + // Signal to pets that their owner was attacked - except when DOT. + if (attacker != victim && damagetype != DOT) + { + for (Unit* controlled : victim->m_Controlled) + if (Creature* cControlled = controlled->ToCreature()) + if (CreatureAI* controlledAI = cControlled->AI()) + controlledAI->OwnerAttackedBy(attacker); + } + + if (victim->ToPlayer()->GetCommandStatus(CHEAT_GOD)) + return 0; + } + + if (damagetype != NODAMAGE) + { + // interrupting auras with SpellAuraInterruptFlags::Damage before checking !damage (absorbed damage breaks that type of auras) + if (spellProto) + { + if (!spellProto->HasAttribute(SPELL_ATTR4_REACTIVE_DAMAGE_PROC)) + victim->RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Damage, spellProto); + } + else + victim->RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Damage); + + // 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 share = CalculatePct(damage, (*i)->GetAmount()); + + /// @todo check packets if damage is done by victim, or by attacker of victim + Unit::DealDamageMods(shareDamageTarget, share, nullptr); + Unit::DealDamage(attacker, shareDamageTarget, share, 0, NODAMAGE, spell->GetSchoolMask(), spell, false); + } + } + + uint32 health = victim->GetHealth(); + + // duel ends when player has 1 or less hp + bool duel_hasEnded = false; + bool duel_wasMounted = false; + if (victim->GetTypeId() == TYPEID_PLAYER && victim->ToPlayer()->duel && damage >= (health-1)) + { + if (!attacker) + return 0; + + // prevent kill only if killed in duel and killed by opponent or opponent controlled creature + if (victim->ToPlayer()->duel->opponent == attacker->GetControllingPlayer()) + damage = health - 1; + + duel_hasEnded = true; + } + else if (victim->IsCreature() && victim != attacker && damage >= health && victim->ToCreature()->HasStaticFlag(CREATURE_STATIC_FLAG_UNKILLABLE)) + { + damage = health - 1; + } + else if (victim->IsVehicle() && damage >= (health-1) && victim->GetCharmer() && victim->GetCharmer()->GetTypeId() == TYPEID_PLAYER) + { + Player* victimRider = victim->GetCharmer()->ToPlayer(); + + if (victimRider && victimRider->duel && victimRider->duel->isMounted) + { + if (!attacker) + return 0; + + // prevent kill only if killed in duel and killed by opponent or opponent controlled creature + if (victimRider->duel->opponent == attacker->GetControllingPlayer()) + damage = health - 1; + + duel_wasMounted = true; + duel_hasEnded = true; + } + } + + if (attacker && attacker != victim) + { + if (Player* killer = attacker->ToPlayer()) + { + // in bg, count dmg if victim is also a player + if (victim->GetTypeId() == TYPEID_PLAYER && !(spellProto && spellProto->HasAttribute(SPELL_ATTR7_DO_NOT_COUNT_FOR_PVP_SCOREBOARD))) + if (Battleground* bg = killer->GetBattleground()) + bg->UpdatePlayerScore(killer, SCORE_DAMAGE_DONE, damage); + + killer->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_DAMAGE_DONE, health > damage ? damage : health, 0, 0, victim); + killer->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HIGHEST_HIT_DEALT, damage); + } + } + + if (victim->GetTypeId() == TYPEID_PLAYER) + victim->ToPlayer()->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HIGHEST_HIT_RECEIVED, damage); + else if (!victim->IsControlledByPlayer() || victim->IsVehicle()) + { + if (!victim->ToCreature()->hasLootRecipient()) + victim->ToCreature()->SetLootRecipient(attacker); + + if (!attacker || attacker->IsControlledByPlayer() || (attacker->ToTempSummon() && attacker->ToTempSummon()->GetSummoner() && attacker->ToTempSummon()->GetSummoner()->GetTypeId() == TYPEID_PLAYER)) + victim->ToCreature()->LowerPlayerDamageReq(health < damage ? health : damage); + } + + if (health <= damage) + { + if (victim->GetTypeId() == TYPEID_PLAYER && victim != attacker) + victim->ToPlayer()->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_TOTAL_DAMAGE_RECEIVED, health); + + Unit::Kill(attacker, victim, durabilityLoss); + } + else + { + if (victim->GetTypeId() == TYPEID_PLAYER) + victim->ToPlayer()->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_TOTAL_DAMAGE_RECEIVED, damage); + + victim->ModifyHealth(-(int32)damage); + + if (damagetype == DIRECT_DAMAGE || damagetype == SPELL_DIRECT_DAMAGE) + victim->RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::NonPeriodicDamage, spellProto); + + if (victim->GetTypeId() != TYPEID_PLAYER) + { + // Part of Evade mechanics. DoT's and Thorns / Retribution Aura do not contribute to this + if (damagetype != DOT && damage > 0 && !victim->GetOwnerGUID().IsPlayer() && (!spellProto || !spellProto->HasAura(SPELL_AURA_DAMAGE_SHIELD))) + victim->ToCreature()->SetLastDamagedTime(GameTime::GetGameTime() + MAX_AGGRO_RESET_TIME); + + if (attacker && (!spellProto || !spellProto->HasAttribute(SPELL_ATTR4_NO_HARMFUL_THREAT))) + victim->GetThreatManager().AddThreat(attacker, float(damage), 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->GetPowerType() == POWER_RAGE) + { + uint32 rageBaseReward = CalcDamageTakenRageGain(victim, std::max(damage, unmitigatedDamage)); + victim->RewardRage(rageBaseReward, false); + } + + if (attacker && attacker->GetTypeId() == TYPEID_PLAYER) + { + // 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 && damagetype != DOT) + { + if (victim != attacker && (!spellProto || !(spellProto->HasAttribute(SPELL_ATTR6_NO_PUSHBACK) || spellProto->HasAttribute(SPELL_ATTR7_DONT_CAUSE_SPELL_PUSHBACK) || spellProto->HasAttribute(SPELL_ATTR3_TREAT_AS_PERIODIC)))) + { + if (Spell* spell = victim->m_currentSpells[CURRENT_GENERIC_SPELL]) + { + if (spell->getState() == SPELL_STATE_PREPARING) + { + auto isCastInterrupted = [&]() + { + if ((victim->IsPlayer() && spell->m_spellInfo->InterruptFlags.HasFlag(SpellInterruptFlags::DamageCancelsPlayerOnly))) + return true; + + return false; + }; + + auto isCastDelayed = [&]() + { + if (!damage) + return false; + + if ((victim->IsPlayer() && spell->m_spellInfo->InterruptFlags.HasFlag(SpellInterruptFlags::DamagePushbackPlayerOnly))) + return true; + + return false; + }; + + if (isCastInterrupted()) + victim->InterruptNonMeleeSpells(false); + else if (isCastDelayed()) + spell->Delayed(); + } + } + + if (damage && victim->IsPlayer()) + if (Spell* spell = victim->m_currentSpells[CURRENT_CHANNELED_SPELL]) + if (spell->getState() == SPELL_STATE_CHANNELING && spell->m_spellInfo->HasChannelInterruptFlag(SpellAuraInterruptFlags::DamageChannelDuration)) + spell->DelayedChannel(); + } + } + + // last damage from duel opponent + if (duel_hasEnded) + { + Player* he = duel_wasMounted ? victim->GetCharmer()->ToPlayer() : victim->ToPlayer(); + + ASSERT(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); + } + } + + return damage; +} + +void Unit::CastStop(uint32 except_spellid) +{ + for (uint32 i = CURRENT_FIRST_NON_MELEE_SPELL; i < CURRENT_MAX_SPELL; i++) + if (m_currentSpells[i] && m_currentSpells[i]->m_spellInfo->Id != except_spellid) + InterruptSpell(CurrentSpellTypes(i), false); +} + +void Unit::CalculateSpellDamageTaken(SpellNonMeleeDamage* damageInfo, int32 damage, SpellInfo const* spellInfo, WeaponAttackType attackType, bool crit, bool blocked /*= false*/) +{ + if (damage < 0) + return; + + Unit* victim = damageInfo->target; + if (!victim || !victim->IsAlive()) + return; + + SpellSchoolMask damageSchoolMask = SpellSchoolMask(damageInfo->schoolMask); + + // Spells with SPELL_ATTR4_IGNORE_DAMAGE_TAKEN_MODIFIERS ignore resilience because their damage is based off another spell's damage. + if (!spellInfo->HasAttribute(SPELL_ATTR4_IGNORE_DAMAGE_TAKEN_MODIFIERS)) + { + if (Unit::IsDamageReducedByArmor(damageSchoolMask, spellInfo)) + damage = Unit::CalcArmorReducedDamage(this, victim, damage, spellInfo, attackType); + + // Per-school calc + switch (spellInfo->DmgClass) + { + // Melee and Ranged Spells + case SPELL_DAMAGE_CLASS_RANGED: + case SPELL_DAMAGE_CLASS_MELEE: + { + if (crit) + { + damageInfo->HitInfo |= SPELL_HIT_TYPE_CRIT; + + // Calculate crit bonus + uint32 crit_bonus = damage; + // Apply crit_damage bonus for melee spells + if (Player* modOwner = GetSpellModOwner()) + modOwner->ApplySpellMod(spellInfo, SpellModOp::CritDamageAndHealing, crit_bonus); + damage += crit_bonus; + + // 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 += (GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_CRIT_DAMAGE_BONUS, spellInfo->GetSchoolMask()) - 1.0f) * 100; + + if (critPctDamageMod != 0) + AddPct(damage, critPctDamageMod); + } + + // Spell weapon based damage CAN BE crit & blocked at same time + if (blocked) + { + // double blocked amount if block is critical + uint32 value = victim->GetBlockPercent(); + if (victim->IsBlockCritical()) + value *= 2; // double blocked percent + + damageInfo->blocked = CalculatePct(damage, value); + if (damage <= int32(damageInfo->blocked)) + { + damageInfo->blocked = uint32(damage); + damageInfo->fullBlock = true; + } + damage -= damageInfo->blocked; + } + + if (CanApplyResilience()) + Unit::ApplyResilience(victim, &damage); + break; + } + // Magical Attacks + case SPELL_DAMAGE_CLASS_NONE: + case SPELL_DAMAGE_CLASS_MAGIC: + { + // If crit add critical bonus + if (crit) + { + damageInfo->HitInfo |= SPELL_HIT_TYPE_CRIT; + damage = Unit::SpellCriticalDamageBonus(this, spellInfo, damage); + } + + if (CanApplyResilience()) + Unit::ApplyResilience(victim, &damage); + break; + + } + default: + break; + } + } + + // Script Hook For CalculateSpellDamageTaken -- Allow scripts to change the Damage post class mitigation calculations + sScriptMgr->ModifySpellDamageTaken(damageInfo->target, damageInfo->attacker, damage); + + // Calculate absorb resist + if (damage < 0) + damage = 0; + + damageInfo->damage = damage; + DamageInfo dmgInfo(*damageInfo, SPELL_DIRECT_DAMAGE, BASE_ATTACK, PROC_HIT_NONE); + Unit::CalcAbsorbResist(dmgInfo); + damageInfo->absorb = dmgInfo.GetAbsorb(); + damageInfo->resist = dmgInfo.GetResist(); + + if (damageInfo->absorb) + damageInfo->HitInfo |= (damageInfo->damage - damageInfo->absorb == 0 ? HITINFO_FULL_ABSORB : HITINFO_PARTIAL_ABSORB); + + if (damageInfo->resist) + damageInfo->HitInfo |= (damageInfo->damage - damageInfo->resist == 0 ? HITINFO_FULL_RESIST : HITINFO_PARTIAL_RESIST); + + damageInfo->damage = dmgInfo.GetDamage(); +} + +void Unit::DealSpellDamage(SpellNonMeleeDamage* damageInfo, bool durabilityLoss) +{ + if (!damageInfo) + return; + + Unit* victim = damageInfo->target; + if (!victim) + return; + + if (!victim->IsAlive() || victim->HasUnitState(UNIT_STATE_IN_FLIGHT) || (victim->GetTypeId() == TYPEID_UNIT && victim->ToCreature()->IsEvadingAttacks())) + return; + + SpellInfo const* spellProto = sSpellMgr->GetSpellInfo(damageInfo->SpellID); + if (spellProto == nullptr) + { + TC_LOG_DEBUG("entities.unit", "Unit::DealSpellDamage has wrong damageInfo->SpellID: %u", damageInfo->SpellID); + return; + } + + // Call default DealDamage + Unit::DealDamage(this, victim, damageInfo->damage, damageInfo->unmitigatedDamage, SPELL_DIRECT_DAMAGE, SpellSchoolMask(damageInfo->schoolMask), spellProto, durabilityLoss); +} + +/*static*/ uint32 Unit::CalcMeleeAttackRageGain(Unit const* attacker, Unit const* victim, WeaponAttackType attType) +{ + if (!attacker || !victim || (attType != BASE_ATTACK && attType != OFF_ATTACK)) + return 0; + + uint32 rage = uint32(attacker->GetBaseAttackTime(attType) / 1000.f * 6.5f); + if (attType == OFF_ATTACK) + rage /= 2; + + // Players below level 10 start with a 50% rage gain reduction that slowly diminishes + if (attacker->IsPlayer() && attacker->getLevel() < 10) + rage -= (rage / 2.f) * (1.0f - (attacker->getLevel() / 10.f)); + + rage *= 10; + + AddPct(rage, attacker->GetTotalAuraModifier(SPELL_AURA_MOD_RAGE_FROM_DAMAGE_DEALT)); + + return rage; +} + +// The minimum amount a damaging hit should award +constexpr uint32 MIN_RAGE_AMOUNT = 10; + +/*static*/ uint32 Unit::CalcDamageTakenRageGain(Unit const* victim, uint32 damage) +{ + if (!victim || damage == 0) + return 0; + + double multiplier = static_cast(damage) / victim->GetMaxHealth(); + + // This formula is based on sampling multiple sniff data at multiple level ranges + // There has never been any leak and the only info we have is that the amount is based on max HP and damage taken and that there is a hidden constant + return std::max(MIN_RAGE_AMOUNT, std::ceil(400 * multiplier)); +} + +/// @todo for melee need create structure as in +void Unit::CalculateMeleeDamage(Unit* victim, CalcDamageInfo* damageInfo, WeaponAttackType attackType /*= BASE_ATTACK*/) +{ + damageInfo->Attacker = this; + damageInfo->Target = victim; + + damageInfo->DamageSchoolMask = GetMeleeDamageSchoolMask(attackType); + damageInfo->Damage = 0; + damageInfo->OriginalDamage = 0; + damageInfo->Absorb = 0; + damageInfo->Resist = 0; + + damageInfo->Blocked = 0; + damageInfo->HitInfo = 0; + damageInfo->TargetState = 0; + + damageInfo->AttackType = attackType; + damageInfo->ProcAttacker = PROC_FLAG_NONE; + damageInfo->ProcVictim = PROC_FLAG_NONE; + damageInfo->UnmitigatedDamage = 0; + damageInfo->HitOutCome = MELEE_HIT_EVADE; + damageInfo->RageGained = 0; + + if (!victim) + return; + + if (!IsAlive() || !victim->IsAlive()) + return; + + // Select HitInfo/procAttacker/procVictim flag based on attack type + switch (attackType) + { + case BASE_ATTACK: + damageInfo->ProcAttacker = PROC_FLAG_DEAL_MELEE_SWING | PROC_FLAG_MAIN_HAND_WEAPON_SWING; + damageInfo->ProcVictim = PROC_FLAG_TAKE_MELEE_SWING; + break; + case OFF_ATTACK: + damageInfo->ProcAttacker = PROC_FLAG_DEAL_MELEE_SWING | PROC_FLAG_OFF_HAND_WEAPON_SWING; + damageInfo->ProcVictim = PROC_FLAG_TAKE_MELEE_SWING; + damageInfo->HitInfo = HITINFO_OFFHAND; + break; + default: + return; + } + + // Physical Immune check + if (damageInfo->Target->IsImmunedToDamage(SpellSchoolMask(damageInfo->DamageSchoolMask))) + { + damageInfo->HitInfo |= HITINFO_NORMALSWING; + damageInfo->TargetState = VICTIMSTATE_IS_IMMUNE; + + damageInfo->Damage = 0; + damageInfo->UnmitigatedDamage = 0; + return; + } + + uint32 damage = 0; + damage += CalculateDamage(damageInfo->AttackType, false, true); + // Add melee damage bonus + damage = MeleeDamageBonusDone(damageInfo->Target, damage, damageInfo->AttackType, DIRECT_DAMAGE, nullptr, MECHANIC_NONE, SpellSchoolMask(damageInfo->DamageSchoolMask)); + damage = damageInfo->Target->MeleeDamageBonusTaken(this, damage, damageInfo->AttackType, nullptr, SpellSchoolMask(damageInfo->DamageSchoolMask)); + + // Script Hook For CalculateMeleeDamage -- Allow scripts to change the Damage pre class mitigation calculations + sScriptMgr->ModifyMeleeDamage(damageInfo->Target, damageInfo->Attacker, damage); + + // Calculate armor reduction + if (Unit::IsDamageReducedByArmor((SpellSchoolMask)(damageInfo->DamageSchoolMask))) + damageInfo->Damage = Unit::CalcArmorReducedDamage(damageInfo->Attacker, damageInfo->Target, damage, nullptr, damageInfo->AttackType); + else + damageInfo->Damage = damage; + + // Store unmitigated damage to reward rage later + damageInfo->UnmitigatedDamage = damage; + + damageInfo->HitOutCome = RollMeleeOutcomeAgainst(damageInfo->Target, damageInfo->AttackType); + + switch (damageInfo->HitOutCome) + { + case MELEE_HIT_EVADE: + damageInfo->HitInfo |= HITINFO_MISS | HITINFO_SWINGNOHITSOUND; + damageInfo->TargetState = VICTIMSTATE_EVADES; + damageInfo->OriginalDamage = damageInfo->Damage; + + damageInfo->Damage = 0; + return; + case MELEE_HIT_MISS: + damageInfo->HitInfo |= HITINFO_MISS; + damageInfo->TargetState = VICTIMSTATE_INTACT; + damageInfo->OriginalDamage = damageInfo->Damage; + + damageInfo->Damage = 0; + damageInfo->UnmitigatedDamage = 0; + break; + case MELEE_HIT_NORMAL: + damageInfo->TargetState = VICTIMSTATE_HIT; + damageInfo->OriginalDamage = damageInfo->Damage; + break; + case MELEE_HIT_CRIT: + { + damageInfo->HitInfo |= HITINFO_CRITICALHIT; + damageInfo->TargetState = VICTIMSTATE_HIT; + + // Crit bonus calc + damageInfo->Damage *= 2; + 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->DamageSchoolMask) - 1.0f) * 100; + + if (mod != 0) + AddPct(damageInfo->Damage, mod); + + damageInfo->OriginalDamage = damageInfo->Damage; + break; + } + case MELEE_HIT_PARRY: + damageInfo->TargetState = VICTIMSTATE_PARRY; + damageInfo->OriginalDamage = damageInfo->Damage; + damageInfo->Damage = 0; + break; + case MELEE_HIT_DODGE: + damageInfo->TargetState = VICTIMSTATE_DODGE; + damageInfo->OriginalDamage = damageInfo->Damage; + damageInfo->Damage = 0; + break; + case MELEE_HIT_BLOCK: + damageInfo->TargetState = VICTIMSTATE_HIT; + damageInfo->HitInfo |= HITINFO_BLOCK; + // 30% damage blocked, double blocked amount if block is critical + damageInfo->Blocked = CalculatePct(damageInfo->Damage, damageInfo->Target->GetBlockPercent()); + if (damageInfo->Target->IsBlockCritical()) + damageInfo->Blocked *= 2; + + damageInfo->OriginalDamage = damageInfo->Damage; + damageInfo->Damage -= damageInfo->Blocked; + break; + case MELEE_HIT_GLANCING: + { + damageInfo->HitInfo |= HITINFO_GLANCING; + damageInfo->TargetState = VICTIMSTATE_HIT; + int32 leveldif = int32(victim->getLevel()) - int32(getLevel()); + if (leveldif > 3) + leveldif = 3; + + damageInfo->OriginalDamage = damageInfo->Damage; + float reducePercent = 1.f - leveldif * 0.1f; + damageInfo->Damage = uint32(reducePercent * damageInfo->Damage); + break; + } + case MELEE_HIT_CRUSHING: + damageInfo->HitInfo |= HITINFO_CRUSHING; + damageInfo->TargetState = VICTIMSTATE_HIT; + // 150% normal damage + damageInfo->Damage += (damageInfo->Damage / 2); + damageInfo->OriginalDamage = damageInfo->Damage; + break; + default: + break; + } + + // Always apply HITINFO_AFFECTS_VICTIM in case its not a miss + if (!(damageInfo->HitInfo & HITINFO_MISS)) + damageInfo->HitInfo |= HITINFO_AFFECTS_VICTIM; + + int32 resilienceReduction = damageInfo->Damage; + if (CanApplyResilience()) + Unit::ApplyResilience(victim, &resilienceReduction); + resilienceReduction = damageInfo->Damage - resilienceReduction; + damageInfo->Damage -= resilienceReduction; + + // Calculate absorb resist + if (int32(damageInfo->Damage) > 0) + { + damageInfo->ProcVictim |= PROC_FLAG_TAKE_ANY_DAMAGE; + // Calculate absorb & resists + DamageInfo dmgInfo(*damageInfo); + Unit::CalcAbsorbResist(dmgInfo); + damageInfo->Absorb = dmgInfo.GetAbsorb(); + damageInfo->Resist = dmgInfo.GetResist(); + + if (damageInfo->Absorb) + damageInfo->HitInfo |= (damageInfo->Damage - damageInfo->Absorb == 0 ? HITINFO_FULL_ABSORB : HITINFO_PARTIAL_ABSORB); + + if (damageInfo->Resist) + damageInfo->HitInfo |= (damageInfo->Damage - damageInfo->Resist == 0 ? HITINFO_FULL_RESIST : HITINFO_PARTIAL_RESIST); + + damageInfo->Damage = dmgInfo.GetDamage(); + } + else // Impossible get negative result but.... + damageInfo->Damage = 0; +} + +void Unit::DealMeleeDamage(CalcDamageInfo* damageInfo, bool durabilityLoss) +{ + Unit* victim = damageInfo->Target; + + if (!victim->IsAlive() || victim->HasUnitState(UNIT_STATE_IN_FLIGHT) || (victim->GetTypeId() == TYPEID_UNIT && victim->ToCreature()->IsEvadingAttacks())) + return; + + if (damageInfo->TargetState == VICTIMSTATE_PARRY && + (GetTypeId() != TYPEID_UNIT || (ToCreature()->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_NO_PARRY_HASTEN) == 0)) + { + // Get attack timers + float offtime = float(victim->getAttackTimer(OFF_ATTACK)); + float basetime = float(victim->getAttackTimer(BASE_ATTACK)); + // Reduce attack time + if (victim->haveOffhandWeapon() && offtime < basetime) + { + float percent20 = victim->GetBaseAttackTime(OFF_ATTACK) * 0.20f; + float percent60 = 3.0f * percent20; + if (offtime > percent20 && offtime <= percent60) + victim->setAttackTimer(OFF_ATTACK, uint32(percent20)); + else if (offtime > percent60) + { + offtime -= 2.0f * percent20; + victim->setAttackTimer(OFF_ATTACK, uint32(offtime)); + } + } + else + { + float percent20 = victim->GetBaseAttackTime(BASE_ATTACK) * 0.20f; + float percent60 = 3.0f * percent20; + if (basetime > percent20 && basetime <= percent60) + victim->setAttackTimer(BASE_ATTACK, uint32(percent20)); + else if (basetime > percent60) + { + basetime -= 2.0f * percent20; + victim->setAttackTimer(BASE_ATTACK, uint32(basetime)); + } + } + } + + // Call default DealDamage + Unit::DealDamage(this, victim, damageInfo->Damage, damageInfo->UnmitigatedDamage, DIRECT_DAMAGE, SpellSchoolMask(damageInfo->DamageSchoolMask), nullptr, durabilityLoss); + + // If this is a creature and it attacks from behind it has a probability to daze it's victim + if ((damageInfo->HitOutCome == MELEE_HIT_CRIT || damageInfo->HitOutCome == MELEE_HIT_CRUSHING || damageInfo->HitOutCome == MELEE_HIT_NORMAL || damageInfo->HitOutCome == MELEE_HIT_GLANCING) && + IsCreature() && !ToCreature()->IsControlledByPlayer() && !ToCreature()->HasStaticFlag(CREATURE_STATIC_FLAG_4_CANNOT_DAZE) && + !victim->HasInArc(float(M_PI), this) && (victim->IsPlayer() || (victim->IsCreature() && !victim->ToCreature()->isWorldBoss())) && !victim->IsVehicle()) + { + // 20% base chance + float chance = 20.0f; + + // there is a newbie protection, at level 10 just 7% base chance; assuming linear function + if (victim->getLevel() < 30) + chance = 0.65f * victim->getLevel() + 0.5f; + + uint32 const victimDefense = victim->GetMaxSkillValueForLevel(this); + uint32 const attackerMeleeSkill = GetMaxSkillValueForLevel(); + + chance *= attackerMeleeSkill / float(victimDefense) * 0.16f; + + // -probability is between 0% and 40% + RoundToInterval(chance, 0.0f, 40.0f); + if (roll_chance_f(chance)) + CastSpell(victim, 1604 /*SPELL_DAZED*/, true); + } + + if (GetTypeId() == TYPEID_PLAYER) + { + DamageInfo dmgInfo(*damageInfo); + ToPlayer()->CastItemCombatSpell(dmgInfo); + } + + // Do effect if any damage done to target + if (damageInfo->Damage) + { + // We're going to call functions which can modify content of the list during iteration over it's elements + // Let's copy the list so we can prevent iterator invalidation + AuraEffectList vDamageShieldsCopy(victim->GetAuraEffectsByType(SPELL_AURA_DAMAGE_SHIELD)); + for (AuraEffect const* aurEff : vDamageShieldsCopy) + { + SpellInfo const* spellInfo = aurEff->GetSpellInfo(); + + // Damage shield can be resisted... + SpellMissInfo missInfo = victim->SpellHitResult(this, spellInfo, false); + if (missInfo != SPELL_MISS_NONE) + { + victim->SendSpellMiss(this, spellInfo->Id, missInfo); + continue; + } + + // ...or immuned + if (IsImmunedToDamage(spellInfo)) + { + victim->SendSpellDamageImmune(this, spellInfo->Id); + continue; + } + + uint32 damage = aurEff->GetAmount(); + if (Unit* caster = aurEff->GetCaster()) + { + damage = caster->SpellDamageBonusDone(this, spellInfo, damage, SPELL_DIRECT_DAMAGE, aurEff->GetEffIndex()); + damage = SpellDamageBonusTaken(caster, spellInfo, damage, SPELL_DIRECT_DAMAGE); + } + + DamageInfo dmgInfo(this, victim, damage, spellInfo, spellInfo->GetSchoolMask(), SPELL_DIRECT_DAMAGE, BASE_ATTACK); + victim->CalcAbsorbResist(dmgInfo); + damage = dmgInfo.GetDamage(); + Unit::DealDamageMods(victim, damage, nullptr); + + /// @todo Move this to a packet handler + WorldPackets::CombatLog::SpellDamageShield damageShield; + damageShield.Attacker = victim->GetGUID(); + damageShield.Defender = GetGUID(); + damageShield.SpellID = spellInfo->Id; + damageShield.TotalDamage = damage; + damageShield.OverKill = std::max(int32(damage) - int32(GetHealth()), 0); + damageShield.SchoolMask = spellInfo->SchoolMask; + damageShield.LogAbsorbed = dmgInfo.GetAbsorb(); + victim->SendMessageToSet(damageShield.Write(), true); + + Unit::DealDamage(victim, this, damage, 0, SPELL_DIRECT_DAMAGE, spellInfo->GetSchoolMask(), spellInfo, true); + } + } +} + +void Unit::HandleEmoteCommand(uint32 emoteId) +{ + WorldPackets::Chat::Emote emote; + emote.Guid = GetGUID(); + emote.EmoteID = emoteId; + + SendMessageToSet(emote.Write(), true); +} + +/*static*/ bool Unit::IsDamageReducedByArmor(SpellSchoolMask schoolMask, SpellInfo const* spellInfo /*= nullptr*/) +{ + // only physical spells damage gets reduced by armor + if ((schoolMask & SPELL_SCHOOL_MASK_NORMAL) == 0) + return false; + + return !spellInfo || !spellInfo->HasAttribute(SPELL_ATTR0_CU_IGNORE_ARMOR); +} + +// Calculation taken from Paperdollframe.lua (15595) +static float GetArmorReduction(float armor, uint8 attackerLevel) +{ + float levelModifier = attackerLevel; + if (levelModifier > 80) + levelModifier = levelModifier + (4.5f * (levelModifier - 59)) + (20 * (levelModifier - 80)); + else if (levelModifier > 59) + levelModifier = levelModifier + (4.5f * (levelModifier - 59)); + + float temp = 0.1f * armor / (8.5f * levelModifier + 40); + temp = temp / (1 + temp); + + if (temp > 0.75f) + return 75.f; + + if (temp < 0.f) + return 0.f; + + return temp * 100; +} + +/*static*/ uint32 Unit::CalcArmorReducedDamage(Unit const* attacker, Unit* victim, const uint32 damage, SpellInfo const* spellInfo, WeaponAttackType /*attackType*/, uint8 attackerLevel /*= 0*/) +{ + float armor = float(victim->GetArmor()); + + if (attacker) + { + // bypass enemy armor by SPELL_AURA_BYPASS_ARMOR_FOR_CASTER + int32 armorBypassPct = victim->GetTotalAuraModifier(SPELL_AURA_BYPASS_ARMOR_FOR_CASTER, [&](AuraEffect const* aurEff) + { + return aurEff->GetCasterGUID() == attacker->GetGUID(); + }); + + armor = CalculatePct(armor, 100 - std::min(armorBypassPct, 100)); + + // Ignore enemy armor by SPELL_AURA_MOD_TARGET_RESISTANCE aura + armor += attacker->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_TARGET_RESISTANCE, SPELL_SCHOOL_MASK_NORMAL); + + if (spellInfo) + if (Player* modOwner = attacker->GetSpellModOwner()) + modOwner->ApplySpellMod(spellInfo, SpellModOp::TargetResistance, armor); + + for (AuraEffect const* aurEff : attacker->GetAuraEffectsByType(SPELL_AURA_MOD_IGNORE_TARGET_RESIST)) + { + if (aurEff->GetMiscValue() & SPELL_SCHOOL_MASK_NORMAL) + armor = std::floor(AddPct(armor, -aurEff->GetAmount())); + } + + if (armor < 0.0f) + armor = 0.0f; + } + + float armorReduction = 100.f - GetArmorReduction(armor, attacker ? attacker->getLevel() : attackerLevel); + return std::max(std::ceil(damage * armorReduction / 100.f), 0); +} + +/*static*/ uint32 Unit::CalcSpellResistedDamage(Unit const* attacker, Unit* victim, uint32 damage, SpellSchoolMask schoolMask, SpellInfo const* spellInfo) +{ + // Magic damage, check for resists + if (!(schoolMask & SPELL_SCHOOL_MASK_MAGIC)) + return 0; + + // Npcs can have holy resistance + if ((schoolMask & SPELL_SCHOOL_MASK_HOLY) && victim->GetTypeId() != TYPEID_UNIT) + return 0; + + float const averageResist = Unit::CalculateAverageResistReduction(attacker, schoolMask, victim, spellInfo); + + float discreteResistProbability[11] = { }; + if (averageResist <= 0.1f) + { + discreteResistProbability[0] = 1.0f - 7.5f * averageResist; + discreteResistProbability[1] = 5.0f * averageResist; + discreteResistProbability[2] = 2.5f * averageResist; + } + else + { + for (uint32 i = 0; i < 11; ++i) + discreteResistProbability[i] = std::max(0.5f - 2.5f * std::fabs(0.1f * i - averageResist), 0.0f); + } + + float roll = rand_norm(); + float probabilitySum = 0.0f; + + uint32 resistance = 0; + for (; resistance < 11; ++resistance) + if (roll < (probabilitySum += discreteResistProbability[resistance])) + break; + + float damageResisted = damage * resistance / 10.f; + if (damageResisted > 0.0f) // if any damage was resisted + { + if (attacker) + { + int32 ignoredResistance = attacker->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_IGNORE_TARGET_RESIST, schoolMask); + ignoredResistance = std::min(ignoredResistance, 100); + ApplyPct(damageResisted, 100 - ignoredResistance); + + // Spells with melee and magic school mask, decide whether resistance or armor absorb is higher + if (spellInfo && spellInfo->HasAttribute(SPELL_ATTR0_CU_SCHOOLMASK_NORMAL_WITH_MAGIC)) + { + uint32 damageAfterArmor = Unit::CalcArmorReducedDamage(attacker, victim, damage, spellInfo, spellInfo->GetAttackType()); + float armorReduction = damage - damageAfterArmor; + + // pick the lower one, the weakest resistance counts + damageResisted = std::min(damageResisted, armorReduction); + } + } + } + + damageResisted = std::max(damageResisted, 0.f); + return uint32(damageResisted); +} + +/*static*/ float Unit::CalculateAverageResistReduction(WorldObject const* caster, SpellSchoolMask schoolMask, Unit const* victim, SpellInfo const* spellInfo) +{ + float victimResistance = float(victim->GetResistance(schoolMask)); + if (caster) + { + // pets inherit 100% of masters penetration + if (Player const* player = caster->GetSpellModOwner()) + { + victimResistance += float(player->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_TARGET_RESISTANCE, schoolMask)); + victimResistance -= float(player->GetSpellPenetrationItemMod()); + } + else if (Unit const* unitCaster = caster->ToUnit()) + victimResistance += float(unitCaster->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_TARGET_RESISTANCE, schoolMask)); + } + + // holy resistance exists in pve and comes from level difference, ignore template values + if (schoolMask & SPELL_SCHOOL_MASK_HOLY) + victimResistance = 0.0f; + + // Chaos Bolt exception, ignore all target resistances (unknown attribute?) + if (spellInfo && spellInfo->SpellFamilyName == SPELLFAMILY_WARLOCK && spellInfo->SpellIconID == 3178) + victimResistance = 0.0f; + + victimResistance = std::max(victimResistance, 0.0f); + + // level-based resistance does not apply to binary spells, and cannot be overcome by spell penetration + // gameobject caster -- should it have level based resistance? + if (caster && caster->GetTypeId() != TYPEID_GAMEOBJECT && (!spellInfo || !spellInfo->HasAttribute(SPELL_ATTR0_CU_BINARY_SPELL))) + victimResistance += std::max((float(victim->getLevelForTarget(caster)) - float(caster->getLevelForTarget(victim))) * 5.0f, 0.0f); + + static uint32 const 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); +} + +/*static*/ void Unit::CalcAbsorbResist(DamageInfo& damageInfo) +{ + if (!damageInfo.GetVictim() || !damageInfo.GetVictim()->IsAlive() || !damageInfo.GetDamage()) + return; + + uint32 resistedDamage = Unit::CalcSpellResistedDamage(damageInfo.GetAttacker(), damageInfo.GetVictim(), damageInfo.GetDamage(), damageInfo.GetSchoolMask(), damageInfo.GetSpellInfo()); + damageInfo.ResistDamage(resistedDamage); + + // Ignore Absorption Auras + float auraAbsorbMod = 0.f; + if (Unit* attacker = damageInfo.GetAttacker()) + { + auraAbsorbMod = attacker->GetMaxPositiveAuraModifierByMiscMask(SPELL_AURA_MOD_TARGET_ABSORB_SCHOOL, damageInfo.GetSchoolMask()); + auraAbsorbMod = std::max(auraAbsorbMod, static_cast(attacker->GetMaxPositiveAuraModifier(SPELL_AURA_MOD_TARGET_ABILITY_ABSORB_SCHOOL, [&damageInfo](AuraEffect const* aurEff) -> bool + { + if (!(aurEff->GetMiscValue() & damageInfo.GetSchoolMask())) + return false; + + if (!aurEff->IsAffectingSpell(damageInfo.GetSpellInfo())) + return false; + + return true; + }))); + } + + RoundToInterval(auraAbsorbMod, 0.0f, 100.0f); + + int32 absorbIgnoringDamage = CalculatePct(damageInfo.GetDamage(), auraAbsorbMod); + + // 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(damageInfo.GetVictim()->GetAuraEffectsByType(SPELL_AURA_SCHOOL_ABSORB)); + vSchoolAbsorbCopy.sort(Trinity::AbsorbAuraOrderPred()); + + // absorb without mana cost + for (AuraEffectList::iterator itr = vSchoolAbsorbCopy.begin(); (itr != vSchoolAbsorbCopy.end()) && (damageInfo.GetDamage() > 0); ++itr) + { + AuraEffect* absorbAurEff = *itr; + // Check if aura was removed during iteration - we don't need to work on such auras + AuraApplication const* aurApp = absorbAurEff->GetBase()->GetApplicationOfTarget(damageInfo.GetVictim()->GetGUID()); + if (!aurApp) + continue; + if (!(absorbAurEff->GetMiscValue() & damageInfo.GetSchoolMask())) + continue; + + // get amount which can be still absorbed by the aura + int32 currentAbsorb = absorbAurEff->GetAmount(); + // aura with infinite absorb amount - let the scripts handle absorbtion amount, set here to 0 for safety + if (currentAbsorb < 0) + currentAbsorb = 0; + + if (!absorbAurEff->GetSpellInfo()->HasAttribute(SPELL_ATTR6_ABSORB_CANNOT_BE_IGNORE)) + damageInfo.ModifyDamage(-absorbIgnoringDamage); + + uint32 tempAbsorb = uint32(currentAbsorb); + + bool defaultPrevented = false; + + absorbAurEff->GetBase()->CallScriptEffectAbsorbHandlers(absorbAurEff, aurApp, damageInfo, tempAbsorb, defaultPrevented); + currentAbsorb = tempAbsorb; + + if (!defaultPrevented) + { + + // absorb must be smaller than the damage itself + currentAbsorb = RoundToInterval(currentAbsorb, 0, int32(damageInfo.GetDamage())); + + damageInfo.AbsorbDamage(currentAbsorb); + + tempAbsorb = currentAbsorb; + absorbAurEff->GetBase()->CallScriptEffectAfterAbsorbHandlers(absorbAurEff, aurApp, damageInfo, tempAbsorb); + + // Check if our aura is using amount to count damage + if (absorbAurEff->GetAmount() >= 0) + { + // Reduce shield amount + absorbAurEff->SetAmount(absorbAurEff->GetAmount() - currentAbsorb); + // Aura cannot absorb anything more - remove it + if (absorbAurEff->GetAmount() <= 0) + absorbAurEff->GetBase()->Remove(AuraRemoveFlags::ByEnemySpell); + } + } + + if (!absorbAurEff->GetSpellInfo()->HasAttribute(SPELL_ATTR6_ABSORB_CANNOT_BE_IGNORE)) + damageInfo.ModifyDamage(absorbIgnoringDamage); + } + + // absorb by mana cost + AuraEffectList vManaShieldCopy(damageInfo.GetVictim()->GetAuraEffectsByType(SPELL_AURA_MANA_SHIELD)); + for (AuraEffectList::const_iterator itr = vManaShieldCopy.begin(); (itr != vManaShieldCopy.end()) && (damageInfo.GetDamage() > 0); ++itr) + { + AuraEffect* absorbAurEff = *itr; + // Check if aura was removed during iteration - we don't need to work on such auras + AuraApplication const* aurApp = absorbAurEff->GetBase()->GetApplicationOfTarget(damageInfo.GetVictim()->GetGUID()); + if (!aurApp) + continue; + // check damage school mask + if (!(absorbAurEff->GetMiscValue() & damageInfo.GetSchoolMask())) + continue; + + // get amount which can be still absorbed by the aura + int32 currentAbsorb = absorbAurEff->GetAmount(); + // aura with infinite absorb amount - let the scripts handle absorbtion amount, set here to 0 for safety + if (currentAbsorb < 0) + currentAbsorb = 0; + + if (!absorbAurEff->GetSpellInfo()->HasAttribute(SPELL_ATTR6_ABSORB_CANNOT_BE_IGNORE)) + damageInfo.ModifyDamage(-absorbIgnoringDamage); + + uint32 tempAbsorb = currentAbsorb; + + bool defaultPrevented = false; + + absorbAurEff->GetBase()->CallScriptEffectManaShieldHandlers(absorbAurEff, aurApp, damageInfo, tempAbsorb, defaultPrevented); + currentAbsorb = tempAbsorb; + + if (!defaultPrevented) + { + + // absorb must be smaller than the damage itself + currentAbsorb = RoundToInterval(currentAbsorb, 0, int32(damageInfo.GetDamage())); + + int32 manaReduction = currentAbsorb; + + // lower absorb amount by talents + if (float manaMultiplier = absorbAurEff->GetSpellInfo()->Effects[absorbAurEff->GetEffIndex()].CalcValueMultiplier(absorbAurEff->GetCaster())) + manaReduction = int32(float(manaReduction) * manaMultiplier); + + int32 manaTaken = -damageInfo.GetVictim()->ModifyPower(POWER_MANA, -manaReduction); + + // take case when mana has ended up into account + currentAbsorb = currentAbsorb ? int32(float(currentAbsorb) * (float(manaTaken) / float(manaReduction))) : 0; + + damageInfo.AbsorbDamage(currentAbsorb); + + tempAbsorb = currentAbsorb; + absorbAurEff->GetBase()->CallScriptEffectAfterManaShieldHandlers(absorbAurEff, aurApp, damageInfo, tempAbsorb); + + // Check if our aura is using amount to count damage + if (absorbAurEff->GetAmount() >= 0) + { + absorbAurEff->SetAmount(absorbAurEff->GetAmount() - currentAbsorb); + if ((absorbAurEff->GetAmount() <= 0)) + absorbAurEff->GetBase()->Remove(AuraRemoveFlags::ByEnemySpell); + } + } + + if (!absorbAurEff->GetSpellInfo()->HasAttribute(SPELL_ATTR6_ABSORB_CANNOT_BE_IGNORE)) + damageInfo.ModifyDamage(absorbIgnoringDamage); + } + + // split damage auras - only when not damaging self + if (damageInfo.GetVictim() != damageInfo.GetAttacker()) + { + // We're going to call functions which can modify content of the list during iteration over it's elements + // Let's copy the list so we can prevent iterator invalidation + AuraEffectList vSplitDamagePctCopy(damageInfo.GetVictim()->GetAuraEffectsByType(SPELL_AURA_SPLIT_DAMAGE_PCT)); + for (AuraEffectList::iterator itr = vSplitDamagePctCopy.begin(); itr != vSplitDamagePctCopy.end() && damageInfo.GetDamage() > 0; ++itr) + { + // Check if aura was removed during iteration - we don't need to work on such auras + AuraApplication const* aurApp = (*itr)->GetBase()->GetApplicationOfTarget(damageInfo.GetVictim()->GetGUID()); + if (!aurApp) + continue; + + // check damage school mask + if (!((*itr)->GetMiscValue() & damageInfo.GetSchoolMask())) + continue; + + // Damage can be splitted only if aura has an alive caster + Unit* caster = (*itr)->GetCaster(); + if (!caster || (caster == damageInfo.GetVictim()) || !caster->IsInWorld() || !caster->IsAlive()) + continue; + + uint32 splitDamage = CalculatePct(damageInfo.GetDamage(), (*itr)->GetAmount()); + + (*itr)->GetBase()->CallScriptEffectSplitHandlers((*itr), aurApp, damageInfo, splitDamage); + + // absorb must be smaller than the damage itself + splitDamage = RoundToInterval(splitDamage, uint32(0), uint32(damageInfo.GetDamage())); + + damageInfo.AbsorbDamage(splitDamage); + + // check if caster is immune to damage + if (caster->IsImmunedToDamage(damageInfo.GetSchoolMask())) + { + damageInfo.GetVictim()->SendSpellMiss(caster, (*itr)->GetSpellInfo()->Id, SPELL_MISS_IMMUNE); + continue; + } + + uint32 split_absorb = 0; + Unit::DealDamageMods(caster, splitDamage, &split_absorb); + + if (Unit* attacker = damageInfo.GetAttacker()) + { + // Sparring Checks + if (Creature* target = damageInfo.GetVictim()->ToCreature()) + if (attacker->IsCreature() && !attacker->IsCharmedOwnedByPlayerOrPlayer()) + if (target->GetNoNpcDamageBelowPctHealthValue() != 0.0f) + if (target->GetHealthPct() <= target->GetNoNpcDamageBelowPctHealthValue()) + damageInfo.ModifyDamage(damageInfo.GetDamage() * -1); + + attacker->SendSpellNonMeleeDamageLog(caster, (*itr)->GetSpellInfo()->Id, splitDamage, damageInfo.GetSchoolMask(), split_absorb, 0, damageInfo.GetDamageType() == DOT, 0, false, true); + } + + Unit::DealDamage(damageInfo.GetAttacker(), caster, splitDamage, 0, DIRECT_DAMAGE, damageInfo.GetSchoolMask(), (*itr)->GetSpellInfo(), false); + + // break 'Fear' and similar auras + Unit::ProcSkillsAndAuras(damageInfo.GetAttacker(), caster, PROC_FLAG_NONE, PROC_FLAG_TAKE_HARMFUL_SPELL, PROC_SPELL_TYPE_DAMAGE, PROC_SPELL_PHASE_HIT, PROC_HIT_NONE, nullptr, &damageInfo, nullptr); + } + } +} + +/*static*/ void Unit::CalcHealAbsorb(HealInfo& healInfo) +{ + if (!healInfo.GetHeal()) + return; + + 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(AuraRemoveFlags::ByEnemySpell); + if (removedAuras + 1 < healInfo.GetTarget()->m_removedAurasCount) + i = vHealAbsorb.begin(); + } + } + } + + if (absorbAmount > 0) + healInfo.AbsorbHeal(absorbAmount); +} + +void Unit::AttackerStateUpdate(Unit* victim, WeaponAttackType attType, bool extra) +{ + if (HasUnitState(UNIT_STATE_CANNOT_AUTOATTACK) || HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PACIFIED)) + return; + + if (HasUnitState(UNIT_STATE_CASTING)) + { + Spell* channeledSpell = GetCurrentSpell(CURRENT_CHANNELED_SPELL); + if (!channeledSpell || !channeledSpell->GetSpellInfo()->HasAttribute(SPELL_ATTR5_ALLOW_ACTIONS_DURING_CHANNEL)) + return; + } + + if (!victim->IsAlive()) + return; + + if ((attType == BASE_ATTACK || attType == OFF_ATTACK) && !IsWithinLOSInMap(victim)) + return; + + AtTargetAttacked(victim, true); + RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Attacking); + + if (attType != BASE_ATTACK && attType != OFF_ATTACK) + return; // ignore ranged case + + // melee attack spell cast at main hand attack only - no normal melee dmg dealt + if (attType == BASE_ATTACK && m_currentSpells[CURRENT_MELEE_SPELL] && !extra) + m_currentSpells[CURRENT_MELEE_SPELL]->cast(); + else + { + // attack can be redirected to another target + victim = GetMeleeHitRedirectTarget(victim); + + AuraEffectList const& meleeAttackOverrides = GetAuraEffectsByType(SPELL_AURA_OVERRIDE_AUTOATTACK_WITH_MELEE_SPELL); + AuraEffect const* meleeAttackAuraEffect = nullptr; + uint32 meleeAttackSpellId = 0; + if (attType == BASE_ATTACK) + { + if (!meleeAttackOverrides.empty()) + { + meleeAttackAuraEffect = meleeAttackOverrides.front(); + meleeAttackSpellId = meleeAttackAuraEffect->GetSpellInfo()->Effects[meleeAttackAuraEffect->GetEffIndex()].TriggerSpell; + } + } + else + { + auto itr = std::find_if(meleeAttackOverrides.begin(), meleeAttackOverrides.end(), [&](AuraEffect const* aurEff) + { + return aurEff->GetAmount() != 0; + }); + + if (itr != meleeAttackOverrides.end()) + { + meleeAttackAuraEffect = *itr; + meleeAttackSpellId = meleeAttackAuraEffect->GetAmount(); + } + } + + if (!meleeAttackAuraEffect) + { + CalcDamageInfo damageInfo; + CalculateMeleeDamage(victim, &damageInfo, attType); + // Send log damage message to client + Unit::DealDamageMods(victim, damageInfo.Damage, &damageInfo.Absorb); + + // Sparring Checks + if (Creature* target = victim->ToCreature()) + if (IsCreature() && !IsCharmedOwnedByPlayerOrPlayer()) + if (target->GetNoNpcDamageBelowPctHealthValue() != 0.0f) + if (target->GetHealthPct() <= target->GetNoNpcDamageBelowPctHealthValue()) + damageInfo.HitInfo |= HITINFO_FAKE_DAMAGE; + + // Rage reward + if (this != victim && damageInfo.HitOutCome != MELEE_HIT_MISS && GetPowerType() == POWER_RAGE) + { + if (uint32 rageReward = CalcMeleeAttackRageGain(this, victim, attType)) + { + damageInfo.HitInfo |= HITINFO_RAGE_GAIN; + damageInfo.RageGained = RewardRage(rageReward, true); + } + } + + SendAttackStateUpdate(&damageInfo); + + DealMeleeDamage(&damageInfo, true); + + DamageInfo dmgInfo(damageInfo); + Unit::ProcSkillsAndAuras(damageInfo.Attacker, damageInfo.Target, damageInfo.ProcAttacker, damageInfo.ProcVictim, PROC_SPELL_TYPE_NONE, PROC_SPELL_PHASE_NONE, dmgInfo.GetHitMask(), nullptr, &dmgInfo, nullptr); + + TC_LOG_DEBUG("entities.unit", "AttackerStateUpdate: %s attacked %s for %u dmg, absorbed %u, blocked %u, resisted %u.", + GetGUID().ToString().c_str(), victim->GetGUID().ToString().c_str(), damageInfo.Damage, damageInfo.Absorb, damageInfo.Blocked, damageInfo.Resist); + } + else + { + CastSpell(victim, meleeAttackSpellId, meleeAttackAuraEffect); + + uint32 hitInfo = HITINFO_AFFECTS_VICTIM | HITINFO_NO_ANIMATION; + if (attType == OFF_ATTACK) + hitInfo |= HITINFO_OFFHAND; + + SendAttackStateUpdate(hitInfo, victim, 0, GetMeleeDamageSchoolMask(), 0, 0, 0, VICTIMSTATE_HIT, 0); + } + } +} + +void Unit::HandleProcExtraAttackFor(Unit* victim) +{ + while (m_extraAttacks) + { + AttackerStateUpdate(victim, BASE_ATTACK, true); + --m_extraAttacks; + } +} + +MeleeHitOutcome Unit::RollMeleeOutcomeAgainst(const Unit* victim, WeaponAttackType attType) const +{ + if (victim->IsCreature() && victim->ToCreature()->IsInEvadeMode()) + return MELEE_HIT_EVADE; + + int32 const attackerMaxSkillValueForLevel = GetMaxSkillValueForLevel(victim); + int32 const victimMaxSkillValueForLevel = victim->GetMaxSkillValueForLevel(this); + + // Miss chance based on melee + int32 miss_chance = int32(MeleeSpellMissChance(victim, attType) * 100.f); + + // Critical hit chance + int32 crit_chance = int32(GetUnitCriticalChanceAgainst(attType, victim) * 100.0f); + + // stunned target cannot dodge and this is check in GetUnitDodgeChance() (returned 0 in this case) + int32 dodge_chance = int32(GetUnitDodgeChance(attType, victim) * 100.0f); + int32 block_chance = int32(GetUnitBlockChance(victim) * 100.0f); + int32 parry_chance = int32(GetUnitParryChance(attType, victim) * 100.0f); + + + // melee attack table implementation + // outcome priority: + // 1. > 2. > 3. > 4. > 5. > 6. > 7. > 8. + // MISS > DODGE > PARRY > GLANCING > BLOCK > CRIT > CRUSHING > HIT + + int32 sum = 0, tmp = 0; + int32 roll = urand(0, 9999); + + float arcAngle = static_cast(M_PI); + if (victim->IsCreature() && victim->ToCreature()->HasStaticFlag(CREATURE_STATIC_FLAG_5_240_DEGREE_BACK_ARC)) + arcAngle -= static_cast(M_PI / 3); + + // check if attack comes from behind, nobody can parry or block if attacker is behind + bool canParryOrBlock = victim->HasInArc(arcAngle, this) || victim->HasAuraType(SPELL_AURA_IGNORE_HIT_DIRECTION); + + // only creatures can dodge if attacker is behind + bool canDodge = victim->GetTypeId() != TYPEID_PLAYER || canParryOrBlock; + + // if victim is casting or cc'd it can't avoid attacks + if (victim->IsNonMeleeSpellCast(false) || victim->HasUnitState(UNIT_STATE_CONTROLLED)) + { + canDodge = false; + canParryOrBlock = false; + } + + // 1. MISS + tmp = miss_chance; + if (tmp > 0 && roll < (sum += tmp)) + return MELEE_HIT_MISS; + + // always crit against a sitting target (except 0 crit chance) + if (victim->IsPlayer() && crit_chance > 0 && !victim->IsStandState()) + return MELEE_HIT_CRIT; + + // 2. DODGE + if (canDodge) + { + tmp = dodge_chance; + if (tmp > 0 // check if unit _can_ dodge + && roll < (sum += tmp)) + return MELEE_HIT_DODGE; + } + + // 3. PARRY + if (canParryOrBlock) + { + tmp = parry_chance; + if (tmp > 0 // check if unit _can_ parry + && roll < (sum += tmp)) + return MELEE_HIT_PARRY; + } + + // 4. GLANCING + // Max 40% chance to score a glancing blow against mobs of the same or higher level (only players and pets, not for ranged weapons). + if ((GetTypeId() == TYPEID_PLAYER || IsPet()) && + victim->GetTypeId() != TYPEID_PLAYER && !victim->IsPet() && + getLevel() <= victim->getLevelForTarget(this)) + { + // cap possible value (with bonuses > max skill) + int32 skill = attackerMaxSkillValueForLevel; + + // against boss-level targets - 24% chance of 25% average damage reduction (damage reduction range : 20-30%) + // against level 82 elites - 18% chance of 15% average damage reduction (damage reduction range : 10-20%) + tmp = 600 + (victimMaxSkillValueForLevel - skill) * 120; + tmp = std::min(tmp, 4000); + if (tmp > 0 && roll < (sum += tmp)) + return MELEE_HIT_GLANCING; + } + + // 5. BLOCK + if (canParryOrBlock) + { + tmp = block_chance; + if (tmp > 0 // check if unit _can_ block + && roll < (sum += tmp)) + return MELEE_HIT_BLOCK; + } + + // 6.CRIT + tmp = crit_chance; + if (tmp > 0 && roll < (sum += tmp)) + return MELEE_HIT_CRIT; + + // 7. CRUSHING + // mobs can score crushing blows if they're 4 or more levels above victim + if (getLevelForTarget(victim) >= victim->getLevelForTarget(this) + 4 && + // can be from by creature (if can) or from controlled player that considered as creature + !IsControlledByPlayer() && + !(IsCreature() && ToCreature()->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_NO_CRUSH)) + { + // when their weapon skill is 15 or more above victim's defense skill + tmp = victimMaxSkillValueForLevel; + // tmp = mob's level * 5 - player's current defense skill + tmp = attackerMaxSkillValueForLevel - tmp; + // minimum of 20 points diff (4 levels difference) + tmp = std::max(tmp, 20); + + // add 2% chance per lacking skill point, min. is 15% + tmp = tmp * 200 - 1500; + if (roll < (sum += tmp)) + return MELEE_HIT_CRUSHING; + } + + // 8. HIT + return MELEE_HIT_NORMAL; +} + +uint32 Unit::CalculateDamage(WeaponAttackType attType, bool normalized, bool addTotalPct) const +{ + float minDamage = 0.0f; + float maxDamage = 0.0f; + + if (normalized || !addTotalPct) + CalculateMinMaxDamage(attType, normalized, addTotalPct, minDamage, maxDamage); + 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)); +} + +void Unit::SendMeleeAttackStart(Unit* victim) +{ + WorldPackets::Combat::AttackStart packet; + packet.Attacker = GetGUID(); + packet.Victim = victim->GetGUID(); + SendMessageToSet(packet.Write(), IsPlayer()); +} + +void Unit::SendMeleeAttackStop(Unit* victim) +{ + SendMessageToSet(WorldPackets::Combat::SAttackStop(this, victim).Write(), IsPlayer()); + + if (victim) + TC_LOG_DEBUG("entities.unit", "%s %u stopped attacking %s %u", (GetTypeId() == TYPEID_PLAYER ? "Player" : "Creature"), GetGUID().GetCounter(), (victim->GetTypeId() == TYPEID_PLAYER ? "player" : "creature"), victim->GetGUID().GetCounter()); + else + TC_LOG_DEBUG("entities.unit", "%s %u stopped attacking", (GetTypeId() == TYPEID_PLAYER ? "Player" : "Creature"), GetGUID().GetCounter()); +} + +bool Unit::IsBlockCritical() const +{ + if (roll_chance_i(GetTotalAuraModifier(SPELL_AURA_MOD_BLOCK_CRIT_CHANCE))) + return true; + return false; +} + +int32 Unit::GetMechanicResistChance(SpellInfo const* spellInfo) const +{ + if (!spellInfo) + return 0; + + int32 resistMech = 0; + for (uint8 eff = 0; eff < MAX_SPELL_EFFECTS; ++eff) + { + if (!spellInfo->Effects[eff].IsEffect()) + break; + + int32 effectMech = spellInfo->GetEffectMechanic(eff); + if (effectMech) + { + int32 temp = GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_MECHANIC_RESISTANCE, effectMech); + if (resistMech < temp) + resistMech = temp; + } + } + return resistMech; +} + +bool Unit::CanUseAttackType(uint8 attacktype) const +{ + switch (attacktype) + { + case BASE_ATTACK: + return !HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_DISARMED); + case OFF_ATTACK: + return !HasFlag(UNIT_FIELD_FLAGS_2, UNIT_FLAG2_DISARM_OFFHAND); + case RANGED_ATTACK: + return !HasFlag(UNIT_FIELD_FLAGS_2, UNIT_FLAG2_DISARM_RANGED); + default: + return true; + } +} + +// Melee based spells hit result calculations +SpellMissInfo Unit::MeleeSpellHitResult(Unit* victim, SpellInfo const* spellInfo) const +{ + if (spellInfo->HasAttribute(SPELL_ATTR3_NO_AVOIDANCE)) + return SPELL_MISS_NONE; + + WeaponAttackType attType = BASE_ATTACK; + + // Check damage class instead of attack type to correctly handle judgements + // - they are meele, but can't be dodged/parried/deflected because of ranged dmg class + if (spellInfo->DmgClass == SPELL_DAMAGE_CLASS_RANGED) + attType = RANGED_ATTACK; + + uint32 roll = urand(0, 9999); + uint32 missChance = uint32(MeleeSpellMissChance(victim, attType, spellInfo) * 100.0f); + + // Roll miss + uint32 tmp = missChance; + if (roll < tmp) + return SPELL_MISS_MISS; + + // Chance resist mechanic + int32 resist_chance = victim->GetMechanicResistChance(spellInfo) * 100; + tmp += resist_chance; + if (roll < tmp) + return SPELL_MISS_RESIST; + + // Same spells cannot be parried/dodged + if (spellInfo->HasAttribute(SPELL_ATTR0_NO_ACTIVE_DEFENSE)) + return SPELL_MISS_NONE; + + bool canDodge = !spellInfo->HasAttribute(SPELL_ATTR7_NO_ATTACK_DODGE); + bool canParry = !spellInfo->HasAttribute(SPELL_ATTR7_NO_ATTACK_PARRY); + bool canBlock = !spellInfo->HasAttribute(SPELL_ATTR8_NO_ATTACK_BLOCK); + + // if victim is casting or cc'd it can't avoid attacks + bool canUseDefenseWhileChanneling = victim->GetCurrentSpell(CURRENT_CHANNELED_SPELL) ? victim->GetCurrentSpell(CURRENT_CHANNELED_SPELL)->GetSpellInfo()->HasAttribute(SPELL_ATTR10_ALLOW_DEFENSE_WHILE_CHANNELING) : false; + bool canUseDefenseWhileCasting = victim->GetCurrentSpell(CURRENT_GENERIC_SPELL) ? victim->GetCurrentSpell(CURRENT_GENERIC_SPELL)->GetSpellInfo()->HasAttribute(SPELL_ATTR10_ALLOW_DEFENSE_WHILE_CASTING) : false; + if ((victim->IsNonMeleeSpellCast(false) || victim->HasUnitState(UNIT_STATE_CONTROLLED)) && !canUseDefenseWhileChanneling && !canUseDefenseWhileCasting) + { + canDodge = false; + canParry = false; + canBlock = false; + } + + // Ranged attacks can only miss, resist and deflect and get blocked + if (attType == RANGED_ATTACK) + { + canDodge = false; + canParry = false; + + // only if in front + if (!victim->HasUnitState(UNIT_STATE_CONTROLLED) && (victim->HasInArc(float(M_PI), this) || victim->HasAuraType(SPELL_AURA_IGNORE_HIT_DIRECTION))) + { + int32 deflect_chance = victim->GetTotalAuraModifier(SPELL_AURA_DEFLECT_SPELLS) * 100; + tmp += deflect_chance; + if (roll < tmp) + return SPELL_MISS_DEFLECT; + } + } + + // Check for attack from behind + if (!victim->HasInArc(float(M_PI), this)) + { + if (!victim->HasAuraType(SPELL_AURA_IGNORE_HIT_DIRECTION)) + { + // Can't dodge from behind in PvP (but its possible in PvE) + if (victim->GetTypeId() == TYPEID_PLAYER) + canDodge = false; + // Can't parry or block + canParry = false; + canBlock = false; + } + else // Only deterrence as of 3.3.5 + { + if (spellInfo->HasAttribute(SPELL_ATTR0_CU_REQ_CASTER_BEHIND_TARGET)) + canParry = false; + } + } + + // Ignore combat result aura + AuraEffectList const& ignore = GetAuraEffectsByType(SPELL_AURA_IGNORE_COMBAT_RESULT); + for (AuraEffect const* aurEff : ignore) + { + if (!aurEff->IsAffectingSpell(spellInfo)) + continue; + + switch (aurEff->GetMiscValue()) + { + case MELEE_HIT_DODGE: + canDodge = false; + break; + case MELEE_HIT_BLOCK: + canBlock = false; + break; + case MELEE_HIT_PARRY: + canParry = false; + break; + default: + TC_LOG_DEBUG("entities.unit", "Spell %u SPELL_AURA_IGNORE_COMBAT_RESULT has unhandled state %d", aurEff->GetId(), aurEff->GetMiscValue()); + break; + } + } + + if (canDodge) + { + // Roll dodge + int32 dodgeChance = int32(GetUnitDodgeChance(attType, victim) * 100.0f); + if (dodgeChance < 0) + dodgeChance = 0; + + if (roll < (tmp += dodgeChance)) + return SPELL_MISS_DODGE; + } + + if (canParry) + { + // Roll parry + int32 parryChance = int32(GetUnitParryChance(attType, victim) * 100.0f); + if (parryChance < 0) + parryChance = 0; + + tmp += parryChance; + if (roll < tmp) + return SPELL_MISS_PARRY; + } + + if (canBlock) + { + int32 blockChance = int32(GetUnitBlockChance(victim) * 100.0f); + if (blockChance < 0) + blockChance = 0; + tmp += blockChance; + + if (roll < tmp) + return SPELL_MISS_BLOCK; + } + + return SPELL_MISS_NONE; +} + +float Unit::GetUnitDodgeChance(WeaponAttackType attType, Unit const* victim) const +{ + int32 attackerSkill = GetMaxSkillValueForLevel(nullptr); + int32 defenderSkill = victim->GetMaxSkillValueForLevel(nullptr); + int32 skillDifference = defenderSkill - attackerSkill; + + float chance = 0.0f; + + if (victim->IsPlayer()) + { + chance += victim->GetFloatValue(PLAYER_DODGE_PERCENTAGE); + chance += skillDifference * 0.04f; + } + else + { + if (!victim->IsTotem()) + { + chance += victim->GetTotalAuraModifier(SPELL_AURA_MOD_DODGE_PERCENT); + if (skillDifference <= 10) + chance = 5.f + skillDifference * 0.1f; + else + chance = 6.f + (skillDifference - 10) * 0.1f; + } + } + + // Reduce enemy dodge chance by SPELL_AURA_MOD_COMBAT_RESULT_CHANCE + chance += GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_COMBAT_RESULT_CHANCE, VICTIMSTATE_DODGE); + + // reduce dodge by SPELL_AURA_MOD_ENEMY_DODGE + chance += GetTotalAuraModifier(SPELL_AURA_MOD_ENEMY_DODGE); + + if (IsPlayer()) + chance -= ToPlayer()->GetExpertiseDodgeOrParryReduction(attType); + else + chance -= GetTotalAuraModifier(SPELL_AURA_MOD_EXPERTISE) / 4.0f; + + return std::clamp(chance, 0.f, 100.f); +} + +float Unit::GetUnitParryChance(WeaponAttackType attType, Unit const* victim) const +{ + int32 attackerSkill = GetMaxSkillValueForLevel(nullptr); + int32 defenderSkill = victim->GetMaxSkillValueForLevel(nullptr); + int32 skillDifference = defenderSkill - attackerSkill; + + float chance = 0.0f; + + if (Player const* playerVictim = victim->ToPlayer()) + { + if (playerVictim->CanParry()) + { + Item* tmpitem = playerVictim->GetWeaponForAttack(BASE_ATTACK, true); + if (!tmpitem) + tmpitem = playerVictim->GetWeaponForAttack(OFF_ATTACK, true); + + if (tmpitem) + chance = playerVictim->GetFloatValue(PLAYER_PARRY_PERCENTAGE); + + chance += skillDifference * 0.04f; + } + } + else + { + // Allow parries for creatures only if it's not a totem, does have a virtual item equipped and does not have CREATURE_FLAG_EXTRA_NO_PARRY + if (!victim->IsTotem() && (victim->GetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 0) || victim->GetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 1)) && + !(victim->ToCreature()->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_NO_PARRY)) + { + chance += victim->GetTotalAuraModifier(SPELL_AURA_MOD_PARRY_PERCENT); + if (skillDifference <= 10) + chance = 5.f + skillDifference * 0.1f; + else + chance = 6.f + (skillDifference - 10) * 0.1f; + } + } + + if (IsPlayer()) + chance -= ToPlayer()->GetExpertiseDodgeOrParryReduction(attType); + else + chance -= GetTotalAuraModifier(SPELL_AURA_MOD_EXPERTISE) / 4.0f; + + return std::max(chance, 0.0f); +} + +float Unit::GetMeleeMissChance(Unit const* victim) const +{ + float chance = 0.f; + + // As of patch 4.0.1, weapon skills have been deprecated so we can go for plain defense skill values + uint16 attackerSkill = GetMaxSkillValueForLevel(nullptr); + uint16 defenderSkill = victim->GetMaxSkillValueForLevel(nullptr); + int32 skillDifference = defenderSkill - attackerSkill; + + if (victim->IsPlayer()) + { + chance = 5.f; + if (skillDifference > 0) + chance += skillDifference * 0.04f; + else if (skillDifference < 0) + chance -= skillDifference * 0.02f; + } + else + { + if (skillDifference <= 10) + chance = 5.f + skillDifference * 0.1f; + else + chance = 6.f + (skillDifference - 10) * 0.4f; + } + + return std::clamp(chance, 0.f, 100.f); +} + +float Unit::GetUnitBlockChance(Unit const* victim) const +{ + float chance = 0.f; + + if (Player const* playerVictim = victim->ToPlayer()) + { + if (playerVictim->CanBlock()) + { + Item* tmpitem = playerVictim->GetUseableItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND); + if (tmpitem && !tmpitem->IsBroken()) + chance = playerVictim->GetFloatValue(PLAYER_BLOCK_PERCENTAGE); + } + } + else + { + if (!victim->IsTotem() && !(victim->ToCreature()->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_NO_BLOCK)) + { + chance = 5.0f; + chance += victim->GetTotalAuraModifier(SPELL_AURA_MOD_BLOCK_PERCENT); + } + } + + return std::max(chance, 0.0f); +} + +float Unit::GetUnitCriticalChanceDone(WeaponAttackType attackType) const +{ + float chance = 0.f; + if (GetTypeId() == TYPEID_PLAYER) + { + switch (attackType) + { + case BASE_ATTACK: + chance = GetFloatValue(PLAYER_CRIT_PERCENTAGE); + break; + case OFF_ATTACK: + chance = GetFloatValue(PLAYER_OFFHAND_CRIT_PERCENTAGE); + break; + case RANGED_ATTACK: + chance = GetFloatValue(PLAYER_RANGED_CRIT_PERCENTAGE); + break; + // Just for good manner + default: + chance = 0.0f; + break; + } + } + else + { + if (!(ToCreature()->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_NO_CRIT)) + { + chance = 5.0f; + chance += GetTotalAuraModifier(SPELL_AURA_MOD_WEAPON_CRIT_PERCENT); + chance += GetTotalAuraModifier(SPELL_AURA_MOD_CRIT_PCT); + } + } + + return chance; +} + +float Unit::GetUnitCriticalChanceTaken(WeaponAttackType attackType, float critDone) const +{ + float chance = critDone; + + // flat aura mods + if (attackType == RANGED_ATTACK) + chance += GetTotalAuraModifier(SPELL_AURA_MOD_ATTACKER_RANGED_CRIT_CHANCE); + else + chance += GetTotalAuraModifier(SPELL_AURA_MOD_ATTACKER_MELEE_CRIT_CHANCE); + + // applied after resilience + chance += GetTotalAuraModifier(SPELL_AURA_MOD_ATTACKER_SPELL_AND_WEAPON_CRIT_CHANCE); + + return std::max(chance, 0.f); +} + +float Unit::GetUnitCriticalChanceAgainst(WeaponAttackType attackType, Unit const* victim) const +{ + float chance = GetUnitCriticalChanceDone(attackType); + return victim->GetUnitCriticalChanceTaken(attackType, chance); +} + +void Unit::_DeleteRemovedAuras() +{ + while (!m_removedAuras.empty()) + { + delete m_removedAuras.front(); + m_removedAuras.pop_front(); + } + + m_removedAurasCount = 0; +} + +void Unit::_UpdateSpells(uint32 time) +{ + m_spellHistory->Update(); + + if (m_currentSpells[CURRENT_AUTOREPEAT_SPELL]) + _UpdateAutoRepeatSpell(); + + // remove finished spells from current pointers + for (Spell*& spell : m_currentSpells) + { + if (spell && spell->getState() == SPELL_STATE_FINISHED) + { + spell->SetReferencedFromCurrent(false); + spell = nullptr; // remove pointer + } + } + + // m_auraUpdateIterator can be updated in indirect called code at aura remove to skip next planned to update but removed auras + for (m_auraUpdateIterator = m_ownedAuras.begin(); m_auraUpdateIterator != m_ownedAuras.end();) + { + Aura* i_aura = m_auraUpdateIterator->second; + ++m_auraUpdateIterator; // need shift to next for allow update if need into aura update + i_aura->UpdateOwner(time, this); + } + + // remove expired auras - do that after updates(used in scripts?) + for (AuraMap::iterator i = m_ownedAuras.begin(); i != m_ownedAuras.end();) + { + if (i->second->IsExpired()) + RemoveOwnedAura(i, AuraRemoveFlags::Expired); + 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()) + { + GameObjectList::iterator itr; + for (itr = m_gameObj.begin(); itr != m_gameObj.end();) + { + if (!(*itr)->isSpawned()) + { + (*itr)->SetOwnerGUID(ObjectGuid::Empty); + (*itr)->SetRespawnTime(0); + (*itr)->Delete(); + m_gameObj.erase(itr++); + } + else + ++itr; + } + } +} + +void Unit::_UpdateAutoRepeatSpell() +{ + SpellInfo const* autoRepeatSpellInfo = m_currentSpells[CURRENT_AUTOREPEAT_SPELL]->m_spellInfo; + + // check "realtime" interrupts + // don't cancel spells which are affected by a SPELL_AURA_CAST_WHILE_WALKING effect + if ((isMoving() && m_currentSpells[CURRENT_AUTOREPEAT_SPELL]->CheckMovement() != SPELL_CAST_OK) || IsNonMeleeSpellCast(false, false, true, autoRepeatSpellInfo->Id == 75)) + { + // cancel wand shoot + if (autoRepeatSpellInfo->Id != 75) + InterruptSpell(CURRENT_AUTOREPEAT_SPELL); + return; + } + + // castroutine + if (isAttackReady(RANGED_ATTACK) && m_currentSpells[CURRENT_AUTOREPEAT_SPELL]->getState() != SPELL_STATE_PREPARING) + { + // Check if able to cast + SpellCastResult result = m_currentSpells[CURRENT_AUTOREPEAT_SPELL]->CheckCast(true); + if (result != SPELL_CAST_OK) + { + if (autoRepeatSpellInfo->Id != 75) + InterruptSpell(CURRENT_AUTOREPEAT_SPELL); + else if (GetTypeId() == TYPEID_PLAYER) + Spell::SendCastResult(ToPlayer(), autoRepeatSpellInfo, 1, result); + + return; + } + + // we want to shoot + Spell* spell = new Spell(this, autoRepeatSpellInfo, TRIGGERED_IGNORE_GCD); + spell->prepare(m_currentSpells[CURRENT_AUTOREPEAT_SPELL]->m_targets); + } +} + +void Unit::SetCurrentCastSpell(Spell* spell) +{ + ASSERT(spell); // nullptr may be never passed here, use InterruptSpell or InterruptNonMeleeSpells + + CurrentSpellTypes CSpellType = spell->GetCurrentContainer(); + + if (spell == m_currentSpells[CSpellType]) // avoid breaking self + return; + + // special breakage effects: + switch (CSpellType) + { + case CURRENT_GENERIC_SPELL: + { + // generic spells always break channeled not delayed spells + if (m_currentSpells[CURRENT_CHANNELED_SPELL] && !m_currentSpells[CURRENT_CHANNELED_SPELL]->GetSpellInfo()->HasAttribute(SPELL_ATTR5_ALLOW_ACTIONS_DURING_CHANNEL)) + InterruptSpell(CURRENT_CHANNELED_SPELL, false); + + // autorepeat breaking + if (m_currentSpells[CURRENT_AUTOREPEAT_SPELL]) + { + // break autorepeat if not Auto Shot + if (m_currentSpells[CURRENT_AUTOREPEAT_SPELL]->GetSpellInfo()->Id != 75) + InterruptSpell(CURRENT_AUTOREPEAT_SPELL); + } + if (spell->GetCastTime() > 0) + AddUnitState(UNIT_STATE_CASTING); + + break; + } + case CURRENT_CHANNELED_SPELL: + { + // channel spells always break generic non-delayed and any channeled spells + InterruptSpell(CURRENT_GENERIC_SPELL, false); + InterruptSpell(CURRENT_CHANNELED_SPELL); + + // it also does break autorepeat if not Auto Shot + if (m_currentSpells[CURRENT_AUTOREPEAT_SPELL] && + m_currentSpells[CURRENT_AUTOREPEAT_SPELL]->GetSpellInfo()->Id != 75) + InterruptSpell(CURRENT_AUTOREPEAT_SPELL); + AddUnitState(UNIT_STATE_CASTING); + + break; + } + case CURRENT_AUTOREPEAT_SPELL: + { + if (m_currentSpells[CSpellType] && m_currentSpells[CSpellType]->getState() == SPELL_STATE_IDLE) + m_currentSpells[CSpellType]->setState(SPELL_STATE_FINISHED); + + // only Auto Shoot does not break anything + if (spell->GetSpellInfo()->Id != 75) + { + // generic autorepeats break generic non-delayed and channeled non-delayed spells + InterruptSpell(CURRENT_GENERIC_SPELL, false); + InterruptSpell(CURRENT_CHANNELED_SPELL, false); + } + break; + } + default: + break; // other spell types don't break anything now + } + + // current spell (if it is still here) may be safely deleted now + if (m_currentSpells[CSpellType]) + m_currentSpells[CSpellType]->SetReferencedFromCurrent(false); + + // set new current spell + m_currentSpells[CSpellType] = spell; + spell->SetReferencedFromCurrent(true); + + spell->m_selfContainer = &(m_currentSpells[spell->GetCurrentContainer()]); +} + +void Unit::InterruptSpell(CurrentSpellTypes spellType, bool withDelayed, bool withInstant) +{ + Spell* spell = m_currentSpells[spellType]; + if (!spell) + return; + + if ((withDelayed || spell->getState() != SPELL_STATE_LAUNCHED) && + (withInstant || spell->GetCastTime() > 0 || spell->getState() == SPELL_STATE_CHANNELING)) + { + // for example, do not let self-stun aura interrupt itself + if (!spell->IsInterruptable()) + return; + + // send autorepeat cancel message for autorepeat spells + if (spellType == CURRENT_AUTOREPEAT_SPELL) + if (GetTypeId() == TYPEID_PLAYER) + ToPlayer()->SendAutoRepeatCancel(this); + + if (spell->getState() != SPELL_STATE_FINISHED) + spell->cancel(); + else + { + m_currentSpells[spellType] = nullptr; + spell->SetReferencedFromCurrent(false); + } + + if (GetTypeId() == TYPEID_UNIT && IsAIEnabled()) + ToCreature()->AI()->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_LAUNCHED)) + { + if (!skipInstant || m_currentSpells[CURRENT_GENERIC_SPELL]->GetCastTime()) + { + if (!isAutoshoot || !(m_currentSpells[CURRENT_GENERIC_SPELL]->m_spellInfo->HasAttribute(SPELL_ATTR2_DO_NOT_RESET_COMBAT_TIMERS))) + return true; + } + } + // channeled spells may be delayed, but they are still considered cast + if (!skipChanneled && m_currentSpells[CURRENT_CHANNELED_SPELL] && + (m_currentSpells[CURRENT_CHANNELED_SPELL]->getState() != SPELL_STATE_FINISHED)) + { + if (!isAutoshoot || !(m_currentSpells[CURRENT_CHANNELED_SPELL]->m_spellInfo->HasAttribute(SPELL_ATTR2_DO_NOT_RESET_COMBAT_TIMERS))) + return true; + } + // autorepeat spells may be finished or delayed, but they are still considered cast + if (!skipAutorepeat && m_currentSpells[CURRENT_AUTOREPEAT_SPELL]) + return true; + + return false; +} + +void Unit::InterruptNonMeleeSpells(bool withDelayed, uint32 spell_id, bool withInstant /* = true */) +{ + // generic spells are interrupted if they are not finished or delayed + if (m_currentSpells[CURRENT_GENERIC_SPELL] && (!spell_id || m_currentSpells[CURRENT_GENERIC_SPELL]->m_spellInfo->Id == spell_id)) + InterruptSpell(CURRENT_GENERIC_SPELL, withDelayed, withInstant); + + // autorepeat spells are interrupted if they are not finished or delayed + if (m_currentSpells[CURRENT_AUTOREPEAT_SPELL] && (!spell_id || m_currentSpells[CURRENT_AUTOREPEAT_SPELL]->m_spellInfo->Id == spell_id)) + InterruptSpell(CURRENT_AUTOREPEAT_SPELL, withDelayed, withInstant); + + // channeled spells are interrupted if they are not finished, even if they are delayed + if (m_currentSpells[CURRENT_CHANNELED_SPELL] && (!spell_id || m_currentSpells[CURRENT_CHANNELED_SPELL]->m_spellInfo->Id == spell_id)) + InterruptSpell(CURRENT_CHANNELED_SPELL, true, true); +} + +Spell* Unit::FindCurrentSpellBySpellId(uint32 spell_id) const +{ + for (Spell* spell : m_currentSpells) + if (spell && spell->m_spellInfo->Id == spell_id) + return spell; + return nullptr; +} + +int32 Unit::GetCurrentSpellCastTime(uint32 spell_id) const +{ + if (Spell const* spell = FindCurrentSpellBySpellId(spell_id)) + return spell->GetCastTime(); + return 0; +} + +bool Unit::IsMovementPreventedByCasting() const +{ + // can always move when not casting + if (!HasUnitState(UNIT_STATE_CASTING)) + return false; + + if (Spell* spell = m_currentSpells[CURRENT_GENERIC_SPELL]) + if (HasAuraTypeWithAffectMask(SPELL_AURA_CAST_WHILE_WALKING, spell->GetSpellInfo()) || spell->getState() == SPELL_STATE_FINISHED || + !spell->m_spellInfo->InterruptFlags.HasFlag(SpellInterruptFlags::Movement)) + return false; + + // channeled spells during channel stage (after the initial cast timer) allow movement with a specific spell attribute + if (Spell* spell = m_currentSpells[CURRENT_CHANNELED_SPELL]) + if (spell->getState() != SPELL_STATE_FINISHED && spell->IsChannelActive()) + if (spell->GetSpellInfo()->IsMoveAllowedChannel() || HasAuraTypeWithAffectMask(SPELL_AURA_CAST_WHILE_WALKING, spell->GetSpellInfo())) + 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 * float(M_PI) - arc, target); +} + +bool Unit::isInAccessiblePlaceFor(Creature const* c) const +{ + // Aquatic creatures are not allowed to leave liquids + if (!IsInWater() && c->IsAquatic()) + return false; + + // Underwater special case. Some creatures may not go below liquid surfaces + if (IsUnderWater() && c->CannotPenetrateWater()) + return false; + + // Water checks + if (IsInWater() && !c->CanEnterWater()) + return false; + + // Some creatures are tied to the ocean floor and cannot chase swimming targets. + if (!IsOnOceanFloor() && c->IsUnderWater() && c->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_CANT_SWIM)) + return false; + + return true; +} + +bool Unit::IsInWater() const +{ + return GetLiquidStatus() & (LIQUID_MAP_IN_WATER | LIQUID_MAP_UNDER_WATER); +} + +bool Unit::IsUnderWater() const +{ + return GetLiquidStatus() & LIQUID_MAP_UNDER_WATER; +} + +bool Unit::IsOnOceanFloor() const +{ + return GetLiquidStatus() & LIQUID_MAP_OCEAN_FLOOR; +} + +void Unit::ProcessPositionDataChanged(PositionFullTerrainStatus const& data) +{ + ZLiquidStatus oldLiquidStatus = GetLiquidStatus(); + WorldObject::ProcessPositionDataChanged(data); + ProcessTerrainStatusUpdate(oldLiquidStatus, data.liquidInfo); + + if (oldLiquidStatus != GetLiquidStatus()) + UpdateMountCapability(); +} + +void Unit::ProcessTerrainStatusUpdate(ZLiquidStatus /*oldLiquidStatus*/, Optional const& newLiquidData) +{ + if (!IsControlledByPlayer()) + return; + + // remove appropriate auras if we are swimming/not swimming respectively + if (IsInWater()) + RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::UnderWater); + else + RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::AboveWater); + + // liquid aura handling + LiquidTypeEntry const* curLiquid = nullptr; + if (IsInWater() && newLiquidData) + curLiquid = sLiquidTypeStore.LookupEntry(newLiquidData->entry); + if (curLiquid != _lastLiquid) + { + if (_lastLiquid && _lastLiquid->SpellID) + RemoveAurasDueToSpell(_lastLiquid->SpellID); + Player* player = GetCharmerOrOwnerPlayerOrPlayerItself(); + + // Set _lastLiquid before casting liquid spell to avoid infinite loops + _lastLiquid = curLiquid; + + if (curLiquid && curLiquid->SpellID && (!player || !player->IsGameMaster())) + CastSpell(this, curLiquid->SpellID, true); + } +} +void Unit::DeMorph() +{ + SetDisplayId(GetNativeDisplayId()); +} + +Aura* Unit::_TryStackingOrRefreshingExistingAura(AuraCreateInfo& createInfo) +{ + ASSERT(createInfo.CasterGUID || createInfo.Caster); + + // Check if these can stack anyway + if (!createInfo.CasterGUID && !createInfo.GetSpellInfo()->IsStackableOnOneSlotWithDifferentCasters()) + createInfo.CasterGUID = createInfo.Caster->GetGUID(); + + // world gameobjects can't own auras and they send empty casterguid + // checked on sniffs with spell 22247 + if (createInfo.CasterGUID.IsGameObject()) + { + createInfo.Caster = nullptr; + createInfo.CasterGUID.Clear(); + } + + // passive and Incanter's Absorption and auras with different type can stack with themselves any number of times + if (!createInfo.GetSpellInfo()->IsMultiSlotAura()) + { + // check if cast item changed + ObjectGuid castItemGUID = createInfo.CastItemGUID; + + // find current aura from spell and change it's stackamount, or refresh it's duration + if (Aura* foundAura = GetOwnedAura(createInfo.GetSpellInfo()->Id, createInfo.GetSpellInfo()->IsStackableOnOneSlotWithDifferentCasters() ? ObjectGuid::Empty : createInfo.CasterGUID, createInfo.GetSpellInfo()->HasAttribute(SPELL_ATTR0_CU_ENCHANT_PROC) ? castItemGUID : ObjectGuid::Empty)) + { + // effect masks do not match + // extremely rare case + // let's just recreate aura + if (createInfo.GetAuraEffectMask() != foundAura->GetEffectMask()) + return nullptr; + + // update basepoints with new values - effect amount will be recalculated in ModStackAmount + for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) + { + if (!foundAura->HasEffect(i)) + continue; + + int32 bp = foundAura->GetSpellInfo()->Effects[i].BasePoints; + if (createInfo.BaseAmount) + bp = *(createInfo.BaseAmount + i); + + 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, AuraRemoveFlags::ByDefault, createInfo.ResetPeriodicTimer); + return foundAura; + } + } + + return nullptr; +} + +void Unit::_AddAura(UnitAura* aura, Unit* caster) +{ + ASSERT(!m_cleanupDone); + m_ownedAuras.emplace(aura->GetId(), aura); + + _RemoveNoStackAurasDueToAura(aura, true); + + if (aura->IsRemoved()) + return; + + aura->SetIsSingleTarget(caster && aura->GetSpellInfo()->IsSingleTarget()); + if (aura->IsSingleTarget()) + { + ASSERT((IsInWorld() && !IsDuringRemoveFromWorld()) || aura->GetCasterGUID() == GetGUID()); + /* @HACK: Player is not in world during loading auras. + * Single target auras are not saved or loaded from database + * but may be created as a result of aura links. + */ + + std::vector aurasSharingLimit; + // remove other single target auras + for (Aura* scAura : caster->GetSingleCastAuras()) + if (scAura->IsSingleTargetWith(aura)) + aurasSharingLimit.push_back(scAura); + + // register single target aura + caster->GetSingleCastAuras().push_front(aura); + + uint32 maxOtherAuras = aura->GetSpellInfo()->MaxAffectedTargets - 1; + while (aurasSharingLimit.size() > maxOtherAuras) + { + aurasSharingLimit.back()->Remove(); + aurasSharingLimit.pop_back(); + } + } +} + +// creates aura application instance and registers it in lists +// aura application effects are handled separately to prevent aura list corruption +AuraApplication* Unit::_CreateAuraApplication(Aura* aura, uint8 effMask) +{ + // can't apply aura on unit which is going to be deleted - to not create a memory leak + ASSERT(!m_cleanupDone); + + // just return if the aura has been already removed + // this can happen if OnEffectHitTarget() script hook killed the unit or the aura owner (which can be different) + if (aura->IsRemoved()) + { + TC_LOG_ERROR("spells", "Unit::_CreateAuraApplication() called with a removed aura. Check if OnEffectHitTarget() is triggering any spell with apply aura effect (that's not allowed!)"); + return nullptr; + } + + // aura mustn't be already applied on target + ASSERT (!aura->IsAppliedOnTarget(GetGUID()) && "Unit::_CreateAuraApplication: aura musn't be applied on target"); + + SpellInfo const* aurSpellInfo = aura->GetSpellInfo(); + uint32 aurId = aurSpellInfo->Id; + + // ghost spell check, allow apply any auras at player loading in ghost mode (will be cleanup after load) + if (!IsAlive() && !aurSpellInfo->IsDeathPersistent() && + (GetTypeId() != TYPEID_PLAYER || !ToPlayer()->GetSession()->PlayerLoading())) + return nullptr; + + Unit* caster = aura->GetCaster(); + + AuraApplication * aurApp = new AuraApplication(this, caster, aura, effMask); + m_appliedAuras.insert(AuraApplicationMap::value_type(aurId, aurApp)); + + if (aurSpellInfo->HasAnyAuraInterruptFlag()) + { + m_interruptableAuras.push_front(aurApp); + AddInterruptMask(aurSpellInfo->AuraInterruptFlags, aurSpellInfo->AuraInterruptFlags2); + } + + if (AuraStateType aState = aura->GetSpellInfo()->GetAuraState()) + m_auraStateAuras.insert(AuraStateAurasMap::value_type(aState, aurApp)); + + aura->_ApplyForTarget(this, caster, aurApp); + return aurApp; +} + +void Unit::_ApplyAuraEffect(Aura* aura, uint8 effIndex) +{ + ASSERT(aura); + ASSERT(aura->HasEffect(effIndex)); + AuraApplication * aurApp = aura->GetApplicationOfTarget(GetGUID()); + ASSERT(aurApp); + if (!aurApp->GetEffectMask()) + _ApplyAura(aurApp, 1 << effIndex); + else + aurApp->_HandleEffect(effIndex, true); +} + +// handles effects of aura application +// should be done after registering aura in lists +void Unit::_ApplyAura(AuraApplication* aurApp, uint8 effMask) +{ + Aura* aura = aurApp->GetBase(); + + _RemoveNoStackAurasDueToAura(aura, false); + + if (aurApp->GetRemoveMode().HasAnyFlag()) + return; + + // Update target aura state flag + if (AuraStateType aState = aura->GetSpellInfo()->GetAuraState()) + ModifyAuraState(aState, true); + + if (aurApp->GetRemoveMode().HasAnyFlag()) + return; + + // Sitdown on apply aura req seated + if (aura->GetSpellInfo()->HasAuraInterruptFlag(SpellAuraInterruptFlags::Standing) && !IsSitState()) + SetStandState(UNIT_STAND_STATE_SIT); + + Unit* caster = aura->GetCaster(); + + if (aurApp->GetRemoveMode().HasAnyFlag()) + 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().HasAnyFlag()) + aurApp->_HandleEffect(i, true); + } + + if (Player* player = ToPlayer()) + { + if (sConditionMgr->IsSpellUsedInSpellClickConditions(aurApp->GetBase()->GetId())) + player->UpdateVisibleGameobjectsOrSpellClicks(); + + player->FailAchievementCriteria(AchievementCriteriaFailEvent::GainAura, aurApp->GetBase()->GetId()); + player->StartAchievementCriteria(AchievementCriteriaStartEvent::GainAura, aurApp->GetBase()->GetId()); + player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_BE_SPELL_TARGET2, aurApp->GetBase()->GetId()); + } +} + +// removes aura application from lists and unapplies effects +void Unit::_UnapplyAura(AuraApplicationMap::iterator& i, AuraRemoveFlags removeMode) +{ + AuraApplication * aurApp = i->second; + ASSERT(aurApp); + ASSERT(!aurApp->GetRemoveMode().HasAnyFlag()); + ASSERT(aurApp->GetTarget() == this); + + aurApp->SetRemoveMode(removeMode); + Aura* aura = aurApp->GetBase(); + TC_LOG_DEBUG("spells", "Aura %u now is remove mode %u", aura->GetId(), static_cast(removeMode)); + + // dead loop is killing the server probably + ASSERT(m_removedAurasCount < 0xFFFFFFFF); + + ++m_removedAurasCount; + + Unit* caster = aura->GetCaster(); + + // Remove all pointers from lists here to prevent possible pointer invalidation on spellcast/auraapply/auraremove + m_appliedAuras.erase(i); + + if (aura->GetSpellInfo()->HasAnyAuraInterruptFlag()) + { + Trinity::Containers::Lists::RemoveUnique(m_interruptableAuras, aurApp); + UpdateInterruptMask(); + } + + bool auraStateFound = false; + AuraStateType auraState = aura->GetSpellInfo()->GetAuraState(); + if (auraState) + { + bool canBreak = false; + // Get mask of all aurastates from remaining auras + for (AuraStateAurasMap::iterator itr = m_auraStateAuras.lower_bound(auraState); itr != m_auraStateAuras.upper_bound(auraState) && !(auraStateFound && canBreak);) + { + if (itr->second == aurApp) + { + m_auraStateAuras.erase(itr); + itr = m_auraStateAuras.lower_bound(auraState); + canBreak = true; + continue; + } + auraStateFound = true; + ++itr; + } + } + + aurApp->_Remove(); + aura->_UnapplyForTarget(this, caster, aurApp); + + // remove effects of the spell - needs to be done after removing aura from lists + for (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().HasFlag(AuraRemoveFlags::Expired) && GetTypeId() == TYPEID_UNIT && IsTotem()) + { + if (ToTotem()->GetSpell() == aura->GetId() && ToTotem()->GetTotemType() == TOTEM_PASSIVE) + ToTotem()->setDeathState(JUST_DIED); + } + + // Remove aurastates only if were not found + if (!auraStateFound) + ModifyAuraState(auraState, false); + + aura->HandleAuraSpecificMods(aurApp, caster, false, false); + + if (Player* player = ToPlayer()) + { + if (sConditionMgr->IsSpellUsedInSpellClickConditions(aurApp->GetBase()->GetId())) + player->UpdateVisibleGameobjectsOrSpellClicks(); + + player->FailAchievementCriteria(AchievementCriteriaFailEvent::LoseAura, aurApp->GetBase()->GetId()); + } + + i = m_appliedAuras.begin(); +} + +void Unit::_UnapplyAura(AuraApplication* aurApp, AuraRemoveFlags removeMode) +{ + // aura can be removed from unit only if it's applied on it, shouldn't happen + ASSERT(aurApp->GetBase()->GetApplicationOfTarget(GetGUID()) == aurApp); + + uint32 spellId = aurApp->GetBase()->GetId(); + AuraApplicationMapBoundsNonConst range = m_appliedAuras.equal_range(spellId); + + for (AuraApplicationMap::iterator iter = range.first; iter != range.second;) + { + if (iter->second == aurApp) + { + _UnapplyAura(iter, removeMode); + return; + } + else + ++iter; + } + ABORT(); +} + +void Unit::_RemoveNoStackAurasDueToAura(Aura* aura, bool owned) +{ + SpellInfo const* spellProto = aura->GetSpellInfo(); + + // passive spell special case (only non stackable with ranks) + if (spellProto->IsPassiveStackableWithRanks()) + return; + + if (!IsHighestExclusiveAura(aura)) + { + aura->Remove(); + return; + } + + if (owned) + RemoveOwnedAuras([aura](Aura const* ownedAura) { return !aura->CanStackWith(ownedAura); }, AuraRemoveFlags::ByDefault); + else + RemoveAppliedAuras([aura](AuraApplication const* appliedAura) { return !aura->CanStackWith(appliedAura->GetBase()); }, AuraRemoveFlags::ByDefault); +} + +void Unit::_RegisterAuraEffect(AuraEffect* aurEff, bool apply) +{ + if (apply) + { + m_modAuras[aurEff->GetAuraType()].push_front(aurEff); + if (Player* player = ToPlayer()) + { + player->StartAchievementCriteria(AchievementCriteriaStartEvent::GainAuraEffect, aurEff->GetAuraType()); + player->FailAchievementCriteria(AchievementCriteriaFailEvent::GainAuraEffect, aurEff->GetAuraType()); + } + } + else + Trinity::Containers::Lists::RemoveUnique(m_modAuras[aurEff->GetAuraType()], aurEff); +} + +// All aura base removes should go through this function! +void Unit::RemoveOwnedAura(AuraMap::iterator& i, AuraRemoveFlags removeMode) +{ + Aura* aura = i->second; + ASSERT(!aura->IsRemoved()); + + // if unit currently update aura list then make safe update iterator shift to next + if (m_auraUpdateIterator == i) + ++m_auraUpdateIterator; + + m_ownedAuras.erase(i); + m_removedAuras.push_front(aura); + + // Unregister single target aura + if (aura->IsSingleTarget()) + aura->UnregisterSingleTarget(); + + aura->_Remove(removeMode); + + i = m_ownedAuras.begin(); +} + +void Unit::RemoveOwnedAura(uint32 spellId, ObjectGuid casterGUID, uint8 reqEffMask, AuraRemoveFlags 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, AuraRemoveFlags removeMode) +{ + if (aura->IsRemoved()) + return; + + ASSERT(aura->GetOwner() == this); + + if (removeMode == AuraRemoveFlags::None) + { + TC_LOG_ERROR("spells", "Unit::RemoveOwnedAura() called with unallowed removeMode AuraRemoveFlags::None, spellId %u", aura->GetId()); + return; + } + + uint32 spellId = aura->GetId(); + AuraMapBoundsNonConst range = m_ownedAuras.equal_range(spellId); + + for (AuraMap::iterator itr = range.first; itr != range.second; ++itr) + { + if (itr->second == aura) + { + RemoveOwnedAura(itr, removeMode); + return; + } + } + + ABORT(); +} + +Aura* Unit::GetOwnedAura(uint32 spellId, ObjectGuid casterGUID, ObjectGuid itemCasterGUID, 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, AuraRemoveFlags mode) +{ + AuraApplication * aurApp = i->second; + // Do not remove aura which is already being removed + if (aurApp->GetRemoveMode().HasAnyFlag()) + 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, AuraRemoveFlags 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, AuraRemoveFlags 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().HasAnyFlag()) + { + // 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) + { + RemoveAura(iter, mode); + return; + } + else + ++iter; + } +} + +void Unit::RemoveAura(Aura* aura, AuraRemoveFlags mode) +{ + if (aura->IsRemoved()) + return; + if (AuraApplication * aurApp = aura->GetApplicationOfTarget(GetGUID())) + RemoveAura(aurApp, mode); +} + +void Unit::RemoveAppliedAuras(std::function const& check, AuraRemoveFlags removeMode /* = AuraRemoveFlags::ByDefault*/) +{ + for (AuraApplicationMap::iterator iter = m_appliedAuras.begin(); iter != m_appliedAuras.end();) + { + if (check(iter->second)) + { + RemoveAura(iter, removeMode); + continue; + } + ++iter; + } +} + +void Unit::RemoveOwnedAuras(std::function const& check, AuraRemoveFlags removeMode /* = AuraRemoveFlags::ByDefault*/) +{ + for (AuraMap::iterator iter = m_ownedAuras.begin(); iter != m_ownedAuras.end();) + { + if (check(iter->second)) + { + RemoveOwnedAura(iter, removeMode); + continue; + } + ++iter; + } +} + +void Unit::RemoveAppliedAuras(uint32 spellId, std::function const& check, AuraRemoveFlags removeMode /* = AuraRemoveFlags::ByDefault*/) +{ + for (AuraApplicationMap::iterator iter = m_appliedAuras.lower_bound(spellId); iter != m_appliedAuras.upper_bound(spellId);) + { + if (check(iter->second)) + { + RemoveAura(iter, removeMode); + iter = m_appliedAuras.lower_bound(spellId); + continue; + } + ++iter; + } +} + +void Unit::RemoveOwnedAuras(uint32 spellId, std::function const& check, AuraRemoveFlags removeMode /* = AuraRemoveFlags::ByDefault*/) +{ + for (AuraMap::iterator iter = m_ownedAuras.lower_bound(spellId); iter != m_ownedAuras.upper_bound(spellId);) + { + if (check(iter->second)) + { + RemoveOwnedAura(iter, removeMode); + iter = m_ownedAuras.lower_bound(spellId); + continue; + } + ++iter; + } +} + +void Unit::RemoveAurasByType(AuraType auraType, std::function const& check, AuraRemoveFlags removeMode /* = AuraRemoveFlags::ByDefault*/) +{ + for (AuraEffectList::iterator iter = m_modAuras[auraType].begin(); iter != m_modAuras[auraType].end();) + { + Aura* aura = (*iter)->GetBase(); + AuraApplication * aurApp = aura->GetApplicationOfTarget(GetGUID()); + ASSERT(aurApp); + + ++iter; + if (check(aurApp)) + { + uint32 removedAuras = m_removedAurasCount; + RemoveAura(aurApp, removeMode); + if (m_removedAurasCount > removedAuras + 1) + iter = m_modAuras[auraType].begin(); + } + } +} + +void Unit::RemoveAurasDueToSpell(uint32 spellId, ObjectGuid casterGUID, uint8 reqEffMask, AuraRemoveFlags 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, AuraRemoveFlags 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, WorldObject* dispeller, uint8 chargesRemoved/*= 1*/) +{ + AuraMapBoundsNonConst range = m_ownedAuras.equal_range(spellId); + for (AuraMap::iterator iter = range.first; iter != range.second;) + { + Aura* aura = iter->second; + if (aura->GetCasterGUID() == casterGUID) + { + DispelInfo dispelInfo(dispeller, dispellerSpellId, chargesRemoved); + + // Call OnDispel hook on AuraScript + aura->CallScriptDispel(&dispelInfo); + + if (aura->GetSpellInfo()->HasAttribute(SPELL_ATTR7_DISPEL_REMOVES_CHARGES)) + aura->ModCharges(-dispelInfo.GetRemovedCharges(), AuraRemoveFlags::ByEnemySpell); + else + aura->ModStackAmount(-dispelInfo.GetRemovedCharges(), AuraRemoveFlags::ByEnemySpell); + + // Call AfterDispel hook on AuraScript + aura->CallScriptAfterDispel(&dispelInfo); + + return; + } + else + ++iter; + } +} + +void Unit::RemoveAurasDueToSpellBySteal(uint32 spellId, ObjectGuid casterGUID, WorldObject* stealer, int32 stolenCharges /*= 1*/) +{ + AuraMapBoundsNonConst range = m_ownedAuras.equal_range(spellId); + for (AuraMap::iterator iter = range.first; iter != range.second;) + { + Aura* aura = iter->second; + if (aura->GetCasterGUID() == casterGUID) + { + 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<GetEffect(i)->CanBeRecalculated()) + recalculateMask |= (1<GetSpellInfo()->HasAttribute(SPELL_ATTR7_DISPEL_REMOVES_CHARGES); + // Cast duration to unsigned to prevent permanent aura's such as Righteous Fury being permanently added to caster + uint32 dur = std::min(2u * MINUTE * IN_MILLISECONDS, uint32(aura->GetDuration())); + + if (Unit* unitStealer = stealer->ToUnit()) + { + if (Aura* oldAura = unitStealer->GetAura(aura->GetId(), aura->GetCasterGUID())) + { + if (stealCharge) + oldAura->ModCharges(stolenCharges); + else + oldAura->ModStackAmount(stolenCharges); + oldAura->SetDuration(int32(dur)); + } + else + { + // single target state must be removed before aura creation to preserve existing single target aura + if (aura->IsSingleTarget()) + aura->UnregisterSingleTarget(); + + AuraCreateInfo createInfo(aura->GetSpellInfo(), effMask, stealer); + createInfo + .SetCasterGUID(aura->GetCasterGUID()) + .SetBaseAmount(baseDamage); + + if (Aura* newAura = Aura::TryRefreshStackOrCreate(createInfo)) + { + // created aura must not be single target aura,, so stealer won't loose it on recast + if (newAura->IsSingleTarget()) + { + newAura->UnregisterSingleTarget(); + // bring back single target aura status to the old aura + aura->SetIsSingleTarget(true); + caster->GetSingleCastAuras().push_front(aura); + } + // FIXME: using aura->GetMaxDuration() maybe not blizzlike but it fixes stealing of spells like Innervate + newAura->SetLoadedState(aura->GetMaxDuration(), int32(dur), stealCharge ? stolenCharges : aura->GetCharges(), stolenCharges, recalculateMask, aura->GetCritChance(), aura->CanApplyResilience(), &damage[0]); + newAura->ApplyForTargets(); + } + } + } + + if (stealCharge) + aura->ModCharges(-stolenCharges, AuraRemoveFlags::ByEnemySpell); + else + aura->ModStackAmount(-stolenCharges, AuraRemoveFlags::ByEnemySpell); + + return; + } + else + ++iter; + } +} + +void Unit::RemoveAurasDueToItemSpell(uint32 spellId, ObjectGuid castItemGuid) +{ + for (AuraApplicationMap::iterator iter = m_appliedAuras.lower_bound(spellId); iter != m_appliedAuras.upper_bound(spellId);) + { + if (iter->second->GetBase()->GetCastItemGUID() == castItemGuid) + { + RemoveAura(iter); + iter = m_appliedAuras.lower_bound(spellId); + } + else + ++iter; + } +} + +void Unit::RemoveAurasByType(AuraType auraType, ObjectGuid casterGUID, Aura* except, bool negative, bool positive) +{ + for (AuraEffectList::iterator iter = m_modAuras[auraType].begin(); iter != m_modAuras[auraType].end();) + { + Aura* aura = (*iter)->GetBase(); + AuraApplication * aurApp = aura->GetApplicationOfTarget(GetGUID()); + ASSERT(aurApp); + + ++iter; + if (aura != except && (!casterGUID || aura->GetCasterGUID() == casterGUID) + && ((negative && !aurApp->IsPositive()) || (positive && aurApp->IsPositive()))) + { + uint32 removedAuras = m_removedAurasCount; + RemoveAura(aurApp); + if (m_removedAurasCount > removedAuras + 1) + iter = m_modAuras[auraType].begin(); + } + } +} + +void Unit::RemoveAurasWithAttribute(uint32 flags) +{ + for (AuraApplicationMap::iterator iter = m_appliedAuras.begin(); iter != m_appliedAuras.end();) + { + SpellInfo const* spell = iter->second->GetBase()->GetSpellInfo(); + if (spell->Attributes & flags) + RemoveAura(iter); + else + ++iter; + } +} + +void Unit::RemoveNotOwnSingleTargetAuras(bool onPhaseChange /*= false*/) +{ + // single target auras from other casters + // Iterate m_ownedAuras - aura is marked as single target in Unit::AddAura (and pushed to m_ownedAuras). + // m_appliedAuras will NOT contain the aura before first Unit::Update after adding it to m_ownedAuras. + // Quickly removing such an aura will lead to it not being unregistered from caster's single cast auras container + // leading to assertion failures if the aura was cast on a player that can + // (and is changing map at the point where this function is called). + // Such situation occurs when player is logging in inside an instance and fails the entry check for any reason. + // The aura that was loaded from db (indirectly, via linked casts) gets removed before it has a chance + // to register in m_appliedAuras + for (AuraMap::iterator iter = m_ownedAuras.begin(); iter != m_ownedAuras.end();) + { + Aura const* aura = iter->second; + + if (aura->GetCasterGUID() != GetGUID() && aura->IsSingleTarget()) + { + if (!onPhaseChange) + RemoveOwnedAura(iter); + else + { + Unit* caster = aura->GetCaster(); + if (!caster || !caster->IsInPhase(this)) + RemoveOwnedAura(iter); + else + ++iter; + } + } + else + ++iter; + } + + // single target auras at other targets + AuraList& scAuras = GetSingleCastAuras(); + for (AuraList::iterator iter = scAuras.begin(); iter != scAuras.end();) + { + Aura* aura = *iter; + if (aura->GetUnitOwner() != this && (!onPhaseChange || !aura->GetUnitOwner()->IsInPhase(this))) + { + aura->Remove(); + iter = scAuras.begin(); + } + else + ++iter; + } +} + +template +bool IsInterruptFlagIgnoredForSpell(InterruptFlag /*flag*/, Unit const* /*unit*/, SpellInfo const* /*auraSpellInfo*/, SpellInfo const* /*interruptSource*/) +{ + return false; +} + +bool IsInterruptFlagIgnoredForSpell(SpellAuraInterruptFlags flag, Unit const* unit, SpellInfo const* auraSpellInfo, SpellInfo const* interruptSource) +{ + switch (flag) + { + case SpellAuraInterruptFlags::Moving: + return unit->HasAuraTypeWithAffectMask(SPELL_AURA_CAST_WHILE_WALKING, auraSpellInfo); + case SpellAuraInterruptFlags::Action: + case SpellAuraInterruptFlags::ActionDelayed: + if (interruptSource) + { + if (interruptSource->HasAttribute(SPELL_ATTR1_ALLOW_WHILE_STEALTHED) && auraSpellInfo->Dispel == DISPEL_STEALTH) + return true; + + if (interruptSource->HasAttribute(SPELL_ATTR2_ALLOW_WHILE_INVISIBLE) && auraSpellInfo->Dispel == DISPEL_INVISIBILITY) + return true; + } + break; + default: + break; + } + + return false; +} + +template +void Unit::RemoveAurasWithInterruptFlags(InterruptFlags flag, SpellInfo const* source) +{ + if (!HasInterruptFlag(flag)) + return; + + // interrupt auras + for (AuraApplicationList::iterator iter = m_interruptableAuras.begin(); iter != m_interruptableAuras.end();) + { + Aura* aura = (*iter)->GetBase(); + ++iter; + if (aura->GetSpellInfo()->HasAuraInterruptFlag(flag) + && (!source || aura->GetId() != source->Id) + && !IsInterruptFlagIgnoredForSpell(flag, this, aura->GetSpellInfo(), source)) + { + 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_CHANNELING + && spell->GetSpellInfo()->HasChannelInterruptFlag(flag) + && (!source || spell->GetSpellInfo()->Id != source->Id) + && !IsInterruptFlagIgnoredForSpell(flag, this, spell->GetSpellInfo(), source)) + InterruptNonMeleeSpells(false, 0, true); + + UpdateInterruptMask(); +} + +template TC_GAME_API void Unit::RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags flag, SpellInfo const* source); +template TC_GAME_API void Unit::RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags2 flag, SpellInfo const* source); + +void Unit::RemoveAurasWithFamily(SpellFamilyNames family, 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, AuraRemoveFlags::ByDefault, 0, true); + + RemoveAurasWithMechanic(1 << MECHANIC_ROOT, AuraRemoveFlags::ByDefault, 0, false); +} + +void Unit::RemoveAurasWithMechanic(uint32 mechanicMaskToRemove, AuraRemoveFlags removeMode, uint32 exceptSpellId, bool withEffectMechanics) +{ + std::vector aurasToUpdateTargets; + RemoveAppliedAuras([=, &aurasToUpdateTargets](AuraApplication const* aurApp) + { + Aura* aura = aurApp->GetBase(); + if (exceptSpellId && aura->GetId() == exceptSpellId) + return false; + + uint32 appliedMechanicMask = aura->GetSpellInfo()->GetSpellMechanicMaskByEffectMask(aurApp->GetEffectMask()); + if (!(appliedMechanicMask & mechanicMaskToRemove)) + return false; + + // spell mechanic matches required mask for removal + if ((1 << aura->GetSpellInfo()->Mechanic) & mechanicMaskToRemove || withEffectMechanics) + return true; + + // effect mechanic matches required mask for removal - don't remove, only update targets + aurasToUpdateTargets.push_back(aura); + return false; + }, removeMode); + + for (Aura* aura : aurasToUpdateTargets) + { + aura->UpdateTargetMap(aura->GetCaster()); + + // Fully remove the aura if all effects were removed + if (!aura->IsPassive() && aura->GetOwner() == this && !aura->GetApplicationOfTarget(GetGUID())) + aura->Remove(removeMode); + } +} + +void Unit::RemoveAurasByShapeShift() +{ + 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)) + { + RemoveAura(iter); + continue; + } + ++iter; + } +} + +void Unit::RemoveAreaAurasDueToLeaveWorld() +{ + // make sure that all area auras not applied on self are removed - prevent access to deleted pointer later + for (AuraMap::iterator iter = m_ownedAuras.begin(); iter != m_ownedAuras.end();) + { + Aura* aura = iter->second; + ++iter; + Aura::ApplicationMap const& appMap = aura->GetApplicationMap(); + for (Aura::ApplicationMap::const_iterator itr = appMap.begin(); itr!= appMap.end();) + { + AuraApplication * aurApp = itr->second; + ++itr; + Unit* target = aurApp->GetTarget(); + if (target == this) + continue; + target->RemoveAura(aurApp); + // things linked on aura remove may apply new area aura - so start from the beginning + iter = m_ownedAuras.begin(); + } + } + + // remove area auras owned by others + for (AuraApplicationMap::iterator iter = m_appliedAuras.begin(); iter != m_appliedAuras.end();) + { + if (iter->second->GetBase()->GetOwner() != this) + { + RemoveAura(iter); + } + else + ++iter; + } +} + +void Unit::RemoveAllAuras() +{ + // this may be a dead loop if some events on aura remove will continiously apply aura on remove + // we want to have all auras removed, so use your brain when linking events + for (uint32 counter = 0; !m_appliedAuras.empty() || !m_ownedAuras.empty(); counter++) + { + AuraApplicationMap::iterator aurAppIter; + for (aurAppIter = m_appliedAuras.begin(); aurAppIter != m_appliedAuras.end();) + _UnapplyAura(aurAppIter, AuraRemoveFlags::ByDefault); + + AuraMap::iterator aurIter; + for (aurIter = m_ownedAuras.begin(); aurIter != m_ownedAuras.end();) + RemoveOwnedAura(aurIter); + + const int maxIteration = 50; + // give this loop a few tries, if there are still auras then log as much information as possible + if (counter >= maxIteration) + { + std::stringstream sstr; + sstr << "Unit::RemoveAllAuras() iterated " << maxIteration << " times already but there are still " + << m_appliedAuras.size() << " m_appliedAuras and " << m_ownedAuras.size() << " m_ownedAuras.\n"; + + TC_LOG_ERROR("entities.unit", "%s", sstr.str().c_str()); + ASSERT(false); + + break; + } + } +} + +void Unit::RemoveArenaAuras() +{ + // in join, remove positive buffs, on end, remove negative + // used to remove positive visible auras in arenas + RemoveAppliedAuras([](AuraApplication const* aurApp) + { + Aura const* aura = aurApp->GetBase(); + return (!aura->GetSpellInfo()->HasAttribute(SPELL_ATTR4_ALLOW_ENTERING_ARENA) // don't remove stances, shadowform, pally/hunter auras + && !aura->IsPassive() // don't remove passive auras + && (aurApp->IsPositive() || !aura->GetSpellInfo()->HasAttribute(SPELL_ATTR3_ALLOW_AURA_WHILE_DEAD))) || // not negative death persistent auras + aura->GetSpellInfo()->HasAttribute(SPELL_ATTR5_REMOVE_ENTERING_ARENA); // special marker, always remove + }); +} + +void Unit::RemoveAurasOnEvade() +{ + if (IsCharmedOwnedByPlayerOrPlayer()) // if it is a player owned creature it should not remove the aura + return; + + // don't remove vehicle auras, passengers aren't supposed to drop off the vehicle + // don't remove clone caster on evade (to be verified) + auto evadeAuraCheck = [](Aura const* aura) + { + if (aura->HasEffectType(SPELL_AURA_CONTROL_VEHICLE)) + return false; + + if (aura->HasEffectType(SPELL_AURA_CLONE_CASTER)) + return false; + + if (aura->GetSpellInfo()->HasAttribute(SPELL_ATTR1_AURA_STAYS_AFTER_COMBAT)) + return false; + + return true; + }; + + auto evadeAuraApplicationCheck = [&evadeAuraCheck](AuraApplication const* aurApp) + { + return evadeAuraCheck(aurApp->GetBase()); + }; + + RemoveAppliedAuras(evadeAuraApplicationCheck); + RemoveOwnedAuras(evadeAuraCheck); +} + +void Unit::RemoveAllAurasOnDeath() +{ + // used just after dieing to remove all visible auras + // and disable the mods for the passive ones + for (AuraApplicationMap::iterator iter = m_appliedAuras.begin(); iter != m_appliedAuras.end();) + { + Aura const* aura = iter->second->GetBase(); + if (!aura->IsPassive() && !aura->IsDeathPersistent()) + _UnapplyAura(iter, AuraRemoveFlags::ByDeath); + else + ++iter; + } + + for (AuraMap::iterator iter = m_ownedAuras.begin(); iter != m_ownedAuras.end();) + { + Aura* aura = iter->second; + if (!aura->IsPassive() && !aura->IsDeathPersistent()) + RemoveOwnedAura(iter, AuraRemoveFlags::ByDeath); + 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, AuraRemoveFlags::ByDefault); + 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, AuraRemoveFlags::ByDefault); + 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, AuraRemoveFlags::ByDefault); + } + + for (AuraMap::iterator iter = m_ownedAuras.begin(); iter != m_ownedAuras.end();) + { + Aura* aura = iter->second; + if (aura->GetSpellInfo()->HasAura(type)) + ++iter; + else + RemoveOwnedAura(iter, AuraRemoveFlags::ByDefault); + } +} + +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, AuraRemoveFlags::ByDefault); + } + + 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, AuraRemoveFlags::ByDefault); + } +} + +void Unit::RemoveAllGroupBuffsFromCaster(ObjectGuid casterGUID) +{ + for (AuraMap::iterator iter = m_ownedAuras.begin(); iter != m_ownedAuras.end();) + { + Aura* aura = iter->second; + if (aura->GetCasterGUID() == casterGUID && aura->GetSpellInfo()->IsGroupBuff()) + { + RemoveOwnedAura(iter); + continue; + } + ++iter; + } +} + +void Unit::DelayOwnedAuras(uint32 spellId, ObjectGuid caster, int32 delaytime) +{ + AuraMapBoundsNonConst range = m_ownedAuras.equal_range(spellId); + for (; range.first != range.second; ++range.first) + { + Aura* aura = range.first->second; + if (!caster || aura->GetCasterGUID() == caster) + { + if (aura->GetDuration() < delaytime) + aura->SetDuration(0); + else + aura->SetDuration(aura->GetDuration() - delaytime); + + // update for out of range group members (on 1 slot use) + aura->SetNeedClientUpdateForTargets(); + } + } +} + +void Unit::_RemoveAllAuraStatMods() +{ + for (AuraApplicationMap::iterator i = m_appliedAuras.begin(); i != m_appliedAuras.end(); ++i) + (*i).second->GetBase()->HandleAllEffects(i->second, AURA_EFFECT_HANDLE_STAT, false); +} + +void Unit::_ApplyAllAuraStatMods() +{ + for (AuraApplicationMap::iterator i = m_appliedAuras.begin(); i != m_appliedAuras.end(); ++i) + (*i).second->GetBase()->HandleAllEffects(i->second, AURA_EFFECT_HANDLE_STAT, true); +} + +bool Unit::HasSingleCastAuraOfSpell(uint32 spellId) const +{ + AuraList const& scAuras = GetSingleCastAuras(); + for (Aura const* aura : scAuras) + if (aura->GetSpellInfo()->Id == spellId) + return true; + + return false; +} + +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 == uint32(name) && !spell->SpellFamilyFlags) + 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::GetDummyAuraEffect(SpellFamilyNames name, uint32 iconId, uint8 effIndex) const +{ + return GetAuraEffect(SPELL_AURA_DUMMY, name, iconId, effIndex); +} + +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(WorldObject const* caster, uint32 dispelMask, DispelChargesList& dispelList, bool isReflect /*= false*/) const +{ + // 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); + + AuraMap const& auras = GetOwnedAuras(); + for (auto itr = auras.begin(); itr != auras.end(); ++itr) + { + Aura* aura = itr->second; + AuraApplication const* aurApp = aura->GetApplicationOfTarget(GetGUID()); + if (!aurApp) + continue; + + // don't try to remove passive auras + if (aura->IsPassive()) + continue; + + if (aura->GetSpellInfo()->GetDispelMask() & dispelMask) + { + // do not remove positive auras if friendly target + // negative auras if non-friendly + // unless we're reflecting (dispeller eliminates one of it's benefitial buffs) + if (isReflect != (aurApp->IsPositive() == IsFriendlyTo(caster))) + continue; + + // 2.4.3 Patch Notes: "Dispel effects will no longer attempt to remove effects that have 100% dispel resistance." + int32 chance = aura->CalcDispelChance(this, !IsFriendlyTo(caster)); + if (!chance) + continue; + + // The charges / stack amounts don't count towards the total number of auras that can be dispelled. + // Ie: A dispel on a target with 5 stacks of Winters Chill and a Polymorph has 1 / (1 + 1) -> 50% chance to dispell + // Polymorph instead of 1 / (5 + 1) -> 16%. + bool const dispelCharges = aura->GetSpellInfo()->HasAttribute(SPELL_ATTR7_DISPEL_REMOVES_CHARGES); + uint8 charges = dispelCharges ? aura->GetCharges() : aura->GetStackAmount(); + if (charges > 0) + dispelList.emplace_back(aura, chance, charges); + } + } +} + +bool Unit::HasAuraEffect(uint32 spellId, uint8 effIndex, ObjectGuid caster) const +{ + AuraApplicationMapBounds range = m_appliedAuras.equal_range(spellId); + for (AuraApplicationMap::const_iterator itr = range.first; itr != range.second; ++itr) + { + if (itr->second->HasEffect(effIndex) + && (!caster || itr->second->GetBase()->GetCasterGUID() == caster)) + { + return true; + } + } + return false; +} + +uint32 Unit::GetAuraCount(uint32 spellId) const +{ + uint32 count = 0; + AuraApplicationMapBounds range = m_appliedAuras.equal_range(spellId); + + for (AuraApplicationMap::const_iterator itr = range.first; itr != range.second; ++itr) + { + if (itr->second->GetBase()->GetStackAmount() == 0) + ++count; + else + count += (uint32)itr->second->GetBase()->GetStackAmount(); + } + + return count; +} + +bool Unit::HasAura(uint32 spellId, ObjectGuid casterGUID, ObjectGuid itemCasterGUID, 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 +{ + for (AuraEffect const* eff : GetAuraEffectsByType(auraType)) + if (caster == eff->GetCasterGUID()) + return true; + return false; +} + +bool Unit::HasAuraTypeWithMiscvalue(AuraType auraType, int32 miscvalue) const +{ + AuraEffectList const& mTotalAuraList = GetAuraEffectsByType(auraType); + for (AuraEffectList::const_iterator i = mTotalAuraList.begin(); i != mTotalAuraList.end(); ++i) + if (miscvalue == (*i)->GetMiscValue()) + return true; + return false; +} + +bool Unit::HasAuraTypeWithAffectMask(AuraType auraType, SpellInfo const* affectedSpell) const +{ + AuraEffectList const& mTotalAuraList = GetAuraEffectsByType(auraType); + for (AuraEffectList::const_iterator i = mTotalAuraList.begin(); i != mTotalAuraList.end(); ++i) + if ((*i)->IsAffectingSpell(affectedSpell)) + return true; + return false; +} + +bool Unit::HasAuraTypeWithValue(AuraType auraType, int32 value) const +{ + AuraEffectList const& mTotalAuraList = GetAuraEffectsByType(auraType); + for (AuraEffectList::const_iterator i = mTotalAuraList.begin(); i != mTotalAuraList.end(); ++i) + if (value == (*i)->GetAmount()) + return true; + return false; +} + +template +bool Unit::HasNegativeAuraWithInterruptFlag(InterruptFlags flag, ObjectGuid guid) const +{ + if (!HasInterruptFlag(flag)) + return false; + + for (AuraApplicationList::const_iterator iter = m_interruptableAuras.begin(); iter != m_interruptableAuras.end(); ++iter) + { + if (!(*iter)->IsPositive() && (*iter)->GetBase()->GetSpellInfo()->HasAuraInterruptFlag(flag) && (!guid || (*iter)->GetBase()->GetCasterGUID() == guid)) + return true; + } + return false; +} + +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; +} + +bool Unit::HasStrongerAuraWithDR(SpellInfo const* auraSpellInfo, Unit* caster, bool triggered) const +{ + DiminishingGroup diminishGroup = auraSpellInfo->GetDiminishingReturnsGroupForSpell(triggered); + DiminishingLevels level = GetDiminishing(diminishGroup); + for (auto itr = m_appliedAuras.begin(); itr != m_appliedAuras.end(); ++itr) + { + SpellInfo const* spellInfo = itr->second->GetBase()->GetSpellInfo(); + if (spellInfo->GetDiminishingReturnsGroupForSpell(triggered) != diminishGroup) + continue; + + int32 existingDuration = itr->second->GetBase()->GetDuration(); + int32 newDuration = auraSpellInfo->GetMaxDuration(); + ApplyDiminishingToDuration(auraSpellInfo, triggered, newDuration, caster, level); + if (newDuration > 0 && newDuration < existingDuration) + return true; + } + + return false; +} + +AuraEffect* Unit::IsScriptOverriden(SpellInfo const* spell, int32 script) const +{ + AuraEffectList const& auras = GetAuraEffectsByType(SPELL_AURA_OVERRIDE_CLASS_SCRIPTS); + for (AuraEffectList::const_iterator i = auras.begin(); i != auras.end(); ++i) + { + if ((*i)->GetMiscValue() == script) + if ((*i)->IsAffectingSpell(spell)) + return (*i); + } + return nullptr; +} + +uint32 Unit::GetDiseasesByCaster(ObjectGuid casterGUID, bool remove) +{ + static const AuraType diseaseAuraTypes[] = + { + SPELL_AURA_PERIODIC_DAMAGE, // Frost Fever and Blood Plague + SPELL_AURA_LINKED // Crypt Fever and Ebon Plague + }; + + // Burning Blood - Blood DK T12 Bonus + // "Causes owning Death Knight's abilities to treat the target as if it had 2 diseases active." + if (HasAura(98957, casterGUID)) + return 2; + + uint32 diseases = 0; + for (AuraType aType : diseaseAuraTypes) + { + for (auto itr = m_modAuras[aType].begin(); itr != m_modAuras[aType].end();) + { + // Get auras with disease dispel type by caster + if ((*itr)->GetSpellInfo()->Dispel == DISPEL_DISEASE + && (*itr)->GetCasterGUID() == casterGUID) + { + ++diseases; + + if (remove) + { + RemoveAura((*itr)->GetId(), (*itr)->GetCasterGUID()); + itr = m_modAuras[aType].begin(); + continue; + } + } + ++itr; + } + } + return diseases; +} + +uint32 Unit::GetDoTsByCaster(ObjectGuid casterGUID) const +{ + static const AuraType diseaseAuraTypes[] = + { + SPELL_AURA_PERIODIC_DAMAGE, + SPELL_AURA_PERIODIC_DAMAGE_PERCENT, + SPELL_AURA_NONE + }; + + uint32 dots = 0; + for (AuraType const* itr = &diseaseAuraTypes[0]; itr && itr[0] != SPELL_AURA_NONE; ++itr) + { + Unit::AuraEffectList const& auras = GetAuraEffectsByType(*itr); + for (AuraEffectList::const_iterator i = auras.begin(); i != auras.end(); ++i) + { + // Get auras by caster + if ((*i)->GetCasterGUID() == casterGUID) + ++dots; + } + } + return dots; +} + +int32 Unit::GetTotalAuraModifier(AuraType auraType, std::function const& predicate) const +{ + AuraEffectList const& mTotalAuraList = GetAuraEffectsByType(auraType); + if (mTotalAuraList.empty()) + return 0; + + std::map sameEffectSpellGroup; + int32 modifier = 0; + + for (AuraEffect const* aurEff : mTotalAuraList) + { + if (predicate(aurEff)) + { + // Check if the Aura Effect has a the Same Effect Stack Rule and if so, use the highest amount of that SpellGroup + // If the Aura Effect does not have this Stack Rule, it returns false so we can add to the multiplier as usual + if (!sSpellMgr->AddSameEffectStackRuleSpellGroups(aurEff->GetSpellInfo(), static_cast(auraType), aurEff->GetAmount(), sameEffectSpellGroup)) + modifier += aurEff->GetAmount(); + } + } + + // Add the highest of the Same Effect Stack Rule SpellGroups to the accumulator + for (auto itr = sameEffectSpellGroup.begin(); itr != sameEffectSpellGroup.end(); ++itr) + modifier += itr->second; + + return modifier; +} + +float Unit::GetTotalAuraMultiplier(AuraType auraType, std::function const& predicate) const +{ + AuraEffectList const& mTotalAuraList = GetAuraEffectsByType(auraType); + if (mTotalAuraList.empty()) + return 1.0f; + + std::map sameEffectSpellGroup; + float multiplier = 1.0f; + + for (AuraEffect const* aurEff : mTotalAuraList) + { + if (predicate(aurEff)) + { + // Check if the Aura Effect has a the Same Effect Stack Rule and if so, use the highest amount of that SpellGroup + // If the Aura Effect does not have this Stack Rule, it returns false so we can add to the multiplier as usual + if (!sSpellMgr->AddSameEffectStackRuleSpellGroups(aurEff->GetSpellInfo(), static_cast(auraType), aurEff->GetAmount(), sameEffectSpellGroup)) + AddPct(multiplier, aurEff->GetAmount()); + } + } + + // Add the highest of the Same Effect Stack Rule SpellGroups to the multiplier + for (auto itr = sameEffectSpellGroup.begin(); itr != sameEffectSpellGroup.end(); ++itr) + AddPct(multiplier, itr->second); + + return multiplier; +} + +int32 Unit::GetMaxPositiveAuraModifier(AuraType auraType, std::function const& predicate) const +{ + AuraEffectList const& mTotalAuraList = GetAuraEffectsByType(auraType); + if (mTotalAuraList.empty()) + return 0; + + int32 modifier = 0; + for (AuraEffect const* aurEff : mTotalAuraList) + { + if (predicate(aurEff)) + modifier = std::max(modifier, aurEff->GetAmount()); + } + + return modifier; +} + +int32 Unit::GetMaxNegativeAuraModifier(AuraType auraType, std::function const& predicate) const +{ + AuraEffectList const& mTotalAuraList = GetAuraEffectsByType(auraType); + if (mTotalAuraList.empty()) + return 0; + + int32 modifier = 0; + for (AuraEffect const* aurEff : mTotalAuraList) + { + if (predicate(aurEff)) + modifier = std::min(modifier, aurEff->GetAmount()); + } + + return modifier; +} + +int32 Unit::GetTotalAuraModifier(AuraType auraType) const +{ + return GetTotalAuraModifier(auraType, [](AuraEffect const* /*aurEff*/) { return true; }); +} + +float Unit::GetTotalAuraMultiplier(AuraType auraType) const +{ + return GetTotalAuraMultiplier(auraType, [](AuraEffect const* /*aurEff*/) { return true; }); +} + +int32 Unit::GetMaxPositiveAuraModifier(AuraType auraType) const +{ + return GetMaxPositiveAuraModifier(auraType, [](AuraEffect const* /*aurEff*/) { return true; }); +} + +int32 Unit::GetMaxNegativeAuraModifier(AuraType auraType) const +{ + return GetMaxNegativeAuraModifier(auraType, [](AuraEffect const* /*aurEff*/) { return true; }); +} + +int32 Unit::GetTotalAuraModifierByMiscMask(AuraType auraType, uint32 miscMask) const +{ + return GetTotalAuraModifier(auraType, [miscMask](AuraEffect const* aurEff) -> bool + { + if ((aurEff->GetMiscValue() & miscMask) != 0) + return true; + return false; + }); +} + +float Unit::GetTotalAuraMultiplierByMiscMask(AuraType auraType, uint32 miscMask) const +{ + return GetTotalAuraMultiplier(auraType, [miscMask](AuraEffect const* aurEff) -> bool + { + if ((aurEff->GetMiscValue() & miscMask) != 0) + return true; + return false; + }); +} + +int32 Unit::GetMaxPositiveAuraModifierByMiscMask(AuraType auraType, uint32 miscMask, AuraEffect const* except /*= nullptr*/) const +{ + return GetMaxPositiveAuraModifier(auraType, [miscMask, except](AuraEffect const* aurEff) -> bool + { + if (except != aurEff && (aurEff->GetMiscValue() & miscMask) != 0) + return true; + return false; + }); +} + +int32 Unit::GetMaxNegativeAuraModifierByMiscMask(AuraType auraType, uint32 miscMask) const +{ + return GetMaxNegativeAuraModifier(auraType, [miscMask](AuraEffect const* aurEff) -> bool + { + if ((aurEff->GetMiscValue() & miscMask) != 0) + return true; + return false; + }); +} + +int32 Unit::GetTotalAuraModifierByMiscValue(AuraType auraType, int32 miscValue) const +{ + return GetTotalAuraModifier(auraType, [miscValue](AuraEffect const* aurEff) -> bool + { + if (aurEff->GetMiscValue() == miscValue) + return true; + return false; + }); +} + +float Unit::GetTotalAuraMultiplierByMiscValue(AuraType auraType, int32 miscValue) const +{ + return GetTotalAuraMultiplier(auraType, [miscValue](AuraEffect const* aurEff) -> bool + { + if (aurEff->GetMiscValue() == miscValue) + return true; + return false; + }); +} + +int32 Unit::GetMaxPositiveAuraModifierByMiscValue(AuraType auraType, int32 miscValue) const +{ + return GetMaxPositiveAuraModifier(auraType, [miscValue](AuraEffect const* aurEff) -> bool + { + if (aurEff->GetMiscValue() == miscValue) + return true; + return false; + }); +} + +int32 Unit::GetMaxNegativeAuraModifierByMiscValue(AuraType auraType, int32 miscValue) const +{ + return GetMaxNegativeAuraModifier(auraType, [miscValue](AuraEffect const* aurEff) -> bool + { + if (aurEff->GetMiscValue() == miscValue) + return true; + return false; + }); +} + +int32 Unit::GetTotalAuraModifierByAffectMask(AuraType auraType, SpellInfo const* affectedSpell) const +{ + return GetTotalAuraModifier(auraType, [affectedSpell](AuraEffect const* aurEff) -> bool + { + if (aurEff->IsAffectingSpell(affectedSpell)) + return true; + return false; + }); +} + +float Unit::GetTotalAuraMultiplierByAffectMask(AuraType auraType, SpellInfo const* affectedSpell) const +{ + return GetTotalAuraMultiplier(auraType, [affectedSpell](AuraEffect const* aurEff) -> bool + { + if (aurEff->IsAffectingSpell(affectedSpell)) + return true; + return false; + }); +} + +int32 Unit::GetMaxPositiveAuraModifierByAffectMask(AuraType auraType, SpellInfo const* affectedSpell) const +{ + return GetMaxPositiveAuraModifier(auraType, [affectedSpell](AuraEffect const* aurEff) -> bool + { + if (aurEff->IsAffectingSpell(affectedSpell)) + return true; + return false; + }); +} + +int32 Unit::GetMaxNegativeAuraModifierByAffectMask(AuraType auraType, SpellInfo const* affectedSpell) const +{ + return GetMaxNegativeAuraModifier(auraType, [affectedSpell](AuraEffect const* aurEff) -> bool + { + if (aurEff->IsAffectingSpell(affectedSpell)) + return true; + return false; + }); +} + +void Unit::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::_RegisterDynObject(DynamicObject* dynObj) +{ + m_dynObj.push_back(dynObj); + if (GetTypeId() == TYPEID_UNIT && IsAIEnabled()) + ToCreature()->AI()->JustRegisteredDynObject(dynObj); +} + +void Unit::_UnregisterDynObject(DynamicObject* dynObj) +{ + m_dynObj.remove(dynObj); + if (GetTypeId() == TYPEID_UNIT && IsAIEnabled()) + ToCreature()->AI()->JustUnregisteredDynObject(dynObj); +} + +DynamicObject* Unit::GetDynObject(uint32 spellId) const +{ + std::vector dynamicobjects = GetDynObjects(spellId); + return dynamicobjects.empty() ? nullptr : dynamicobjects.front(); +} + +std::vector Unit::GetDynObjects(uint32 spellId) const +{ + std::vector dynamicobjects; + for (DynObjectList::const_iterator i = m_dynObj.begin(); i != m_dynObj.end(); ++i) + if ((*i)->GetSpellId() == spellId) + dynamicobjects.push_back(*i); + + return dynamicobjects; +} + +void Unit::RemoveDynObject(uint32 spellId) +{ + if (m_dynObj.empty()) + return; + for (DynObjectList::iterator i = m_dynObj.begin(); i != m_dynObj.end();) + { + DynamicObject* dynObj = *i; + if (dynObj->GetSpellId() == spellId) + { + dynObj->Remove(); + i = m_dynObj.begin(); + } + else + ++i; + } +} + +void Unit::RemoveAllDynObjects() +{ + while (!m_dynObj.empty()) + m_dynObj.front()->Remove(); +} + +GameObject* Unit::GetGameObject(uint32 spellId) const +{ + std::vector gameobjects = GetGameObjects(spellId); + return gameobjects.empty() ? nullptr : gameobjects.front(); +} + +std::vector Unit::GetGameObjects(uint32 spellId) const +{ + std::vector gameobjects; + for (GameObjectList::const_iterator i = m_gameObj.begin(); i != m_gameObj.end(); ++i) + if ((*i)->GetSpellId() == spellId) + gameobjects.push_back(*i); + + return gameobjects; +} + +void Unit::AddGameObject(GameObject* gameObj) +{ + if (!gameObj || gameObj->GetOwnerGUID()) + return; + + m_gameObj.push_back(gameObj); + gameObj->SetOwnerGUID(GetGUID()); + + if (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) + GetSpellHistory()->StartCooldown(createBySpell, 0, nullptr, true); + } + + if (GetTypeId() == TYPEID_UNIT && ToCreature()->IsAIEnabled()) + ToCreature()->AI()->JustSummonedGameobject(gameObj); +} + +void Unit::RemoveGameObject(GameObject* gameObj, bool del) +{ + if (!gameObj || gameObj->GetOwnerGUID() != GetGUID()) + return; + + gameObj->SetOwnerGUID(ObjectGuid::Empty); + + for (uint8 i = 0; i < MAX_GAMEOBJECT_SLOT; ++i) + { + if (m_ObjectSlot[i] == gameObj->GetGUID()) + { + m_ObjectSlot[i].Clear(); + break; + } + } + + // GO created by some spell + if (uint32 spellid = gameObj->GetSpellId()) + { + RemoveAurasDueToSpell(spellid); + + SpellInfo const* createBySpell = sSpellMgr->GetSpellInfo(spellid); + // Need activate spell use for owner + if (createBySpell && createBySpell->IsCooldownStartedOnEvent()) + // note: item based cooldowns and cooldown spell mods with charges ignored (unknown existing cases) + GetSpellHistory()->SendCooldownEvent(createBySpell); + } + + m_gameObj.remove(gameObj); + + if (GetTypeId() == TYPEID_UNIT && ToCreature()->IsAIEnabled()) + ToCreature()->AI()->SummonedGameobjectDespawn(gameObj); + + if (del) + { + gameObj->SetRespawnTime(0); + gameObj->Delete(); + } +} + +void Unit::RemoveGameObject(uint32 spellid, bool del) +{ + if (m_gameObj.empty()) + return; + GameObjectList::iterator i, next; + for (i = m_gameObj.begin(); i != m_gameObj.end(); i = next) + { + next = i; + if (spellid == 0 || (*i)->GetSpellId() == spellid) + { + (*i)->SetOwnerGUID(ObjectGuid::Empty); + if (del) + { + (*i)->SetRespawnTime(0); + (*i)->Delete(); + } + + next = m_gameObj.erase(i); + } + else + ++next; + } +} + +void Unit::RemoveAllGameObjects() +{ + // remove references to unit + while (!m_gameObj.empty()) + { + GameObjectList::iterator i = m_gameObj.begin(); + (*i)->SetOwnerGUID(ObjectGuid::Empty); + (*i)->SetRespawnTime(0); + (*i)->Delete(); + m_gameObj.erase(i); + } +} + +void Unit::SendSpellNonMeleeDamageLog(SpellNonMeleeDamage* log) +{ + WorldPacket data(SMSG_SPELLNONMELEEDAMAGELOG, (16+4+4+4+1+4+4+1+1+4+4+1)); // we guess size + data << log->target->GetPackGUID(); + data << log->attacker->GetPackGUID(); + data << uint32(log->SpellID); + data << uint32(log->damage); // damage amount + int32 overkill = log->damage - log->target->GetHealth(); + data << uint32(overkill > 0 ? overkill : 0); // overkill + data << uint8 (log->schoolMask); // damage school + data << uint32(log->absorb); // AbsorbedDamage + data << uint32(log->resist); // resist + data << uint8 (log->periodicLog); // 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(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, uint32 spellID, uint32 damage, SpellSchoolMask damageSchoolMask, uint32 absorbedDamage, uint32 resist, bool isPeriodic, uint32 blocked, bool criticalHit, bool split) +{ + SpellNonMeleeDamage log(this, target, spellID, damageSchoolMask); + log.damage = damage - absorbedDamage - resist - blocked; + log.absorb = absorbedDamage; + log.resist = resist; + log.periodicLog = isPeriodic; + log.blocked = blocked; + log.HitInfo = 0; + if (criticalHit) + log.HitInfo |= SPELL_HIT_TYPE_CRIT; + if (split) + log.HitInfo |= SPELL_HIT_TYPE_SPLIT; + SendSpellNonMeleeDamageLog(&log); +} + +/*static*/ void Unit::ProcSkillsAndAuras(Unit* actor, Unit* actionTarget, ProcFlags typeMaskActor, ProcFlags typeMaskActionTarget, + ProcFlagsSpellType spellTypeMask, ProcFlagsSpellPhase spellPhaseMask, ProcFlagsHit hitMask, Spell* spell, DamageInfo* damageInfo, HealInfo* healInfo) +{ + static constexpr int32 ProcChainHardLimit = 10; + if (spell && spell->GetProcChainLength() >= ProcChainHardLimit) + { + TC_LOG_ERROR("spells.aura.effect", "Unit::ProcSkillsAndAuras: Possible infinite proc loop detected, current triggering spell %u", spell->GetSpellInfo()->Id); + return; + } + + WeaponAttackType attType = damageInfo ? damageInfo->GetAttackType() : BASE_ATTACK; + SpellInfo const* spellInfo = [&]() -> SpellInfo const* + { + if (spell) + return spell->GetSpellInfo(); + if (damageInfo) + return damageInfo->GetSpellInfo(); + if (healInfo) + return healInfo->GetSpellInfo(); + return nullptr; + }(); + + if (typeMaskActor && actor && !(spellInfo && spellInfo->HasAttribute(SPELL_ATTR3_SUPPRESS_CASTER_PROCS))) + actor->ProcSkillsAndReactives(false, actionTarget, typeMaskActor, hitMask, attType); + + if (typeMaskActionTarget && actionTarget && !(spellInfo && spellInfo->HasAttribute(SPELL_ATTR3_SUPPRESS_TARGET_PROCS))) + actionTarget->ProcSkillsAndReactives(true, actor, typeMaskActionTarget, hitMask, attType); + + if (actor) + actor->TriggerAurasProcOnEvent(actionTarget, typeMaskActor, typeMaskActionTarget, spellTypeMask, spellPhaseMask, hitMask, spell, damageInfo, healInfo); +} + +void Unit::SendPeriodicAuraLog(SpellPeriodicAuraLogInfo* pInfo) +{ + AuraEffect const* aura = pInfo->auraEff; + + WorldPackets::CombatLog::SpellPeriodicAuraLog periodicAuraLog; + periodicAuraLog.TargetGUID = GetGUID(); + periodicAuraLog.CasterGUID = aura->GetCasterGUID(); + periodicAuraLog.SpellID = aura->GetId(); + + WorldPackets::CombatLog::PeriodicAuraLogEffect& logEffect = periodicAuraLog.Entries.emplace_back(); + logEffect.Effect = aura->GetAuraType(); + logEffect.Amount = pInfo->damage; + logEffect.OverHealOrKill = pInfo->overDamage; + logEffect.Resisted = pInfo->resist; + logEffect.Crit = pInfo->critical; + + switch (logEffect.Effect) + { + case SPELL_AURA_PERIODIC_DAMAGE: + case SPELL_AURA_PERIODIC_DAMAGE_PERCENT: + logEffect.AbsorbedOrAmplitude = pInfo->absorb; + logEffect.SchoolMaskOrPower = aura->GetSpellInfo()->GetSchoolMask(); + break; + case SPELL_AURA_PERIODIC_HEAL: + case SPELL_AURA_OBS_MOD_HEALTH: + logEffect.AbsorbedOrAmplitude = pInfo->absorb; + break; + case SPELL_AURA_OBS_MOD_POWER: + case SPELL_AURA_PERIODIC_ENERGIZE: + logEffect.SchoolMaskOrPower = aura->GetMiscValue(); + break; + case SPELL_AURA_PERIODIC_MANA_LEECH: + logEffect.SchoolMaskOrPower = aura->GetMiscValue(); + logEffect.AbsorbedOrAmplitude = pInfo->multiplier; + break; + default: + TC_LOG_ERROR("entities.unit", "Unit::SendPeriodicAuraLog: unknown aura %u", uint32(aura->GetAuraType())); + return; + } + + SendMessageToSet(periodicAuraLog.Write(), true); +} + +void Unit::SendSpellDamageResist(Unit* target, uint32 spellId) +{ + WorldPacket data(SMSG_PROCRESIST, 8+8+4+1); + data << uint64(GetGUID()); + data << uint64(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 << uint64(GetGUID()); + data << uint64(target->GetGUID()); + data << uint32(spellId); + data << uint8(0); // bool - log format: 0-default, 1-debug + SendMessageToSet(&data, true); +} + +void Unit::SendAttackStateUpdate(CalcDamageInfo* damageInfo) +{ + WorldPackets::CombatLog::AttackerStateUpdate packet; + packet.HitInfo = damageInfo->HitInfo; + packet.AttackerGUID = damageInfo->Attacker->GetGUID(); + packet.VictimGUID = damageInfo->Target->GetGUID(); + packet.Damage = damageInfo->Damage; + int32 overkill = damageInfo->Damage - damageInfo->Target->GetHealth(); + packet.OverDamage = (overkill < 0 ? -1 : overkill); + + if (damageInfo->Damage) + { + packet.SubDmg.emplace(); + packet.SubDmg->SchoolMask = damageInfo->DamageSchoolMask; // School of sub damage + packet.SubDmg->FDamage = damageInfo->Damage; // sub damage + packet.SubDmg->Damage = damageInfo->Damage; // Sub Damage + packet.SubDmg->Absorbed = damageInfo->Absorb; + packet.SubDmg->Resisted = damageInfo->Resist; + } + + packet.VictimState = damageInfo->TargetState; + packet.BlockAmount = damageInfo->Blocked; + packet.RageGained = damageInfo->RageGained; + + SendMessageToSet(packet.Write(), 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.Damage = Damage - AbsorbDamage - Resist - BlockedAmount; + dmgInfo.DamageSchoolMask = damageSchoolMask; + dmgInfo.Absorb = AbsorbDamage; + dmgInfo.Resist = Resist; + dmgInfo.TargetState = TargetState; + dmgInfo.Blocked = BlockedAmount; + dmgInfo.RageGained = 0; + SendAttackStateUpdate(&dmgInfo); +} + +void Unit::SetPowerType(PowerType power) +{ + if (GetPowerType() == power) + return; + + SetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_POWER_TYPE, power); + + // Creatures can swap out their power type at index 0 so we do keep track of the new power type here + if (IsCreature()) + { + _usedPowerTypes[GetPowerIndex(power)] = power; + UpdatePowerRegeneration(power); + } + + if (GetTypeId() == TYPEID_PLAYER) + { + 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->GetTypeId() == TYPEID_PLAYER) && owner->ToPlayer()->GetGroup()) + owner->ToPlayer()->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_PET_POWER_TYPE); + } + } + + // Update max power + UpdateMaxPower(power); + + // Update current power + switch (power) + { + case POWER_MANA: // Keep the same (druid form switching...) + case POWER_ENERGY: + break; + case POWER_RAGE: // Reset to zero + SetPower(POWER_RAGE, 0); + break; + case POWER_FOCUS: // Make it full + SetFullPower(power); + break; + default: + break; + } +} + +PowerType Unit::CalculateDisplayPowerType() const +{ + PowerType displayPower = POWER_MANA; + switch (GetShapeshiftForm()) + { + case FORM_GHOUL: + case FORM_CAT: + displayPower = POWER_ENERGY; + break; + case FORM_BEAR: + displayPower = POWER_RAGE; + break; + case FORM_TRAVEL: + case FORM_GHOSTWOLF: + displayPower = POWER_MANA; + break; + default: + { + ChrClassesEntry const* cEntry = sChrClassesStore.LookupEntry(getClass()); + if (cEntry && cEntry->DisplayPower < MAX_POWERS) + displayPower = PowerType(cEntry->DisplayPower); + + if (Vehicle* vehicle = GetVehicleKit()) + { + if (PowerDisplayEntry const* powerDisplay = sPowerDisplayStore.LookupEntry(vehicle->GetVehicleInfo()->PowerDisplayID[0])) // To-do: 4.x has 3 power display id fields. + displayPower = PowerType(powerDisplay->ActualType); + else if (getClass() == CLASS_ROGUE) + displayPower = POWER_ENERGY; + } + else if (Pet const* pet = ToPet()) + { + if (pet->getPetType() == HUNTER_PET) // Hunter pets have focus + displayPower = POWER_FOCUS; + else if (pet->IsPetGhoul() || pet->IsRisenAlly()) // DK pets have energy + displayPower = POWER_ENERGY; + } + break; + } + } + + return displayPower; +} + +void Unit::UpdateDisplayPower() +{ + SetPowerType(CalculateDisplayPowerType()); +} + +void Unit::SetSheath(SheathState sheathed) +{ + SetByteValue(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_SHEATH_STATE, sheathed); + if (sheathed == SHEATH_STATE_UNARMED) + RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Sheathing); +} + +void Unit::_addAttacker(Unit* pAttacker) +{ + m_attackers.insert(pAttacker); +} + +void Unit::_removeAttacker(Unit* pAttacker) +{ + m_attackers.erase(pAttacker); +} + +Unit* Unit::getAttackerForHelper() const // If someone wants to help, who to give them +{ + if (!IsEngaged()) + return nullptr; + + if (Unit* victim = GetVictim()) + if ((!IsPet() && !IsCharmerOrSelfPlayer()) || IsInCombatWith(victim)) + return victim; + + CombatManager const& mgr = GetCombatManager(); + // pick arbitrary targets; our pvp combat > owner's pvp combat > our pve combat > owner's pve combat + Unit* owner = GetCharmerOrOwner(); + if (mgr.HasPvPCombat()) + return mgr.GetPvPCombatRefs().begin()->second->GetOther(this); + if (owner && (owner->GetCombatManager().HasPvPCombat())) + return owner->GetCombatManager().GetPvPCombatRefs().begin()->second->GetOther(owner); + if (mgr.HasPvECombat()) + return mgr.GetPvECombatRefs().begin()->second->GetOther(this); + if (owner && (owner->GetCombatManager().HasPvECombat())) + return owner->GetCombatManager().GetPvECombatRefs().begin()->second->GetOther(owner); + return nullptr; +} + +bool Unit::Attack(Unit* victim, bool meleeAttack) +{ + if (!victim || victim == this) + return false; + + // dead units can neither attack nor be attacked + if (!IsAlive() || !victim->IsInWorld() || !victim->IsAlive()) + return false; + + // player cannot attack in mount state + if (GetTypeId() == TYPEID_PLAYER && IsMounted()) + return false; + + Creature* creature = ToCreature(); + if (creature) + { + // creatures cannot attack while evading + if (creature->IsInEvadeMode()) + return false; + + if (!creature->CanMelee()) + meleeAttack = false; + } + + if (HasAuraType(SPELL_AURA_DISABLE_ATTACKING_EXCEPT_ABILITIES)) + return false; + + // nobody can attack GM in GM-mode + if (victim->GetTypeId() == TYPEID_PLAYER) + { + if (victim->ToPlayer()->IsGameMaster()) + return false; + } + else + { + if (victim->ToCreature()->IsEvadingAttacks()) + return false; + } + + if (m_attacking) + { + if (m_attacking == victim) + { + // switch to melee attack from ranged/magic + if (meleeAttack) + { + if (!HasUnitState(UNIT_STATE_MELEE_ATTACKING)) + { + AddUnitState(UNIT_STATE_MELEE_ATTACKING); + SendMeleeAttackStart(victim); + return true; + } + } + else if (HasUnitState(UNIT_STATE_MELEE_ATTACKING)) + { + ClearUnitState(UNIT_STATE_MELEE_ATTACKING); + SendMeleeAttackStop(victim); + return true; + } + return false; + } + + // switch target + InterruptSpell(CURRENT_MELEE_SPELL); + if (!meleeAttack) + ClearUnitState(UNIT_STATE_MELEE_ATTACKING); + } + + if (m_attacking) + m_attacking->_removeAttacker(this); + + m_attacking = victim; + m_attacking->_addAttacker(this); + + // Set our target + SetTarget(victim->GetGUID()); + + if (meleeAttack) + AddUnitState(UNIT_STATE_MELEE_ATTACKING); + + // set position before any AI calls/assistance + //if (GetTypeId() == TYPEID_UNIT) + // ToCreature()->SetCombatStartPosition(GetPositionX(), GetPositionY(), GetPositionZ()); + + if (creature && !IsControlledByPlayer()) + { + EngageWithTarget(victim); // ensure that anything we're attacking has threat + creature->CallAssistance(); + + // Remove emote state - will be restored on creature reset + SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_ONESHOT_NONE); + } + + // delay offhand weapon attack by 50% of the base attack time + if (haveOffhandWeapon() && GetTypeId() != TYPEID_PLAYER) + setAttackTimer(OFF_ATTACK, std::max(getAttackTimer(OFF_ATTACK), getAttackTimer(BASE_ATTACK) + uint32(CalculatePct(GetFloatValue(UNIT_FIELD_BASEATTACKTIME), 50)))); + + + if (meleeAttack) + SendMeleeAttackStart(victim); + + // Let the pet know we've started attacking someting. Handles melee attacks only + // Spells such as auto-shot and others handled in WorldSession::HandleCastSpellOpcode + if (GetTypeId() == TYPEID_PLAYER) + { + for (Unit* controlled : m_Controlled) + if (Creature* cControlled = controlled->ToCreature()) + if (CreatureAI* controlledAI = cControlled->AI()) + controlledAI->OwnerAttacked(victim); + } + + return true; +} + +bool Unit::AttackStop() +{ + if (!m_attacking) + return false; + + Unit* victim = m_attacking; + + m_attacking->_removeAttacker(this); + m_attacking = nullptr; + + // Clear our target + SetTarget(ObjectGuid::Empty); + + ClearUnitState(UNIT_STATE_MELEE_ATTACKING); + + InterruptSpell(CURRENT_MELEE_SPELL); + + // reset only at real combat stop + if (Creature* creature = ToCreature()) + creature->SetNoCallAssistance(false); + + SendMeleeAttackStop(victim); + + return true; +} + +void Unit::ValidateAttackersAndOwnTarget() +{ + // iterate attackers + UnitVector toRemove; + AttackerSet const& attackers = getAttackers(); + for (Unit* attacker : attackers) + if (!attacker->IsValidAttackTarget(this)) + toRemove.push_back(attacker); + + for (Unit* attacker : toRemove) + attacker->AttackStop(); + + // remove our own victim + if (Unit* victim = GetVictim()) + if (!IsValidAttackTarget(victim)) + AttackStop(); +} + +void Unit::CombatStop(bool includingCast, bool mutualPvP, bool (*unitFilter)(Unit const* otherUnit)) +{ + if (includingCast && IsNonMeleeSpellCast(false)) + InterruptNonMeleeSpells(false); + + AttackStop(); + + if (!unitFilter) + RemoveAllAttackers(); + else + { + std::vector attackersToRemove; + attackersToRemove.reserve(m_attackers.size()); + std::copy_if(m_attackers.begin(), m_attackers.end(), std::back_inserter(attackersToRemove), unitFilter); + + for (Unit* attacker : attackersToRemove) + attacker->AttackStop(); + } + if (GetTypeId() == TYPEID_PLAYER) + ToPlayer()->SendAttackSwingCancelAttack(); // melee and ranged forced attack cancel + + m_combatManager.EndAllPvECombat(unitFilter); + if (mutualPvP) + m_combatManager.EndAllPvPCombat(unitFilter); + else // vanish and brethren are weird + m_combatManager.SuppressPvPCombat(unitFilter); +} + +void Unit::CombatStopWithPets(bool includingCast) +{ + CombatStop(includingCast); + + for (Unit* minion : m_Controlled) + minion->CombatStop(includingCast); +} + +bool Unit::isAttackingPlayer() const +{ + if (HasUnitState(UNIT_STATE_ATTACK_PLAYER)) + return true; + + for (ControlList::const_iterator itr = m_Controlled.begin(); itr != m_Controlled.end(); ++itr) + if ((*itr)->isAttackingPlayer()) + return true; + + for (uint8 i = 0; i < MAX_SUMMON_SLOT; ++i) + if (m_SummonSlot[i]) + if (Creature* summon = GetMap()->GetCreature(m_SummonSlot[i])) + if (summon->isAttackingPlayer()) + return true; + + return false; +} + +void Unit::RemoveAllAttackers() +{ + while (!m_attackers.empty()) + { + AttackerSet::iterator iter = m_attackers.begin(); + if (!(*iter)->AttackStop()) + { + TC_LOG_ERROR("entities.unit", "WORLD: Unit has an attacker that isn't attacking it!"); + m_attackers.erase(iter); + } + } +} + +void Unit::ModifyAuraState(AuraStateType flag, bool apply) +{ + if (apply) + { + if (!HasFlag(UNIT_FIELD_AURASTATE, 1<<(flag-1))) + { + SetFlag(UNIT_FIELD_AURASTATE, 1<<(flag-1)); + if (GetTypeId() == TYPEID_PLAYER) + { + PlayerSpellMap const& sp_list = ToPlayer()->GetSpellMap(); + for (PlayerSpellMap::const_iterator itr = sp_list.begin(); itr != sp_list.end(); ++itr) + { + if (itr->second.state == PLAYERSPELL_REMOVED || itr->second.disabled) + continue; + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(itr->first); + if (!spellInfo || !spellInfo->IsPassive()) + continue; + if (spellInfo->CasterAuraState == uint32(flag)) + CastSpell(this, itr->first, true); + } + } + else if (Pet* pet = ToCreature()->ToPet()) + { + for (PetSpellMap::const_iterator itr = pet->m_spells.begin(); itr != pet->m_spells.end(); ++itr) + { + if (itr->second.state == PETSPELL_REMOVED) + continue; + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(itr->first); + if (!spellInfo || !spellInfo->IsPassive()) + continue; + if (spellInfo->CasterAuraState == uint32(flag)) + CastSpell(this, itr->first, true); + } + } + } + } + else + { + if (HasFlag(UNIT_FIELD_AURASTATE, 1<<(flag-1))) + { + RemoveFlag(UNIT_FIELD_AURASTATE, 1<<(flag-1)); + + Unit::AuraApplicationMap& tAuras = GetAppliedAuras(); + for (Unit::AuraApplicationMap::iterator itr = tAuras.begin(); itr != tAuras.end();) + { + SpellInfo const* spellProto = itr->second->GetBase()->GetSpellInfo(); + if (itr->second->GetBase()->GetCasterGUID() == GetGUID() && spellProto->CasterAuraState == uint32(flag) && (spellProto->IsPassive() || flag != AURA_STATE_ENRAGE)) + RemoveAura(itr); + else + ++itr; + } + } + } +} + +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 (AuraEffect const* aurEff : stateAuras) + if (aurEff->IsAffectingSpell(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; + + // 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(GetMapId()); + WorldPacket packet; + BuildValuesUpdateBlockForPlayer(&udata, player); + udata.BuildPacket(&packet); + player->SendDirectMessage(&packet); + + RemoveFieldNotifyFlag(UF_FLAG_OWNER); +} + +Player* Unit::GetControllingPlayer() const +{ + if (ObjectGuid guid = GetCharmerOrOwnerGUID()) + { + if (Unit* master = ObjectAccessor::GetUnit(*this, guid)) + return master->GetControllingPlayer(); + return nullptr; + } + else + return const_cast(ToPlayer()); +} + +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; + + TC_LOG_ERROR("entities.unit", "Unit::GetFirstMinion: Minion %s not exist.", pet_guid.ToString().c_str()); + 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; + + TC_LOG_FATAL("entities.unit", "Unit::GetGuardianPet: Guardian %s not exist.", pet_guid.ToString().c_str()); + const_cast(this)->SetPetGUID(ObjectGuid::Empty); + } + + return nullptr; +} + +void Unit::SetMinion(Minion* minion, bool apply) +{ + if (!minion) + { + TC_LOG_ERROR("entities.unit", "Unit::SetMinion: Unit %s tried to reference a non existing minion", GetGUID().ToString().c_str()); + return; + } + + TC_LOG_DEBUG("entities.unit", "SetMinion %u for %u, apply %u", minion->GetEntry(), GetEntry(), apply); + + if (apply) + { + if (minion->GetOwnerGUID()) + { + TC_LOG_FATAL("entities.unit", "SetMinion: Minion %u is not the minion of owner %u", minion->GetEntry(), GetEntry()); + return; + } + + minion->SetOwnerGUID(GetGUID()); + + m_Controlled.insert(minion); + + if (GetTypeId() == TYPEID_PLAYER) + { + minion->m_ControlledByPlayer = true; + minion->SetFlag(UNIT_FIELD_FLAGS, 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 (oldPet->IsPet()) + ((Pet*)oldPet)->Remove(PET_SAVE_DISMISS); + else + oldPet->UnSummon(); + SetPetGUID(minion->GetGUID()); + SetMinionGUID(ObjectGuid::Empty); + } + } + else + { + SetPetGUID(minion->GetGUID()); + SetMinionGUID(ObjectGuid::Empty); + } + } + + if (minion->HasUnitTypeMask(UNIT_MASK_CONTROLABLE_GUARDIAN)) + AddGuidValue(UNIT_FIELD_SUMMON, minion->GetGUID()); + + if (minion->m_Properties && SummonTitle(minion->m_Properties->Title) == SummonTitle::Companion) + SetCritterGUID(minion->GetGUID()); + + // PvP, FFAPvP + minion->SetByteValue(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PVP_FLAG, GetByteValue(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PVP_FLAG)); + + // 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()) + GetSpellHistory()->StartCooldown(spellInfo, 0, nullptr, true); + } + else + { + if (minion->GetOwnerGUID() != GetGUID()) + { + TC_LOG_FATAL("entities.unit", "SetMinion: Minion %u is not the minion of owner %u", minion->GetEntry(), GetEntry()); + return; + } + + if (m_Controlled.find(minion) != m_Controlled.end()) + m_Controlled.erase(minion); + + if (minion->m_Properties && SummonTitle(minion->m_Properties->Title) == SummonTitle::Companion) + if (GetCritterGUID() == minion->GetGUID()) + SetCritterGUID(ObjectGuid::Empty); + + if (minion->IsGuardianPet()) + { + if (GetPetGUID() == minion->GetGUID()) + SetPetGUID(ObjectGuid::Empty); + } + + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(minion->GetUInt32Value(UNIT_CREATED_BY_SPELL)); + // Remove infinity cooldown + if (spellInfo && (spellInfo->IsCooldownStartedOnEvent())) + GetSpellHistory()->SendCooldownEvent(spellInfo); + + //if (minion->HasUnitTypeMask(UNIT_MASK_GUARDIAN)) + { + if (RemoveGuidValue(UNIT_FIELD_SUMMON, minion->GetGUID())) + { + // Check if there is another minion + for (Unit const* controlled : m_Controlled) + { + // do not use this check, creature do not have charm guid + //if (GetCharmedGUID() == (*itr)->GetGUID()) + if (GetGUID() == controlled->GetCharmerGUID()) + continue; + + //ASSERT((*itr)->GetOwnerGUID() == GetGUID()); + if (controlled->GetOwnerOrCreatorGUID() != GetGUID()) + { + OutDebugInfo(); + controlled->OutDebugInfo(); + ABORT(); + } + ASSERT(controlled->IsCreature()); + + if (!controlled->HasUnitTypeMask(UNIT_MASK_CONTROLABLE_GUARDIAN)) + continue; + + if (AddGuidValue(UNIT_FIELD_SUMMON, controlled->GetGUID())) + { + // show another pet bar if there is no charm bar + if (IsPlayer() && !GetCharmedGUID()) + { + if (controlled->IsPet()) + ToPlayer()->PetSpellInitialize(); + else + ToPlayer()->CharmSpellInitialize(); + } + } + break; + } + } + } + } + UpdatePetCombatState(); +} + +void Unit::GetAllMinionsByEntry(std::list& Minions, uint32 entry) +{ + for (Unit::ControlList::iterator itr = m_Controlled.begin(); itr != m_Controlled.end();) + { + Unit* unit = *itr; + ++itr; + if (unit->GetEntry() == entry && unit->GetTypeId() == TYPEID_UNIT + && unit->IsSummon()) // minion, actually + Minions.push_back(unit->ToCreature()); + } +} + +void Unit::RemoveAllMinionsByEntry(uint32 entry) +{ + for (Unit::ControlList::iterator itr = m_Controlled.begin(); itr != m_Controlled.end();) + { + Unit* unit = *itr; + ++itr; + if (unit->GetEntry() == entry && unit->GetTypeId() == TYPEID_UNIT + && unit->IsSummon()) // minion, actually + unit->ToTempSummon()->UnSummon(); + // i think this is safe because i have never heard that a despawned minion will trigger a same minion + } +} + +void Unit::SetCharm(Unit* charm, bool apply) +{ + if (apply) + { + if (GetTypeId() == TYPEID_PLAYER) + { + ASSERT(AddGuidValue(UNIT_FIELD_CHARM, charm->GetGUID()), + "Player %s is trying to charm unit %u, but it already has a charmed unit %s", GetName().c_str(), charm->GetEntry(), GetCharmedGUID().ToString().c_str()); + m_charmed = charm; + + charm->m_ControlledByPlayer = true; + /// @todo maybe we can use this flag to check if controlled by player + charm->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED); + } + else + charm->m_ControlledByPlayer = false; + + // PvP, FFAPvP + charm->SetByteValue(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PVP_FLAG, GetByteValue(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PVP_FLAG)); + + ASSERT(charm->AddGuidValue(UNIT_FIELD_CHARMEDBY, GetGUID()), + "Unit %u is being charmed, but it already has a charmer %s", charm->GetEntry(), charm->GetCharmerGUID().ToString().c_str()); + charm->m_charmer = this; + + _isWalkingBeforeCharm = charm->IsWalking(); + if (_isWalkingBeforeCharm) + charm->SetWalk(false); + + m_Controlled.insert(charm); + } + else + { + charm->ClearUnitState(UNIT_STATE_CHARMED); + + if (GetTypeId() == TYPEID_PLAYER) + { + ASSERT(RemoveGuidValue(UNIT_FIELD_CHARM, charm->GetGUID()), + "Player %s is trying to uncharm unit %u, but it has another charmed unit %s", GetName().c_str(), charm->GetEntry(), GetCharmedGUID().ToString().c_str()); + m_charmed = nullptr; + } + + ASSERT(charm->RemoveGuidValue(UNIT_FIELD_CHARMEDBY, GetGUID()), + "Unit %u is being uncharmed, but it has another charmer %s", charm->GetEntry(), charm->GetCharmerGUID().ToString().c_str()); + charm->m_charmer = nullptr; + + if (charm->GetTypeId() == TYPEID_PLAYER) + { + charm->m_ControlledByPlayer = true; + charm->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED); + charm->ToPlayer()->UpdatePvPState(); + } + else if (Player* player = charm->GetCharmerOrOwnerPlayerOrPlayerItself()) + { + charm->m_ControlledByPlayer = true; + charm->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED); + charm->SetByteValue(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PVP_FLAG, player->GetByteValue(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PVP_FLAG)); + } + else + { + charm->m_ControlledByPlayer = false; + charm->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED); + charm->SetByteValue(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PVP_FLAG, 0); + } + + if (charm->IsWalking() != _isWalkingBeforeCharm) + charm->SetWalk(_isWalkingBeforeCharm); + + if (charm->GetTypeId() == TYPEID_PLAYER + || !charm->ToCreature()->HasUnitTypeMask(UNIT_MASK_MINION) + || charm->GetOwnerOrCreatorGUID() != GetGUID()) + { + m_Controlled.erase(charm); + } + } + UpdatePetCombatState(); +} + +/*static*/ void Unit::DealHeal(HealInfo& healInfo) +{ + int32 gain = 0; + Unit* healer = healInfo.GetHealer(); + Unit* victim = healInfo.GetTarget(); + uint32 addhealth = healInfo.GetHeal(); + + if (healer) + { + if (UnitAI* victimAI = victim->GetAI()) + victimAI->HealReceived(healer, addhealth); + + if (UnitAI* healerAI = healer->GetAI()) + healerAI->HealDone(victim, addhealth); + } + + if (addhealth) + gain = victim->ModifyHealth(int32(addhealth)); + + // Hook for OnHeal Event + sScriptMgr->OnHeal(healer, victim, (uint32&)gain); + + Unit* unit = healer; + if (healer && healer->GetTypeId() == TYPEID_UNIT && healer->IsTotem()) + unit = healer->GetOwner(); + + if (unit) + { + if (Player* player = unit->ToPlayer()) + { + if (!healInfo.GetSpellInfo() || !healInfo.GetSpellInfo()->HasAttribute(SPELL_ATTR7_DO_NOT_COUNT_FOR_PVP_SCOREBOARD)) + if (Battleground* bg = player->GetBattleground()) + bg->UpdatePlayerScore(player, SCORE_HEALING_DONE, gain); + + // use the actual gain, as the overheal shall not be counted, skip gain 0 (it ignored anyway in to criteria) + if (gain) + player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HEALING_DONE, gain, 0, 0, victim); + + player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HIGHEST_HEAL_CAST, addhealth); + } + } + + if (Player* player = victim->ToPlayer()) + { + player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_TOTAL_HEALING_RECEIVED, gain); + player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HIGHEST_HEALING_RECEIVED, addhealth); + } + + if (gain) + healInfo.SetEffectiveHeal(gain > 0 ? static_cast(gain) : 0UL); +} + +bool Unit::IsMagnet() const +{ + // Grounding Totem + if (GetUInt32Value(UNIT_CREATED_BY_SPELL) == 8177) /// @todo: find a more generic solution + return true; + + return false; +} + +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(AuraRemoveFlags::Expired); + return magnet; + } + } + return victim; +} + +Unit* Unit::GetFirstControlled() const +{ + // Sequence: charmed, pet, other guardians + Unit* unit = GetCharmed(); + if (!unit) + if (ObjectGuid guid = GetMinionGUID()) + unit = ObjectAccessor::GetUnit(*this, guid); + + return unit; +} + +void Unit::RemoveAllControlled() +{ + // possessed pet and vehicle + if (GetTypeId() == TYPEID_PLAYER) + ToPlayer()->StopCastingCharm(); + + while (!m_Controlled.empty()) + { + Unit* target = *m_Controlled.begin(); + m_Controlled.erase(m_Controlled.begin()); + if (target->GetCharmerGUID() == GetGUID()) + target->RemoveCharmAuras(); + else if (target->GetOwnerOrCreatorGUID() == GetGUID() && target->IsSummon()) + target->ToTempSummon()->UnSummon(); + else + TC_LOG_ERROR("entities.unit", "Unit %u is trying to release unit %u which is neither charmed nor owned by it", GetEntry(), target->GetEntry()); + } + if (GetPetGUID()) + TC_LOG_FATAL("entities.unit", "Unit %u is not able to release its pet %s", GetEntry(), GetPetGUID().ToString().c_str()); + if (GetMinionGUID()) + TC_LOG_FATAL("entities.unit", "Unit %u is not able to release its minion %s", GetEntry(), GetMinionGUID().ToString().c_str()); + if (GetCharmedGUID()) + TC_LOG_FATAL("entities.unit", "Unit %u is not able to release its charm %s", GetEntry(), GetCharmedGUID().ToString().c_str()); + if (!IsPet()) // pets don't use the flag for this + RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PET_IN_COMBAT); // m_controlled is now empty, so we know none of our minions are in combat +} + +bool Unit::isPossessedByPlayer() const +{ + return HasUnitState(UNIT_STATE_POSSESSED) && GetCharmerGUID().IsPlayer(); +} + +bool Unit::isPossessing(Unit* u) const +{ + return u->isPossessed() && GetCharmedGUID() == u->GetGUID(); +} + +bool Unit::isPossessing() const +{ + if (Unit* u = GetCharmed()) + return u->isPossessed(); + else + return false; +} + +Unit* Unit::GetCharmerOrSelf() const +{ + if (IsCharmed()) + return GetCharmer(); + else + return const_cast(this); +} + +Unit* Unit::GetNextRandomRaidMemberOrPet(float radius) +{ + Player* player = nullptr; + if (GetTypeId() == TYPEID_PLAYER) + player = ToPlayer(); + // Should we enable this also for charmed units? + else if (GetTypeId() == TYPEID_UNIT && IsPet()) + player = GetOwner()->ToPlayer(); + + if (!player) + return nullptr; + Group* group = player->GetGroup(); + // When there is no group check pet presence + if (!group) + { + // We are pet now, return owner + if (player != this) + return IsWithinDistInMap(player, radius) ? player : nullptr; + Unit* pet = GetGuardianPet(); + // No pet, no group, nothing to return + if (!pet) + return nullptr; + // We are owner now, return pet + return IsWithinDistInMap(pet, radius) ? pet : nullptr; + } + + std::vector nearMembers; + // reserve place for players and pets because resizing vector every unit push is unefficient (vector is reallocated then) + nearMembers.reserve(group->GetMembersCount() * 2); + + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + if (Player* Target = itr->GetSource()) + { + // IsHostileTo check duel and controlled by enemy + if (Target != this && IsWithinDistInMap(Target, radius) && Target->IsAlive() && !IsHostileTo(Target)) + nearMembers.push_back(Target); + + // Push player's pet to vector + if (Unit* pet = Target->GetGuardianPet()) + if (pet != this && IsWithinDistInMap(pet, radius) && pet->IsAlive() && !IsHostileTo(pet)) + nearMembers.push_back(pet); + } + + if (nearMembers.empty()) + return nullptr; + + uint32 randTarget = urand(0, nearMembers.size()-1); + return nearMembers[randTarget]; +} + +// only called in Player::SetSeer +// so move it to Player? +void Unit::AddPlayerToVision(Player* player) +{ + if (m_sharedVision.empty()) + { + setActive(true); + SetWorldObject(true); + } + m_sharedVision.push_back(player); +} + +// only called in Player::SetSeer +void Unit::RemovePlayerFromVision(Player* player) +{ + m_sharedVision.remove(player); + if (m_sharedVision.empty()) + { + setActive(false); + SetWorldObject(false); + } +} + +void Unit::RemoveBindSightAuras() +{ + RemoveAurasByType(SPELL_AURA_BIND_SIGHT); +} + +void Unit::RemoveCharmAuras() +{ + RemoveAurasByType(SPELL_AURA_MOD_CHARM); + RemoveAurasByType(SPELL_AURA_MOD_POSSESS); + RemoveAurasByType(SPELL_AURA_AOE_CHARM); +} + +void Unit::UnsummonAllTotems() +{ + for (uint8 i = 0; i < MAX_SUMMON_SLOT; ++i) + { + if (!m_SummonSlot[i]) + continue; + + if (Creature* OldTotem = GetMap()->GetCreature(m_SummonSlot[i])) + if (OldTotem->IsSummon()) + OldTotem->ToTempSummon()->UnSummon(); + } +} + +void Unit::SendHealSpellLog(HealInfo& healInfo, bool critical /*= false*/) +{ + // we guess size + WorldPacket data(SMSG_SPELLHEALLOG, 8 + 8 + 4 + 4 + 4 + 4 + 1 + 1); + data << healInfo.GetTarget()->GetPackGUID(); + data << healInfo.GetHealer()->GetPackGUID(); + data << uint32(healInfo.GetSpellInfo()->Id); + data << uint32(healInfo.GetHeal()); + data << uint32(healInfo.GetHeal() - healInfo.GetEffectiveHeal()); + 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 /*= false*/) +{ + // calculate heal absorb and reduce healing + Unit::CalcHealAbsorb(healInfo); + Unit::DealHeal(healInfo); + SendHealSpellLog(healInfo, critical); + return healInfo.GetEffectiveHeal(); +} + +void Unit::SendEnergizeSpellLog(Unit* victim, uint32 spellId, int32 damage, PowerType powerType) +{ + WorldPackets::CombatLog::SpellEnergizeLog packet; + packet.TargetGUID = victim->GetGUID(); + packet.CasterGUID = GetGUID(); + packet.SpellID = spellId; + packet.Amount = damage; + packet.Type = powerType; + + SendMessageToSet(packet.Write(), true); +} + +void Unit::EnergizeBySpell(Unit* victim, uint32 spellId, int32 damage, PowerType powerType) +{ + if (SpellInfo const* info = sSpellMgr->GetSpellInfo(spellId)) + EnergizeBySpell(victim, info, damage, powerType); +} + +void Unit::EnergizeBySpell(Unit* victim, SpellInfo const* spellInfo, int32 damage, PowerType powerType) +{ + victim->ModifyPower(powerType, damage, false); + victim->GetThreatManager().ForwardThreatForAssistingMe(this, float(damage) / 2, spellInfo, true); + SendEnergizeSpellLog(victim, spellInfo->Id, damage, powerType); +} + +int32 Unit::SpellDamageBonusDone(Unit* victim, SpellInfo const* spellProto, int32 pdamage, DamageEffectType damagetype, uint8 effIndex, uint32 stack /*= 1*/, Spell* spell /*= nullptr*/, AuraEffect const* aurEff /*= nullptr*/) const +{ + if (!spellProto || !victim) + return pdamage; + + int32 DoneTotal = 0; + float DoneTotalMod = 1.0f; + + auto callDamageScript = [&](int32& dmg, int32& flatMod, float& pctMod) + { + if (spell) + spell->CallScriptCalcDamageHandlers(victim, dmg, flatMod, pctMod); + else if (aurEff) + aurEff->GetBase()->CallScriptCalcDamageAndHealingHandlers(aurEff, aurEff->GetBase()->GetApplicationOfTarget(victim->GetGUID()), victim, dmg, flatMod, pctMod); + }; + + // Some spells don't benefit from done mods + if (damagetype == DIRECT_DAMAGE || spellProto->HasAttribute(SPELL_ATTR3_IGNORE_CASTER_MODIFIERS)) + { + callDamageScript(pdamage, DoneTotal, DoneTotalMod); + return int32(std::max(float(pdamage + DoneTotal) * DoneTotalMod, 0.0f)); + } + + // Totems get their bonus damage from their owner + if (IsCreature() && IsTotem()) + if (Unit* creator = ObjectAccessor::GetUnit(*this, GetCreatorGUID())) + return creator->SpellDamageBonusDone(victim, spellProto, pdamage, damagetype, effIndex, stack, spell, aurEff); + + DoneTotalMod = SpellDamagePctDone(victim, spellProto, damagetype); + + // done scripted mod (take it from owner) + Unit const* owner = GetOwner() ? GetOwner() : this; + DoneTotal += owner->GetTotalAuraModifier(SPELL_AURA_OVERRIDE_CLASS_SCRIPTS, [spellProto](AuraEffect const* aurEff) -> bool + { + if (!aurEff->IsAffectingSpell(spellProto)) + return false; + + switch (aurEff->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 + return true; + default: + break; + } + + return false; + }); + + // Some spells don't benefit from pct done mods + if (!spellProto->HasAttribute(SPELL_ATTR6_IGNORE_CASTER_DAMAGE_MODIFIERS)) + DoneTotal += GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_FLAT_SPELL_DAMAGE_VERSUS, victim->GetCreatureTypeMask()); + + // Done fixed damage bonus auras + int32 DoneAdvertisedBenefit = SpellBaseDamageBonusDone(spellProto->GetSchoolMask(), true); + // modify spell power by victim's SPELL_AURA_MOD_DAMAGE_TAKEN auras (eg Amplify/Dampen Magic) + DoneAdvertisedBenefit += GetTotalAuraModifier(SPELL_AURA_MOD_DAMAGE_TAKEN, [spellProto](AuraEffect const* aurEff) + { + if (spellProto->HasAttribute(SPELL_ATTR10_IGNORE_POSITIVE_DAMAGE_TAKEN_MODS) && aurEff->GetAmount() > 0) + return false; + + if ((aurEff->GetMiscValue() & spellProto->GetSchoolMask()) != 0) + return true; + + return false; + }); + + // Custom scripted damage + switch (spellProto->SpellFamilyName) + { + case SPELLFAMILY_HUNTER: + // Hunter Pet basic attacks (RAP * 0.4 * 0.2) + if (spellProto->SpellFamilyFlags[0] & 0x00080000) + DoneTotal += (owner->GetTotalAttackPowerValue(RANGED_ATTACK) * 0.4f) * 0.2f; + + // Kill Command + if (spellProto->SpellFamilyFlags[1] & 0x00000800) + DoneTotal += owner->GetTotalAttackPowerValue(RANGED_ATTACK) * 0.516f; + break; + case SPELLFAMILY_WARLOCK: + // Warlock Minions inherit 50% of their owner's spell power + if (IsMinion() && GetOwner()) + DoneAdvertisedBenefit = owner->SpellBaseDamageBonusDone(spellProto->GetSchoolMask(), true) * 0.5f; + break; + case SPELLFAMILY_MAGE: + // Mage Water Elementals inherit 40% of their owner's spell power + if (IsMinion() && GetOwner()) + DoneAdvertisedBenefit = owner->SpellBaseDamageBonusDone(spellProto->GetSchoolMask(), true) * 0.4f; + break; + default: + break; + } + + if (spellProto->BonusCoefficientFromAP > 0.0f) + { + float ApCoeffMod = spellProto->BonusCoefficientFromAP; + if (Player* modOwner = GetSpellModOwner()) + { + ApCoeffMod *= 100.0f; + modOwner->ApplySpellMod(spellProto, SpellModOp::BonusCoefficient, ApCoeffMod); + ApCoeffMod /= 100.0f; + } + + WeaponAttackType const attType = [&]() + { + if ((spellProto->IsRangedWeaponSpell() && spellProto->DmgClass != SPELL_DAMAGE_CLASS_MELEE)) + return RANGED_ATTACK; + + if (spellProto->HasAttribute(SPELL_ATTR3_REQUIRES_OFF_HAND_WEAPON) && !spellProto->HasAttribute(SPELL_ATTR3_REQUIRES_MAIN_HAND_WEAPON)) + return OFF_ATTACK; + + return BASE_ATTACK; + }(); + + float APbonus = float(victim->GetTotalAuraModifier(attType != RANGED_ATTACK ? SPELL_AURA_MELEE_ATTACK_POWER_ATTACKER_BONUS : SPELL_AURA_RANGED_ATTACK_POWER_ATTACKER_BONUS)); + APbonus += GetTotalAttackPowerValue(attType); + DoneTotal += int32(stack * ApCoeffMod * APbonus); + } + + // Default calculation + if (DoneAdvertisedBenefit) + { + float coeff = spellProto->Effects[effIndex].BonusMultiplier * spellProto->GetSpellScalingMultiplier(getLevel(), false); + if (Player* modOwner = GetSpellModOwner()) + { + coeff *= 100.0f; + modOwner->ApplySpellMod(spellProto, SpellModOp::BonusCoefficient, coeff); + coeff /= 100.0f; + } + + DoneTotal += int32(DoneAdvertisedBenefit * coeff * stack); + } + + callDamageScript(pdamage, DoneTotal, DoneTotalMod); + + float tmpDamage = float(pdamage + DoneTotal) * DoneTotalMod; + + // apply spellmod to Done damage (flat and pct) + if (Player* modOwner = GetSpellModOwner()) + modOwner->ApplySpellMod(spellProto, damagetype == DOT ? SpellModOp::PeriodicHealingAndDamage : SpellModOp::HealingAndDamage, tmpDamage); + + return int32(std::max(tmpDamage, 0.0f)); +} + +float Unit::SpellDamagePctDone(Unit* victim, SpellInfo const* spellProto, DamageEffectType damagetype) const +{ + if (!spellProto || !victim || damagetype == DIRECT_DAMAGE) + return 1.0f; + + // Some spells don't benefit from done mods + if (spellProto->HasAttribute(SPELL_ATTR3_IGNORE_CASTER_MODIFIERS)) + return 1.0f; + + // Some spells don't benefit from pct done mods + if (spellProto->HasAttribute(SPELL_ATTR6_IGNORE_CASTER_DAMAGE_MODIFIERS)) + return 1.0f; + + // For totems pct done mods are calculated when its calculation is run on the player in SpellDamageBonusDone. + if (GetTypeId() == TYPEID_UNIT && IsTotem()) + return 1.0f; + + // Done total percent damage auras + float DoneTotalMod = 1.0f; + + // Pet damage? + if (GetTypeId() == TYPEID_UNIT && !IsPet()) + DoneTotalMod *= ToCreature()->GetSpellDamageMod(ToCreature()->GetCreatureTemplate()->rank); + + float maxModDamagePercentSchool = 0.0f; + if (GetTypeId() == TYPEID_PLAYER) + { + for (uint32 i = 0; i < MAX_SPELL_SCHOOL; ++i) + if (spellProto->GetSchoolMask() & (1 << i)) + maxModDamagePercentSchool = std::max(maxModDamagePercentSchool, GetFloatValue(PLAYER_FIELD_MOD_DAMAGE_DONE_PCT + i)); + } + else + maxModDamagePercentSchool = GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_DAMAGE_PERCENT_DONE, spellProto->GetSchoolMask()); + + DoneTotalMod *= maxModDamagePercentSchool; + + uint32 creatureTypeMask = victim->GetCreatureTypeMask(); + + DoneTotalMod *= GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_DAMAGE_DONE_VERSUS, creatureTypeMask); + + // bonus against aurastate + DoneTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_DAMAGE_DONE_VERSUS_AURASTATE, [victim](AuraEffect const* aurEff) -> bool + { + if (victim->HasAuraState(static_cast(aurEff->GetMiscValue()))) + return true; + return false; + }); + + // Add SPELL_AURA_MOD_DAMAGE_DONE_FOR_MECHANIC percent bonus + AddPct(DoneTotalMod, GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_DAMAGE_DONE_FOR_MECHANIC, spellProto->Mechanic)); + + // done scripted mod (take it from owner) + Unit const* 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)->IsAffectingSpell(spellProto)) + continue; + + switch ((*i)->GetMiscValue()) + { + case 4920: // Molten Fury + case 4919: + { + if (victim->HasAuraState(AURA_STATE_HEALTHLESS_35_PERCENT, spellProto, this)) + AddPct(DoneTotalMod, (*i)->GetAmount()); + break; + } + case 6917: // Death's Embrace damage effect + case 6926: + case 6928: + { + // Health at 25% or less (25% stored at effect 2 of the spell) + if (victim->HealthBelowPct(CalculateSpellDamage(this, (*i)->GetSpellInfo(), EFFECT_2))) + AddPct(DoneTotalMod, (*i)->GetAmount()); + break; + } + case 6916: // Death's Embrace heal effect + case 6925: + case 6927: + if (HealthBelowPct(CalculateSpellDamage(this, (*i)->GetSpellInfo(), EFFECT_2))) + AddPct(DoneTotalMod, (*i)->GetAmount()); + break; + case 4992: // Soul Siphon + case 4993: + { + // EFFECT_0 contains the bonus amount per application, EFFECT_1 contains the maximum bonus amount + int32 bonusPerApplication = CalculateSpellDamage(this, (*i)->GetSpellInfo(), EFFECT_0); + int32 pctBonus = 0; + for (auto const& itr : victim->GetAppliedAuras()) + { + Aura const* aura = itr.second->GetBase(); + if (aura->GetCasterGUID() != GetGUID() || !aura->GetSpellInfo()->IsAbilityOfSkillType(SKILL_AFFLICTION)) + continue; + + pctBonus += bonusPerApplication; + } + + pctBonus = std::min(pctBonus, (*i)->GetAmount()); + AddPct(DoneTotalMod, pctBonus); + break; + } + case 5481: // Starfire Bonus + { + if (victim->GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_DRUID, 0x200002, 0, 0)) + AddPct(DoneTotalMod, (*i)->GetAmount()); + break; + } + } + } + + // Add SPELL_AURA_MOD_DAMAGE_FROM_MANA percent bonus + if (HasAuraType(SPELL_AURA_MOD_DAMAGE_FROM_MANA) && GetMaxPower(POWER_MANA)) + { + float totalBonus = owner->GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_DAMAGE_FROM_MANA, spellProto->GetSchoolMask()) - 1.f; + totalBonus *= ((float)GetPower(POWER_MANA) / GetMaxPower(POWER_MANA)); + if (totalBonus != 0.f) + DoneTotalMod += DoneTotalMod * totalBonus; + } + + // Custom scripted damage + switch (spellProto->SpellFamilyName) + { + case SPELLFAMILY_MAGE: + // Mastery: Frostburn + if (AuraEffect* aurEff = GetAuraEffect(76613, 0)) + if (victim->HasAuraState(AURA_STATE_FROZEN)) + if (aurEff->IsAffectingSpell(spellProto)) + AddPct(DoneTotalMod, aurEff->GetAmount()); + + // Ice Lance + if (spellProto->SpellIconID == 186) + { + if (victim->HasAuraState(AURA_STATE_FROZEN, spellProto, this) + || GetAuraEffect(SPELL_AURA_ABILITY_IGNORE_AURASTATE, SPELLFAMILY_MAGE, 0, 0, 0x0000000A, GetGUID())) + DoneTotalMod *= 2.0f; + } + + // Fingers of Frost + if (spellProto->SpellFamilyFlags[0] & 0x00020000) + if (GetAuraEffect(SPELL_AURA_ABILITY_IGNORE_AURASTATE, SPELLFAMILY_MAGE, 0, 0, 0x0000000A, GetGUID())) + AddPct(DoneTotalMod, 25.0f); + + // Torment the weak + if (spellProto->GetSchoolMask() & SPELL_SCHOOL_MASK_ARCANE) + { + if (victim->HasAuraWithMechanic((1<GetSpellInfo()->SpellIconID == 2215) + { + AddPct(DoneTotalMod, (*i)->GetAmount()); + break; + } + } + } + } + break; + case SPELLFAMILY_PRIEST: + // Smite + 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()); + } + break; + case SPELLFAMILY_DEATHKNIGHT: + // Sigil of the Vengeful Heart + if (spellProto->SpellFamilyFlags[0] & 0x2000) + if (AuraEffect* aurEff = GetAuraEffect(64962, EFFECT_1)) + DoneTotalMod += aurEff->GetAmount(); + break; + } + + return DoneTotalMod; +} + +int32 Unit::SpellDamageBonusTaken(Unit* caster, SpellInfo const* spellProto, int32 pdamage, DamageEffectType damagetype) const +{ + if (!spellProto || damagetype == DIRECT_DAMAGE) + return pdamage; + + float TakenTotalMod = 1.0f; + + // Mod damage from spell mechanic + if (uint32 mechanicMask = spellProto->GetAllEffectsMechanicMask()) + { + TakenTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_MECHANIC_DAMAGE_TAKEN_PERCENT, [mechanicMask, spellProto](AuraEffect const* aurEff) + { + if (spellProto->HasAttribute(SPELL_ATTR10_IGNORE_POSITIVE_DAMAGE_TAKEN_MODS) && aurEff->GetAmount() > 0) + return false; + + if (mechanicMask & uint32(1 << (aurEff->GetMiscValue()))) + return true; + return false; + }); + } + + //.. taken pct: dummy auras + AuraEffectList const& mDummyAuras = GetAuraEffectsByType(SPELL_AURA_DUMMY); + for (AuraEffectList::const_iterator i = mDummyAuras.begin(); i != mDummyAuras.end(); ++i) + { + switch ((*i)->GetSpellInfo()->SpellIconID) + { + // Cheat Death + case 2109: + if ((*i)->GetMiscValue() & SPELL_SCHOOL_MASK_NORMAL) + AddPct(TakenTotalMod, (*i)->GetAmount()); + break; + } + } + + // Spells with SPELL_ATTR4_IGNORE_DAMAGE_TAKEN_MODIFIERS should only benefit from mechanic damage mod auras. + if (!spellProto->HasAttribute(SPELL_ATTR4_IGNORE_DAMAGE_TAKEN_MODIFIERS)) + { + // from positive and negative SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN + // multiplicative bonus, for example Dispersion + Shadowform (0.10*0.85=0.085) + TakenTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN, [spellProto](AuraEffect const* aurEff) + { + if (spellProto->HasAttribute(SPELL_ATTR10_IGNORE_POSITIVE_DAMAGE_TAKEN_MODS) && aurEff->GetAmount() > 0) + return false; + + if ((aurEff->GetMiscValue() & spellProto->GetSchoolMask()) != 0) + return true; + + return false; + }); + + // From caster spells + if (caster) + { + TakenTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_DAMAGE_FROM_CASTER, [caster, spellProto](AuraEffect const* aurEff) + { + if (spellProto->HasAttribute(SPELL_ATTR10_IGNORE_POSITIVE_DAMAGE_TAKEN_MODS) && aurEff->GetAmount() > 0) + return false; + + if (aurEff->GetCasterGUID() == caster->GetGUID() && aurEff->IsAffectingSpell(spellProto)) + return true; + return false; + }); + } + } + + // Sanctified Wrath (bypass damage reduction) + if (caster && TakenTotalMod < 1.0f) + { + float damageReduction = 1.0f - TakenTotalMod; + Unit::AuraEffectList const& casterIgnoreResist = caster->GetAuraEffectsByType(SPELL_AURA_MOD_IGNORE_TARGET_RESIST); + for (AuraEffect const* aurEff : casterIgnoreResist) + { + if (!(aurEff->GetMiscValue() & spellProto->GetSchoolMask())) + continue; + + AddPct(damageReduction, -aurEff->GetAmount()); + } + + TakenTotalMod = 1.0f - damageReduction; + } + + float tmpDamage = pdamage * TakenTotalMod; + return int32(std::max(tmpDamage, 0.0f)); +} + +int32 Unit::SpellBaseDamageBonusDone(SpellSchoolMask schoolMask, bool withSpellPowerPctMod /*= false*/) const +{ + if (IsPlayer()) + { + float overrideSP = GetFloatValue(PLAYER_FIELD_OVERRIDE_SPELL_POWER_BY_AP_PCT); + if (overrideSP > 0.0f) + return int32(CalculatePct(GetTotalAttackPowerValue(BASE_ATTACK), overrideSP) + 0.5f); + } + + int32 DoneAdvertisedBenefit = GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_DAMAGE_DONE, schoolMask); + + if (Player const* player = ToPlayer()) + { + // Base value + DoneAdvertisedBenefit += player->GetBaseSpellPowerBonus(); + + // Check if we are ever using mana - PaperDollFrame.lua + if (GetPowerIndex(POWER_MANA) != MAX_POWERS) + DoneAdvertisedBenefit += std::max(0, int32(GetStat(StatType::Intellect)) - 10); // spellpower from intellect + + // Spell Power bonus from SPELL_AURA_MOD_SPELL_POWER_PCT + if (withSpellPowerPctMod) + DoneAdvertisedBenefit *= player->GetFloatValue(PLAYER_FIELD_MOD_SPELL_POWER_PCT); + + // Damage bonus from stats + AuraEffectList const& mDamageDoneOfStatPercent = GetAuraEffectsByType(SPELL_AURA_MOD_SPELL_DAMAGE_OF_STAT_PERCENT); + for (AuraEffect const* aurEff : mDamageDoneOfStatPercent) + { + if ((aurEff->GetMiscValue() & schoolMask) != 0) + { + // stat used stored in miscValueB for this aura + StatType const usedStat = static_cast(aurEff->GetMiscValueB()); + DoneAdvertisedBenefit += static_cast(CalculatePct(GetStat(usedStat), aurEff->GetAmount())); + } + } + + // ... and attack power + DoneAdvertisedBenefit += static_cast(CalculatePct(GetTotalAttackPowerValue(BASE_ATTACK), GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_SPELL_DAMAGE_OF_ATTACK_POWER, schoolMask))); + } + + return DoneAdvertisedBenefit; +} + +float Unit::SpellCritChanceDone(SpellInfo const* spellInfo, SpellSchoolMask schoolMask, WeaponAttackType attackType /*= BASE_ATTACK*/, bool isPeriodic /*= false*/) const +{ + //! Mobs can't crit with spells. (Except player controlled) + if (GetTypeId() == TYPEID_UNIT && !GetSpellModOwner()) + return 0.0f; + + // not critting spell + if (!isPeriodic && !spellInfo->HasAttribute(SPELL_ATTR0_CU_CAN_CRIT)) + return 0.0f; + + float crit_chance = 0.0f; + switch (spellInfo->DmgClass) + { + case SPELL_DAMAGE_CLASS_MAGIC: + { + if (schoolMask & SPELL_SCHOOL_MASK_NORMAL) + crit_chance = 0.0f; + // For other schools + else if (GetTypeId() == TYPEID_PLAYER) + crit_chance = GetFloatValue(PLAYER_SPELL_CRIT_PERCENTAGE1 + uint32(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: + { + crit_chance += GetUnitCriticalChanceDone(attackType); + crit_chance += GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_SPELL_CRIT_CHANCE_SCHOOL, schoolMask); + break; + } + 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(spellInfo, SpellModOp::CritChance, crit_chance); + + return std::max(crit_chance, 0.0f); +} + +float Unit::SpellCritChanceTaken(Unit const* caster, SpellInfo const* spellInfo, SpellSchoolMask schoolMask, float doneChance, WeaponAttackType attackType /*= BASE_ATTACK*/, bool isPeriodic /*= false*/) const +{ + // not critting spell + if (!isPeriodic && !spellInfo->HasAttribute(SPELL_ATTR0_CU_CAN_CRIT)) + return 0.0f; + + float crit_chance = doneChance; + switch (spellInfo->DmgClass) + { + case SPELL_DAMAGE_CLASS_MAGIC: + { + // taken + if (!spellInfo->IsPositive()) + { + // Modify critical chance by victim SPELL_AURA_MOD_ATTACKER_SPELL_CRIT_CHANCE + crit_chance += GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_ATTACKER_SPELL_CRIT_CHANCE, schoolMask); + + // Modify critical chance by victim SPELL_AURA_MOD_ATTACKER_SPELL_AND_WEAPON_CRIT_CHANCE + // applied after resilience + crit_chance += GetTotalAuraModifier(SPELL_AURA_MOD_ATTACKER_SPELL_AND_WEAPON_CRIT_CHANCE); + } + + // scripted (increase crit chance ... against ... target by x% + if (caster) + { + AuraEffectList const& mOverrideClassScript = caster->GetAuraEffectsByType(SPELL_AURA_OVERRIDE_CLASS_SCRIPTS); + for (AuraEffect const* aurEff : mOverrideClassScript) + { + if (!aurEff->IsAffectingSpell(spellInfo)) + continue; + + switch (aurEff->GetMiscValue()) + { + case 911: // Shatter + if (HasAuraState(AURA_STATE_FROZEN, spellInfo, caster)) + crit_chance *= (1 * aurEff->GetSpellInfo()->GetRank()); + else if (spellInfo->SpellFamilyFlags[0] & 0x00020000 || spellInfo->SpellFamilyFlags[1] & 0x00100000) + if (GetAuraEffect(SPELL_AURA_ABILITY_IGNORE_AURASTATE, SPELLFAMILY_MAGE, 0, 0, 0x0000000A, caster->GetGUID())) + crit_chance *= (1 * aurEff->GetSpellInfo()->GetRank()); + break; + case 57470: // Renewed Hope + case 57472: + if (HasAura(6788) || HasAura(47930)) + crit_chance += aurEff->GetAmount(); + break; + default: + break; + } + } + // Custom crit by class + switch (spellInfo->SpellFamilyName) + { + case SPELLFAMILY_MAGE: + // Glyph of Fire Blast + if (spellInfo->SpellFamilyFlags[0] == 0x2 && spellInfo->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_ROGUE: + // Shiv-applied poisons can't crit + if (caster->FindCurrentSpellBySpellId(5938)) + crit_chance = 0.0f; + break; + case SPELLFAMILY_PALADIN: + // Flash of light + if (spellInfo->SpellFamilyFlags[0] & 0x40000000) + { + // Sacred Shield + if (AuraEffect const* aura = GetAuraEffect(58597, 1, GetGUID())) + crit_chance += aura->GetAmount(); + break; + } + // Exorcism + else if (spellInfo->GetCategory() == 19) + { + if (GetCreatureTypeMask() & CREATURE_TYPEMASK_DEMON_OR_UNDEAD) + return 100.0f; + break; + } + + // Last Word + if (AuraEffect const* effect = caster->GetDummyAuraEffect(SPELLFAMILY_PALADIN, 2139, EFFECT_0)) + if (effect->IsAffectingSpell(spellInfo)) + if (HasAuraState(AURA_STATE_HEALTHLESS_35_PERCENT)) + crit_chance += effect->GetAmount(); + break; + case SPELLFAMILY_SHAMAN: + // Lava Burst + if ((spellInfo->SpellFamilyFlags[1] & 0x1000) != 0 || (spellInfo->SpellFamilyFlags[2] & 0x400000) != 0) + { + 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; + case SPELLFAMILY_WARLOCK: + // Searing Pain - Soulburn bonus + if (spellInfo->SpellFamilyFlags[0] & 0x00000100) + if (HasAura(74434, GetGUID())) + return 100.0f; + + // Improved Searing Pain + if (AuraEffect const* effect = caster->GetDummyAuraEffect(SPELLFAMILY_WARLOCK, 816, EFFECT_0)) + if (effect->IsAffectingSpell(spellInfo)) + if (GetHealthPct() <= 25.f) + crit_chance += effect->GetAmount(); + break; + } + + // Spell crit suppression + if (GetTypeId() == TYPEID_UNIT) + { + int32 const levelDiff = static_cast(getLevelForTarget(caster)) - caster->getLevel(); + if (levelDiff > 0) + crit_chance -= levelDiff * 0.7f; + } + } + break; + } + case SPELL_DAMAGE_CLASS_MELEE: + { + // Custom crit by class + if (caster) + { + switch (spellInfo->SpellFamilyName) + { + case SPELLFAMILY_DRUID: + // Rend and Tear - bonus crit chance for Ferocious Bite on bleeding targets + if (spellInfo->SpellFamilyFlags[0] & 0x00800000 + && spellInfo->SpellIconID == 1680 + && HasAuraState(AURA_STATE_BLEEDING)) + { + if (AuraEffect const* rendAndTear = caster->GetDummyAuraEffect(SPELLFAMILY_DRUID, 2859, 1)) + crit_chance += rendAndTear->GetAmount(); + break; + } + + // Predatory Strikes + if (spellInfo->SpellFamilyFlags[0] & 0x00010000) + if (AuraEffect const* critAuraEff = caster->GetDummyAuraEffect(SPELLFAMILY_DRUID, 1563, EFFECT_0)) + if (AuraEffect const* healthAuraEff = caster->GetDummyAuraEffect(SPELLFAMILY_DRUID, 1563, EFFECT_1)) + if (GetHealthPct() >= healthAuraEff->GetAmount()) + crit_chance += critAuraEff->GetAmount(); + break; + case SPELLFAMILY_HUNTER: + // Kill Command - Improved Kill Command + if (spellInfo->SpellFamilyFlags[1] & 0x00000800) + if (Player* player = caster->GetSpellModOwner()) + if (AuraEffect const* improvedKillCommand = player->GetDummyAuraEffect(SPELLFAMILY_HUNTER, 2221, EFFECT_0)) + crit_chance += improvedKillCommand->GetAmount(); + break; + } + } + [[fallthrough]]; // Calculate critical strike chance for both Ranged and Melee spells + } + case SPELL_DAMAGE_CLASS_RANGED: + if (caster) + { + switch (spellInfo->SpellFamilyName) + { + case SPELLFAMILY_HUNTER: + { + // Careful Aim + if (GetHealthPct() >= 90) + { + switch (spellInfo->Id) + { + case 56641: // Steady Shot + case 77767: // Cobra Shot + case 19434: // Aimed Shot + case 82928: // Aimed Shot! + if (AuraEffect* aurEff = caster->GetDummyAuraEffect(SPELLFAMILY_HUNTER, 2222, 0)) + crit_chance += aurEff->GetAmount(); + break; + default: + break; + } + } + break; + } + } + crit_chance = GetUnitCriticalChanceTaken(attackType, crit_chance); + } + break; + case SPELL_DAMAGE_CLASS_NONE: + default: + return 0.f; + } + + // for this types the bonus was already added in GetUnitCriticalChance, do not add twice + if (caster) + { + crit_chance += GetTotalAuraModifier(SPELL_AURA_MOD_CRIT_CHANCE_FOR_CASTER, [caster, spellInfo](AuraEffect const* aurEff) -> bool + { + if (aurEff->GetCasterGUID() == caster->GetGUID() && aurEff->IsAffectingSpell(spellInfo)) + return true; + return false; + }); + } + + return std::max(crit_chance, 0.0f); +} + +/*static*/ uint32 Unit::SpellCriticalDamageBonus(Unit const* caster, SpellInfo const* spellProto, uint32 damage, bool periodicDamage /*= false*/) +{ + // Calculate critical bonus + int32 crit_bonus = damage; + float crit_mod = 0.0f; + + switch (spellProto->DmgClass) + { + // Spells which are either marked as ranged or melee damage, will deal 100% additional damage + case SPELL_DAMAGE_CLASS_MELEE: + crit_bonus += damage; + break; + case SPELL_DAMAGE_CLASS_RANGED: + // Ranged spells which have magic periodic damage effect on the other hand, only deal 50% damage, just like spells + // This is a very specific case that is currently only known for Hunter spells, such as Serpent Sting and Black Arrow + if (periodicDamage && (spellProto->GetSchoolMask() & SPELL_SCHOOL_MASK_MAGIC) != 0) + crit_bonus += damage / 2; + else + crit_bonus += damage; + break; + default: + // Magic spells will simply deal 50% additional crit damage + crit_bonus += damage / 2; + break; + } + + if (caster) + { + crit_mod += (caster->GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_CRIT_DAMAGE_BONUS, spellProto->GetSchoolMask()) - 1.0f) * 100; + + if (crit_bonus != 0) + AddPct(crit_bonus, crit_mod); + + crit_bonus -= damage; + + if (damage > uint32(crit_bonus)) + { + // adds additional damage to critBonus (from talents) + if (Player* modOwner = caster->GetSpellModOwner()) + modOwner->ApplySpellMod(spellProto, SpellModOp::CritDamageAndHealing, crit_bonus); + } + + crit_bonus += damage; + } + + return crit_bonus; +} + +/*static*/ uint32 Unit::SpellCriticalHealingBonus(Unit const* caster, uint32 damage) +{ + // As of Patch 4.2.0 (2011-06-28): all healing spells now crit for 200% instead of 150% + damage *= 2; + + if (caster) + damage = int32(float(damage) * caster->GetTotalAuraMultiplier(SPELL_AURA_MOD_CRITICAL_HEALING_AMOUNT)); + + return damage; +} + +int32 Unit::SpellHealingBonusDone(Unit* victim, SpellInfo const* spellProto, int32 healamount, DamageEffectType damagetype, uint8 effIndex, uint32 stack /*= 1*/, Spell* spell /*= nullptr*/, AuraEffect const* aurEff /*= nullptr*/) const +{ + // For totems get healing bonus from owner (statue isn't totem in fact) + if (GetTypeId() == TYPEID_UNIT && IsTotem()) + if (Unit* creator = ObjectAccessor::GetUnit(*this, GetCreatorGUID())) + return creator->SpellHealingBonusDone(victim, spellProto, healamount, damagetype, effIndex, stack, spell, aurEff); + + // Some spells don't benefit from done mods + if (spellProto->HasAttribute(SPELL_ATTR3_IGNORE_CASTER_MODIFIERS)) + return healamount; + + // No bonus healing for potion spells + if (spellProto->SpellFamilyName == SPELLFAMILY_POTION) + return healamount; + + int32 DoneTotal = 0; + float DoneTotalMod = SpellHealingPctDone(victim, spellProto); + + // done scripted mod (take it from owner) + Unit const* owner = GetOwner() ? GetOwner() : this; + for (AuraEffect const* overrideClassScripts : owner->GetAuraEffectsByType(SPELL_AURA_OVERRIDE_CLASS_SCRIPTS)) + { + if (!overrideClassScripts->IsAffectingSpell(spellProto)) + continue; + + switch (overrideClassScripts->GetMiscValue()) + { + case 4415: // Increased Rejuvenation Healing + case 4953: + DoneTotal += overrideClassScripts->GetAmount(); + break; + default: + break; + } + } + + // Done fixed damage bonus auras + int32 DoneAdvertisedBenefit = SpellBaseHealingBonusDone(spellProto->GetSchoolMask(), true); + // modify spell power by victim's SPELL_AURA_MOD_HEALING auras (eg Amplify/Dampen Magic) + DoneAdvertisedBenefit += victim->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_HEALING, spellProto->GetSchoolMask()); + + // Pets just add their bonus damage to their spell damage + // note that their spell damage is just gain of their own auras + if (HasUnitTypeMask(UNIT_MASK_GUARDIAN)) + DoneAdvertisedBenefit += static_cast(this)->GetBonusDamage(); + + + // Check for table values + if (spellProto->BonusCoefficientFromAP > 0.0f) + { + WeaponAttackType const attType = (spellProto->IsRangedWeaponSpell() && spellProto->DmgClass != SPELL_DAMAGE_CLASS_MELEE) ? RANGED_ATTACK : BASE_ATTACK; + float APbonus = float(victim->GetTotalAuraModifier(attType == BASE_ATTACK ? SPELL_AURA_MELEE_ATTACK_POWER_ATTACKER_BONUS : SPELL_AURA_RANGED_ATTACK_POWER_ATTACKER_BONUS)); + APbonus += GetTotalAttackPowerValue(attType); + + DoneTotal += int32(spellProto->BonusCoefficientFromAP * stack * APbonus); + } + + // Default calculation + if (DoneAdvertisedBenefit) + { + float coeff = spellProto->Effects[effIndex].BonusMultiplier * spellProto->GetSpellScalingMultiplier(getLevel(), false); + if (Player* modOwner = GetSpellModOwner()) + { + coeff *= 100.0f; + modOwner->ApplySpellMod(spellProto, SpellModOp::BonusCoefficient, coeff); + coeff /= 100.0f; + } + + DoneTotal += int32(DoneAdvertisedBenefit * coeff * stack); + } + + 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; + } + if (spellProto->Effects[i].Effect == SPELL_EFFECT_HEALTH_LEECH) + DoneTotal = 0; + } + + if (spell) + spell->CallScriptCalcHealingHandlers(victim, healamount, DoneTotal, DoneTotalMod); + else if (aurEff) + aurEff->GetBase()->CallScriptCalcDamageAndHealingHandlers(aurEff, aurEff->GetBase()->GetApplicationOfTarget(victim->GetGUID()), victim, healamount, DoneTotal, DoneTotalMod); + + float heal = float(healamount + DoneTotal) * DoneTotalMod; + + + // apply spellmod to Done amount + if (Player* modOwner = GetSpellModOwner()) + modOwner->ApplySpellMod(spellProto, damagetype == DOT ? SpellModOp::PeriodicHealingAndDamage : SpellModOp::HealingAndDamage, heal); + + return int32(std::max(heal, 0.0f)); +} + +float Unit::SpellHealingPctDone(Unit* victim, SpellInfo const* spellProto) const +{ + // For totems pct done mods are calculated when its calculation is run on the player in SpellHealingBonusDone. + if (GetTypeId() == TYPEID_UNIT && IsTotem()) + return 1.0f; + + // No bonus healing for potion spells + if (spellProto->SpellFamilyName == SPELLFAMILY_POTION) + return 1.0f; + + float DoneTotalMod = 1.0f; + + // Healing done percent + if (IsPlayer()) + DoneTotalMod *= GetFloatValue(PLAYER_FIELD_MOD_HEALING_DONE_PCT); + else + DoneTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_HEALING_DONE_PERCENT); + + // done scripted mod (take it from owner) + Unit const* owner = GetOwner() ? GetOwner() : this; + for (AuraEffect const* overrideClassScripts : owner->GetAuraEffectsByType(SPELL_AURA_OVERRIDE_CLASS_SCRIPTS)) + { + if (!overrideClassScripts->IsAffectingSpell(spellProto)) + continue; + + switch (overrideClassScripts->GetMiscValue()) + { + case 21: // Test of Faith + case 6935: + case 6918: + if (victim->HealthBelowPct(50)) + AddPct(DoneTotalMod, overrideClassScripts->GetAmount()); + break; + case 8477: // Nourish Heal Boost + { + int32 modPercent = 0; + for (AuraEffect const* periodicHeal : victim->GetAuraEffectsByType(SPELL_AURA_PERIODIC_HEAL)) + { + if (periodicHeal->GetCasterGUID() != GetGUID() || periodicHeal->GetSpellInfo()->SpellFamilyName != SPELLFAMILY_DRUID) + continue; + + if (periodicHeal->GetSpellInfo()->SpellFamilyFlags.HasFlag(0x10 | 0x40, 0x10 | 0x4000000, 0)) + modPercent += overrideClassScripts->GetAmount() * periodicHeal->GetBase()->GetStackAmount(); + } + AddPct(DoneTotalMod, modPercent); + break; + } + default: + break; + } + } + + // Custom scripted damage + switch (spellProto->SpellFamilyName) + { + case SPELLFAMILY_SHAMAN: + { + // Nature's Blessing + if (AuraEffect const* blessing = GetDummyAuraEffect(SPELLFAMILY_SHAMAN, 2012, EFFECT_0)) + if (blessing->IsAffectingSpell(spellProto) && victim->HasAura(974, GetGUID())) + AddPct(DoneTotalMod, blessing->GetAmount()); + break; + } + default: + break; + } + + float healthPctDiff = 100.f - victim->GetHealthPct(); + for (AuraEffect const* healingDonePctVsTargetHealth : owner->GetAuraEffectsByType(SPELL_AURA_MOD_HEALING_DONE_PCT_VERSUS_TARGET_HEALTH)) + { + if (!healingDonePctVsTargetHealth->IsAffectingSpell(spellProto)) + continue; + + AddPct(DoneTotalMod, CalculatePct(float(healingDonePctVsTargetHealth->GetAmount()), healthPctDiff)); + } + + return DoneTotalMod; +} + +int32 Unit::SpellHealingBonusTaken(Unit* caster, SpellInfo const* spellProto, int32 healamount, DamageEffectType damagetype) const +{ + float TakenTotalMod = 1.0f; + + // Healing taken percent + if (IsPlayer()) + TakenTotalMod *= GetFloatValue(PLAYER_FIELD_MOD_HEALING_PCT); + else + { + float 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()); + + // Unused in Cataclysm (15595) + if (damagetype == DOT) + { + // Healing over time taken percent + float minval_hot = float(GetMaxNegativeAuraModifier(SPELL_AURA_259)); + if (minval_hot) + AddPct(TakenTotalMod, minval_hot); + + float maxval_hot = float(GetMaxPositiveAuraModifier(SPELL_AURA_259)); + if (maxval_hot) + AddPct(TakenTotalMod, maxval_hot); + } + + if (caster) + { + TakenTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_HEALING_RECEIVED, [caster, spellProto](AuraEffect const* aurEff) -> bool + { + if (caster->GetGUID() == aurEff->GetCasterGUID() && aurEff->IsAffectingSpell(spellProto)) + return true; + return false; + }); + } + + float heal = healamount * TakenTotalMod; + + return int32(std::max(heal, 0.0f)); +} + +int32 Unit::SpellBaseHealingBonusDone(SpellSchoolMask schoolMask, bool withSpellPowerPctMod /*= false*/) const +{ + if (IsPlayer()) + { + float overrideSP = GetFloatValue(PLAYER_FIELD_OVERRIDE_SPELL_POWER_BY_AP_PCT); + if (overrideSP > 0.0f) + return int32(CalculatePct(GetTotalAttackPowerValue(BASE_ATTACK), overrideSP) + 0.5f); + } + + int32 advertisedBenefit = GetTotalAuraModifier(SPELL_AURA_MOD_HEALING_DONE, [schoolMask](AuraEffect const* aurEff) -> bool + { + if (!aurEff->GetMiscValue() || (aurEff->GetMiscValue() & schoolMask) != 0) + return true; + return false; + }); + + // Healing bonus of spirit, intellect and strength + if (Player const* player = ToPlayer()) + { + // Base value + advertisedBenefit += player->GetBaseSpellPowerBonus(); + + // Check if we are ever using mana - PaperDollFrame.lua + if (GetPowerIndex(POWER_MANA) != MAX_POWERS) + advertisedBenefit += std::max(0, int32(GetStat(StatType::Intellect)) - 10); // spellpower from intellect + + // Spell Power bonus from SPELL_AURA_MOD_SPELL_POWER_PCT + if (withSpellPowerPctMod) + advertisedBenefit *= player->GetFloatValue(PLAYER_FIELD_MOD_SPELL_POWER_PCT); + + // 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) + StatType usedStat = StatType((*i)->GetSpellInfo()->Effects[(*i)->GetEffIndex()].MiscValue); + advertisedBenefit += int32(CalculatePct(GetStat(usedStat), (*i)->GetAmount())); + } + + // ... and attack power + advertisedBenefit += CalculatePct(GetTotalAttackPowerValue(BASE_ATTACK), GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_SPELL_HEALING_OF_ATTACK_POWER, schoolMask)); + } + return advertisedBenefit; +} + +bool Unit::IsImmunedToDamage(SpellSchoolMask schoolMask) const +{ + if (schoolMask == SPELL_SCHOOL_MASK_NONE) + return false; + + // If m_immuneToSchool type contain this school type, IMMUNE damage. + uint32 schoolImmunityMask = GetSchoolImmunityMask(); + if ((schoolImmunityMask & schoolMask) == schoolMask) // We need to be immune to all types + return true; + + // If m_immuneToDamage type contain magic, IMMUNE damage. + uint32 damageImmunityMask = GetDamageImmunityMask(); + if ((damageImmunityMask & schoolMask) == schoolMask) // We need to be immune to all types + return true; + + return false; +} + +bool Unit::IsImmunedToDamage(SpellInfo const* spellInfo) const +{ + if (!spellInfo) + return false; + + // for example 40175 + if (spellInfo->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES) && spellInfo->HasAttribute(SPELL_ATTR3_ALWAYS_HIT)) + return false; + + if (spellInfo->HasAttribute(SPELL_ATTR1_IMMUNITY_TO_HOSTILE_AND_FRIENDLY_EFFECTS) || spellInfo->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES)) + return false; + + if (uint32 schoolMask = spellInfo->GetSchoolMask()) + { + // If m_immuneToSchool type contain this school type, IMMUNE damage. + uint32 schoolImmunityMask = 0; + SpellImmuneContainer const& schoolList = m_spellImmune[IMMUNITY_SCHOOL]; + for (auto itr = schoolList.begin(); itr != schoolList.end(); ++itr) + if ((itr->first & schoolMask) && !spellInfo->CanPierceImmuneAura(sSpellMgr->GetSpellInfo(itr->second))) + schoolImmunityMask |= itr->first; + + // // We need to be immune to all types + if ((schoolImmunityMask & schoolMask) == schoolMask) + return true; + + // If m_immuneToDamage type contain magic, IMMUNE damage. + uint32 damageImmunityMask = GetDamageImmunityMask(); + if ((damageImmunityMask & schoolMask) == schoolMask) // We need to be immune to all types + return true; + } + + return false; +} + +bool Unit::IsImmunedToSpell(SpellInfo const* spellInfo, WorldObject const* caster, bool requireImmunityPurgesEffectAttribute /*= false*/) const +{ + if (!spellInfo) + return false; + + auto hasImmunity = [requireImmunityPurgesEffectAttribute](SpellImmuneContainer const& container, uint32 key) + { + Trinity::IteratorPair range = Trinity::Containers::MapEqualRange(container, key); + if (!requireImmunityPurgesEffectAttribute) + return range.begin() != range.end(); + + return std::any_of(range.begin(), range.end(), [](SpellImmuneContainer::value_type const& entry) + { + if (SpellInfo const* immunitySourceSpell = sSpellMgr->GetSpellInfo(entry.second)) + if (immunitySourceSpell->HasAttribute(SPELL_ATTR1_IMMUNITY_PURGES_EFFECT)) + return true; + + return false; + }); + }; + + // Single spell immunity. + SpellImmuneContainer const& idList = m_spellImmune[IMMUNITY_ID]; + if (hasImmunity(idList, spellInfo->Id)) + return true; + + if (spellInfo->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES)) + return false; + + if (uint32 dispel = spellInfo->Dispel) + { + SpellImmuneContainer const& dispelList = m_spellImmune[IMMUNITY_DISPEL]; + if (hasImmunity(dispelList, dispel)) + return true; + } + + // Spells that don't have effectMechanics. + if (uint32 mechanic = spellInfo->Mechanic) + { + SpellImmuneContainer const& mechanicList = m_spellImmune[IMMUNITY_MECHANIC]; + if (hasImmunity(mechanicList, mechanic)) + return true; + } + + bool immuneToAllEffects = true; + for (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; + + if (!IsImmunedToSpellEffect(spellInfo, i, caster, requireImmunityPurgesEffectAttribute)) + { + immuneToAllEffects = false; + break; + } + if (spellInfo->HasAttribute(SPELL_ATTR4_NO_PARTIAL_IMMUNITY)) + return true; + } + + if (immuneToAllEffects) //Return immune only if the target is immune to all spell effects. + return true; + + if (uint32 schoolMask = spellInfo->GetSchoolMask()) + { + uint32 schoolImmunityMask = 0; + SpellImmuneContainer const& schoolList = m_spellImmune[IMMUNITY_SCHOOL]; + for (auto itr = schoolList.begin(); itr != schoolList.end(); ++itr) + { + if ((itr->first & schoolMask) == 0) + continue; + + SpellInfo const* immuneSpellInfo = sSpellMgr->GetSpellInfo(itr->second); + if (requireImmunityPurgesEffectAttribute) + if (!immuneSpellInfo || !immuneSpellInfo->HasAttribute(SPELL_ATTR1_IMMUNITY_PURGES_EFFECT)) + continue; + + // Consider the school immune if any of these conditions are not satisfied. + // In case of no immuneSpellInfo, ignore that condition and check only the other conditions + if ((immuneSpellInfo && !immuneSpellInfo->IsPositive()) || !spellInfo->IsPositive() || !caster || !IsFriendlyTo(caster)) + if (!spellInfo->CanPierceImmuneAura(immuneSpellInfo)) + schoolImmunityMask |= itr->first; + } + + if ((schoolImmunityMask & schoolMask) == schoolMask) + return true; + } + + return false; +} + +uint32 Unit::GetSchoolImmunityMask() const +{ + uint32 mask = 0; + SpellImmuneContainer const& schoolList = m_spellImmune[IMMUNITY_SCHOOL]; + for (auto itr = schoolList.begin(); itr != schoolList.end(); ++itr) + mask |= itr->first; + + return mask; +} + +uint32 Unit::GetDamageImmunityMask() const +{ + uint32 mask = 0; + SpellImmuneContainer const& damageList = m_spellImmune[IMMUNITY_DAMAGE]; + for (auto itr = damageList.begin(); itr != damageList.end(); ++itr) + mask |= itr->first; + + return mask; +} + +uint32 Unit::GetMechanicImmunityMask() const +{ + uint32 mask = 0; + SpellImmuneContainer const& mechanicList = m_spellImmune[IMMUNITY_MECHANIC]; + for (auto itr = mechanicList.begin(); itr != mechanicList.end(); ++itr) + mask |= (1 << itr->first); + + return mask; +} + +bool Unit::IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index, WorldObject const* caster, + bool requireImmunityPurgesEffectAttribute /*= false*/) const +{ + if (!spellInfo || !spellInfo->Effects[index].IsEffect()) + return false; + + if (spellInfo->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES)) + return false; + + auto hasImmunity = [requireImmunityPurgesEffectAttribute](SpellImmuneContainer const& container, uint32 key) + { + Trinity::IteratorPair range = Trinity::Containers::MapEqualRange(container, key); + if (!requireImmunityPurgesEffectAttribute) + return range.begin() != range.end(); + + return std::any_of(range.begin(), range.end(), [](SpellImmuneContainer::value_type const& entry) + { + if (SpellInfo const* immunitySourceSpell = sSpellMgr->GetSpellInfo(entry.second)) + if (immunitySourceSpell->HasAttribute(SPELL_ATTR1_IMMUNITY_PURGES_EFFECT)) + return true; + + return false; + }); + }; + + + // If m_immuneToEffect type contain this effect type, IMMUNE effect. + uint32 effect = spellInfo->Effects[index].Effect; + SpellImmuneContainer const& effectList = m_spellImmune[IMMUNITY_EFFECT]; + if (hasImmunity(effectList, effect)) + return true; + + if (uint32 mechanic = spellInfo->Effects[index].Mechanic) + { + SpellImmuneContainer const& mechanicList = m_spellImmune[IMMUNITY_MECHANIC]; + if (hasImmunity(mechanicList, mechanic)) + return true; + } + + if (!spellInfo->HasAttribute(SPELL_ATTR3_ALWAYS_HIT)) + { + if (uint32 aura = spellInfo->Effects[index].ApplyAuraName) + { + SpellImmuneContainer const& list = m_spellImmune[IMMUNITY_STATE]; + if (hasImmunity(list, aura)) + return true; + + if (!spellInfo->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES)) + { + // Check for immune to application of harmful magical effects + AuraEffectList const& immuneAuraApply = GetAuraEffectsByType(SPELL_AURA_MOD_IMMUNE_AURA_APPLY_SCHOOL); + for (AuraEffectList::const_iterator iter = immuneAuraApply.begin(); iter != immuneAuraApply.end(); ++iter) + if (((*iter)->GetMiscValue() & spellInfo->GetSchoolMask()) && // Check school + ((caster && !IsFriendlyTo(caster)) || !spellInfo->IsPositiveEffect(index))) // Harmful + return true; + } + } + } + + return false; +} + +int32 Unit::MeleeDamageBonusDone(Unit* victim, int32 damage, WeaponAttackType attType, DamageEffectType damagetype, SpellInfo const* spellProto /*= nullptr*/, Mechanics mechanic /*= MECHANIC_NONE*/, SpellSchoolMask damageSchoolMask /*= SPELL_SCHOOL_MASK_NORMAL*/, Spell* spell /*= nullptr*/, AuraEffect const* aurEff /*= nullptr*/) +{ + if (!victim || damage == 0) + return 0; + + 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 const normalized = spellProto && spellProto->HasEffect(SPELL_EFFECT_NORMALIZED_WEAPON_DMG); + DoneFlatBenefit += int32(APbonus / 14.0f * GetAPMultiplier(attType, normalized)); + } + + // Done total percent damage auras + float DoneTotalMod = 1.0f; + + SpellSchoolMask schoolMask = spellProto ? spellProto->GetSchoolMask() : damageSchoolMask; + + if (!(schoolMask & SPELL_SCHOOL_MASK_NORMAL)) + { + // Some spells don't benefit from pct done mods + // mods for SPELL_SCHOOL_MASK_NORMAL are already factored in base melee damage calculation + if (!spellProto || !spellProto->HasAttribute(SPELL_ATTR6_IGNORE_CASTER_DAMAGE_MODIFIERS)) + { + float maxModDamagePercentSchool = 0.0f; + if (GetTypeId() == TYPEID_PLAYER) + { + for (uint32 i = SPELL_SCHOOL_HOLY; i < MAX_SPELL_SCHOOL; ++i) + if (schoolMask & (1 << i)) + maxModDamagePercentSchool = std::max(maxModDamagePercentSchool, GetFloatValue(PLAYER_FIELD_MOD_DAMAGE_DONE_PCT + i)); + } + else if (spellProto) + maxModDamagePercentSchool = GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_DAMAGE_PERCENT_DONE, spellProto->GetSchoolMask()); + + DoneTotalMod *= maxModDamagePercentSchool; + } + } + + DoneTotalMod *= GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_DAMAGE_DONE_VERSUS, creatureTypeMask); + + // bonus against aurastate + DoneTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_DAMAGE_DONE_VERSUS_AURASTATE, [victim](AuraEffect const* aurEff) -> bool + { + if (victim->HasAuraState(AuraStateType(aurEff->GetMiscValue()))) + return true; + return false; + }); + + // Add SPELL_AURA_MOD_DAMAGE_DONE_FOR_MECHANIC percent bonus + // Add SPELL_AURA_MOD_DAMAGE_DONE_FOR_MECHANIC percent bonus + if (mechanic != MECHANIC_NONE) + AddPct(DoneTotalMod, GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_DAMAGE_DONE_FOR_MECHANIC, mechanic)); + else if (spellProto && spellProto->Mechanic) + AddPct(DoneTotalMod, GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_DAMAGE_DONE_FOR_MECHANIC, spellProto->Mechanic)); + + if (!spellProto) + { + // Add SPELL_AURA_MOD_AUTOATTACK_DAMAGE percent bonus + AddPct(DoneTotalMod, GetTotalAuraModifier(SPELL_AURA_MOD_AUTOATTACK_DAMAGE)); + + // Add SPELL_AURA_MOD_MELEE_DAMAGE_FROM_CASTER percent bonus + // This aura name is a bit misleading but it's actually only suposed to increase melee auto attack damage + AddPct(DoneTotalMod, victim->GetTotalAuraMultiplier(SPELL_AURA_MOD_MELEE_DAMAGE_FROM_CASTER, [this](AuraEffect const* effect)->bool + { + return effect->GetCasterGUID() == GetGUID(); + })); + } + + if (spell) + spell->CallScriptCalcDamageHandlers(victim, damage, DoneFlatBenefit, DoneTotalMod); + else if (aurEff) + aurEff->GetBase()->CallScriptCalcDamageAndHealingHandlers(aurEff, aurEff->GetBase()->GetApplicationOfTarget(victim->GetGUID()), victim, damage, DoneFlatBenefit, DoneTotalMod); + + float damageF = float(damage + DoneFlatBenefit) * DoneTotalMod; + + // apply spellmod to Done damage + if (spellProto) + if (Player* modOwner = GetSpellModOwner()) + modOwner->ApplySpellMod(spellProto, damagetype == DOT ? SpellModOp::PeriodicHealingAndDamage : SpellModOp::HealingAndDamage, damageF); + + // bonus result can be negative + return int32(std::max(damageF, 0.0f)); +} + +int32 Unit::MeleeDamageBonusTaken(Unit* attacker, int32 pdamage, WeaponAttackType attType, SpellInfo const* spellProto /*= nullptr*/, SpellSchoolMask damageSchoolMask /*= SPELL_SCHOOL_MASK_NORMAL*/) +{ + if (pdamage == 0) + return 0; + + int32 TakenFlatBenefit = 0; + + uint32 meleeDamageSchoolMask = spellProto ? spellProto->GetSchoolMask() : attacker->GetMeleeDamageSchoolMask(); + + // ..taken + TakenFlatBenefit += GetTotalAuraModifier(SPELL_AURA_MOD_DAMAGE_TAKEN, [meleeDamageSchoolMask, spellProto](AuraEffect const* aurEff) + { + if (spellProto && spellProto->HasAttribute(SPELL_ATTR10_IGNORE_POSITIVE_DAMAGE_TAKEN_MODS) && aurEff->GetAmount() > 0) + return false; + + if ((aurEff->GetMiscValue() & meleeDamageSchoolMask) != 0) + return true; + + return false; + }); + + AuraType modDamageTaken = SPELL_AURA_MOD_RANGED_DAMAGE_TAKEN; + if (attType != RANGED_ATTACK) + modDamageTaken = SPELL_AURA_MOD_MELEE_DAMAGE_TAKEN; + + TakenFlatBenefit += GetTotalAuraModifier(modDamageTaken, [spellProto](AuraEffect const* aurEff) + { + if (spellProto && spellProto->HasAttribute(SPELL_ATTR10_IGNORE_POSITIVE_DAMAGE_TAKEN_MODS) && aurEff->GetAmount() > 0) + return false; + + return true; + }); + + if ((TakenFlatBenefit < 0) && (pdamage < -TakenFlatBenefit)) + return 0; + + // Taken total percent damage auras + float TakenTotalMod = 1.0f; + + // ..taken + TakenTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN, [meleeDamageSchoolMask, spellProto](AuraEffect const* aurEff) + { + if (spellProto && spellProto->HasAttribute(SPELL_ATTR10_IGNORE_POSITIVE_DAMAGE_TAKEN_MODS) && aurEff->GetAmount() > 0) + return false; + + if ((aurEff->GetMiscValue() & meleeDamageSchoolMask) != 0) + return true; + + return false; + }); + + // .. taken pct (special attacks) + if (spellProto) + { + // From caster spells + TakenTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_DAMAGE_FROM_CASTER, [attacker, spellProto](AuraEffect const* aurEff) -> bool + { + if (spellProto && spellProto->HasAttribute(SPELL_ATTR10_IGNORE_POSITIVE_DAMAGE_TAKEN_MODS) && aurEff->GetAmount() > 0) + return false; + + if (aurEff->GetCasterGUID() == attacker->GetGUID() && aurEff->IsAffectingSpell(spellProto)) + return true; + return false; + }); + + // 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, spellProto](AuraEffect const* aurEff) -> bool + { + if (spellProto && spellProto->HasAttribute(SPELL_ATTR10_IGNORE_POSITIVE_DAMAGE_TAKEN_MODS) && aurEff->GetAmount() > 0) + return false; + + if (mechanicMask & uint32(1 << (aurEff->GetMiscValue()))) + return true; + return false; + }); + } + } + + // .. taken pct: dummy auras + AuraEffectList const& mDummyAuras = GetAuraEffectsByType(SPELL_AURA_DUMMY); + for (AuraEffectList::const_iterator i = mDummyAuras.begin(); i != mDummyAuras.end(); ++i) + { + switch ((*i)->GetSpellInfo()->SpellIconID) + { + // Cheat Death + case 2109: + if ((*i)->GetMiscValue() & SPELL_SCHOOL_MASK_NORMAL) + { + if (Player* player = ToPlayer()) + { + float mod = player->GetRatingBonusValue(CR_RESILIENCE_PLAYER_DAMAGE_TAKEN) * (-8.0f); + AddPct(TakenTotalMod, std::max(mod, float((*i)->GetAmount()))); + } + } + break; + } + } + + + AuraType modDamageTakenPct = SPELL_AURA_MOD_RANGED_DAMAGE_TAKEN_PCT; + if (attType != RANGED_ATTACK) + modDamageTakenPct = SPELL_AURA_MOD_MELEE_DAMAGE_TAKEN_PCT; + + TakenTotalMod *= GetTotalAuraMultiplier(modDamageTakenPct, [spellProto](AuraEffect const* aurEff) + { + if (spellProto && spellProto->HasAttribute(SPELL_ATTR10_IGNORE_POSITIVE_DAMAGE_TAKEN_MODS) && aurEff->GetAmount() > 0) + return false; + + return true; + }); + + // Sanctified Wrath (bypass damage reduction) + if (TakenTotalMod < 1.0f) + { + SpellSchoolMask const attackSchoolMask = spellProto ? spellProto->GetSchoolMask() : damageSchoolMask; + + float damageReduction = 1.0f - TakenTotalMod; + Unit::AuraEffectList const& casterIgnoreResist = attacker->GetAuraEffectsByType(SPELL_AURA_MOD_IGNORE_TARGET_RESIST); + for (AuraEffect const* aurEff : casterIgnoreResist) + { + if (!(aurEff->GetMiscValue() & attackSchoolMask)) + continue; + + AddPct(damageReduction, -aurEff->GetAmount()); + } + + TakenTotalMod = 1.0f - damageReduction; + } + + return int32(std::max(std::ceil(static_cast(pdamage + TakenFlatBenefit) * TakenTotalMod), 0.0f)); +} + +void Unit::ApplySpellImmune(uint32 spellId, uint32 op, uint32 type, bool apply) +{ + if (apply) + m_spellImmune[op].emplace(type, spellId); + else + { + auto bounds = m_spellImmune[op].equal_range(type); + for (auto itr = bounds.first; itr != bounds.second;) + { + if (itr->second == spellId) + itr = m_spellImmune[op].erase(itr); + else + ++itr; + } + } +} + +float Unit::GetWeaponProcChance() const +{ + // normalized proc chance for weapon attack speed + // (odd formula...) + if (isAttackReady(BASE_ATTACK)) + return (GetBaseAttackTime(BASE_ATTACK) * 1.8f / 1000.0f); + else if (haveOffhandWeapon() && isAttackReady(OFF_ATTACK)) + return (GetBaseAttackTime(OFF_ATTACK) * 1.6f / 1000.0f); + return 0; +} + +float Unit::GetPPMProcChance(uint32 WeaponSpeed, float PPM, SpellInfo const* spellProto) const +{ + // proc per minute chance calculation + if (PPM <= 0) + return 0.0f; + + // Apply chance modifer aura + if (spellProto) + if (Player* modOwner = GetSpellModOwner()) + modOwner->ApplySpellMod(spellProto, SpellModOp::ProcFrequency, PPM); + + return std::floor((WeaponSpeed * PPM) / 600.0f); // result is chance in percents (probability = Speed_in_sec * (PPM / 60)) +} + +void Unit::Mount(uint32 mount, uint32 VehicleId, uint32 creatureEntry) +{ + if (mount) + SetUInt32Value(UNIT_FIELD_MOUNTDISPLAYID, mount); + + SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_MOUNT); + + if (Player* player = ToPlayer()) + { + // mount as a vehicle + if (VehicleId) + { + if (CreateVehicleKit(VehicleId, creatureEntry)) + { + player->SendOnCancelExpectedVehicleRideAura(); + + // 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->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_STUNNED); + else + player->UnsummonPetTemporaryIfAny(); + } + + // if we have charmed npc, stun him also (everywhere) + if (Unit* charm = player->GetCharmed()) + if (charm->GetTypeId() == TYPEID_UNIT) + charm->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_STUNNED); + + if (player->IsInWorld()) + player->SendMovementSetCollisionHeight(player->GetCollisionHeight(), UPDATE_COLLISION_HEIGHT_MOUNT); + } + + RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Mount); +} + +void Unit::Dismount() +{ + if (!IsMounted()) + return; + + SetUInt32Value(UNIT_FIELD_MOUNTDISPLAYID, 0); + RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_MOUNT); + + if (Player* thisPlayer = ToPlayer()) + thisPlayer->SendMovementSetCollisionHeight(thisPlayer->GetCollisionHeight(), UPDATE_COLLISION_HEIGHT_MOUNT); + + WorldPackets::Misc::Dismount packet; + packet.Guid = GetGUID(); + SendMessageToSet(packet.Write(), IsPlayer()); + + // dismount as a vehicle + if (GetTypeId() == TYPEID_PLAYER && GetVehicleKit()) + RemoveVehicleKit(); + + RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Dismount); + + // only resummon old pet if the player is already added to a map + // this prevents adding a pet to a not created map which would otherwise cause a crash + // (it could probably happen when logging in after a previous crash) + if (Player* player = ToPlayer()) + { + if (Pet* pPet = player->GetPet()) + { + if (pPet->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_STUNNED) && !pPet->HasUnitState(UNIT_STATE_STUNNED)) + pPet->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_STUNNED); + } + else + player->ResummonPetTemporaryUnSummonedIfAny(); + + // if we have charmed npc, remove stun also + if (Unit* charm = player->GetCharmed()) + if (charm->GetTypeId() == TYPEID_UNIT && charm->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_STUNNED) && !charm->HasUnitState(UNIT_STATE_STUNNED)) + charm->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_STUNNED); + } +} + +MountCapabilityEntry const* Unit::GetMountCapability(uint32 mountType) const +{ + if (!mountType) + return nullptr; + + MountTypeEntry const* mountTypeEntry = sMountTypeStore.LookupEntry(mountType); + if (!mountTypeEntry) + return nullptr; + + + uint32 areaId = GetAreaId(); + uint32 ridingSkill = 5000; + EnumFlag mountFlags = AreaMountFlags::None; + bool isUnderwater = false; + bool isInWater = false; + + if (GetTypeId() == TYPEID_PLAYER) + ridingSkill = ToPlayer()->GetSkillValue(SKILL_RIDING); + + if (HasAuraType(SPELL_AURA_MOUNT_RESTRICTIONS)) + { + for (AuraEffect const* auraEffect : GetAuraEffectsByType(SPELL_AURA_MOUNT_RESTRICTIONS)) + mountFlags |= AreaMountFlags(auraEffect->GetMiscValue()); + } + else if (AreaTableEntry const* areaTable = sAreaTableStore.LookupEntry(areaId)) + mountFlags = areaTable->GetMountFlags(); + + ZLiquidStatus liquidStatus = GetLiquidStatus(); + isUnderwater = (liquidStatus & LIQUID_MAP_UNDER_WATER) != 0; + isInWater = (liquidStatus & LIQUID_MAP_IN_WATER) != 0; + + for (uint32 i = MAX_MOUNT_CAPABILITIES; i > 0; --i) + { + MountCapabilityEntry const* mountCapability = sMountCapabilityStore.LookupEntry(mountTypeEntry->Capability[i - 1]); + if (!mountCapability) + continue; + + if (ridingSkill < mountCapability->ReqRidingSkill) + continue; + + if (!(mountCapability->Flags & MOUNT_CAPABIILTY_FLAG_IGNORE_RESTRICTIONS)) + { + if (mountCapability->Flags & MOUNT_CAPABILITY_FLAG_GROUND && !(mountFlags.HasFlag(AreaMountFlags::AllowGroundMounts))) + continue; + if (mountCapability->Flags & MOUNT_CAPABILITY_FLAG_FLYING && !(mountFlags.HasFlag(AreaMountFlags::AllowFlyingMounts))) + continue; + if (mountCapability->Flags & MOUNT_CAPABILITY_FLAG_FLOAT && !(mountFlags.HasFlag(AreaMountFlags::AllowSurfaceSwimmingMounts))) + continue; + if (mountCapability->Flags & MOUNT_CAPABILITY_FLAG_UNDERWATER && !(mountFlags.HasFlag(AreaMountFlags::AllowUnderwaterSwimmingMounts))) + continue; + } + + // Do not allow underwater restricted capabilities when not underwater + if (!(mountCapability->Flags & MOUNT_CAPABILITY_FLAG_UNDERWATER) && isUnderwater) + continue; + + // Do not allow water surface restricted capabilities when not on water surface + if (!(mountCapability->Flags & MOUNT_CAPABILITY_FLAG_FLOAT) && isInWater) + continue; + + // Do not allow liquid restricted mounts on ground or in air + if (!(mountCapability->Flags & (MOUNT_CAPABILITY_FLAG_GROUND | MOUNT_CAPABILITY_FLAG_FLYING)) && !isUnderwater && !isInWater) + continue; + + if (mountCapability->ReqMapID != -1 && + int32(GetMapId()) != mountCapability->ReqMapID && + GetMap()->GetEntry()->ParentMapID != mountCapability->ReqMapID) + continue; + + if (mountCapability->ReqAreaID && !DBCManager::IsInArea(areaId, mountCapability->ReqAreaID)) + continue; + + if (mountCapability->ReqSpellAuraID && !HasAura(mountCapability->ReqSpellAuraID)) + continue; + + if (mountCapability->ReqSpellKnownID && !HasSpell(mountCapability->ReqSpellKnownID)) + continue; + + return mountCapability; + } + + return nullptr; +} + +void Unit::UpdateMountCapability() +{ + AuraEffectList mounts = GetAuraEffectsByType(SPELL_AURA_MOUNTED); + for (AuraEffect* aurEff : mounts) + { + aurEff->RecalculateAmount(); + if (!aurEff->GetAmount()) + aurEff->GetBase()->Remove(); + else if (MountCapabilityEntry const* capability = sMountCapabilityStore.LookupEntry(aurEff->GetAmount())) // aura may get removed by interrupt flag, reapply + if (!HasAura(capability->ModSpellAuraID)) + CastSpell(this, capability->ModSpellAuraID, aurEff); + } +} + +bool Unit::IsServiceProvider() const +{ + return HasFlag(UNIT_NPC_FLAGS, + UNIT_NPC_FLAG_VENDOR | UNIT_NPC_FLAG_TRAINER | UNIT_NPC_FLAG_FLIGHTMASTER | + UNIT_NPC_FLAG_PETITIONER | UNIT_NPC_FLAG_BATTLEMASTER | UNIT_NPC_FLAG_BANKER | + UNIT_NPC_FLAG_INNKEEPER | UNIT_NPC_FLAG_SPIRITHEALER | + UNIT_NPC_FLAG_SPIRITGUIDE | UNIT_NPC_FLAG_TABARDDESIGNER | UNIT_NPC_FLAG_AUCTIONEER); +} + +void Unit::EngageWithTarget(Unit* enemy) +{ + if (!enemy) + return; + + if (CanHaveThreatList()) + m_threatManager.AddThreat(enemy, 0.0f, nullptr, true, true); + else + SetInCombatWith(enemy); +} + +void Unit::SetImmuneToAll(bool apply, bool keepCombat) +{ + if (apply) + { + SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC | UNIT_FLAG_IMMUNE_TO_NPC); + ValidateAttackersAndOwnTarget(); + if (!keepCombat) + m_combatManager.EndAllCombat(); + } + else + RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC | UNIT_FLAG_IMMUNE_TO_NPC); +} + +void Unit::SetImmuneToPC(bool apply, bool keepCombat) +{ + if (apply) + { + SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC); + ValidateAttackersAndOwnTarget(); + if (!keepCombat) + { + std::list toEnd; + for (auto const& pair : m_combatManager.GetPvECombatRefs()) + if (pair.second->GetOther(this)->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED)) + toEnd.push_back(pair.second); + for (auto const& pair : m_combatManager.GetPvPCombatRefs()) + if (pair.second->GetOther(this)->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED)) + toEnd.push_back(pair.second); + for (CombatReference* ref : toEnd) + ref->EndCombat(); + } + } + else + RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC); +} + +void Unit::SetImmuneToNPC(bool apply, bool keepCombat) +{ + if (apply) + { + SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_NPC); + ValidateAttackersAndOwnTarget(); + if (!keepCombat) + { + std::list toEnd; + for (auto const& pair : m_combatManager.GetPvECombatRefs()) + if (!pair.second->GetOther(this)->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED)) + toEnd.push_back(pair.second); + for (auto const& pair : m_combatManager.GetPvPCombatRefs()) + if (!pair.second->GetOther(this)->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED)) + toEnd.push_back(pair.second); + for (CombatReference* ref : toEnd) + ref->EndCombat(); + } + } + else + RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_NPC); +} + +bool Unit::IsThreatened() const +{ + return !m_threatManager.IsThreatListEmpty(); +} + +bool Unit::isTargetableForAttack(bool checkFakeDeath) const +{ + if (!IsAlive()) + return false; + + if (HasFlag(UNIT_FIELD_FLAGS, + UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NOT_SELECTABLE)) + return false; + + if (GetTypeId() == TYPEID_PLAYER && ToPlayer()->IsGameMaster()) + return false; + + return !HasUnitState(UNIT_STATE_UNATTACKABLE) && (!checkFakeDeath || !HasUnitState(UNIT_STATE_DIED)); +} + +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(PowerType power, int32 dVal, bool withPowerUpdate /*= true*/) +{ + int32 gain = 0; + + if (dVal == 0) + return 0; + + int32 curPower = GetPower(power); + + int32 val = dVal + curPower; + if (val <= GetMinPower(power)) + { + SetPower(power, GetMinPower(power), withPowerUpdate); + return -curPower; + } + + int32 maxPower = GetMaxPower(power); + + if (val < maxPower) + { + SetPower(power, val, withPowerUpdate); + gain = val - curPower; + } + else if (curPower != maxPower) + { + SetPower(power, maxPower, withPowerUpdate); + gain = maxPower - curPower; + } + + return gain; +} + +// Based on client function +float Unit::GetPowerRegen(PowerType powerType, bool isInCombat) const +{ + uint32 powerSlot = MAX_POWERS; + float totalRegeneration = 0.f; + double result = DBCManager::GetBasePowerRegen(powerType, isInCombat, _powerBarId); + float combatRegeneration = result; + + powerSlot = GetPowerIndex(powerType); + if (powerSlot != MAX_POWERS) + { + uint32 powerRegenIndex = isInCombat ? UNIT_FIELD_POWER_REGEN_INTERRUPTED_FLAT_MODIFIER : UNIT_FIELD_POWER_REGEN_FLAT_MODIFIER; + totalRegeneration = GetFloatValue(powerRegenIndex + powerSlot) + combatRegeneration; + result = totalRegeneration; + } + else + totalRegeneration = result; + + if (IsPlayer() && DBCManager::IsPowerTypeAffectedByHaste(powerType)) + { + float hasteRegen = GetFloatValue(PLAYER_FIELD_MOD_HASTE_REGEN); + if (G3D::fuzzyNe(hasteRegen, 0.f)) + result = totalRegeneration / hasteRegen; + } + + return result; +} + +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; + + return false; +} + +bool Unit::IsVisible() const +{ + return (m_serverSideVisibility.GetValue(SERVERSIDE_VISIBILITY_GM) > SEC_PLAYER) ? false : true; +} + +void Unit::SetVisible(bool x) +{ + if (!x) + m_serverSideVisibility.SetValue(SERVERSIDE_VISIBILITY_GM, SEC_GAMEMASTER); + else + m_serverSideVisibility.SetValue(SERVERSIDE_VISIBILITY_GM, SEC_PLAYER); + + UpdateObjectVisibility(); +} + +void Unit::UpdateSpeed(UnitMoveType mtype) +{ + int32 main_speed_mod = 0; + float stack_bonus = 1.0f; + float non_stack_bonus = 1.0f; + + switch (mtype) + { + // Only apply debuffs + case MOVE_FLIGHT_BACK: + case MOVE_RUN_BACK: + case MOVE_SWIM_BACK: + break; + case MOVE_WALK: + return; + case MOVE_RUN: + { + if (IsMounted()) // Use on mount auras + { + main_speed_mod = GetMaxPositiveAuraModifier(SPELL_AURA_MOD_INCREASE_MOUNTED_SPEED); + stack_bonus = GetTotalAuraMultiplier(SPELL_AURA_MOD_MOUNTED_SPEED_ALWAYS); + non_stack_bonus += GetMaxPositiveAuraModifier(SPELL_AURA_MOD_MOUNTED_SPEED_NOT_STACK) / 100.0f; + } + else + { + main_speed_mod = GetMaxPositiveAuraModifier(SPELL_AURA_MOD_INCREASE_SPEED); + stack_bonus = GetTotalAuraMultiplier(SPELL_AURA_MOD_SPEED_ALWAYS); + non_stack_bonus += GetMaxPositiveAuraModifier(SPELL_AURA_MOD_SPEED_NOT_STACK) / 100.0f; + } + break; + } + case MOVE_SWIM: + { + main_speed_mod = GetMaxPositiveAuraModifier(SPELL_AURA_MOD_INCREASE_SWIM_SPEED); + break; + } + case MOVE_FLIGHT: + { + if (GetTypeId() == TYPEID_UNIT && IsControlledByPlayer()) // not sure if good for pet + { + main_speed_mod = GetMaxPositiveAuraModifier(SPELL_AURA_MOD_INCREASE_VEHICLE_FLIGHT_SPEED); + stack_bonus = GetTotalAuraMultiplier(SPELL_AURA_MOD_VEHICLE_SPEED_ALWAYS); + + // for some spells this mod is applied on vehicle owner + int32 owner_speed_mod = 0; + + if (Unit* owner = GetCharmer()) + owner_speed_mod = owner->GetMaxPositiveAuraModifier(SPELL_AURA_MOD_INCREASE_VEHICLE_FLIGHT_SPEED); + + main_speed_mod = std::max(main_speed_mod, owner_speed_mod); + } + else if (IsMounted()) + { + main_speed_mod = GetMaxPositiveAuraModifier(SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED); + stack_bonus = GetTotalAuraMultiplier(SPELL_AURA_MOD_MOUNTED_FLIGHT_SPEED_ALWAYS); + } + else // Use not mount (shapeshift for example) auras (should stack) + main_speed_mod = GetTotalAuraModifier(SPELL_AURA_MOD_INCREASE_FLIGHT_SPEED) + GetTotalAuraModifier(SPELL_AURA_MOD_INCREASE_VEHICLE_FLIGHT_SPEED); + + non_stack_bonus += GetMaxPositiveAuraModifier(SPELL_AURA_MOD_FLIGHT_SPEED_NOT_STACK) / 100.0f; + + // Update speed for vehicle if available + if (GetTypeId() == TYPEID_PLAYER && GetVehicle()) + GetVehicleBase()->UpdateSpeed(MOVE_FLIGHT); + break; + } + default: + TC_LOG_ERROR("entities.unit", "Unit::UpdateSpeed: Unsupported move type (%d)", 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()) + { + CreatureMovementInfo const& movementInfo = ToCreature()->GetCreatureMovementInfo(); + speed *= movementInfo.HasRunSpeedOverriden ? (movementInfo.RunSpeed / baseMoveSpeed[MOVE_RUN]) : 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; + } + + // Apply strongest slow aura mod to speed + int32 slow = GetMaxNegativeAuraModifier(SPELL_AURA_MOD_DECREASE_SPEED); + if (slow) + AddPct(speed, slow); + + if (float minSpeedMod = (float)GetMaxPositiveAuraModifier(SPELL_AURA_MOD_MINIMUM_SPEED)) + { + float baseMinSpeed = 1.0f; + if (!GetOwnerOrCreatorGUID().IsPlayer() && !IsHunterPet() && IsCreature()) + { + CreatureMovementInfo const& movementInfo = ToCreature()->GetCreatureMovementInfo(); + baseMinSpeed = movementInfo.HasRunSpeedOverriden ? (movementInfo.RunSpeed / baseMoveSpeed[MOVE_RUN]) : ToCreature()->GetCreatureTemplate()->speed_run; + } + + float min_speed = CalculatePct(baseMinSpeed, minSpeedMod); + if (speed < min_speed) + speed = min_speed; + } + + SetSpeedRate(mtype, speed); +} + +float Unit::GetSpeed(UnitMoveType mtype) const +{ + return m_speed_rate[mtype]*(IsControlledByPlayer() ? playerBaseMoveSpeed[mtype] : baseMoveSpeed[mtype]); +} + +void Unit::SetSpeed(UnitMoveType mtype, float newValue) +{ + SetSpeedRate(mtype, newValue / (IsControlledByPlayer() ? playerBaseMoveSpeed[mtype] : baseMoveSpeed[mtype])); +} + +void Unit::SetSpeedRate(UnitMoveType mtype, float rate) +{ + if (rate < 0) + rate = 0.0f; + + // Update speed only on change + if (m_speed_rate[mtype] == rate) + return; + + + float newSpeedFlat = rate * (IsControlledByPlayer() ? playerBaseMoveSpeed[mtype] : baseMoveSpeed[mtype]); + if (IsMovedByClient() && IsInWorld()) + { + MovementPacketSender::SendSpeedChangeToMover(this, mtype, newSpeedFlat); + SetSpeedRateReal(mtype, rate); + } + else if (IsMovedByClient() && !IsInWorld()) // (1) + SetSpeedRateReal(mtype, rate); + else // <=> if(!IsMovedByPlayer()) + { + SetSpeedRateReal(mtype, rate); + MovementPacketSender::SendSpeedChangeToAll(this, mtype, newSpeedFlat); + } + + // explaination of (1): + // If the player is not in the world yet, it won't reply to the packets requiring an ack. And once the player is in the world, next time a movement + // packet which requires an ack is sent to the client (change of speed for example), the client is kicked from the + // server on the ground that it should have replied to the first packet first. That line is a hacky fix + // in the sense that it doesn't work like that in retail since buffs are applied only after the player has been + // initialized in the world. cf description of PR #18771 +} + +void Unit::SetSpeedRateReal(UnitMoveType mtype, float rate) +{ + if (!IsInCombat() && ToPlayer()) + if (Pet* pet = ToPlayer()->GetPet()) + pet->SetSpeedRate(mtype, rate); + + m_speed_rate[mtype] = rate; + PropagateSpeedChange(); +} + +void Unit::FollowTarget(Unit* target) +{ + if (!target) + { + TC_LOG_ERROR("entities.unit", "Unit::FollowTarget: Unit (%s) tried to follow a non-existant target.", GetGUID().ToString().c_str()); + return; + } + + // Determine follow configuration + bool joinFormation = false; // unit will follow its target in a generated formation shape and catches up to its target + bool catchUpToTarget = false; // unit will allign to the target speed and catches up to the target automatically + bool faceTarget = false; // unit will face its target with every spline + float distance = DEFAULT_FOLLOW_DISTANCE_PET; + + if (TempSummon* summon = ToTempSummon()) + { + if (SummonPropertiesEntry const* properties = summon->m_Properties) + { + // Allied summons, pet summons join a formation unless the following exceptions are being met. + if (properties->Control == SUMMON_CATEGORY_ALLY || properties->Control == SUMMON_CATEGORY_PET) + joinFormation = true; + + // Companion minipets will always be able to catch up to their target + if (properties->Slot == SUMMON_SLOT_MINIPET) + { + joinFormation = false; + catchUpToTarget = true; + faceTarget = true; + distance = DEFAULT_FOLLOW_DISTANCE; + } + + // Quest npcs follow their target outside of formations + if (properties->Slot == SUMMON_SLOT_QUEST) + { + joinFormation = false; + distance = DEFAULT_FOLLOW_DISTANCE; + } + } + + // Pets and minions alwys move in a formation of their target + if (summon->IsPet()) + joinFormation = true; + } + else if (IsCharmed()) + joinFormation = true; + + // Unit is already following its target + if (joinFormation && target->HasFormationFollower(this)) + return; + + GetMotionMaster()->MoveFollow(target, distance, DEFAULT_FOLLOW_ANGLE, joinFormation, catchUpToTarget, faceTarget); +} + +void Unit::RemoveFormationFollower(Unit* follower) +{ + for (FormationFollowerGUIDContainer::const_iterator itr = _formationFollowers.begin(); itr != _formationFollowers.end();) + { + ObjectGuid guid = *itr; + if (Unit* followingUnit = ObjectAccessor::GetUnit(*this, guid)) + { + if (followingUnit == follower) + itr = _formationFollowers.erase(itr); + else + ++itr; + } + else + itr = _formationFollowers.erase(itr); + } +} + +bool Unit::HasFormationFollower(Unit* follower) const +{ + for (ObjectGuid guid : _formationFollowers) + if (Unit* followingUnit = ObjectAccessor::GetUnit(*this, guid)) + if (followingUnit == follower) + return true; + + return false; +} + +void Unit::RemoveAllPursuers() +{ + for (uint8 i = 0; i < AsUnderlyingType(PursuingType::Max); ++i) + { + while (!_unitsPursuingMe[i].empty()) + (*_unitsPursuingMe[i].begin())->SetTarget(PursuingType(i), nullptr); + } +} + +void Unit::setDeathState(DeathState s) +{ + // Death state needs to be updated before RemoveAllAurasOnDeath() is called, to prevent entering combat + m_deathState = s; + + if (s != ALIVE && s != JUST_RESPAWNED) + { + CombatStop(); + ClearComboPointHolders(); // any combo points pointed to unit lost at it death + + if (IsNonMeleeSpellCast(false)) + InterruptNonMeleeSpells(false); + + ExitVehicle(); // Exit vehicle before calling RemoveAllControlled + // vehicles use special type of charm that is not removed by the next function + // triggering an assert + UnsummonAllTotems(); + RemoveAllControlled(); + RemoveAllAurasOnDeath(); + } + + if (s == JUST_DIED) + { + // remove aurastates allowing special moves + ClearAllReactives(); + ClearDiminishings(); + CancelPendingCastRequest(); + if (IsInWorld()) + { + // Only clear MotionMaster for entities that exists in world + // Avoids crashes in the following conditions : + // * Using 'call pet' on dead pets + // * Using 'call stabled pet' + // * Logging in with dead pets + GetMotionMaster()->Clear(false); + GetMotionMaster()->MoveIdle(); + } + StopMoving(); + DisableSpline(); + // without this when removing IncreaseMaxHealth aura player may stuck with 1 hp + // do not why since in IncreaseMaxHealth currenthealth is checked + SetHealth(0); + SetPower(GetPowerType(), 0); + SetUInt32Value(UNIT_NPC_EMOTESTATE, 0); + + // players in instance don't have ZoneScript, but they have InstanceScript + if (ZoneScript* zoneScript = GetZoneScript() ? GetZoneScript() : GetInstanceScript()) + zoneScript->OnUnitDeath(this); + } + else if (s == JUST_RESPAWNED) + RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SKINNABLE); // clear skinnable for creature and player (at battleground) +} + +//====================================================================== + +void Unit::AtEnterCombat() +{ + if (Spell* spell = m_currentSpells[CURRENT_GENERIC_SPELL]) + if (spell->getState() == SPELL_STATE_PREPARING + && spell->m_spellInfo->HasAttribute(SPELL_ATTR0_NOT_IN_COMBAT_ONLY_PEACEFUL) + && spell->m_spellInfo->InterruptFlags.HasFlag(SpellInterruptFlags::Combat) + && !spell->m_spellInfo->HasAura(SPELL_AURA_MOUNTED)) // for some reason mounts are not interrupted by entering combat even with both interrupt flag and attribute + InterruptSpell(CURRENT_GENERIC_SPELL, false, false); + + RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::EnteringCombat); + ProcSkillsAndAuras(this, nullptr, PROC_FLAG_ENTER_COMBAT, PROC_FLAG_NONE, PROC_SPELL_TYPE_MASK_ALL, PROC_SPELL_PHASE_NONE, PROC_HIT_NONE, nullptr, nullptr, nullptr); +} + +void Unit::AtExitCombat() +{ + RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::LeavingCombat); +} + +void Unit::AtTargetAttacked(Unit* target, bool canInitialAggro) +{ + if (!target->IsEngaged() && !canInitialAggro) + return; + target->EngageWithTarget(this); + if (Unit* targetOwner = target->GetCharmerOrOwner()) + targetOwner->EngageWithTarget(this); + + Player* myPlayerOwner = GetCharmerOrOwnerPlayerOrPlayerItself(); + Player* targetPlayerOwner = target->GetCharmerOrOwnerPlayerOrPlayerItself(); + if (myPlayerOwner && targetPlayerOwner && !(myPlayerOwner->duel && myPlayerOwner->duel->opponent == targetPlayerOwner)) + { + myPlayerOwner->UpdatePvP(true); + myPlayerOwner->SetContestedPvP(targetPlayerOwner); + myPlayerOwner->RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::PvPActive); + } +} + +void Unit::UpdatePetCombatState() +{ + ASSERT(!IsPet()); // player pets do not use UNIT_FLAG_PET_IN_COMBAT for this purpose - but player pets should also never have minions of their own to call this + + // UNIT_FLAG_PET_IN_COMBAT is set by default when the summoner is in combat by himself + if (IsInCombat()) + return; + + bool state = false; + for (Unit* minion : m_Controlled) + if (minion->IsInCombat()) + { + state = true; + break; + } + + if (state) + SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PET_IN_COMBAT); + else + RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PET_IN_COMBAT); +} + +//====================================================================== + +DiminishingLevels Unit::GetDiminishing(DiminishingGroup group) const +{ + DiminishingReturn const& diminish = m_Diminishing[group]; + if (!diminish.hitCount) + return DIMINISHING_LEVEL_1; + + // If last spell was cast more than 15 seconds ago - reset level + if (!diminish.stack && GetMSTimeDiffToNow(diminish.hitTime) > 15000) + return DIMINISHING_LEVEL_1; + + return DiminishingLevels(diminish.hitCount); +} + +void Unit::IncrDiminishing(SpellInfo const* auraSpellInfo, bool triggered) +{ + DiminishingGroup group = auraSpellInfo->GetDiminishingReturnsGroupForSpell(triggered); + uint32 currentLevel = GetDiminishing(group); + uint32 const maxLevel = auraSpellInfo->GetDiminishingReturnsMaxLevel(triggered); + + DiminishingReturn& diminish = m_Diminishing[group]; + if (currentLevel < maxLevel) + diminish.hitCount = currentLevel + 1; +} + +bool Unit::ApplyDiminishingToDuration(SpellInfo const* auraSpellInfo, bool triggered, int32& duration, WorldObject* caster, DiminishingLevels previousLevel) const +{ + DiminishingGroup const group = auraSpellInfo->GetDiminishingReturnsGroupForSpell(triggered); + if (duration == -1 || group == DIMINISHING_NONE) + return true; + + int32 const limitDuration = auraSpellInfo->GetDiminishingReturnsLimitDuration(triggered); + + // test pet/charm masters instead pets/charmeds + Unit const* targetOwner = GetCharmerOrOwner(); + Unit const* casterOwner = caster->GetCharmerOrOwner(); + + // 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; + WorldObject const* source = casterOwner ? casterOwner : caster; + + if (target->IsAffectedByDiminishingReturns() && source->GetTypeId() == TYPEID_PLAYER) + duration = limitDuration; + } + + float mod = 1.0f; + if (group == DIMINISHING_TAUNT) + { + if (GetTypeId() == TYPEID_UNIT && (ToCreature()->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_TAUNT_DIMINISH)) + { + DiminishingLevels diminish = previousLevel; + switch (diminish) + { + case DIMINISHING_LEVEL_1: break; + case DIMINISHING_LEVEL_2: mod = 0.65f; break; + case DIMINISHING_LEVEL_3: mod = 0.4225f; break; + case DIMINISHING_LEVEL_4: mod = 0.274625f; break; + case DIMINISHING_LEVEL_TAUNT_IMMUNE: mod = 0.0f; break; + default: break; + } + } + } + // Some diminishings applies to mobs too (for example, Stun) + else if (auraSpellInfo->GetDiminishingReturnsGroupType(triggered) == DRTYPE_ALL || + (auraSpellInfo->GetDiminishingReturnsGroupType(triggered) == DRTYPE_PLAYER && + (targetOwner ? targetOwner->IsAffectedByDiminishingReturns() : IsAffectedByDiminishingReturns()))) + { + DiminishingLevels diminish = previousLevel; + switch (diminish) + { + case DIMINISHING_LEVEL_1: break; + case DIMINISHING_LEVEL_2: mod = 0.5f; break; + case DIMINISHING_LEVEL_3: mod = 0.25f; break; + case DIMINISHING_LEVEL_IMMUNE: mod = 0.0f; break; + default: break; + } + } + + duration = int32(duration * mod); + return (duration != 0); +} + +void Unit::ApplyDiminishingAura(DiminishingGroup group, bool apply) +{ + // Checking for existing in the table + DiminishingReturn& diminish = m_Diminishing[group]; + + if (apply) + ++diminish.stack; + else if (diminish.stack) + { + --diminish.stack; + + // Remember time after last aura from group removed + if (!diminish.stack) + diminish.hitTime = getMSTime(); + } +} + +void Unit::ClearDiminishings() +{ + for (DiminishingReturn& dim : m_Diminishing) + dim.Clear(); +} + +uint32 Unit::GetCreatureType() const +{ + if (GetTypeId() == TYPEID_PLAYER) + { + ShapeshiftForm form = GetShapeshiftForm(); + SpellShapeshiftFormEntry const* ssEntry = sSpellShapeshiftFormStore.LookupEntry(form); + if (ssEntry && ssEntry->CreatureType > 0) + return ssEntry->CreatureType; + else + return CREATURE_TYPE_HUMANOID; + } + else + return ToCreature()->GetCreatureTemplate()->type; +} + +uint32 Unit::GetCreatureTypeMask() const +{ + uint32 creatureType = GetCreatureType(); + return (creatureType >= 1) ? (1 << (creatureType - 1)) : 0; +} + +void Unit::SetShapeshiftForm(ShapeshiftForm form) +{ + SetByteValue(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_SHAPESHIFT_FORM, form); +} + +bool Unit::IsInFeralForm() const +{ + ShapeshiftForm form = GetShapeshiftForm(); + return form == FORM_CAT || form == FORM_BEAR; +} + +bool Unit::IsInDisallowedMountForm() const +{ + if (ShapeshiftForm form = GetShapeshiftForm()) + { + SpellShapeshiftFormEntry const* shapeshift = sSpellShapeshiftFormStore.LookupEntry(form); + if (!shapeshift) + return true; + + if (!shapeshift->GetFlags().HasFlag(SpellShapeshiftFormFlags::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->Flags & 0x80)) + if (race && !(race->Flags & 0x4)) + return true; + + return false; +} + +/*####################################### +######## ######## +######## STAT SYSTEM ######## +######## ######## +#######################################*/ + +void Unit::HandleStatFlatModifier(UnitMods unitMod, UnitModifierFlatType modifierType, float amount, bool apply) +{ + if (unitMod >= UNIT_MOD_END || modifierType >= MODIFIER_TYPE_FLAT_END) + { + TC_LOG_ERROR("entities.unit", "ERROR in HandleStatFlatModifier(): non-existing UnitMods or wrong UnitModifierType!"); + return; + } + + if (!amount) + return; + + switch (modifierType) + { + case BASE_VALUE: + case TOTAL_VALUE: + m_auraFlatModifiersGroup[unitMod][modifierType] += apply ? amount : -amount; + break; + default: + break; + } + + UpdateUnitMod(unitMod); +} + +void Unit::ApplyStatPctModifier(UnitMods unitMod, UnitModifierPctType modifierType, float pct) +{ + if (unitMod >= UNIT_MOD_END || modifierType >= MODIFIER_TYPE_PCT_END) + { + TC_LOG_ERROR("entities.unit", "ERROR in ApplyStatPctModifier(): non-existing UnitMods or wrong UnitModifierType!"); + return; + } + + if (!pct) + return; + + switch (modifierType) + { + case BASE_PCT: + case TOTAL_PCT: + AddPct(m_auraPctModifiersGroup[unitMod][modifierType], pct); + break; + default: + break; + } + + UpdateUnitMod(unitMod); +} + +void Unit::SetStatFlatModifier(UnitMods unitMod, UnitModifierFlatType modifierType, float val) +{ + if (m_auraFlatModifiersGroup[unitMod][modifierType] == val) + return; + + m_auraFlatModifiersGroup[unitMod][modifierType] = val; + UpdateUnitMod(unitMod); +} + +void Unit::SetStatPctModifier(UnitMods unitMod, UnitModifierPctType modifierType, float val) +{ + if (m_auraPctModifiersGroup[unitMod][modifierType] == val) + return; + + m_auraPctModifiersGroup[unitMod][modifierType] = val; + UpdateUnitMod(unitMod); +} + +float Unit::GetFlatModifierValue(UnitMods unitMod, UnitModifierFlatType modifierType) const +{ + if (unitMod >= UNIT_MOD_END || modifierType >= MODIFIER_TYPE_FLAT_END) + { + TC_LOG_ERROR("entities.unit", "attempt to access non-existing modifier value from UnitMods!"); + return 0.0f; + } + + return m_auraFlatModifiersGroup[unitMod][modifierType]; +} + +float Unit::GetPctModifierValue(UnitMods unitMod, UnitModifierPctType modifierType) const +{ + if (unitMod >= UNIT_MOD_END || modifierType >= MODIFIER_TYPE_PCT_END) + { + TC_LOG_ERROR("entities.unit", "attempt to access non-existing modifier value from UnitMods!"); + return 0.0f; + } + + return m_auraPctModifiersGroup[unitMod][modifierType]; +} + +void Unit::UpdateUnitMod(UnitMods unitMod) +{ + if (!CanModifyStats()) + return; + + switch (unitMod) + { + case UNIT_MOD_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_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::GetTotalAuraModValue(UnitMods unitMod) const +{ + if (unitMod >= UNIT_MOD_END) + { + TC_LOG_ERROR("entities.unit", "attempt to access non-existing UnitMods in GetTotalAuraModValue()!"); + return 0.0f; + } + + float value = 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; +} + +PowerType 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_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) const +{ + if (attType == RANGED_ATTACK) + { + int32 ap = GetInt32Value(UNIT_FIELD_RANGED_ATTACK_POWER) + + GetInt32Value(UNIT_FIELD_RANGED_ATTACK_POWER_MOD_POS) + - GetInt32Value(UNIT_FIELD_RANGED_ATTACK_POWER_MOD_NEG); + + 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_MOD_POS) + - GetInt32Value(UNIT_FIELD_ATTACK_POWER_MOD_NEG); + + if (ap < 0) + return 0.0f; + return ap * (1.0f + GetFloatValue(UNIT_FIELD_ATTACK_POWER_MULTIPLIER)); + } +} + +float Unit::GetWeaponDamageRange(WeaponAttackType attType, WeaponDamageRange type) const +{ + if (attType == OFF_ATTACK && !haveOffhandWeapon()) + return 0.0f; + + return m_weaponDamage[attType][type]; +} + +bool Unit::CanFreeMove() const +{ + return !HasUnitState(UNIT_STATE_CONFUSED | UNIT_STATE_FLEEING | UNIT_STATE_IN_FLIGHT | + UNIT_STATE_ROOT | UNIT_STATE_STUNNED | UNIT_STATE_DISTRACTED) && GetOwnerOrCreatorGUID().IsEmpty(); +} + +void Unit::SetLevel(uint8 lvl) +{ + SetUInt32Value(UNIT_FIELD_LEVEL, lvl); + + if (Player* player = ToPlayer()) + { + // group update + if (player->GetGroup()) + player->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_LEVEL); + + sCharacterCache->UpdateCharacterLevel(GetGUID(), lvl); + } +} + +void Unit::SetHealth(uint32 val) +{ + if (getDeathState() == JUST_DIED || getDeathState() == CORPSE) + val = 0; + else if (GetTypeId() == TYPEID_PLAYER && getDeathState() == DEAD) + val = 1; + else + { + uint32 maxHealth = GetMaxHealth(); + if (maxHealth < val) + val = maxHealth; + } + + SetUInt32Value(UNIT_FIELD_HEALTH, val); + + // group update + if (Player* player = ToPlayer()) + { + if (player->GetGroup()) + player->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_CUR_HP); + } + else if (Pet* pet = ToCreature()->ToPet()) + { + if (pet->isControlled()) + { + Unit* owner = GetOwner(); + if (owner && (owner->GetTypeId() == TYPEID_PLAYER) && owner->ToPlayer()->GetGroup()) + owner->ToPlayer()->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 (GetTypeId() == TYPEID_PLAYER) + { + if (ToPlayer()->GetGroup()) + ToPlayer()->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_MAX_HP); + } + else if (Pet* pet = ToCreature()->ToPet()) + { + if (pet->isControlled()) + { + Unit* owner = GetOwner(); + if (owner && (owner->GetTypeId() == TYPEID_PLAYER) && owner->ToPlayer()->GetGroup()) + owner->ToPlayer()->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_PET_MAX_HP); + } + } + + if (val < health) + SetHealth(val); +} + +int32 Unit::GetPower(PowerType power) const +{ + uint32 powerIndex = GetPowerIndex(power); + if (powerIndex == MAX_POWERS) + return 0; + + return GetUInt32Value(UNIT_FIELD_POWER1 + powerIndex); +} + +int32 Unit::GetMaxPower(PowerType power) const +{ + uint32 powerIndex = GetPowerIndex(power); + if (powerIndex == MAX_POWERS) + return 0; + + return GetInt32Value(UNIT_FIELD_MAXPOWER1 + powerIndex); +} + +void Unit::SetPower(PowerType power, int32 val, bool withPowerUpdate /*= true*/) +{ + uint32 powerIndex = GetPowerIndex(power); + if (powerIndex == MAX_POWERS) + return; + + int32 maxPower = int32(GetMaxPower(power)); + if (maxPower < val) + val = maxPower; + + SetInt32Value(UNIT_FIELD_POWER1 + powerIndex, val); + + if (IsInWorld() && withPowerUpdate) + { + WorldPackets::Combat::PowerUpdate packet; + packet.Guid = GetGUID(); + packet.Powers.emplace_back(val, power); + SendMessageToSet(packet.Write(), IsPlayer()); + } + + // group update + if (Player* player = ToPlayer()) + { + 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->GetTypeId() == TYPEID_PLAYER) && owner->ToPlayer()->GetGroup()) + owner->ToPlayer()->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_PET_CUR_POWER); + } + } +} + +void Unit::SetMaxPower(PowerType power, int32 val) +{ + uint32 powerIndex = GetPowerIndex(power); + if (powerIndex == MAX_POWERS) + return; + + int32 cur_power = GetPower(power); + SetInt32Value(UNIT_FIELD_MAXPOWER1 + powerIndex, val); + + // group update + if (GetTypeId() == TYPEID_PLAYER) + { + if (ToPlayer()->GetGroup()) + ToPlayer()->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_MAX_POWER); + } + else if (Pet* pet = ToCreature()->ToPet()) + { + if (pet->isControlled()) + { + Unit* owner = GetOwner(); + if (owner && (owner->GetTypeId() == TYPEID_PLAYER) && owner->ToPlayer()->GetGroup()) + owner->ToPlayer()->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_PET_MAX_POWER); + } + } + + if (val < cur_power) + SetPower(power, val); +} + +void Unit::Regenerate(PowerType powerType, uint32 diff) +{ + uint32 maxValue = GetMaxPower(powerType); + if (!maxValue) + return; + + if (!HasFlag(UNIT_FIELD_FLAGS_2, UNIT_FLAG2_REGENERATE_POWER)) + return; + + uint32 curValue = GetPower(powerType); + + // Block power regeneration + if (HasAuraTypeWithValue(SPELL_AURA_PREVENT_REGENERATE_POWER, powerType) || HasAuraType(SPELL_AURA_INTERRUPT_REGEN)) + return; + + // Skip regeneration for power type we cannot have + uint32 powerIndex = GetPowerIndex(powerType); + if (powerIndex == MAX_POWERS) + return; + + float addValue = GetPowerRegen(powerType, IsInCombat()) * (diff / float(IN_MILLISECONDS)); + + // Apply config values + switch (powerType) + { + case POWER_MANA: + addValue *= sWorld->getRate(RATE_POWER_MANA); + break; + case POWER_RAGE: + addValue *= sWorld->getRate(RATE_POWER_RAGE_LOSS); + break; + case POWER_RUNIC_POWER: + addValue *= sWorld->getRate(RATE_POWER_RUNICPOWER_LOSS); + break; + case POWER_FOCUS: + addValue *= sWorld->getRate(RATE_POWER_FOCUS); + break; + case POWER_ENERGY: + addValue *= sWorld->getRate(RATE_POWER_ENERGY); + break; + default: + break; + } + + if (addValue < 0.0f) + { + if (curValue == 0) + return; + } + else if (addValue > 0.0f) + { + if (curValue == maxValue) + return; + } + else + return; + + addValue += _powerFraction[powerIndex]; + uint32 integerValue = uint32(std::fabs(addValue)); + + if (addValue < 0.0f) + { + if (curValue > integerValue) + { + curValue -= integerValue; + _powerFraction[powerIndex] = addValue + integerValue; + } + else + { + curValue = 0; + _powerFraction[powerIndex] = 0; + } + } + else + { + curValue += integerValue; + + if (curValue > maxValue) + { + curValue = maxValue; + _powerFraction[powerIndex] = 0; + } + else + _powerFraction[powerIndex] = addValue - integerValue; + } + + // Players update their powers in 2 seconds intervals, creatures in 1 second ones + if (_powerUpdateTimer <= 0) + SetPower(powerType, curValue); + else + UpdateUInt32Value(UNIT_FIELD_POWER1 + powerIndex, curValue); +} + +void Unit::RegisterPowerTypes() +{ + for (uint8 i = POWER_MANA; i < MAX_POWERS; ++i) + { + uint32 powerIndex = GetPowerIndex(PowerType(i)); + if (powerIndex == MAX_POWERS || powerIndex == MAX_POWERS_PER_CLASS) + continue; + + _usedPowerTypes[powerIndex] = static_cast(i); + } +} + +void Unit::AIUpdateTick(uint32 diff) +{ + if (UnitAI* ai = GetAI()) + { + m_aiLocked = true; + ai->UpdateAI(diff); + m_aiLocked = false; + } +} + +void Unit::PushAI(UnitAI* newAI) +{ + i_AIs.emplace(newAI); +} + +void Unit::SetAI(UnitAI* newAI) +{ + PushAI(newAI); + RefreshAI(); +} + +bool Unit::PopAI() +{ + if (!i_AIs.empty()) + { + i_AIs.pop(); + return true; + } + else + return false; +} + +void Unit::RefreshAI() +{ + ASSERT(!m_aiLocked, "Tried to change current AI during UpdateAI()"); + if (i_AIs.empty()) + i_AI = nullptr; + else + i_AI = i_AIs.top(); +} + +void Unit::ScheduleAIChange() +{ + bool const charmed = IsCharmed(); + + if (charmed) + PushAI(GetScheduledChangeAI()); + else + { + RestoreDisabledAI(); + PushAI(GetScheduledChangeAI()); //This could actually be PopAI() to get the previous AI but it's required atm to trigger UpdateCharmAI() + } +} + +void Unit::RestoreDisabledAI() +{ + // Keep popping the stack until we either reach the bottom or find a valid AI + while (PopAI()) + if (GetTopAI() && dynamic_cast(GetTopAI()) == nullptr) + return; +} + +UnitAI* Unit::GetScheduledChangeAI() +{ + if (Creature* creature = ToCreature()) + return new ScheduledChangeAI(creature); + else + return nullptr; +} + +bool Unit::HasScheduledAIChange() const +{ + if (UnitAI* ai = GetAI()) + return dynamic_cast(ai) != nullptr; + else + return true; +} + +void Unit::AddToWorld() +{ + WorldObject::AddToWorld(); + + RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::EnterWorld); +} + +void Unit::RemoveFromWorld() +{ + // cleanup + ASSERT(GetGUID()); + + if (IsInWorld()) + { + m_duringRemoveFromWorld = true; + if (UnitAI* ai = GetAI()) + ai->LeavingWorld(); + + if (IsVehicle()) + RemoveVehicleKit(true); + + RemoveCharmAuras(); + RemoveBindSightAuras(); + RemoveNotOwnSingleTargetAuras(); + RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::LeaveWorld); + + RemoveAllGameObjects(); + RemoveAllDynObjects(); + + ExitVehicle(); // Remove applied auras with SPELL_AURA_CONTROL_VEHICLE + UnsummonAllTotems(); + RemoveAllControlled(); + + RemoveAreaAurasDueToLeaveWorld(); + + // clear idle movement slot to finalize follow movement to unregister formation targets + GetMotionMaster()->Clear(MOTION_SLOT_IDLE); + RemoveAllPursuers(); + + if (IsCharmed()) + RemoveCharmedBy(nullptr); + + ASSERT(!GetCharmedGUID(), "Unit %u has charmed guid when removed from world", GetEntry()); + ASSERT(!GetCharmerGUID(), "Unit %u has charmer guid when removed from world", GetEntry()); + + if (Unit* owner = GetOwner()) + { + if (owner->m_Controlled.find(this) != owner->m_Controlled.end()) + { + TC_LOG_FATAL("entities.unit", "Unit %u is in controlled list of %u when removed from world", GetEntry(), owner->GetEntry()); + ABORT(); + } + } + + WorldObject::RemoveFromWorld(); + m_duringRemoveFromWorld = false; + } +} + +void Unit::CleanupBeforeRemoveFromMap(bool finalCleanup) +{ + // This needs to be before RemoveFromWorld to make GetCaster() return a valid pointer on aura removal + InterruptNonMeleeSpells(true); + + if (IsInWorld()) + RemoveFromWorld(); + else + { + // cleanup that must happen even if not in world + if (IsVehicle()) + RemoveVehicleKit(true); + } + + ASSERT(GetGUID()); + + // A unit may be in removelist and not in world, but it is still in grid + // and may have some references during delete + RemoveAllAuras(); + RemoveAllGameObjects(); + + if (finalCleanup) + m_cleanupDone = true; + + CombatStop(); + ClearComboPointHolders(); +} + +void Unit::CleanupsBeforeDelete(bool finalCleanup) +{ + CleanupBeforeRemoveFromMap(finalCleanup); + + WorldObject::CleanupsBeforeDelete(finalCleanup); +} + +void Unit::UpdateCharmAI() +{ + if (IsCharmed()) + { + UnitAI* newAI = nullptr; + if (IsPlayer()) + { + if (Unit* charmer = GetCharmer()) + { + // first, we check if the creature's own AI specifies an override playerai for its owned players + if (Creature* creatureCharmer = charmer->ToCreature()) + { + if (CreatureAI* charmerAI = creatureCharmer->AI()) + newAI = charmerAI->GetAIForCharmedPlayer(ToPlayer()); + } + else + TC_LOG_ERROR("misc", "Attempt to assign charm AI to player %s who is charmed by non-creature %s.", GetGUID().ToString().c_str(), GetCharmerGUID().ToString().c_str()); + } + if (!newAI) // otherwise, we default to the generic one + newAI = new SimpleCharmedPlayerAI(ToPlayer()); + } + else + { + ASSERT(IsCreature()); + if (isPossessed() || IsVehicle()) + newAI = new PossessedAI(ToCreature()); + else + newAI = new PetAI(ToCreature()); + } + + ASSERT(newAI); + SetAI(newAI); + newAI->OnCharmed(true); + } + else + { + RestoreDisabledAI(); + // Hack: this is required because we want to call OnCharmed(true) on the restored AI + RefreshAI(); + if (UnitAI* ai = GetAI()) + ai->OnCharmed(true); + } +} + +CharmInfo* Unit::InitCharmInfo() +{ + if (!m_charmInfo) + m_charmInfo = std::make_unique(this); + + return m_charmInfo.get(); +} + +void Unit::DeleteCharmInfo() +{ + if (!m_charmInfo) + return; + + m_charmInfo->RestoreState(); + m_charmInfo.reset(); +} + +bool Unit::isFrozen() const +{ + return HasAuraState(AURA_STATE_FROZEN); +} + +ProcFlagsHit createProcHitMask(SpellNonMeleeDamage* damageInfo, SpellMissInfo missCondition) +{ + ProcFlagsHit hitMask = PROC_HIT_NONE; + // Check victim state + if (missCondition != SPELL_MISS_NONE) + { + switch (missCondition) + { + case SPELL_MISS_MISS: + hitMask |= PROC_HIT_MISS; + break; + case SPELL_MISS_DODGE: + hitMask |= PROC_HIT_DODGE; + break; + case SPELL_MISS_PARRY: + hitMask |= PROC_HIT_PARRY; + break; + case SPELL_MISS_BLOCK: + // spells can't be partially blocked (it's damage can though) + hitMask |= PROC_HIT_BLOCK | PROC_HIT_FULL_BLOCK; + break; + case SPELL_MISS_EVADE: + hitMask |= PROC_HIT_EVADE; + break; + case SPELL_MISS_IMMUNE: + case SPELL_MISS_IMMUNE2: + hitMask |= PROC_HIT_IMMUNE; + break; + case SPELL_MISS_DEFLECT: + hitMask |= PROC_HIT_DEFLECT; + break; + case SPELL_MISS_ABSORB: + hitMask |= PROC_HIT_ABSORB; + break; + case SPELL_MISS_REFLECT: + hitMask |= PROC_HIT_REFLECT; + break; + case SPELL_MISS_RESIST: + hitMask |= PROC_HIT_FULL_RESIST; + break; + default: + break; + } + } + else + { + // On block + if (damageInfo->blocked) + { + hitMask |= PROC_HIT_BLOCK; + if (damageInfo->fullBlock) + hitMask |= PROC_HIT_FULL_BLOCK; + } + // On absorb + if (damageInfo->absorb) + hitMask |= PROC_HIT_ABSORB; + + // Don't set hit/crit hitMask if damage is nullified + bool const damageNullified = (damageInfo->HitInfo & (HITINFO_FULL_ABSORB | HITINFO_FULL_RESIST)) != 0 || (hitMask & PROC_HIT_FULL_BLOCK) != 0; + if (!damageNullified) + { + // On crit + if (damageInfo->HitInfo & SPELL_HIT_TYPE_CRIT) + hitMask |= PROC_HIT_CRITICAL; + else + hitMask |= PROC_HIT_NORMAL; + } + else if ((damageInfo->HitInfo & HITINFO_FULL_RESIST) != 0) + hitMask |= PROC_HIT_FULL_RESIST; + } + + return hitMask; +} + +void Unit::ProcSkillsAndReactives(bool isVictim, Unit* procTarget, ProcFlags typeMask, ProcFlagsHit hitMask, WeaponAttackType /*attType*/) +{ + // Player is loaded now - do not allow passive spell casts to proc + if (GetTypeId() == TYPEID_PLAYER && ToPlayer()->GetSession()->PlayerLoading()) + return; + + // For melee/ranged based attack need update skills and set some Aura states if victim present + if (typeMask & MELEE_BASED_TRIGGER_MASK && procTarget) + { + // If exist crit/parry/dodge/block need update aura state (for victim and attacker) + if (hitMask & (PROC_HIT_CRITICAL | PROC_HIT_PARRY | PROC_HIT_DODGE | PROC_HIT_BLOCK)) + { + // for victim + if (isVictim) + { + // Skip checks for Death Knights when in Blood Presence (Rune Strike) + if (getClass() == CLASS_DEATH_KNIGHT && hitMask & (PROC_HIT_DODGE | PROC_HIT_PARRY | PROC_HIT_BLOCK)) + if (GetAuraEffect(SPELL_AURA_MOD_BASE_RESISTANCE_PCT, SPELLFAMILY_DEATHKNIGHT, 0x800000, 0, 0, GetGUID())) + return; + + // if victim and dodge attack + if (hitMask & PROC_HIT_DODGE) + { + // Update AURA_STATE on dodge + if (getClass() != CLASS_ROGUE) // skip Rogue Riposte + { + ModifyAuraState(AURA_STATE_DEFENSE, true); + StartReactiveTimer(REACTIVE_DEFENSE); + } + } + // if victim and parry attack + if (hitMask & PROC_HIT_PARRY) + { + // For Hunters only Counterattack (skip Mongoose bite) + if (getClass() == CLASS_HUNTER) + { + 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 (hitMask & PROC_HIT_BLOCK) + { + ModifyAuraState(AURA_STATE_DEFENSE, true); + StartReactiveTimer(REACTIVE_DEFENSE); + } + } + else // For attacker + { + // Overpower on victim dodge + if ((hitMask & PROC_HIT_DODGE) && GetTypeId() == TYPEID_PLAYER && getClass() == CLASS_WARRIOR) + { + ToPlayer()->AddComboPoints(procTarget, 1); + StartReactiveTimer(REACTIVE_OVERPOWER); + } + } + } + } +} + +void Unit::GetProcAurasTriggeredOnEvent(AuraApplicationProcContainer& aurasTriggeringProc, AuraApplicationList* procAuras, ProcEventInfo& eventInfo) +{ + std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now(); + + auto processAuraApplication = [&](AuraApplication* aurApp) + { + if (uint32 procEffectMask = aurApp->GetBase()->GetProcEffectMask(aurApp, eventInfo, now)) + { + aurApp->GetBase()->PrepareProcToTrigger(aurApp, eventInfo, now); + aurasTriggeringProc.emplace_back(procEffectMask, aurApp); + } + else + { + if (aurApp->GetBase()->GetSpellInfo()->HasAttribute(SPELL_ATTR0_PROC_FAILURE_BURNS_CHARGE)) + { + if (SpellProcEntry const* procEntry = sSpellMgr->GetSpellProcEntry(aurApp->GetBase()->GetSpellInfo()->Id)) + { + aurApp->GetBase()->PrepareProcChargeDrop(procEntry, eventInfo); + aurasTriggeringProc.emplace_back(0, aurApp); + } + } + + if (aurApp->GetBase()->GetSpellInfo()->HasAttribute(SPELL_ATTR2_PROC_COOLDOWN_ON_FAILURE)) + if (SpellProcEntry const* procEntry = sSpellMgr->GetSpellProcEntry(aurApp->GetBase()->GetSpellInfo()->Id)) + aurApp->GetBase()->AddProcCooldown(procEntry, now); + } + }; + + // use provided list of auras which can proc + if (procAuras) + { + for (AuraApplication* aurApp : *procAuras) + { + ASSERT(aurApp->GetTarget() == this); + processAuraApplication(aurApp); + } + } + // or generate one on our own + else + { + for (AuraApplicationMap::iterator itr = GetAppliedAuras().begin(); itr != GetAppliedAuras().end(); ++itr) + processAuraApplication(itr->second); + } +} + +void Unit::TriggerAurasProcOnEvent(Unit* actionTarget, ProcFlags typeMaskActor, ProcFlags typeMaskActionTarget, ProcFlagsSpellType spellTypeMask, + ProcFlagsSpellPhase spellPhaseMask, ProcFlagsHit hitMask, Spell* spell, DamageInfo* damageInfo, HealInfo* healInfo) +{ + // prepare data for self trigger + ProcEventInfo myProcEventInfo(this, actionTarget, actionTarget, typeMaskActor, spellTypeMask, spellPhaseMask, hitMask, spell, damageInfo, healInfo); + if (typeMaskActor) + { + AuraApplicationProcContainer myAurasTriggeringProc; + GetProcAurasTriggeredOnEvent(myAurasTriggeringProc, nullptr, myProcEventInfo); + + // needed for example for Cobra Strikes, pet does the attack, but aura is on owner + if (Player* modOwner = GetSpellModOwner()) + { + if (modOwner != this && spell) + { + AuraApplicationList modAuras; + for (auto itr = modOwner->GetAppliedAuras().begin(); itr != modOwner->GetAppliedAuras().end(); ++itr) + { + if (spell->m_appliedMods.count(itr->second->GetBase()) != 0) + modAuras.push_front(itr->second); + } + modOwner->GetProcAurasTriggeredOnEvent(myAurasTriggeringProc, &modAuras, myProcEventInfo); + } + } + TriggerAurasProcOnEvent(myProcEventInfo, myAurasTriggeringProc); + } + + // prepare data for target trigger + ProcEventInfo targetProcEventInfo(this, actionTarget, this, typeMaskActionTarget, spellTypeMask, spellPhaseMask, hitMask, spell, damageInfo, healInfo); + if (typeMaskActionTarget && actionTarget) + { + AuraApplicationProcContainer targetAurasTriggeringProc; + actionTarget->GetProcAurasTriggeredOnEvent(targetAurasTriggeringProc, nullptr, targetProcEventInfo); + actionTarget->TriggerAurasProcOnEvent(targetProcEventInfo, targetAurasTriggeringProc); + } +} + +void Unit::TriggerAurasProcOnEvent(ProcEventInfo& eventInfo, AuraApplicationProcContainer& aurasTriggeringProc) +{ + Spell const* triggeringSpell = eventInfo.GetProcSpell(); + bool const disableProcs = triggeringSpell && triggeringSpell->IsProcDisabled(); + + int32 oldProcChainLength = std::exchange(m_procChainLength, std::max(m_procChainLength + 1, triggeringSpell ? triggeringSpell->GetProcChainLength() : 0)); + + if (disableProcs) + SetCantProc(true); + + for (auto const& [procEffectMask, aurApp] : aurasTriggeringProc) + { + if (aurApp->GetRemoveMode().HasAnyFlag()) + continue; + + aurApp->GetBase()->TriggerProcOnEvent(procEffectMask, aurApp, eventInfo); + } + + if (disableProcs) + SetCantProc(false); + + m_procChainLength = oldProcChainLength; +} + +///----------Pet responses methods----------------- +void Unit::SendPetActionFeedback(uint8 response, uint32 spellId /*=0*/) +{ + Unit* owner = GetOwner(); + if (!owner || owner->GetTypeId() != TYPEID_PLAYER) + return; + + WorldPackets::Pet::PetActionFeedback actionFeedback; + actionFeedback.Response = response; + actionFeedback.SpellID = spellId; + owner->ToPlayer()->SendDirectMessage(actionFeedback.Write()); +} + +void Unit::SendPetActionSound(int32 action) +{ + Unit* owner = GetOwner(); + if (!owner || owner->GetTypeId() != TYPEID_PLAYER) + return; + + WorldPackets::Pet::PetActionSound actionSound; + actionSound.UnitGUID = GetGUID(); + actionSound.Action = action; + owner->ToPlayer()->SendDirectMessage(actionSound.Write()); +} + +void Unit::SendPetAIReaction(ObjectGuid guid) +{ + Unit* owner = GetOwner(); + if (!owner || owner->GetTypeId() != TYPEID_PLAYER) + return; + + WorldPackets::Combat::AIReaction packet; + packet.UnitGUID = guid; + packet.Reaction = AI_REACTION_HOSTILE; + + owner->ToPlayer()->SendDirectMessage(packet.Write()); +} + +///----------End of Pet responses methods---------- + +void Unit::PropagateSpeedChange() +{ + GetMotionMaster()->PropagateSpeedChange(); +} + +void Unit::StopMoving() +{ + ClearUnitState(UNIT_STATE_MOVING); + + // not need send any packets if not in world or not moving + if (!IsInWorld() || movespline->Finalized()) + return; + + // Update position now since Stop does not start a new movement that can be updated later + if (movespline->HasStarted()) + UpdateSplinePosition(); + Movement::MoveSplineInit init(this); + init.Stop(); +} + +void Unit::PauseMovement(uint32 timer/* = 0*/, uint8 slot/* = 0*/, bool forced/* = true*/) +{ + if (IsInvalidMovementSlot(slot)) + return; + + if (MovementGenerator* movementGenerator = GetMotionMaster()->GetMotionSlot(MovementSlot(slot))) + movementGenerator->Pause(timer); + + if (forced && GetMotionMaster()->GetCurrentSlot() == MovementSlot(slot)) + StopMoving(); +} + +void Unit::ResumeMovement(uint32 timer/* = 0*/, uint8 slot/* = 0*/) +{ + if (IsInvalidMovementSlot(slot)) + return; + + if (MovementGenerator* movementGenerator = GetMotionMaster()->GetMotionSlot(MovementSlot(slot))) + movementGenerator->Resume(timer); +} + +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; +} + +void Unit::SetStandState(uint8 state) +{ + SetByteValue(UNIT_FIELD_BYTES_1, UNIT_BYTES_1_OFFSET_STAND_STATE, state); + + if (IsStandState()) + RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Standing); + + if (IsPlayer()) + { + WorldPackets::Misc::StandStateUpdate packet; + packet.State = state; + ToPlayer()->SendDirectMessage(packet.Write()); + } +} + +void Unit::SetAnimTier(AnimTier animTier, bool notifyClient /*= true*/) +{ + SetByteValue(UNIT_FIELD_BYTES_1, UNIT_BYTES_1_OFFSET_ANIM_TIER, AsUnderlyingType(animTier)); + + if (notifyClient) + { + WorldPackets::Misc::SetAnimTier setAnimTier; + setAnimTier.Unit = GetGUID(); + setAnimTier.Tier = AsUnderlyingType(animTier); + SendMessageToSet(setAnimTier.Write(), true); + } +} + +bool Unit::IsPolymorphed() const +{ + uint32 transformId = 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 = GetTypeId() == TYPEID_PLAYER ? 0.1 : 0.01; + SetObjectScale(std::max(scale, scaleMin)); +} + +void Unit::SetDisplayId(uint32 modelId, bool setNative /*= false*/) +{ + SetUInt32Value(UNIT_FIELD_DISPLAYID, modelId); + // Set Gender by modelId + if (CreatureModelInfo const* minfo = sObjectMgr->GetCreatureModelInfo(modelId)) + SetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_GENDER, minfo->gender); + + if (setNative) + SetUInt32Value(UNIT_FIELD_NATIVEDISPLAYID, modelId); +} + +void Unit::RestoreDisplayId() +{ + AuraEffect* handledAura = nullptr; + // try to receive model from transform auras + AuraEffectList const& transforms = GetAuraEffectsByType(SPELL_AURA_TRANSFORM); + if (!transforms.empty()) + { + // iterate over already applied transform auras - from newest to oldest + for (auto i = transforms.begin(); i != transforms.end(); ++i) + { + if (AuraApplication const* aurApp = (*i)->GetBase()->GetApplicationOfTarget(GetGUID())) + { + if (!handledAura) + handledAura = (*i); + // prefer negative auras + if (!aurApp->IsPositive()) + { + handledAura = (*i); + break; + } + } + } + } + + AuraEffectList const& shapeshiftAura = GetAuraEffectsByType(SPELL_AURA_MOD_SHAPESHIFT); + + // transform aura was found + if (handledAura) + { + handledAura->HandleEffect(this, AURA_EFFECT_HANDLE_SEND_FOR_CLIENT, true); + return; + } + 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; + } + } + + // no auras found - set modelid to default + SetDisplayId(GetNativeDisplayId()); +} + +void Unit::SendComboPoints() +{ + if (!IsPlayer()) + return; + + if (Unit* combotarget = ObjectAccessor::GetUnit(*this, m_comboTarget)) + { + WorldPacket data; + Player* movingMe = GetCharmerOrSelfPlayer(); + if (movingMe != this) + { + data.Initialize(SMSG_PET_UPDATE_COMBO_POINTS, movingMe->GetPackGUID().size() + combotarget->GetPackGUID().size() + 1); + data << movingMe->GetPackGUID(); + } + else + data.Initialize(SMSG_UPDATE_COMBO_POINTS, combotarget->GetPackGUID().size() + 1); + data << combotarget->GetPackGUID(); + data << uint8(m_comboPoints); + ToPlayer()->SendDirectMessage(&data); + } +} + +void Unit::AddComboPoints(Unit* target, int8 count, Spell* spell) +{ + if (!count) + return; + + int8* comboPoints = spell ? &spell->m_comboPointGain : &m_comboPoints; + + // without combo points lost (duration checked in aura) + RemoveAurasByType(SPELL_AURA_RETAIN_COMBO_POINTS); + + if (target->GetGUID() == m_comboTarget) + *comboPoints += count; + else + { + if (m_comboTarget) + if (Unit* target2 = ObjectAccessor::GetUnit(*this, m_comboTarget)) + target2->RemoveComboPointHolder(GetGUID()); + + // Spells will always add value to m_comboPoints eventualy, so it must be cleared first + if (spell) + m_comboPoints = 0; + + m_comboTarget = target->GetGUID(); + *comboPoints = count; + + target->AddComboPointHolder(GetGUID()); + } + + if (*comboPoints > 5) + *comboPoints = 5; + else if (*comboPoints < 0) + *comboPoints = 0; + + if (!spell) + SendComboPoints(); +} + +void Unit::GainSpellComboPoints(int8 count) +{ + if (!count) + return; + + m_comboPoints += count; + if (m_comboPoints > 5) m_comboPoints = 5; + else if (m_comboPoints < 0) m_comboPoints = 0; + + SendComboPoints(); +} + +void Unit::ClearComboPoints() +{ + if (!m_comboTarget) + return; + + // without combopoints lost (duration checked in aura) + RemoveAurasByType(SPELL_AURA_RETAIN_COMBO_POINTS); + + m_comboPoints = 0; + + SendComboPoints(); + + if (Unit* target = ObjectAccessor::GetUnit(*this, m_comboTarget)) + target->RemoveComboPointHolder(GetGUID()); + + m_comboTarget.Clear(); +} + +void Unit::ClearComboPointHolders() +{ + while (!m_ComboPointHolders.empty()) + { + ObjectGuid guid = *m_ComboPointHolders.begin(); + + Player* player = ObjectAccessor::GetPlayer(*this, guid); + if (player && player->GetComboTarget() == GetGUID()) // recheck for safe + player->ClearComboPoints(); // remove also guid from m_ComboPointHolders; + else + m_ComboPointHolders.erase(guid); // or remove manually + } +} + +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 (getClass() == CLASS_HUNTER && HasAuraState(AURA_STATE_HUNTER_PARRY)) + ModifyAuraState(AURA_STATE_HUNTER_PARRY, false); + if (getClass() == CLASS_WARRIOR && GetTypeId() == TYPEID_PLAYER) + ToPlayer()->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 (getClass() == CLASS_HUNTER && HasAuraState(AURA_STATE_HUNTER_PARRY)) + ModifyAuraState(AURA_STATE_HUNTER_PARRY, false); + break; + case REACTIVE_OVERPOWER: + if (getClass() == CLASS_WARRIOR && GetTypeId() == TYPEID_PLAYER) + ToPlayer()->ClearComboPoints(); + break; + } + } + else + { + m_reactiveTimer[reactive] -= p_time; + } + } +} + +Unit* Unit::SelectNearbyTarget(Unit* exclude, float dist) const +{ + std::list targets; + Trinity::AnyUnfriendlyUnitInObjectRangeCheck u_check(this, this, dist); + Trinity::UnitListSearcher searcher(this, targets, u_check); + Cell::VisitAllObjects(this, searcher, dist); + + // remove current target + if (GetVictim()) + targets.remove(GetVictim()); + + if (exclude) + targets.remove(exclude); + + // remove not LoS targets + for (std::list::iterator tIter = targets.begin(); tIter != targets.end();) + { + if (!IsWithinLOSInMap(*tIter) || (*tIter)->IsTotem() || (*tIter)->IsSpiritService() || (*tIter)->IsCritter()) + targets.erase(tIter++); + else + ++tIter; + } + + // no appropriate targets + if (targets.empty()) + return nullptr; + + // select random + return Trinity::Containers::SelectRandomContainerElement(targets); +} + +void ApplyPercentModFloatVar(float& var, float val, bool apply) +{ + var *= (apply ? (100.0f + val) / 100.0f : 100.0f / (100.0f + val)); +} + +uint32 Unit::GetBaseAttackTime(WeaponAttackType att) const +{ + return m_baseAttackSpeed[att]; +} + +void Unit::SetBaseAttackTime(WeaponAttackType att, uint32 val) +{ + m_baseAttackSpeed[att] = val; + UpdateAttackTimeField(att); +} + +void Unit::UpdateAttackTimeField(WeaponAttackType att) +{ + switch (att) + { + case BASE_ATTACK: + case OFF_ATTACK: + SetFloatValue(UNIT_FIELD_BASEATTACKTIME + AsUnderlyingType(att), uint32(m_baseAttackSpeed[att] * m_modAttackSpeedPct[att])); + break; + case RANGED_ATTACK: + SetFloatValue(UNIT_FIELD_RANGEDATTACKTIME, uint32(m_baseAttackSpeed[RANGED_ATTACK] * m_modAttackSpeedPct[RANGED_ATTACK])); + break; + default: + break; + } +} + +void Unit::ApplyAttackTimePercentMod(WeaponAttackType att, float val, bool apply) +{ + float amount = GetFloatValue(UNIT_FIELD_BASEATTACKTIME + AsUnderlyingType(att)); + float hasteAmount = GetTypeId() == TYPEID_PLAYER ? GetFloatValue(PLAYER_FIELD_MOD_HASTE) : 0.f; + float rangedHasteAmount = GetTypeId() == TYPEID_PLAYER ? GetFloatValue(PLAYER_FIELD_MOD_RANGED_HASTE) : 0.f; + + float remainingTimePct = float(m_attackTimer[att]) / (m_baseAttackSpeed[att] * m_modAttackSpeedPct[att]); + if (val > 0.f) + { + ApplyPercentModFloatVar(m_modAttackSpeedPct[att], val, !apply); + ApplyPercentModFloatVar(amount, val, !apply); + + if (GetTypeId() == TYPEID_PLAYER) + { + if (att == BASE_ATTACK) + ApplyPercentModFloatVar(hasteAmount, val, !apply); + else if (att == RANGED_ATTACK) + ApplyPercentModFloatVar(rangedHasteAmount, val, !apply); + } + } + else + { + ApplyPercentModFloatVar(m_modAttackSpeedPct[att], -val, apply); + ApplyPercentModFloatVar(amount, -val, apply); + + if (GetTypeId() == TYPEID_PLAYER) + { + if (att == BASE_ATTACK) + ApplyPercentModFloatVar(hasteAmount, -val, apply); + else if (att == RANGED_ATTACK) + ApplyPercentModFloatVar(rangedHasteAmount, -val, apply); + } + } + + SetFloatValue(UNIT_FIELD_BASEATTACKTIME + AsUnderlyingType(att), amount); + + if (GetTypeId() == TYPEID_PLAYER) + { + SetFloatValue(PLAYER_FIELD_MOD_HASTE, hasteAmount); + SetFloatValue(PLAYER_FIELD_MOD_RANGED_HASTE, rangedHasteAmount); + } + + UpdateAttackTimeField(att); + m_attackTimer[att] = uint32(m_baseAttackSpeed[att] * m_modAttackSpeedPct[att] * remainingTimePct); +} + +void Unit::ApplyHasteRegenMod(float val, bool apply) +{ + if (!IsPlayer()) + return; + + float amount = GetFloatValue(PLAYER_FIELD_MOD_HASTE_REGEN); + + if (val > 0.f) + ApplyPercentModFloatVar(amount, val, !apply); + else + ApplyPercentModFloatVar(amount, -val, apply); + + SetFloatValue(PLAYER_FIELD_MOD_HASTE_REGEN, amount); + + for (PowerType powerType : GetUsedPowerTypes()) + { + if (powerType == MAX_POWERS || GetPowerIndex(powerType) == MAX_POWERS) + continue; + + if (DBCManager::IsPowerTypeAffectedByHaste(powerType)) + UpdatePowerRegeneration(powerType); + } + + if (IsPlayer() && getClass() == CLASS_DEATH_KNIGHT) + UpdatePowerRegeneration(POWER_RUNE); +} + +void Unit::ApplyCastTimePercentMod(float val, bool apply, bool withCastHaste /*= true*/, bool withCastSpeed /*= true*/) +{ + float speedAmount = GetFloatValue(UNIT_MOD_CAST_SPEED); + float hasteAmount = GetFloatValue(UNIT_MOD_CAST_HASTE); + + if (val > 0.f) + { + if (withCastSpeed) + ApplyPercentModFloatVar(speedAmount, val, !apply); + if (withCastHaste) + ApplyPercentModFloatVar(hasteAmount, val, !apply); + } + else + { + if (withCastSpeed) + ApplyPercentModFloatVar(speedAmount, -val, apply); + if (withCastHaste) + ApplyPercentModFloatVar(hasteAmount, -val, apply); + } + + SetFloatValue(UNIT_MOD_CAST_SPEED, speedAmount); + SetFloatValue(UNIT_MOD_CAST_HASTE, hasteAmount); +} + +uint32 Unit::GetCastingTimeForBonus(SpellInfo const* spellProto, DamageEffectType damagetype, uint32 CastingTime) const +{ + // Not apply this to creature cast spells with casttime == 0 + if (CastingTime == 0 && GetTypeId() == TYPEID_UNIT && !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; + } + break; + default: + break; + } + + if (spellProto->Effects[i].IsTargetingArea()) + AreaEffect = true; + } + + // Combined Spells with Both Over Time and Direct Damage + if (overTime > 0 && CastingTime > 0 && DirectDamage) + { + // mainly for DoTs which are 3500 here otherwise + uint32 OriginalCastTime = spellProto->CalcCastTime(getLevel()); + 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 (GetTypeId() == TYPEID_UNIT && IsPet()) + { + Pet* pet = ((Pet*)this); + if (pet->isControlled()) + { + Unit* owner = GetOwner(); + if (owner && (owner->GetTypeId() == TYPEID_PLAYER) && owner->ToPlayer()->GetGroup()) + { + owner->ToPlayer()->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_PET_AURAS); + pet->SetAuraUpdateMaskForRaid(slot); + } + } + } +} + +void Unit::SetCantProc(bool apply) +{ + if (apply) + ++m_procDeep; + else + { + ASSERT(m_procDeep); + --m_procDeep; + } +} + +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(getLevel()); + // 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) const +{ + if (GetTypeId() != TYPEID_PLAYER || (IsInFeralForm() && !normalized)) + return GetBaseAttackTime(attType) / 1000.0f; + + Item* weapon = ToPlayer()->GetWeaponForAttack(attType, true); + if (!weapon) + return 2.0f; + + if (!normalized) + return weapon->GetTemplate()->GetDelay() / 1000.0f; + + switch (weapon->GetTemplate()->GetSubClass()) + { + case ITEM_SUBCLASS_WEAPON_AXE2: + case ITEM_SUBCLASS_WEAPON_MACE2: + case ITEM_SUBCLASS_WEAPON_POLEARM: + case ITEM_SUBCLASS_WEAPON_SWORD2: + case ITEM_SUBCLASS_WEAPON_STAFF: + case ITEM_SUBCLASS_WEAPON_FISHING_POLE: + return 3.3f; + case ITEM_SUBCLASS_WEAPON_BOW: + case ITEM_SUBCLASS_WEAPON_GUN: + case ITEM_SUBCLASS_WEAPON_CROSSBOW: + case ITEM_SUBCLASS_WEAPON_THROWN: + return 2.8f; + case ITEM_SUBCLASS_WEAPON_AXE: + case ITEM_SUBCLASS_WEAPON_MACE: + case ITEM_SUBCLASS_WEAPON_SWORD: + case ITEM_SUBCLASS_WEAPON_EXOTIC: + case ITEM_SUBCLASS_WEAPON_EXOTIC2: + case ITEM_SUBCLASS_WEAPON_FIST_WEAPON: + return 2.4f; + case ITEM_SUBCLASS_WEAPON_DAGGER: + return 1.7f; + default: + return weapon->GetTemplate()->GetDelay() / 1000.0f; + } +} + +Pet* Unit::CreateTamedPetFrom(Creature* creatureTarget, uint32 spell_id) +{ + if (GetTypeId() != TYPEID_PLAYER) + return nullptr; + + Pet* pet = new Pet(ToPlayer(), HUNTER_PET); + + if (!pet->CreateBaseAtCreature(creatureTarget)) + { + delete pet; + return nullptr; + } + + uint8 level = getLevel(); + + InitTamedPet(pet, level, spell_id); + + return pet; +} + +Pet* Unit::CreateTamedPetFrom(uint32 creatureEntry, uint32 spell_id) +{ + if (GetTypeId() != TYPEID_PLAYER) + return nullptr; + + CreatureTemplate const* creatureInfo = sObjectMgr->GetCreatureTemplate(creatureEntry); + if (!creatureInfo) + return nullptr; + + Pet* pet = new Pet(ToPlayer(), HUNTER_PET); + + if (!pet->CreateBaseAtCreatureInfo(creatureInfo, this) || !InitTamedPet(pet, getLevel(), spell_id)) + { + delete pet; + return nullptr; + } + + return pet; +} + +bool Unit::InitTamedPet(Pet* pet, uint8 level, uint32 spell_id) +{ + pet->SetCreatorGUID(GetGUID()); + pet->SetFaction(GetFaction()); + pet->SetUInt32Value(UNIT_CREATED_BY_SPELL, spell_id); + + if (GetTypeId() == TYPEID_PLAYER) + pet->SetUInt32Value(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED); + + if (!pet->InitStatsForLevel(level)) + { + TC_LOG_ERROR("entities.unit", "Pet::InitStatsForLevel() failed for creature (Entry: %u)!", pet->GetEntry()); + return false; + } + + PhasingHandler::InheritPhaseShift(pet, this); + + pet->GetCharmInfo()->SetPetNumber(sObjectMgr->GeneratePetNumber(), true); + // this enables pet details window (Shift+P) + pet->InitPetCreateSpells(); + //pet->InitLevelupSpellsForLevel(); + pet->SetFullHealth(); + pet->CastSpell(pet, 99289, true); // Energize + pet->SetReactState(REACT_ASSIST); + return true; +} + +void Unit::SendDurabilityLoss(Player* receiver, uint32 percent) +{ + WorldPackets::Misc::DurabilityDamageDeath packet; + packet.Percent = percent; + receiver->SendDirectMessage(packet.Write()); +} + +void Unit::PlayOneShotAnimKitId(uint16 animKitId) +{ + WorldPackets::Misc::PlayOneShotAnimKit packet; + packet.Unit = GetGUID(); + packet.AnimKitID = animKitId; + SendMessageToSet(packet.Write(), true); +} + +/*static*/ void Unit::Kill(Unit* attacker, Unit* victim, bool durabilityLoss /*= true*/) +{ + // Prevent killing unit twice (and giving reward from kill twice) + if (!victim->GetHealth()) + return; + + if (attacker && !attacker->IsInMap(victim)) + attacker = nullptr; + + // find player: owner of controlled `this` or `this` itself maybe + Player* player = nullptr; + if (attacker) + player = attacker->GetCharmerOrOwnerPlayerOrPlayerItself(); + + Creature* creature = victim->ToCreature(); + + bool isRewardAllowed = true; + if (creature) + { + isRewardAllowed = creature->IsDamageEnoughForLootingAndReward(); + if (!isRewardAllowed) + creature->SetLootRecipient(nullptr); + } + + if (isRewardAllowed && creature) + { + if (Player* lootRecipient = creature->GetLootRecipient()) + { + // Loot recipient can be in a different map + if (!creature->IsInMap(lootRecipient)) + { + if (Group* group = creature->GetLootRecipientGroup()) + { + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (!member || !creature->IsInMap(member)) + continue; + + player = member; + break; + } + } + } + else + player = creature->GetLootRecipient(); + } + } + + // Exploit fix + if (creature && creature->IsPet() && creature->GetOwnerOrCreatorGUID().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 << uint64(player->GetGUID()); // player with killing blow + data << uint64(victim->GetGUID()); // victim + + Player* looter = player; + Group* group = player->GetGroup(); + bool hasLooterGuid = false; + + if (group) + { + group->BroadcastPacket(&data, group->GetMemberGroup(player->GetGUID()) != 0); + + if (creature && !creature->IsLootDisabled()) + { + group->UpdateLooterGuid(creature, true); + if (group->GetLooterGuid()) + { + 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 && !creature->IsLootDisabled()) + { + WorldPackets::Loot::LootList lootList; + lootList.Owner = creature->GetGUID(); + lootList.RoundRobinWinner = player->GetGUID(); + + player->SendMessageToSet(lootList.Write(), false); + } + } + + // Generate loot before updating looter + if (creature && !creature->IsLootDisabled()) + { + Loot* loot = &creature->loot; + loot->clear(); + + if (uint32 lootid = creature->GetCreatureTemplate()->lootid) + loot->FillLoot(lootid, LootTemplates_Creature, looter, false, false, creature->GetLootMode()); + + if (creature->GetLootMode() > 0) + 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 (!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 (attacker && (attacker->IsPet() || attacker->IsTotem())) + { + // proc only once for victim + if (Unit* owner = attacker->GetOwner()) + Unit::ProcSkillsAndAuras(owner, victim, PROC_FLAG_KILL, PROC_FLAG_NONE, PROC_SPELL_TYPE_MASK_ALL, PROC_SPELL_PHASE_NONE, PROC_HIT_NONE, nullptr, nullptr, nullptr); + } + + if (!victim->IsCritter()) + Unit::ProcSkillsAndAuras(attacker, victim, PROC_FLAG_KILL, PROC_FLAG_NONE, PROC_SPELL_TYPE_MASK_ALL, PROC_SPELL_PHASE_NONE, PROC_HIT_NONE, nullptr, nullptr, nullptr); + + // Proc auras on death - must be before aura/combat remove + Unit::ProcSkillsAndAuras(victim, victim, PROC_FLAG_NONE, PROC_FLAG_DEATH, PROC_SPELL_TYPE_MASK_ALL, PROC_SPELL_PHASE_NONE, PROC_HIT_NONE, nullptr, nullptr, nullptr); + + // update get killing blow achievements, must be done before setDeathState to be able to require auras on target + // and before Spirit of Redemption as it also removes auras + if (attacker) + if (Player* killerPlayer = attacker->GetCharmerOrOwnerPlayerOrPlayerItself()) + killerPlayer->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_GET_KILLING_BLOWS, 1, 0, 0, victim); + + TC_LOG_DEBUG("entities.unit", "SET JUST_DIED"); + victim->setDeathState(JUST_DIED); + + // Inform pets (if any) when player kills target) + // MUST come after victim->setDeathState(JUST_DIED); or pet next target + // selection will get stuck on same target and break pet react state + if (player) + { + Pet* pet = player->GetPet(); + if (pet && pet->IsAlive() && pet->isControlled()) + { + if (pet->IsAIEnabled()) + pet->AI()->KilledUnit(victim); + else + TC_LOG_ERROR("entities.unit", "Pet doesn't have any AI in Unit::Kill(). %s", pet->GetGUID().ToString().c_str()); + } + } + + // 10% durability loss on death + if (Player* plrVictim = victim->ToPlayer()) + { + // remember victim PvP death for corpse type and corpse reclaim delay + // at original death (not at SpiritOfRedemtionTalent timeout) + plrVictim->SetPvPDeath(player != nullptr); + + // only if not player and not controlled by player pet. And not at BG + if ((durabilityLoss && !player && !victim->ToPlayer()->InBattleground()) || (player && sWorld->getBoolConfig(CONFIG_DURABILITY_LOSS_IN_PVP))) + { + double baseLoss = sWorld->getRate(RATE_DURABILITY_LOSS_ON_DEATH); + uint32 loss = uint32(baseLoss - (baseLoss * plrVictim->GetTotalAuraMultiplier(SPELL_AURA_MOD_DURABILITY_LOSS))); + TC_LOG_DEBUG("entities.unit", "We are dead, losing %u percent durability", loss); + // Durability loss is calculated more accurately again for each item in Player::DurabilityLoss + plrVictim->DurabilityLossAll(baseLoss, false); + // durability lost message + plrVictim->SendDurabilityLoss(plrVictim, loss); + } + // Call KilledUnit for creatures + if (attacker && attacker->IsCreature() && attacker->IsAIEnabled()) + attacker->ToCreature()->AI()->KilledUnit(victim); + + // last damage from non duel opponent or opponent controlled creature + if (plrVictim->duel) + { + plrVictim->duel->opponent->CombatStopWithPets(true); + plrVictim->CombatStopWithPets(true); + plrVictim->DuelComplete(DUEL_INTERRUPTED); + } + } + else // creature died + { + TC_LOG_DEBUG("entities.unit", "DealDamageNotPlayer"); + + if (creature && !creature->IsPet()) + { + // must be after setDeathState which resets dynamic flags + if (!creature->loot.isLooted()) + creature->SetFlag(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_LOOTABLE); + else + creature->AllLootRemovedFromCorpse(); + } + + // Call KilledUnit for creatures, this needs to be called after the lootable flag is set + if (attacker && attacker->IsCreature() && attacker->IsAIEnabled()) + attacker->ToCreature()->AI()->KilledUnit(victim); + + // Call creature just died function + if (CreatureAI* ai = creature->AI()) + ai->JustDied(attacker); + + if (TempSummon* summon = creature->ToTempSummon()) + if (Unit* summoner = summon->GetSummoner()) + if (summoner->ToCreature() && summoner->IsAIEnabled()) + summoner->ToCreature()->AI()->SummonedCreatureDies(creature, attacker); + + // Dungeon specific stuff, only applies to players killing creatures + if (creature->GetInstanceId()) + { + Map* instanceMap = creature->GetMap(); + /// @todo do instance binding anyway if the charmer/owner is offline + + if (instanceMap->IsDungeon() && ((attacker && attacker->GetCharmerOrOwnerPlayerOrPlayerItself()) || attacker == victim)) + { + if (instanceMap->IsRaidOrHeroicDungeon()) + { + if (creature->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_INSTANCE_BIND) + instanceMap->ToInstanceMap()->PermBindAllPlayers(); + } + else + { + // the reset time is set but not added to the scheduler + // until the players leave the instance + time_t resettime = GameTime::GetGameTime() + 2 * HOUR; + if (InstanceSave* save = sInstanceSaveMgr->GetInstanceSave(creature->GetInstanceId())) + if (save->GetResetTime() < resettime) + save->SetResetTime(resettime); + } + } + } + } + + // outdoor pvp things, do these after setting the death state, else the player activity notify won't work... doh... + // handle player kill only if not suicide (spirit of redemption for example) + if (player && attacker != victim) + { + if (OutdoorPvP* pvp = player->GetOutdoorPvP()) + pvp->HandleKill(player, victim); + + if (Battlefield* bf = sBattlefieldMgr->GetBattlefieldToZoneId(player->GetMap(), player->GetZoneId())) + bf->HandleKill(player, victim); + } + + //if (victim->GetTypeId() == TYPEID_PLAYER) + // if (OutdoorPvP* pvp = victim->ToPlayer()->GetOutdoorPvP()) + // pvp->HandlePlayerActivityChangedpVictim->ToPlayer(); + + // battleground things (do this at the end, so the death state flag will be properly set to handle in the bg->handlekill) + if (player && player->InBattleground()) + { + if (Battleground* bg = player->GetBattleground()) + { + if (Player* playerVictim = victim->ToPlayer()) + bg->HandleKillPlayer(playerVictim, player); + else + bg->HandleKillUnit(victim->ToCreature(), player); + } + } + + // achievement stuff + if (attacker && victim->GetTypeId() == TYPEID_PLAYER) + { + if (attacker->GetTypeId() == TYPEID_UNIT) + victim->ToPlayer()->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_KILLED_BY_CREATURE, attacker->GetEntry()); + else if (attacker->GetTypeId() == TYPEID_PLAYER && victim != attacker) + victim->ToPlayer()->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_KILLED_BY_PLAYER, 1, attacker->ToPlayer()->GetTeam()); + } + + // Hook for OnPVPKill Event + if (attacker) + { + if (Player* killerPlr = attacker->ToPlayer()) + { + if (Player* killedPlr = victim->ToPlayer()) + sScriptMgr->OnPVPKill(killerPlr, killedPlr); + else if (Creature* killedCre = victim->ToCreature()) + sScriptMgr->OnCreatureKill(killerPlr, killedCre); + } + else if (Creature* killerCre = attacker->ToCreature()) + { + if (Player* killed = victim->ToPlayer()) + sScriptMgr->OnPlayerKilledByCreature(killerCre, killed); + } + } +} + +void Unit::SetControlled(bool apply, UnitState state) +{ + if (apply) + { + if (HasUnitState(state)) + return; + + if (state & UNIT_STATE_CONTROLLED) + CastStop(); + + AddUnitState(state); + switch (state) + { + case UNIT_STATE_STUNNED: + SetStunned(true); + break; + case UNIT_STATE_ROOT: + if (!HasUnitState(UNIT_STATE_STUNNED)) + SetRooted(true); + break; + case UNIT_STATE_CONFUSED: + if (!HasUnitState(UNIT_STATE_STUNNED)) + { + ClearUnitState(UNIT_STATE_MELEE_ATTACKING); + SendMeleeAttackStop(); + // SendAutoRepeatCancel ? + SetConfused(true); + } + break; + case UNIT_STATE_FLEEING: + if (!HasUnitState(UNIT_STATE_STUNNED | UNIT_STATE_CONFUSED)) + { + ClearUnitState(UNIT_STATE_MELEE_ATTACKING); + SendMeleeAttackStop(); + // SendAutoRepeatCancel ? + SetFeared(true); + } + break; + default: + break; + } + } + else + { + switch (state) + { + case UNIT_STATE_STUNNED: + if (HasAuraType(SPELL_AURA_MOD_STUN)) + return; + + ClearUnitState(state); + SetStunned(false); + break; + case UNIT_STATE_ROOT: + if (HasAuraType(SPELL_AURA_MOD_ROOT) || GetVehicle() || (IsCreature() && ToCreature()->IsSessile())) + return; + + ClearUnitState(state); + SetRooted(false); + break; + case UNIT_STATE_CONFUSED: + if (HasAuraType(SPELL_AURA_MOD_CONFUSE)) + return; + + ClearUnitState(state); + SetConfused(false); + break; + case UNIT_STATE_FLEEING: + if (HasAuraType(SPELL_AURA_MOD_FEAR)) + return; + + ClearUnitState(state); + SetFeared(false); + break; + default: + return; + } + + ApplyControlStatesIfNeeded(); + } +} + +void Unit::ApplyControlStatesIfNeeded() +{ + // Unit States might have been already cleared but auras still present. I need to check with HasAuraType + if (HasUnitState(UNIT_STATE_STUNNED) || HasAuraType(SPELL_AURA_MOD_STUN)) + SetStunned(true); + + if (HasUnitState(UNIT_STATE_ROOT) || HasAuraType(SPELL_AURA_MOD_ROOT)) + SetRooted(true); + + if (HasUnitState(UNIT_STATE_CONFUSED) || HasAuraType(SPELL_AURA_MOD_CONFUSE)) + SetConfused(true); + + if (HasUnitState(UNIT_STATE_FLEEING) || HasAuraType(SPELL_AURA_MOD_FEAR)) + SetFeared(true); +} + +void Unit::SetStunned(bool apply) +{ + if (apply) + { + SetTarget(ObjectGuid::Empty); + SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_STUNNED); + + // Do not stop movement when the unit is affected by parabolic spline movement (knockbacks, pulls, scripted leaps etc.) + if (!movespline->isParabolic()) + StopMoving(); + + if (GetTypeId() == TYPEID_PLAYER) + SetStandState(UNIT_STAND_STATE_STAND); + SetRooted(true); + + CastStop(); + } + else + { + if (IsAlive() && GetVictim()) + SetTarget(EnsureVictim()->GetGUID()); + + // don't remove UNIT_FLAG_STUNNED for pet when owner is mounted (disabled pet's interface) + Unit* owner = GetCharmerOrOwner(); + if (!owner || owner->GetTypeId() != TYPEID_PLAYER || !owner->ToPlayer()->IsMounted()) + RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_STUNNED); + + if (!HasUnitState(UNIT_STATE_ROOT)) // prevent moving if it also has root effect + SetRooted(false); + } +} + +void Unit::SetRooted(bool apply, bool packetOnly /*= false*/) +{ + if (!packetOnly) + { + if (apply) + { + // MOVEMENTFLAG_ROOT cannot be used in conjunction with MOVEMENTFLAG_MASK_MOVING (tested 3.3.5a) + // this will freeze clients. That's why we remove MOVEMENTFLAG_MASK_MOVING before + // setting MOVEMENTFLAG_ROOT + RemoveUnitMovementFlag(MOVEMENTFLAG_MASK_MOVING); + AddUnitMovementFlag(MOVEMENTFLAG_ROOT); + + // Do not stop movement when the unit is affected by parabolic spline movement (knockbacks, pulls, scripted leaps etc.) + if (!movespline->isParabolic()) + StopMoving(); + } + else + RemoveUnitMovementFlag(MOVEMENTFLAG_ROOT); + } + + if (apply) + Movement::PacketSender(this, SMSG_SPLINE_MOVE_ROOT, SMSG_MOVE_ROOT, SMSG_MOVE_ROOT).Send(); + else + Movement::PacketSender(this, SMSG_SPLINE_MOVE_UNROOT, SMSG_MOVE_UNROOT, SMSG_MOVE_UNROOT).Send(); +} + +void Unit::SetFeared(bool apply) +{ + if (apply) + { + // block control to real player in control (eg charmer) + if (GetCharmerOrSelfPlayer()) + GetCharmerOrSelfPlayer()->SetClientControl(this, false); + + SetTarget(ObjectGuid::Empty); + + Unit* caster = nullptr; + Unit::AuraEffectList const& fearAuras = GetAuraEffectsByType(SPELL_AURA_MOD_FEAR); + if (!fearAuras.empty()) + caster = ObjectAccessor::GetUnit(*this, fearAuras.front()->GetCasterGUID()); + if (!caster) + caster = getAttackerForHelper(); + GetMotionMaster()->MoveFleeing(caster, fearAuras.empty() ? sWorld->getIntConfig(CONFIG_CREATURE_FAMILY_FLEE_DELAY) : 0); // caster == nullptr processed in MoveFleeing + } + else + { + if (IsAlive()) + { + if (GetMotionMaster()->GetCurrentMovementGeneratorType() == FLEEING_MOTION_TYPE) + GetMotionMaster()->MovementExpired(); + if (GetVictim()) + SetTarget(EnsureVictim()->GetGUID()); + } + + // allow control to real player in control (eg charmer) + if (GetCharmerOrSelfPlayer()) + GetCharmerOrSelfPlayer()->SetClientControl(this, true); + } +} + +void Unit::SetConfused(bool apply) +{ + if (apply) + { + // block control to real player in control (eg charmer) + if (GetCharmerOrSelfPlayer()) + GetCharmerOrSelfPlayer()->SetClientControl(this, false); + + SetTarget(ObjectGuid::Empty); + GetMotionMaster()->MoveConfused(); + } + else + { + if (IsAlive()) + { + if (GetMotionMaster()->GetCurrentMovementGeneratorType() == CONFUSED_MOTION_TYPE) + GetMotionMaster()->MovementExpired(); + if (GetVictim()) + SetTarget(EnsureVictim()->GetGUID()); + } + + // allow control to real player in control (eg charmer) + if (GetCharmerOrSelfPlayer()) + GetCharmerOrSelfPlayer()->SetClientControl(this, true); + } +} + +bool Unit::SetCharmedBy(Unit* charmer, CharmType type, AuraApplication const* aurApp) +{ + if (!charmer) + return false; + + // dismount players when charmed + if (GetTypeId() == TYPEID_PLAYER) + RemoveAurasByType(SPELL_AURA_MOUNTED); + + if (charmer->GetTypeId() == TYPEID_PLAYER) + charmer->RemoveAurasByType(SPELL_AURA_MOUNTED); + + ASSERT(type != CHARM_TYPE_POSSESS || charmer->GetTypeId() == TYPEID_PLAYER); + ASSERT((type == CHARM_TYPE_VEHICLE) == (GetVehicleKit() && GetVehicleKit()->IsControllableVehicle())); + + TC_LOG_DEBUG("entities.unit", "SetCharmedBy: charmer %u (GUID %u), charmed %u (GUID %u), type %u.", charmer->GetEntry(), charmer->GetGUID().GetCounter(), GetEntry(), GetGUID().GetCounter(), uint32(type)); + + if (this == charmer) + { + TC_LOG_FATAL("entities.unit", "Unit::SetCharmedBy: Unit %u (GUID %u) is trying to charm itself!", GetEntry(), GetGUID().GetCounter()); + return false; + } + + //if (HasUnitState(UNIT_STATE_UNATTACKABLE)) + // return false; + + if (GetTypeId() == TYPEID_PLAYER && ToPlayer()->GetTransport()) + { + TC_LOG_FATAL("entities.unit", "Unit::SetCharmedBy: Player on transport is trying to charm %u (GUID %u)", GetEntry(), GetGUID().GetCounter()); + return false; + } + + // Already charmed + if (GetCharmerGUID()) + { + TC_LOG_FATAL("entities.unit", "Unit::SetCharmedBy: %u (GUID %u) has already been charmed but %u (GUID %u) is trying to charm it!", GetEntry(), GetGUID().GetCounter(), charmer->GetEntry(), charmer->GetGUID().GetCounter()); + return false; + } + + CastStop(); + CombatStop(); /// @todo CombatStop(true) may cause crash (interrupt spells) + + Player* playerCharmer = charmer->ToPlayer(); + + // Charmer stop charming + if (playerCharmer) + { + playerCharmer->StopCastingCharm(); + playerCharmer->StopCastingBindSight(); + } + + // Charmed stop charming + if (GetTypeId() == TYPEID_PLAYER) + { + ToPlayer()->StopCastingCharm(); + ToPlayer()->StopCastingBindSight(); + } + + // StopCastingCharm may remove a possessed pet? + if (!IsInWorld()) + { + TC_LOG_FATAL("entities.unit", "Unit::SetCharmedBy: %u (GUID %u) is not in world but %u (GUID %u) is trying to charm it!", GetEntry(), GetGUID().GetCounter(), charmer->GetEntry(), charmer->GetGUID().GetCounter()); + 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().HasAnyFlag()) + { + // properly clean up charm changes up to this point to avoid leaving the unit in partially charmed state + SetFaction(_oldFactionId); + GetMotionMaster()->InitDefault(); + charmer->SetCharm(this, false); + return false; + } + + _oldFactionId = GetFaction(); + SetFaction(charmer->GetFaction()); + + // Set charmed + charmer->SetCharm(this, true); + + if (GetTypeId() == TYPEID_UNIT) + { + GetMotionMaster()->Clear(MOTION_SLOT_ACTIVE); + PauseMovement(0, MOTION_SLOT_IDLE); + StopMoving(); + } + else if (Player* player = ToPlayer()) + { + if (player->isAFK()) + player->ToggleAFK(); + + player->SetClientControl(this, false); + } + + // charm is set by aura, and aura effect remove handler was called during apply handler execution + // prevent undefined behaviour + if (aurApp && aurApp->GetRemoveMode().HasAnyFlag()) + return false; + + // Pets already have a properly initialized CharmInfo, don't overwrite it. + if (type != CHARM_TYPE_VEHICLE && !GetCharmInfo()) + { + if (CharmInfo* charmInfo = InitCharmInfo()) + { + if (type == CHARM_TYPE_POSSESS) + charmInfo->InitPossessCreateSpells(); + else + charmInfo->InitCharmCreateSpells(); + } + } + + if (playerCharmer) + { + switch (type) + { + case CHARM_TYPE_VEHICLE: + SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_POSSESSED); + playerCharmer->SetClientControl(this, true); + playerCharmer->VehicleSpellInitialize(); + break; + case CHARM_TYPE_POSSESS: + SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_POSSESSED); + charmer->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_REMOVE_CLIENT_CONTROL); + playerCharmer->SetClientControl(this, true); + playerCharmer->PossessSpellInitialize(); + AddUnitState(UNIT_STATE_POSSESSED); + break; + case CHARM_TYPE_CHARM: + if (GetTypeId() == TYPEID_UNIT && charmer->getClass() == CLASS_WARLOCK) + { + CreatureTemplate const* cinfo = ToCreature()->GetCreatureTemplate(); + if (cinfo && cinfo->type == CREATURE_TYPE_DEMON) + { + // to prevent client crash + SetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_CLASS, (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())); // cast can't be helped + } + } + playerCharmer->CharmSpellInitialize(); + break; + default: + case CHARM_TYPE_CONVERT: + break; + } + } + + AddUnitState(UNIT_STATE_CHARMED); + + if ((GetTypeId() != TYPEID_PLAYER) || (charmer->GetTypeId() != TYPEID_PLAYER)) + { + // AI will schedule its own change if appropriate + if (UnitAI* ai = GetAI()) + ai->OnCharmed(false); + else + ScheduleAIChange(); + } + return true; +} + +void Unit::RemoveCharmedBy(Unit* charmer) +{ + if (!IsCharmed()) + return; + + if (charmer) + ASSERT(charmer == GetCharmer()); + else + charmer = GetCharmer(); + + ASSERT(charmer); + + CharmType type; + if (HasUnitState(UNIT_STATE_POSSESSED)) + type = CHARM_TYPE_POSSESS; + else if (charmer && charmer->IsOnVehicle(this)) + type = CHARM_TYPE_VEHICLE; + else + type = CHARM_TYPE_CHARM; + + CastStop(); + AttackStop(); + + if (_oldFactionId) + { + SetFaction(_oldFactionId); + _oldFactionId = 0; + } + else + RestoreFaction(); + + GetMotionMaster()->InitDefault(); + + // Vehicle should not attack its passenger after he exists the seat + if (type != CHARM_TYPE_VEHICLE) + LastCharmerGUID = charmer->GetGUID(); + + ASSERT(type != CHARM_TYPE_POSSESS || charmer->GetTypeId() == TYPEID_PLAYER); + ASSERT(type != CHARM_TYPE_VEHICLE || (GetTypeId() == TYPEID_UNIT && IsVehicle())); + + charmer->SetCharm(this, false); + m_combatManager.RevalidateCombat(); + + Player* playerCharmer = charmer->ToPlayer(); + + if (playerCharmer) + { + switch (type) + { + case CHARM_TYPE_VEHICLE: + playerCharmer->SetClientControl(this, false); + playerCharmer->SetClientControl(charmer, true); + RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_POSSESSED); + break; + case CHARM_TYPE_POSSESS: + ClearUnitState(UNIT_STATE_POSSESSED); + playerCharmer->SetClientControl(this, false); + playerCharmer->SetClientControl(charmer, true); + charmer->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_REMOVE_CLIENT_CONTROL); + RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_POSSESSED); + break; + case CHARM_TYPE_CHARM: + if (GetTypeId() == TYPEID_UNIT && charmer->getClass() == CLASS_WARLOCK) + { + CreatureTemplate const* cinfo = ToCreature()->GetCreatureTemplate(); + if (cinfo && cinfo->type == CREATURE_TYPE_DEMON) + { + SetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_CLASS, uint8(cinfo->unit_class)); + if (GetCharmInfo()) + GetCharmInfo()->SetPetNumber(0, true); + else + TC_LOG_ERROR("entities.unit", "Aura::HandleModCharm: %s has a charm aura but no charm info!", GetGUID().ToString().c_str()); + } + } + break; + case CHARM_TYPE_CONVERT: + break; + } + } + + if (Player* player = ToPlayer()) + player->SetClientControl(this, true); + + EngageWithTarget(charmer); + + if (playerCharmer && this != charmer->GetFirstControlled()) + playerCharmer->SendRemoveControlBar(); + + // a guardian should always have charminfo + if (!IsGuardian()) + DeleteCharmInfo(); + + // reset confused movement for example + ApplyControlStatesIfNeeded(); + + if (GetTypeId() != TYPEID_PLAYER || charmer->GetTypeId() == TYPEID_UNIT) + { + if (UnitAI* charmedAI = GetAI()) + charmedAI->OnCharmed(false); // AI will potentially schedule a charm ai update + else + ScheduleAIChange(); + } +} + +void Unit::RestoreFaction() +{ + if (HasAuraType(SPELL_AURA_MOD_FACTION)) + { + SetFaction(GetAuraEffectsByType(SPELL_AURA_MOD_FACTION).front()->GetMiscValue()); + return; + } + + if (GetTypeId() == TYPEID_PLAYER) + ToPlayer()->SetFactionForRace(getRace()); + else + { + if (HasUnitTypeMask(UNIT_MASK_MINION)) + { + if (Unit* owner = GetOwner()) + { + SetFaction(owner->GetFaction()); + return; + } + } + + if (CreatureTemplate const* cinfo = ToCreature()->GetCreatureTemplate()) // normal creature + SetFaction(cinfo->faction); + } +} + +bool Unit::CreateVehicleKit(uint32 id, uint32 creatureEntry, bool loading /*= false*/) +{ + VehicleEntry const* vehInfo = sVehicleStore.LookupEntry(id); + if (!vehInfo) + return false; + + m_vehicleKit = std::make_unique(this, vehInfo, creatureEntry); + m_updateFlag.Vehicle = true; + m_unitTypeMask |= UNIT_MASK_VEHICLE; + + if (!loading) + SendSetVehicleRecId(id); + + return true; +} + +void Unit::RemoveVehicleKit(bool onRemoveFromWorld /*= false*/) +{ + if (!m_vehicleKit) + return; + + if (!onRemoveFromWorld) + SendSetVehicleRecId(0); + + m_vehicleKit->Uninstall(); + m_vehicleKit.reset(); + + m_updateFlag.Vehicle = false; + m_unitTypeMask &= ~UNIT_MASK_VEHICLE; + RemoveFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_SPELLCLICK | UNIT_NPC_FLAG_PLAYER_VEHICLE); +} + +bool Unit::IsOnVehicle(Unit const* vehicle) const +{ + return m_vehicle && m_vehicle == vehicle->GetVehicleKit(); +} + +Unit* Unit::GetVehicleBase() const +{ + return m_vehicle ? m_vehicle->GetBase() : nullptr; +} + +Creature* Unit::GetVehicleCreatureBase() const +{ + if (Unit* veh = GetVehicleBase()) + if (Creature* c = veh->ToCreature()) + return c; + + return nullptr; +} + +ObjectGuid Unit::GetTransGUID() const +{ + if (GetVehicle()) + return GetVehicleBase()->GetGUID(); + if (GetTransport()) + return GetTransport()->GetTransportGUID(); + + return ObjectGuid::Empty; +} + +TransportBase* Unit::GetDirectTransport() const +{ + if (Vehicle* veh = GetVehicle()) + return veh; + return GetTransport(); +} + +bool Unit::IsInPartyWith(Unit const* unit) const +{ + if (this == unit) + return true; + + Unit const* u1 = GetCharmerOrOwnerOrCreatorOrSelf(); + Unit const* u2 = unit->GetCharmerOrOwnerOrCreatorOrSelf(); + if (u1 == u2) + return true; + + if (u1->GetTypeId() == TYPEID_PLAYER && u2->GetTypeId() == TYPEID_PLAYER) + return u1->ToPlayer()->IsInSameGroupWith(u2->ToPlayer()); + else if ((u2->GetTypeId() == TYPEID_PLAYER && u1->GetTypeId() == TYPEID_UNIT && u1->ToCreature()->HasStaticFlag(CREATURE_STATIC_FLAG_4_TREAT_AS_RAID_UNIT_FOR_HELPFUL_SPELLS)) || + (u1->GetTypeId() == TYPEID_PLAYER && u2->GetTypeId() == TYPEID_UNIT && u2->ToCreature()->HasStaticFlag(CREATURE_STATIC_FLAG_4_TREAT_AS_RAID_UNIT_FOR_HELPFUL_SPELLS))) + return true; + + return u1->GetTypeId() == TYPEID_UNIT && u2->GetTypeId() == TYPEID_UNIT && u1->GetFaction() == u2->GetFaction(); +} + +bool Unit::IsInRaidWith(Unit const* unit) const +{ + if (this == unit) + return true; + + Unit const* u1 = GetCharmerOrOwnerOrCreatorOrSelf(); + Unit const* u2 = unit->GetCharmerOrOwnerOrCreatorOrSelf(); + if (u1 == u2) + return true; + + if (u1->GetTypeId() == TYPEID_PLAYER && u2->GetTypeId() == TYPEID_PLAYER) + return u1->ToPlayer()->IsInSameRaidWith(u2->ToPlayer()); + else if ((u2->GetTypeId() == TYPEID_PLAYER && u1->GetTypeId() == TYPEID_UNIT && u1->ToCreature()->HasStaticFlag(CREATURE_STATIC_FLAG_4_TREAT_AS_RAID_UNIT_FOR_HELPFUL_SPELLS)) || + (u1->GetTypeId() == TYPEID_PLAYER && u2->GetTypeId() == TYPEID_UNIT && u2->ToCreature()->HasStaticFlag(CREATURE_STATIC_FLAG_4_TREAT_AS_RAID_UNIT_FOR_HELPFUL_SPELLS))) + return true; + + return u1->GetTypeId() == TYPEID_UNIT && u2->GetTypeId() == TYPEID_UNIT && u1->GetFaction() == u2->GetFaction(); +} + +void Unit::GetPartyMembers(std::list &TagUnitMap) +{ + Unit* owner = GetCharmerOrOwnerOrSelf(); + Group* group = nullptr; + if (owner->GetTypeId() == TYPEID_PLAYER) + group = owner->ToPlayer()->GetGroup(); + + if (group) + { + uint8 subgroup = owner->ToPlayer()->GetSubGroup(); + + for (GroupReference* 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); + + if (Guardian* pet = Target->GetGuardianPet()) + if (pet->IsAlive()) + TagUnitMap.push_back(pet); + } + } + } + else + { + if ((owner == this || IsInMap(owner)) && owner->IsAlive()) + TagUnitMap.push_back(owner); + if (Guardian* pet = owner->GetGuardianPet()) + if ((pet == this || IsInMap(pet)) && pet->IsAlive()) + TagUnitMap.push_back(pet); + } +} + +bool Unit::IsContestedGuard() const +{ + if (FactionTemplateEntry const* entry = GetFactionTemplateEntry()) + return entry->IsHostileToPvpActivePlayers(); + + return false; +} + +void Unit::SetPvP(bool state) +{ + if (state) + SetByteFlag(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PVP_FLAG, UNIT_BYTE2_FLAG_PVP); + else + RemoveByteFlag(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PVP_FLAG, UNIT_BYTE2_FLAG_PVP); +} + +Aura* Unit::AddAura(uint32 spellId, Unit* target) +{ + if (!target) + return nullptr; + + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); + if (!spellInfo) + 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->IsAlive() && !spellInfo->IsPassive() && !spellInfo->HasAttribute(SPELL_ATTR2_ALLOW_DEAD_TARGET)) + return nullptr; + + if (target->IsImmunedToSpell(spellInfo, this)) + return nullptr; + + for (uint32 i = 0; i < MAX_SPELL_EFFECTS; ++i) + { + if (!(effMask & (1 << i))) + continue; + + if (target->IsImmunedToSpellEffect(spellInfo, i, this)) + effMask &= ~(1 << i); + } + + if (!effMask) + return nullptr; + + AuraCreateInfo createInfo(spellInfo, effMask, target); + createInfo.SetCaster(this); + + if (Aura* aura = Aura::TryRefreshStackOrCreate(createInfo)) + { + aura->ApplyForTargets(); + return aura; + } + return nullptr; +} + +void Unit::SetAuraStack(uint32 spellId, Unit* target, uint32 stack) +{ + Aura* aura = target->GetAura(spellId, GetGUID()); + if (!aura) + aura = AddAura(spellId, target); + if (aura && stack) + aura->SetStackAmount(stack); +} + +void Unit::SendPlaySpellVisualKit(uint32 id, uint32 type, uint32 duration) const +{ + if (!sSpellVisualKitStore.LookupEntry(id)) + return; + + WorldPackets::Spells::PlaySpellVisualKit packet; + packet.Unit = GetGUID(); + packet.KitRecID = id; // SpellVisualKit.dbc index + packet.KitType = type; + packet.Duration = duration; + SendMessageToSet(packet.Write(), IsPlayer()); +} + +void Unit::SendPlaySpellVisual(uint32 spellVisualId, Unit const* target /*= nullptr*/, Optional targetPosition /*= {}*/, float travelSpeed /*= 0.f*/, uint16 missReason /*= 0*/, uint16 reflectStatus /*= 0*/, bool speedAsTime /*= false*/) const +{ + if (!sSpellVisualStore.LookupEntry(spellVisualId)) + return; + + WorldPackets::Spells::PlaySpellVisual packet; + packet.SpellVisualID = spellVisualId; + packet.Source = GetGUID(); + if (target) + packet.Target = target->GetGUID(); + + if (targetPosition) + packet.TargetPosition = *targetPosition; + + packet.TravelSpeed = travelSpeed; + packet.MissReason = missReason; + packet.ReflectStatus = reflectStatus; + packet.SpeedAsTime = speedAsTime; + + SendMessageToSet(packet.Write(), true); +} + +void Unit::CancelSpellMissiles(uint32 spellId, bool reverseMissile /*= false*/) +{ + bool hasMissile = false; + for (auto& itr : m_Events.GetEvents()) + { + if (Spell const* spell = Spell::ExtractSpellFromEvent(itr.second)) + { + if (spell->GetSpellInfo()->Id == spellId) + { + if (!itr.second->IsAbortScheduled()) + { + itr.second->ScheduleAbort(); + hasMissile = true; + } + } + } + } + + if (hasMissile) + { + WorldPackets::Spells::MissileCancel packet; + packet.OwnerGUID = GetGUID(); + packet.SpellID = spellId; + packet.Reverse = reverseMissile; + SendMessageToSet(packet.Write(), false); + } +} + +bool Unit::CanApplyResilience() const +{ + return !IsVehicle() && GetOwnerGUID().IsPlayer(); +} + +/*static*/ void Unit::ApplyResilience(Unit const* victim, int32* damage) +{ + // player mounted on multi-passenger mount is also classified as vehicle + if (victim->IsVehicle() && victim->GetTypeId() != TYPEID_PLAYER) + return; + + Unit const* target = nullptr; + if (victim->GetTypeId() == TYPEID_PLAYER) + target = victim; + else // victim->GetTypeId() == TYPEID_UNIT + { + if (Unit* owner = victim->GetOwner()) + if (owner->GetTypeId() == TYPEID_PLAYER) + target = owner; + } + + if (!target) + return; + + *damage -= target->GetDamageReduction(*damage); +} + +int32 Unit::CalculateAOEAvoidance(int32 damage, uint32 schoolMask, bool npcCaster) const +{ + damage = int32(float(damage) * GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_AOE_DAMAGE_AVOIDANCE, schoolMask)); + if (npcCaster) + damage = int32(float(damage) * GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_CREATURE_AOE_DAMAGE_AVOIDANCE, schoolMask)); + + return damage; +} + +// Melee based spells can be miss, parry or dodge on this step +// Crit or block - determined on damage calculation phase! (and can be both in some time) +float Unit::MeleeSpellMissChance(Unit const* victim, WeaponAttackType attType, SpellInfo const* spellInfo /*= nullptr*/) const +{ + if (spellInfo && spellInfo->HasAttribute(SPELL_ATTR7_NO_ATTACK_MISS)) + return 0.f; + + // Calculate miss chance + float missChance = GetMeleeMissChance(victim); + + if (!spellInfo && haveOffhandWeapon()) + missChance += 19.f; + + // Spellmod from SpellModOp::HitChance + float resistMissChance = 100.0f; + if (spellInfo) + if (Player* modOwner = GetSpellModOwner()) + modOwner->ApplySpellMod(spellInfo, SpellModOp::HitChance, resistMissChance); + + missChance -= resistMissChance - 100.0f; + + if (attType == RANGED_ATTACK) + missChance -= m_modRangedHitChance; + else + missChance -= m_modMeleeHitChance; + + // miss chance from auras after calculating skill based miss + missChance -= GetTotalAuraModifier(SPELL_AURA_MOD_HIT_CHANCE); + if (attType == RANGED_ATTACK) + missChance -= victim->GetTotalAuraModifier(SPELL_AURA_MOD_ATTACKER_RANGED_HIT_CHANCE); + else + missChance -= victim->GetTotalAuraModifier(SPELL_AURA_MOD_ATTACKER_MELEE_HIT_CHANCE); + + return std::max(missChance, 0.f); +} + +void Unit::OnPhaseChange() +{ + if (!IsInWorld()) + return; + + // Inform player groups that a member's phase has changed so the next PartyMemberState packet will update the phasing accordingly + if (IsPlayer() && ToPlayer()->GetGroup()) + ToPlayer()->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_PHASE); +} + +void Unit::UpdateObjectVisibility(bool forced) +{ + if (!forced) + AddToNotify(NOTIFY_VISIBILITY_CHANGED); + else + { + WorldObject::UpdateObjectVisibility(true); + // call MoveInLineOfSight for nearby creatures + Trinity::AIRelocationNotifier notifier(*this); + Cell::VisitAllObjects(this, notifier, GetVisibilityRange()); + } +} + +void Unit::SendMoveKnockBack(Player* player, float speedXY, float speedZ, float vcos, float vsin) +{ + WorldPackets::Movement::MoveKnockBack moveKnockBack; + moveKnockBack.MoverGUID = GetGUID(); + moveKnockBack.SequenceIndex = m_movementCounter++; + moveKnockBack.Speeds.HorzSpeed = speedXY; + moveKnockBack.Speeds.VertSpeed = speedZ; + moveKnockBack.Direction = Position(vcos, vsin); + player->SendDirectMessage(moveKnockBack.Write()); +} + +void Unit::KnockbackFrom(float x, float y, float speedXY, float speedZ) +{ + if (IsMovedByServer()) + GetMotionMaster()->MoveKnockbackFrom(x, y, speedXY, speedZ); + else + { + float vcos, vsin; + GetSinCos(x, y, vsin, vcos); + SendMoveKnockBack(GetGameClientMovingMe()->GetBasePlayer(), speedXY, -speedZ, vcos, vsin); + + if (HasAuraType(SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED) || HasAuraType(SPELL_AURA_FLY)) + SetCanFly(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) const +{ + // Hardcoded cases + switch (spellId) + { + case 7090: // Bear Form + return 29414; + case 35200: // Roc Form + return 4877; + default: + break; + } + + if (GetTypeId() == TYPEID_PLAYER) + { + switch (form) + { + case FORM_CAT: + // Based on Hair color + if (getRace() == RACE_NIGHTELF) + { + uint8 hairColor = GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_HAIR_COLOR_ID); + switch (hairColor) + { + case 7: // Violet + case 8: + return 29405; + case 3: // Light Blue + return 29406; + case 0: // Green + case 1: // Light Green + case 2: // Dark Green + return 29407; + case 4: // White + return 29408; + default: // original - Dark Blue + return 892; + } + } + else if (getRace() == RACE_TROLL) + { + uint8 hairColor = GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_HAIR_COLOR_ID); + switch (hairColor) + { + case 0: // Red + case 1: + return 33668; + case 2: // Yellow + case 3: + return 33667; + case 4: // Blue + case 5: + case 6: + return 33666; + case 7: // Purple + case 10: + return 33665; + default: // original - white + return 33669; + } + } + else if (getRace() == RACE_WORGEN) + { + // Based on Skin color + uint8 skinColor = GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_SKIN_ID); + // Male + if (getGender() == GENDER_MALE) + { + switch (skinColor) + { + case 1: // Brown + return 33662; + case 2: // Black + case 7: + return 33661; + case 4: // yellow + return 33664; + case 3: // White + case 5: + return 33663; + default: // original - Gray + return 33660; + } + } + // Female + else + { + switch (skinColor) + { + case 5: // Brown + case 6: + return 33662; + case 7: // Black + case 8: + return 33661; + case 3: // yellow + case 4: + return 33664; + case 2: // White + return 33663; + default: // original - Gray + return 33660; + } + } + } + // Based on Skin color + else if (getRace() == RACE_TAUREN) + { + uint8 skinColor = GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_SKIN_ID); + // Male + if (getGender() == GENDER_MALE) + { + switch (skinColor) + { + case 12: // White + case 13: + case 14: + case 18: // Completly White + return 29409; + case 9: // Light Brown + case 10: + case 11: + return 29410; + case 6: // Brown + case 7: + case 8: + return 29411; + case 0: // Dark + case 1: + case 2: + case 3: // Dark Grey + case 4: + case 5: + return 29412; + default: // original - Grey + return 8571; + } + } + // Female + else + { + switch (skinColor) + { + case 10: // White + return 29409; + case 6: // Light Brown + case 7: + return 29410; + case 4: // Brown + case 5: + return 29411; + case 0: // Dark + case 1: + case 2: + case 3: + return 29412; + default: // original - Grey + return 8571; + } + } + } + else if (Player::TeamForRace(getRace()) == ALLIANCE) + return 892; + else + return 8571; + case FORM_BEAR: + // Based on Hair color + if (getRace() == RACE_NIGHTELF) + { + uint8 hairColor = GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_HAIR_COLOR_ID); + switch (hairColor) + { + case 0: // Green + case 1: // Light Green + case 2: // Dark Green + return 29413; // 29415? + case 6: // Dark Blue + return 29414; + case 4: // White + return 29416; + case 3: // Light Blue + return 29417; + default: // original - Violet + return 2281; + } + } + else if (getRace() == RACE_TROLL) + { + uint8 hairColor = GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_HAIR_COLOR_ID); + switch (hairColor) + { + case 0: // Red + case 1: + return 33657; + case 2: // Yellow + case 3: + return 33659; + case 7: // Purple + case 10: + return 33656; + case 8: // White + case 9: + case 11: + case 12: + return 33658; + default: // original - Blue + return 33655; + } + } + else if (getRace() == RACE_WORGEN) + { + // Based on Skin color + uint8 skinColor = GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_SKIN_ID); + // Male + if (getGender() == GENDER_MALE) + { + switch (skinColor) + { + case 1: // Brown + return 33653; + case 2: // Black + case 7: + return 33651; + case 4: // Yellow + return 33652; + case 3: // White + case 5: + return 33654; + default: // original - Gray + return 33650; + } + } + // Female + else + { + switch (skinColor) + { + case 5: // Brown + case 6: + return 33652; + case 7: // Black + case 8: + return 33651; + case 3: // yellow + case 4: + return 33654; + case 2: // White + return 33653; + default: // original - Gray + return 33650; + } + } + } + // Based on Skin color + else if (getRace() == RACE_TAUREN) + { + uint8 skinColor = GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_SKIN_ID); + // Male + if (getGender() == GENDER_MALE) + { + switch (skinColor) + { + case 0: // Dark (Black) + case 1: + case 2: + return 29418; + case 3: // White + case 4: + case 5: + case 12: + case 13: + case 14: + return 29419; + case 9: // Light Brown/Grey + case 10: + case 11: + case 15: + case 16: + case 17: + return 29420; + case 18: // Completly White + return 29421; + default: // original - Brown + return 2289; + } + } + // Female + else + { + switch (skinColor) + { + case 0: // Dark (Black) + case 1: + return 29418; + case 2: // White + case 3: + return 29419; + case 6: // Light Brown/Grey + case 7: + case 8: + case 9: + return 29420; + case 10: // Completly White + return 29421; + default: // original - Brown + return 2289; + } + } + } + else if (Player::TeamForRace(getRace()) == ALLIANCE) + return 2281; + else + return 2289; + case FORM_FLIGHT: + if (Player::TeamForRace(getRace()) == ALLIANCE) + return 20857; + return 20872; + case FORM_FLIGHT_EPIC: + if (Player::TeamForRace(getRace()) == ALLIANCE) + return (getRace() == RACE_WORGEN ? 37729 : 21243); + if (getRace() == RACE_TROLL) + return 37730; + return 21244; + case FORM_MOONKIN: + if (getRace() == RACE_TROLL) + return 37174; + if (getRace() == RACE_WORGEN) + return 37173; + return 15374; + case FORM_GHOSTWOLF: + if (HasAura(58135)) //! Glyph of Arctic Wolf + return 27312; + return 4613; + default: + break; + } + } + + uint32 modelid = 0; + SpellShapeshiftFormEntry const* formEntry = sSpellShapeshiftFormStore.LookupEntry(form); + if (formEntry && formEntry->CreatureDisplayID[0]) + { + // Take the alliance modelid as default + if (GetTypeId() != TYPEID_PLAYER) + return formEntry->CreatureDisplayID[0]; + else + { + if (Player::TeamForRace(getRace()) == ALLIANCE) + modelid = formEntry->CreatureDisplayID[0]; + else + modelid = formEntry->CreatureDisplayID[1]; + + // If the player is horde but there are no values for the horde modelid - take the alliance modelid + if (!modelid && Player::TeamForRace(getRace()) == HORDE) + modelid = formEntry->CreatureDisplayID[0]; + } + } + + return modelid; +} + +uint32 Unit::GetModelForTotem(PlayerTotemType totemType) +{ + switch (getRace()) + { + case RACE_ORC: + { + switch (totemType) + { + case SUMMON_TYPE_TOTEM_FIRE: // fire + return 30758; + case SUMMON_TYPE_TOTEM_EARTH: // earth + return 30757; + case SUMMON_TYPE_TOTEM_WATER: // water + return 30759; + case SUMMON_TYPE_TOTEM_AIR: // air + return 30756; + } + break; + } + case RACE_DWARF: + { + switch (totemType) + { + case SUMMON_TYPE_TOTEM_FIRE: // fire + return 30754; + case SUMMON_TYPE_TOTEM_EARTH: // earth + return 30753; + case SUMMON_TYPE_TOTEM_WATER: // water + return 30755; + case SUMMON_TYPE_TOTEM_AIR: // air + return 30736; + } + break; + } + case RACE_TROLL: + { + switch (totemType) + { + case SUMMON_TYPE_TOTEM_FIRE: // fire + return 30762; + case SUMMON_TYPE_TOTEM_EARTH: // earth + return 30761; + case SUMMON_TYPE_TOTEM_WATER: // water + return 30763; + case SUMMON_TYPE_TOTEM_AIR: // air + return 30760; + } + break; + } + case RACE_TAUREN: + { + switch (totemType) + { + case SUMMON_TYPE_TOTEM_FIRE: // fire + return 4589; + case SUMMON_TYPE_TOTEM_EARTH: // earth + return 4588; + case SUMMON_TYPE_TOTEM_WATER: // water + return 4587; + case SUMMON_TYPE_TOTEM_AIR: // air + return 4590; + } + break; + } + case RACE_DRAENEI: + { + switch (totemType) + { + case SUMMON_TYPE_TOTEM_FIRE: // fire + return 19074; + case SUMMON_TYPE_TOTEM_EARTH: // earth + return 19073; + case SUMMON_TYPE_TOTEM_WATER: // water + return 19075; + case SUMMON_TYPE_TOTEM_AIR: // air + return 19071; + } + break; + } + case RACE_GOBLIN: + { + switch (totemType) + { + case SUMMON_TYPE_TOTEM_FIRE: // fire + return 30783; + case SUMMON_TYPE_TOTEM_EARTH: // earth + return 30782; + case SUMMON_TYPE_TOTEM_WATER: // water + return 30784; + case SUMMON_TYPE_TOTEM_AIR: // air + return 30781; + } + break; + } + } + return 0; +} + +void Unit::JumpTo(float speedXY, float speedZ, bool forward, Optional dest) +{ + float angle = forward ? 0 : float(M_PI); + if (dest) + angle += GetRelativeAngle(*dest); + + if (GetTypeId() == TYPEID_UNIT) + GetMotionMaster()->MoveJumpTo(angle, speedXY, speedZ); + else + { + float vcos = std::cos(angle + GetOrientation()); + float vsin = std::sin(angle + GetOrientation()); + SendMoveKnockBack(ToPlayer(), speedXY, -speedZ, vcos, vsin); + } +} + +void Unit::JumpTo(WorldObject* obj, float speedZ, bool withOrientation) +{ + float x, y, z; + obj->GetContactPoint(this, x, y, z); + float speedXY = GetExactDist2d(x, y) * 10.0f / speedZ; + GetMotionMaster()->MoveJump(x, y, z, GetAngle(obj), speedXY, speedZ, EVENT_JUMP, withOrientation); +} + +bool Unit::HandleSpellClick(Unit* clicker, int8 seatId) +{ + bool result = false; + uint32 spellClickEntry = GetVehicleKit() ? GetVehicleKit()->GetCreatureEntry() : GetEntry(); + TriggerCastFlags const flags = GetVehicleKit() ? TRIGGERED_IGNORE_CASTER_MOUNTED_OR_ON_VEHICLE : TRIGGERED_NONE; + + 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 + if (!sConditionMgr->IsObjectMeetingSpellClickConditions(spellClickEntry, itr->second.spellId, clicker, this)) + 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) ? GetOwnerOrCreatorGUID() : clicker->GetGUID(); + + SpellInfo const* spellEntry = sSpellMgr->GetSpellInfo(itr->second.spellId); + // if (!spellEntry) should be checked at npc_spellclick load + + if (seatId > -1) + { + uint8 i = 0; + bool valid = false; + while (i < MAX_SPELL_EFFECTS && !valid) + { + if (spellEntry->Effects[i].ApplyAuraName == SPELL_AURA_CONTROL_VEHICLE) + { + valid = true; + break; + } + ++i; + } + + if (!valid) + { + TC_LOG_ERROR("sql.sql", "Spell %u specified in npc_spellclick_spells is not a valid vehicle enter aura!", itr->second.spellId); + continue; + } + + if (IsInMap(caster)) + { + CastSpellExtraArgs args(flags); + args.OriginalCaster = origCasterGUID; + args.AddSpellMod(SpellValueMod(SPELLVALUE_BASE_POINT0 + i), seatId + 1); + caster->CastSpell(target, itr->second.spellId, args); + } + else // This can happen during Player::_LoadAuras + { + int32 bp[MAX_SPELL_EFFECTS] = { }; + for (uint32 j = 0; j < MAX_SPELL_EFFECTS; ++j) + bp[j] = spellEntry->Effects[j].BasePoints; + + bp[i] = seatId; + + AuraCreateInfo createInfo(spellEntry, MAX_EFFECT_MASK, this); + createInfo + .SetCaster(clicker) + .SetBaseAmount(bp) + .SetCasterGUID(origCasterGUID); + + Aura::TryRefreshStackOrCreate(createInfo); + } + } + else + { + if (IsInMap(caster)) + caster->CastSpell(target, spellEntry->Id, CastSpellExtraArgs().SetOriginalCaster(origCasterGUID)); + else + { + AuraCreateInfo createInfo(spellEntry, MAX_EFFECT_MASK, this); + createInfo + .SetCaster(clicker) + .SetCasterGUID(origCasterGUID); + + Aura::TryRefreshStackOrCreate(createInfo); + } + } + + result = true; + } + + Creature* creature = ToCreature(); + if (creature && creature->IsAIEnabled()) + creature->AI()->OnSpellClick(clicker, result); + + return result; +} + +void Unit::EnterVehicle(Unit* base, int8 seatId) +{ + CastSpellExtraArgs args(TRIGGERED_IGNORE_CASTER_MOUNTED_OR_ON_VEHICLE); + args.AddSpellBP0(seatId + 1); + CastSpell(base, VEHICLE_SPELL_RIDE_HARDCODED, args); +} + +void Unit::_EnterVehicle(Vehicle* vehicle, int8 seatId, AuraApplication const* aurApp) +{ + // Must be called only from aura handler + ASSERT(aurApp); + + if (!IsAlive() || GetVehicleKit() == vehicle || vehicle->GetBase()->IsOnVehicle(this)) + return; + + if (m_vehicle) + { + if (m_vehicle != vehicle) + { + TC_LOG_DEBUG("entities.vehicle", "EnterVehicle: %u exit %u and enter %u.", GetEntry(), m_vehicle->GetBase()->GetEntry(), vehicle->GetBase()->GetEntry()); + ExitVehicle(); + } + else if (seatId >= 0 && seatId == GetTransSeat()) + return; + else + { + //Exit the current vehicle because unit will reenter in a new seat. + m_vehicle->GetBase()->RemoveAurasByType(SPELL_AURA_CONTROL_VEHICLE, GetGUID(), aurApp->GetBase()); + } + } + + if (aurApp->GetRemoveMode().HasAnyFlag()) + return; + + if (Player* player = ToPlayer()) + { + if (vehicle->GetBase()->GetTypeId() == TYPEID_PLAYER && player->IsInCombat()) + { + vehicle->GetBase()->RemoveAura(const_cast(aurApp)); + return; + } + + if (vehicle->GetBase()->GetTypeId() == TYPEID_UNIT) + { + // If a player entered a vehicle that is part of a formation, remove it from said formation + if (CreatureGroup* creatureGroup = vehicle->GetBase()->ToCreature()->GetFormation()) + creatureGroup->RemoveMember(vehicle->GetBase()->ToCreature()); + } + } + + // If vehicle flag for fixed position set (cannons), or if the following hardcoded units, then set state rooted + // 30236 | Argent Cannon + // 39759 | Tankbuster Cannon + if ((vehicle->GetVehicleInfo()->Flags & VEHICLE_FLAG_FIXED_POSITION) || vehicle->GetBase()->GetEntry() == 30236 || vehicle->GetBase()->GetEntry() == 39759) + SetControlled(true, UNIT_STATE_ROOT); + + ASSERT(!m_vehicle); + (void)vehicle->AddVehiclePassenger(this, seatId); +} + +void Unit::ChangeSeat(int8 seatId, bool next) +{ + if (!m_vehicle) + return; + + // Don't change if current and new seat are identical + if (seatId == GetTransSeat()) + return; + + SeatMap::const_iterator seat = (seatId < 0 ? m_vehicle->GetNextEmptySeat(GetTransSeat(), next) : m_vehicle->Seats.find(seatId)); + // The second part of the check will only return true if seatId >= 0. @Vehicle::GetNextEmptySeat makes sure of that. + if (seat == m_vehicle->Seats.end() || !seat->second.IsEmpty()) + return; + + AuraEffect* rideVehicleEffect = nullptr; + AuraEffectList const& vehicleAuras = m_vehicle->GetBase()->GetAuraEffectsByType(SPELL_AURA_CONTROL_VEHICLE); + for (AuraEffectList::const_iterator itr = vehicleAuras.begin(); itr != vehicleAuras.end(); ++itr) + { + if ((*itr)->GetCasterGUID() != GetGUID()) + continue; + + // Make sure there is only one ride vehicle aura on target cast by the unit changing seat + ASSERT(!rideVehicleEffect); + rideVehicleEffect = *itr; + } + + // Unit riding a vehicle must always have control vehicle aura on target + ASSERT(rideVehicleEffect); + + rideVehicleEffect->ChangeAmount(seat->first + 1); +} + +void Unit::ExitVehicle(Position const* /*exitPosition*/) +{ + //! This function can be called at upper level code to initialize an exit from the passenger's side. + if (!m_vehicle) + return; + + GetVehicleBase()->RemoveAurasByType(SPELL_AURA_CONTROL_VEHICLE, GetGUID()); + //! The following call would not even be executed successfully as the + //! SPELL_AURA_CONTROL_VEHICLE unapply handler already calls _ExitVehicle without + //! specifying an exitposition. The subsequent call below would return on if (!m_vehicle). + /*_ExitVehicle(exitPosition);*/ + //! To do: + //! We need to allow SPELL_AURA_CONTROL_VEHICLE unapply handlers in spellscripts + //! to specify exit coordinates and either store those per passenger, or we need to + //! init spline movement based on those coordinates in unapply handlers, and + //! relocate exiting passengers based on Unit::moveSpline data. Either way, + //! Coming Soon(TM) +} + +void Unit::_ExitVehicle(Position const* exitPosition) +{ + /// It's possible m_vehicle is nullptr, when this function is called indirectly from @VehicleJoinEvent::Abort. + /// In that case it was not possible to add the passenger to the vehicle. The vehicle aura has already been removed + /// from the target in the aforementioned function and we don't need to do anything else at this point. + if (!m_vehicle) + return; + + // This should be done before dismiss, because there may be some aura removal + VehicleSeatAddon const* seatAddon = m_vehicle->GetSeatAddonForSeatOfPassenger(this); + Vehicle* vehicle = m_vehicle->RemovePassenger(this); + + if (!vehicle) + { + TC_LOG_ERROR("entities.vehicle", "RemovePassenger() couldn't remove current unit from vehicle."); + return; + } + + Player* player = ToPlayer(); + + // If the player is on mounted duel and exits the mount, he should immediatly lose the duel + if (player && player->duel && player->duel->isMounted) + player->DuelComplete(DUEL_FLED); + + SetControlled(false, UNIT_STATE_ROOT); // SMSG_MOVE_FORCE_UNROOT, ~MOVEMENTFLAG_ROOT + + AddUnitState(UNIT_STATE_MOVE); + + if (player) + player->SetFallInformation(0, GetPositionZ()); + + Position pos; + // If we ask for a specific exit position, use that one. Otherwise allow scripts to modify it + if (exitPosition) + pos = *exitPosition; + else + { + // Set exit position to vehicle position and use the current orientation + pos = vehicle->GetBase()->GetPosition(); + pos.SetOrientation(GetOrientation()); + + // Change exit position based on seat entry + 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 }); + } + } + + float height = pos.GetPositionZ() + vehicle->GetBase()->GetCollisionHeight(); + + Movement::MoveSplineInit init(this); + + // Creatures without inhabit type air should begin falling after exiting the vehicle + if (GetTypeId() == TYPEID_UNIT && !CanFly() && height > GetMap()->GetWaterOrGroundLevel(GetPhaseShift(), pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ() + vehicle->GetBase()->GetCollisionHeight(), &height)) + init.SetFall(); + + init.MoveTo(pos.GetPositionX(), pos.GetPositionY(), height, false); + init.SetFacing(pos.GetOrientation()); + init.SetTransportExit(); + GetMotionMaster()->LaunchMoveSpline(std::move(init), EVENT_VEHICLE_EXIT, MOTION_SLOT_CONTROLLED); + + if (player) + player->ResummonPetTemporaryUnSummonedIfAny(); + + if (vehicle->GetBase()->HasUnitTypeMask(UNIT_MASK_MINION) && vehicle->GetBase()->GetTypeId() == TYPEID_UNIT) + if (((Minion*)vehicle->GetBase())->GetOwner() == this) + vehicle->GetBase()->ToCreature()->DespawnOrUnsummon(1); + + if (HasUnitTypeMask(UNIT_MASK_ACCESSORY)) + { + // Vehicle just died, we die too + if (vehicle->GetBase()->getDeathState() == JUST_DIED) + setDeathState(JUST_DIED); + // If for other reason we as minion are exiting the vehicle (ejected, master dismounted) - unsummon + else + ToTempSummon()->UnSummon(2000); // Approximation + } +} + +bool Unit::IsFalling() const +{ + return m_movementInfo.HasMovementFlag(MOVEMENTFLAG_FALLING | MOVEMENTFLAG_FALLING_FAR) || movespline->isFalling(); +} + +bool Unit::CanSwim() const +{ + // Mirror client behavior, if this method returns false then client will not use swimming animation and for players will apply gravity as if there was no water + if (HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_CANT_SWIM)) + return false; + if (HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED)) // is player + return true; + if (HasFlag(UNIT_FIELD_FLAGS_2, UNIT_FLAG2_AI_WILL_ONLY_SWIM_IF_TARGET_SWIMS)) + return false; + if (HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PET_IN_COMBAT)) + return true; + return HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_RENAME | UNIT_FLAG_CAN_SWIM); +} + +void Unit::NearTeleportTo(Position const& pos, bool casting /*= false*/) +{ + DisableSpline(); + if (GetTypeId() == TYPEID_PLAYER) + { + WorldLocation target(GetMapId(), pos); + ToPlayer()->TeleportTo(target, TELE_TO_NOT_LEAVE_TRANSPORT | TELE_TO_NOT_LEAVE_COMBAT | TELE_TO_NOT_UNSUMMON_PET | (casting ? TELE_TO_SPELL : TELE_TO_NONE)); + } + else + { + SendTeleportPacket(pos); + UpdatePosition(pos, true); + UpdateObjectVisibility(); + } +} + +void Unit::WriteMovementInfo(WorldPacket& data, Movement::ExtraMovementStatusElement* extras /*= nullptr*/, uint32* movementCounter /*= nullptr*/) +{ + MovementInfo const& mi = m_movementInfo; + + bool hasMovementFlags = GetUnitMovementFlags() != 0; + bool hasMovementFlags2 = GetExtraUnitMovementFlags() != 0; + bool hasTimestamp = true; + bool hasOrientation = !G3D::fuzzyEq(GetOrientation(), 0.0f); + bool hasTransportData = GetTransGUID() != 0; + bool hasSpline = IsSplineEnabled(); + + bool hasTransportTime2 = hasTransportData && m_movementInfo.transport.time2 != 0; + bool hasTransportVehicleId = hasTransportData && m_movementInfo.transport.vehicleId != 0; + bool hasPitch = HasUnitMovementFlag(MovementFlags(MOVEMENTFLAG_SWIMMING | MOVEMENTFLAG_FLYING)) || HasExtraUnitMovementFlag(MOVEMENTFLAG2_ALWAYS_ALLOW_PITCHING); + bool hasFallDirection = HasUnitMovementFlag(MOVEMENTFLAG_FALLING); + bool hasFallData = hasFallDirection || m_movementInfo.jump.fallTime != 0; + bool hasSplineElevation = HasUnitMovementFlag(MOVEMENTFLAG_SPLINE_ELEVATION); + + MovementStatusElements const* sequence = GetMovementStatusElementsSequence(static_cast(data.GetOpcode())); + if (!sequence) + { + TC_LOG_ERROR("network", "Unit::WriteMovementInfo: No movement sequence found for opcode %s", GetOpcodeNameForLogging(static_cast(data.GetOpcode())).c_str()); + return; + } + + ObjectGuid guid = GetGUID(); + ObjectGuid tguid = hasTransportData ? GetTransGUID() : ObjectGuid::Empty; + + for (; *sequence != MSEEnd; ++sequence) + { + MovementStatusElements const& element = *sequence; + + switch (element) + { + case MSEHasGuidByte0: + case MSEHasGuidByte1: + case MSEHasGuidByte2: + case MSEHasGuidByte3: + case MSEHasGuidByte4: + case MSEHasGuidByte5: + case MSEHasGuidByte6: + case MSEHasGuidByte7: + data.WriteBit(guid[element - MSEHasGuidByte0]); + break; + case MSEHasTransportGuidByte0: + case MSEHasTransportGuidByte1: + case MSEHasTransportGuidByte2: + case MSEHasTransportGuidByte3: + case MSEHasTransportGuidByte4: + case MSEHasTransportGuidByte5: + case MSEHasTransportGuidByte6: + case MSEHasTransportGuidByte7: + if (hasTransportData) + data.WriteBit(tguid[element - MSEHasTransportGuidByte0]); + break; + case MSEGuidByte0: + case MSEGuidByte1: + case MSEGuidByte2: + case MSEGuidByte3: + case MSEGuidByte4: + case MSEGuidByte5: + case MSEGuidByte6: + case MSEGuidByte7: + data.WriteByteSeq(guid[element - MSEGuidByte0]); + break; + case MSETransportGuidByte0: + case MSETransportGuidByte1: + case MSETransportGuidByte2: + case MSETransportGuidByte3: + case MSETransportGuidByte4: + case MSETransportGuidByte5: + case MSETransportGuidByte6: + case MSETransportGuidByte7: + if (hasTransportData) + data.WriteByteSeq(tguid[element - MSETransportGuidByte0]); + break; + case MSEHasMovementFlags: + data.WriteBit(!hasMovementFlags); + break; + case MSEHasMovementFlags2: + data.WriteBit(!hasMovementFlags2); + break; + case MSEHasTimestamp: + data.WriteBit(!hasTimestamp); + break; + case MSEHasOrientation: + data.WriteBit(!hasOrientation); + break; + case MSEHasTransportData: + data.WriteBit(hasTransportData); + break; + case MSEHasTransportTime2: + if (hasTransportData) + data.WriteBit(hasTransportTime2); + break; + case MSEHasVehicleId: + if (hasTransportData) + data.WriteBit(hasTransportVehicleId); + break; + case MSEHasPitch: + data.WriteBit(!hasPitch); + break; + case MSEHasFallData: + data.WriteBit(hasFallData); + break; + case MSEHasFallDirection: + if (hasFallData) + data.WriteBit(hasFallDirection); + break; + case MSEHasSplineElevation: + data.WriteBit(!hasSplineElevation); + break; + case MSEHasSpline: + data.WriteBit(hasSpline); + break; + case MSEHasHeightChangeFailed: + data.WriteBit(mi.HasHeightChangeFailed()); + break; + case MSEMovementFlags: + if (hasMovementFlags) + data.WriteBits(GetUnitMovementFlags(), 30); + break; + case MSEMovementFlags2: + if (hasMovementFlags2) + data.WriteBits(GetExtraUnitMovementFlags(), 12); + break; + case MSETimestamp: + if (hasTimestamp) + data << mi.time; + break; + case MSEPositionX: + data << GetPositionX(); + break; + case MSEPositionY: + data << GetPositionY(); + break; + case MSEPositionZ: + data << GetPositionZ(); + break; + case MSEOrientation: + if (hasOrientation) + data << GetOrientation(); + break; + case MSETransportPositionX: + if (hasTransportData) + data << GetTransOffsetX(); + break; + case MSETransportPositionY: + if (hasTransportData) + data << GetTransOffsetY(); + break; + case MSETransportPositionZ: + if (hasTransportData) + data << GetTransOffsetZ(); + break; + case MSETransportOrientation: + if (hasTransportData) + data << GetTransOffsetO(); + break; + case MSETransportSeat: + if (hasTransportData) + data << GetTransSeat(); + break; + case MSETransportTime: + if (hasTransportData) + data << GetTransTime(); + break; + case MSETransportTime2: + if (hasTransportData && hasTransportTime2) + data << mi.transport.time2; + break; + case MSETransportVehicleId: + if (hasTransportData && hasTransportVehicleId) + data << mi.transport.vehicleId; + break; + case MSEPitch: + if (hasPitch) + data << mi.pitch; + break; + case MSEFallTime: + if (hasFallData) + data << mi.jump.fallTime; + break; + case MSEFallVerticalSpeed: + if (hasFallData) + data << mi.jump.zspeed; + break; + case MSEFallCosAngle: + if (hasFallData && hasFallDirection) + data << mi.jump.cosAngle; + break; + case MSEFallSinAngle: + if (hasFallData && hasFallDirection) + data << mi.jump.sinAngle; + break; + case MSEFallHorizontalSpeed: + if (hasFallData && hasFallDirection) + data << mi.jump.xyspeed; + break; + case MSESplineElevation: + if (hasSplineElevation) + data << mi.splineElevation; + break; + case MSECounter: + data << uint32(movementCounter ? *movementCounter : 0); + break; + case MSEZeroBit: + data.WriteBit(0); + break; + case MSEOneBit: + data.WriteBit(1); + break; + case MSEFlushBits: + data.FlushBits(); + break; + case MSEExtraElement: + extras->WriteNextElement(data); + break; + default: + ASSERT(Movement::PrintInvalidSequenceElement(element, __FUNCTION__)); + break; + } + } +} + +void Unit::SendTeleportPacket(Position const& pos) +{ + // SMSG_MOVE_UPDATE_TELEPORT is sent to nearby players to signal the teleport + // MSG_MOVE_TELEPORT is sent to self in order to trigger MSG_MOVE_TELEPORT_ACK and update the position server side + if (IsMovedByClient()) + { + Player* playerMover = GetGameClientMovingMe()->GetBasePlayer(); + float x, y, z, o; + pos.GetPosition(x, y, z, o); + if (TransportBase* transportBase = GetDirectTransport()) + transportBase->CalculatePassengerOffset(x, y, z, &o); + + WorldPackets::Movement::MoveTeleport moveTeleport; + moveTeleport.MoverGUID = GetGUID(); + moveTeleport.Pos = Position(x, y, z); + if (GetTransGUID()) + moveTeleport.TransportGUID = GetTransGUID(); + moveTeleport.Facing = o; + moveTeleport.SequenceIndex = m_movementCounter++; + playerMover->SendDirectMessage(moveTeleport.Write()); + } + else + { + // This is the only packet sent for creatures which contains MovementInfo structure + // we do not update m_movementInfo for creatures so it needs to be done manually here + WorldPackets::Movement::MoveUpdateTeleport moveUpdateTeleport; + moveUpdateTeleport.Status = &m_movementInfo; + moveUpdateTeleport.Status->guid = GetGUID(); + moveUpdateTeleport.Status->pos.Relocate(pos); + moveUpdateTeleport.Status->time = GameTime::GetGameTimeMS(); + SendMessageToSet(moveUpdateTeleport.Write(), false); + } +} + +bool Unit::UpdatePosition(float x, float y, float z, float orientation, bool teleport) +{ + // prevent crash when a bad coord is sent by the client + if (!Trinity::IsValidMapCoord(x, y, z, orientation)) + { + TC_LOG_DEBUG("entities.unit", "Unit::UpdatePosition(%f, %f, %f) .. bad coordinates!", x, y, z); + return false; + } + + // Check if angular distance changed + bool const turn = G3D::fuzzyGt(M_PI - fabs(fabs(GetOrientation() - orientation) - M_PI), 0.0f); + + // G3D::fuzzyEq won't help here, in some cases magnitudes differ by a little more than G3D::eps, but should be considered equal + bool const relocated = (teleport || + std::fabs(GetPositionX() - x) > 0.001f || + std::fabs(GetPositionY() - y) > 0.001f || + std::fabs(GetPositionZ() - z) > 0.001f); + + if (relocated) + { + // move and update visible state if need + if (GetTypeId() == TYPEID_PLAYER) + GetMap()->PlayerRelocation(ToPlayer(), x, y, z, orientation); + else + GetMap()->CreatureRelocation(ToCreature(), x, y, z, orientation); + } + else if (turn) + UpdateOrientation(orientation); + + _positionUpdateInfo.Relocated = relocated; + _positionUpdateInfo.Turned = turn; + + return (relocated || turn); +} + +bool Unit::UpdatePosition(Position const& pos, bool teleport) +{ + return UpdatePosition(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), pos.GetOrientation(), teleport); +} + +//! Only server-side orientation update, does not broadcast to client +void Unit::UpdateOrientation(float orientation) +{ + SetOrientation(orientation); + if (IsVehicle()) + GetVehicleKit()->RelocatePassengers(); +} + +//! Only server-side height update, does not broadcast to client +void Unit::UpdateHeight(float newZ) +{ + Relocate(GetPositionX(), GetPositionY(), newZ); + if (IsVehicle()) + GetVehicleKit()->RelocatePassengers(); +} + +void Unit::ClearPendingMovementChangeForType(MovementChangeType changeType) +{ + m_pendingMovementChanges.erase(changeType); +} + +void Unit::AssignPendingMovementChange(MovementChangeType changeType, PlayerMovementPendingChange&& newChange) +{ + m_pendingMovementChanges[changeType] = newChange; +} + +PlayerMovementPendingChange const* Unit::GetPendingMovementChange(MovementChangeType changeType) const +{ + return Trinity::Containers::MapGetValuePtr(m_pendingMovementChanges, changeType); +} + +void Unit::CheckPendingMovementAcks() +{ + if (sWorld->getIntConfig(CONFIG_PENDING_MOVE_CHANGES_TIMEOUT) == 0) + return; + + if (!HasPendingMovementChange()) + return; + + for (std::pair const& pendingChange : m_pendingMovementChanges) + { + if (GameTime::GetGameTimeMS() > pendingChange.second.time + sWorld->getIntConfig(CONFIG_PENDING_MOVE_CHANGES_TIMEOUT)) + { + /* + when players are teleported from one corner of a map to an other (example: from Dragonblight to the entrance of Naxxramas, both in the same map: Northend), + is it done through what is called a 'near' teleport. A near teleport always involve teleporting a player from one point to an other in the same map, even if + the distance is huge. When that distance is big enough, a loading screen appears on the client side. During that time, the client loads the surrounding zone + of the new location (and everything it contains). The problem is that, as long as the client hasn't finished loading the new zone, it will NOT ack the near + teleport. So if the server sends a near teleport order at a certain time and the client takes 20s to load the new zone (let's imagine a very slow computer), + even with zero latency, the server will receive an ack from the client only after 20s. + + For this reason and because the current implementation is simple (you dear reader, feel free to improve it if you can), we will just ignore checking for + near teleport acks (for now. @todo). + */ + if (pendingChange.second.movementChangeType == MovementChangeType::TELEPORT) + return; + + GameClient* controller = GetGameClientMovingMe(); + controller->GetWorldSession()->KickPlayer(); + TC_LOG_INFO("cheat", "Unit::CheckPendingMovementAcks: Player GUID: %s took too long to acknowledge a movement change. He was therefore kicked.", controller->GetBasePlayer()->GetGUID().ToString().c_str()); + break; + } + } +} + +void Unit::PurgeAndApplyPendingMovementChanges(bool informObservers /* = true */) +{ + for (std::pair const& pendingChange : m_pendingMovementChanges) + { + float speedFlat = pendingChange.second.newValue; + MovementChangeType changeType = pendingChange.second.movementChangeType; + Optional moveType; + switch (changeType) + { + case MovementChangeType::SPEED_CHANGE_WALK: moveType = MOVE_WALK; break; + case MovementChangeType::SPEED_CHANGE_RUN: moveType = MOVE_RUN; break; + case MovementChangeType::SPEED_CHANGE_RUN_BACK: moveType = MOVE_RUN_BACK; break; + case MovementChangeType::SPEED_CHANGE_SWIM: moveType = MOVE_SWIM; break; + case MovementChangeType::SPEED_CHANGE_SWIM_BACK: moveType = MOVE_SWIM_BACK; break; + case MovementChangeType::RATE_CHANGE_TURN: moveType = MOVE_TURN_RATE; break; + case MovementChangeType::SPEED_CHANGE_FLIGHT_SPEED: moveType = MOVE_FLIGHT; break; + case MovementChangeType::SPEED_CHANGE_FLIGHT_BACK_SPEED: moveType = MOVE_FLIGHT_BACK; break; + case MovementChangeType::RATE_CHANGE_PITCH: moveType = MOVE_PITCH_RATE; break; + case MovementChangeType::GRAVITY_DISABLE: + Unit::SetDisableGravity(pendingChange.second.apply); + break; + case MovementChangeType::SET_CAN_FLY: + Unit::SetCanFly(pendingChange.second.apply); + break; + case MovementChangeType::SET_CAN_TRANSITION_BETWEEN_SWIM_AND_FLY: + Unit::SetCanTransitionBetweenSwimAndFly(pendingChange.second.apply); + break; + case MovementChangeType::SET_COLLISION_HGT: + break; + default: + ASSERT(false); + return; + } + + if (moveType.has_value()) + { + float newSpeedRate = speedFlat / (IsControlledByPlayer() ? playerBaseMoveSpeed[*moveType] : baseMoveSpeed[*moveType]); + SetSpeedRateReal(*moveType, newSpeedRate); + + if (informObservers) + MovementPacketSender::SendSpeedChangeToObservers(this, *moveType, speedFlat); + } + else if (informObservers) + { + switch (changeType) + { + case MovementChangeType::GRAVITY_DISABLE: + case MovementChangeType::SET_CAN_FLY: + case MovementChangeType::SET_CAN_TRANSITION_BETWEEN_SWIM_AND_FLY: + MovementPacketSender::SendMovementFlagChangeToObservers(this); + break; + default: + break; + } + } + } + + m_pendingMovementChanges.clear(); +} + +PlayerMovementPendingChange::PlayerMovementPendingChange() +{ + time = GameTime::GetGameTimeMS(); +} + +// baseRage means damage taken when attacker = false +int32 Unit::RewardRage(uint32 baseRage, bool asAttacker) +{ + float addRage = baseRage; + + addRage *= sWorld->getRate(RATE_POWER_RAGE_INCOME); + return ModifyPower(POWER_RAGE, static_cast(addRage), !asAttacker); +} + +void Unit::StopAttackFaction(uint32 faction_id) +{ + if (Unit* victim = GetVictim()) + { + if (victim->GetFactionTemplateEntry()->Faction == faction_id) + { + AttackStop(); + if (IsNonMeleeSpellCast(false)) + InterruptNonMeleeSpells(false); + + // melee and ranged forced attack cancel + if (GetTypeId() == TYPEID_PLAYER) + ToPlayer()->SendAttackSwingCancelAttack(); + } + } + + AttackerSet const& attackers = getAttackers(); + for (AttackerSet::const_iterator itr = attackers.begin(); itr != attackers.end();) + { + if ((*itr)->GetFactionTemplateEntry()->Faction == faction_id) + { + (*itr)->AttackStop(); + itr = attackers.begin(); + } + else + ++itr; + } + + std::vector refsToEnd; + for (auto const& pair : m_combatManager.GetPvECombatRefs()) + if (pair.second->GetOther(this)->GetFactionTemplateEntry()->Faction == faction_id) + refsToEnd.push_back(pair.second); + for (CombatReference* ref : refsToEnd) + ref->EndCombat(); + + for (Unit* minion : m_Controlled) + minion->StopAttackFaction(faction_id); +} + +void Unit::OutDebugInfo() const +{ + TC_LOG_ERROR("entities.unit", "Unit::OutDebugInfo"); + TC_LOG_DEBUG("entities.unit", "%s name %s", GetGUID().ToString().c_str(), GetName().c_str()); + TC_LOG_DEBUG("entities.unit", "Owner %s, Minion %s, Charmer %s, Charmed %s", GetOwnerOrCreatorGUID().ToString().c_str(), GetMinionGUID().ToString().c_str(), GetCharmerGUID().ToString().c_str(), GetCharmedGUID().ToString().c_str()); + TC_LOG_DEBUG("entities.unit", "In world %u, unit type mask %u", (uint32)(IsInWorld() ? 1 : 0), m_unitTypeMask); + if (IsInWorld()) + TC_LOG_DEBUG("entities.unit", "Mapid %u", GetMapId()); + + std::ostringstream o; + o << "Summon Slot: "; + for (uint32 i = 0; i < MAX_SUMMON_SLOT; ++i) + o << m_SummonSlot[i].ToString() << ", "; + + TC_LOG_DEBUG("entities.unit", "%s", o.str().c_str()); + o.str(""); + + o << "Controlled List: "; + for (ControlList::const_iterator itr = m_Controlled.begin(); itr != m_Controlled.end(); ++itr) + o << (*itr)->GetGUID().ToString() << ", "; + TC_LOG_DEBUG("entities.unit", "%s", o.str().c_str()); + o.str(""); + + o << "Aura List: "; + for (AuraApplicationMap::const_iterator itr = m_appliedAuras.begin(); itr != m_appliedAuras.end(); ++itr) + o << itr->first << ", "; + TC_LOG_DEBUG("entities.unit", "%s", o.str().c_str()); + o.str(""); + + if (IsVehicle()) + { + o << "Passenger List: "; + for (SeatMap::iterator itr = GetVehicleKit()->Seats.begin(); itr != GetVehicleKit()->Seats.end(); ++itr) + if (Unit* passenger = ObjectAccessor::GetUnit(*GetVehicleBase(), itr->second.Passenger.Guid)) + o << passenger->GetGUID().ToString() << ", "; + TC_LOG_DEBUG("entities.unit", "%s", o.str().c_str()); + } + + if (GetVehicle()) + TC_LOG_DEBUG("entities.unit", "On vehicle %u.", GetVehicleBase()->GetEntry()); +} + +void Unit::SendClearTarget() +{ + WorldPackets::Combat::BreakTarget packet; + packet.UnitGUID = GetGUID(); + SendMessageToSet(packet.Write(), false); +} + +uint32 Unit::GetResistance(SpellSchoolMask mask) const +{ + int32 resist = -1; + for (int32 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 CharmInfo::SetIsCommandAttack(bool val) +{ + _isCommandAttack = val; +} + +bool CharmInfo::IsCommandAttack() +{ + return _isCommandAttack; +} + +void CharmInfo::SetIsCommandFollow(bool val) +{ + _isCommandFollow = val; +} + +bool CharmInfo::IsCommandFollow() +{ + return _isCommandFollow; +} + +void CharmInfo::SaveStayPosition() +{ + //! At this point a new spline destination is enabled because of Unit::StopMoving() + G3D::Vector3 stayPos = _unit->movespline->FinalDestination(); + + if (_unit->movespline->onTransport) + if (TransportBase* transport = _unit->GetDirectTransport()) + transport->CalculatePassengerPosition(stayPos.x, stayPos.y, stayPos.z); + + _stayX = stayPos.x; + _stayY = stayPos.y; + _stayZ = stayPos.z; +} + +void CharmInfo::GetStayPosition(float &x, float &y, float &z) +{ + x = _stayX; + y = _stayY; + z = _stayZ; +} + +void CharmInfo::SetIsAtStay(bool val) +{ + _isAtStay = val; +} + +bool CharmInfo::IsAtStay() +{ + return _isAtStay; +} + +void CharmInfo::SetIsFollowing(bool val) +{ + _isFollowing = val; +} + +bool CharmInfo::IsFollowing() +{ + return _isFollowing; +} + +void CharmInfo::SetIsReturning(bool val) +{ + _isReturning = val; +} + +bool CharmInfo::IsReturning() +{ + return _isReturning; +} + +void Unit::SetOrientationTowards(WorldObject const* target) +{ + SetOrientation(GetAngle(target)); +} + +void Unit::SetFacingTo(float ori, bool force) +{ + // do not face when already moving + if (!force && (!IsStopped() || !movespline->Finalized())) + return; + + Movement::MoveSplineInit init(this); + init.MoveTo(GetPositionX(), GetPositionY(), GetPositionZ(), false); + if (GetTransport()) + init.DisableTransportPathTransformations(); // It makes no sense to target global orientation + init.SetFacing(ori); + init.Launch(); + UpdateSplineMovement(1); +} + +void Unit::SetFacingToObject(WorldObject const* object, bool force) +{ + // do not face when already moving + if (!force && (!IsStopped() || !movespline->Finalized())) + return; + + /// @todo figure out under what conditions creature will move towards object instead of facing it where it currently is. + Movement::MoveSplineInit init(this); + init.MoveTo(GetPositionX(), GetPositionY(), GetPositionZ(), false); + init.SetFacing(GetAngle(object)); // when on transport, GetAngle will still return global coordinates (and angle) that needs transforming + init.Launch(); + UpdateSplineMovement(1); +} + +void Unit::SetFacingToPoint(Position const& point, bool force) +{ + // do not face when already moving + if (!force && (!IsStopped() || !movespline->Finalized())) + return; + + /// @todo figure out under what conditions creature will move towards object instead of facing it where it currently is. + Movement::MoveSplineInit init(this); + init.MoveTo(GetPositionX(), GetPositionY(), GetPositionZ(), false); + if (GetTransport()) + init.DisableTransportPathTransformations(); // It makes no sense to target global orientation + init.SetFacing(point.GetPositionX(), point.GetPositionY(), point.GetPositionZ()); + init.Launch(); + UpdateSplineMovement(1); +} + +bool Unit::SetWalk(bool enable) +{ + if (enable == IsWalking()) + return false; + + if (enable) + AddUnitMovementFlag(MOVEMENTFLAG_WALKING); + else + RemoveUnitMovementFlag(MOVEMENTFLAG_WALKING); + + ///@ TODO: Find proper opcode for walk mode setting in player mind controlling a player case + if (enable) + Movement::PacketSender(this, SMSG_SPLINE_MOVE_SET_WALK_MODE, SMSG_SPLINE_MOVE_SET_WALK_MODE).Send(); + else + Movement::PacketSender(this, SMSG_SPLINE_MOVE_SET_RUN_MODE, SMSG_SPLINE_MOVE_SET_RUN_MODE).Send(); + + return true; +} + +bool Unit::SetDisableGravity(bool disable, bool /*updateAnimTier = true*/) +{ + if (disable == IsGravityDisabled()) + return false; + + if (disable) + { + AddUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY); + RemoveUnitMovementFlag(MOVEMENTFLAG_SWIMMING | MOVEMENTFLAG_SPLINE_ELEVATION); + SetFall(false); + } + else + { + RemoveUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY); + if (!HasUnitMovementFlag(MOVEMENTFLAG_CAN_FLY)) + SetFall(true); + } + + if (IsAlive()) + { + if (IsGravityDisabled() || IsHovering()) + SetPlayHoverAnim(true); + else + SetPlayHoverAnim(false); + } + else if (IsPlayer()) // To update player who dies while flying/hovering + SetPlayHoverAnim(false, false); + + return true; +} + +bool Unit::SetFall(bool enable) +{ + if (enable == HasUnitMovementFlag(MOVEMENTFLAG_FALLING)) + return false; + + if (enable) + { + AddUnitMovementFlag(MOVEMENTFLAG_FALLING); + m_movementInfo.SetFallTime(0); + } + else + RemoveUnitMovementFlag(MOVEMENTFLAG_FALLING | MOVEMENTFLAG_FALLING_FAR); + + return true; +} + +bool Unit::SetSwim(bool enable) +{ + if (enable == HasUnitMovementFlag(MOVEMENTFLAG_SWIMMING)) + return false; + + if (enable) + AddUnitMovementFlag(MOVEMENTFLAG_SWIMMING); + else + RemoveUnitMovementFlag(MOVEMENTFLAG_SWIMMING); + + if (enable) + Movement::PacketSender(this, SMSG_SPLINE_MOVE_START_SWIM, NULL_OPCODE).Send(); + else + Movement::PacketSender(this, SMSG_SPLINE_MOVE_STOP_SWIM, NULL_OPCODE).Send(); + + return true; +} + +bool Unit::SetCanFly(bool enable) +{ + if (enable == HasUnitMovementFlag(MOVEMENTFLAG_CAN_FLY)) + return false; + + if (enable) + { + AddUnitMovementFlag(MOVEMENTFLAG_CAN_FLY); + RemoveUnitMovementFlag(MOVEMENTFLAG_SWIMMING | MOVEMENTFLAG_SPLINE_ELEVATION); + SetFall(false); + } + else + { + if (IsFlying() && !IsGravityDisabled()) + SetFall(true); + RemoveUnitMovementFlag(MOVEMENTFLAG_CAN_FLY | MOVEMENTFLAG_MASK_MOVING_FLY); + } + + return true; +} + +bool Unit::SetCanTransitionBetweenSwimAndFly(bool enable) +{ + if (enable == HasExtraUnitMovementFlag(MOVEMENTFLAG2_CAN_SWIM_TO_FLY_TRANS)) + return false; + + if (enable) + AddExtraUnitMovementFlag(MOVEMENTFLAG2_CAN_SWIM_TO_FLY_TRANS); + else + RemoveExtraUnitMovementFlag(MOVEMENTFLAG2_CAN_SWIM_TO_FLY_TRANS); + + return true; +} + +bool Unit::SetWaterWalking(bool enable) +{ + if (enable == HasUnitMovementFlag(MOVEMENTFLAG_WATERWALKING)) + return false; + + if (enable) + AddUnitMovementFlag(MOVEMENTFLAG_WATERWALKING); + else + RemoveUnitMovementFlag(MOVEMENTFLAG_WATERWALKING); + + if (enable) + Movement::PacketSender(this, SMSG_SPLINE_MOVE_SET_WATER_WALK, SMSG_MOVE_WATER_WALK).Send(); + else + Movement::PacketSender(this, SMSG_SPLINE_MOVE_SET_LAND_WALK, SMSG_MOVE_LAND_WALK).Send(); + + return true; +} + +bool Unit::SetFeatherFall(bool enable) +{ + if (enable == HasUnitMovementFlag(MOVEMENTFLAG_FALLING_SLOW)) + return false; + + if (enable) + AddUnitMovementFlag(MOVEMENTFLAG_FALLING_SLOW); + else + RemoveUnitMovementFlag(MOVEMENTFLAG_FALLING_SLOW); + + if (enable) + Movement::PacketSender(this, SMSG_SPLINE_MOVE_SET_FEATHER_FALL, SMSG_MOVE_FEATHER_FALL).Send(); + else + Movement::PacketSender(this, SMSG_SPLINE_MOVE_SET_NORMAL_FALL, SMSG_MOVE_NORMAL_FALL).Send(); + + return true; +} + +bool Unit::SetHover(bool enable, bool /*updateAnimTier = true*/) +{ + if (enable == HasUnitMovementFlag(MOVEMENTFLAG_HOVER)) + return false; + + float hoverHeight = GetFloatValue(UNIT_FIELD_HOVERHEIGHT); + + if (enable) + { + //! No need to check height on ascent + AddUnitMovementFlag(MOVEMENTFLAG_HOVER); + if (hoverHeight && GetPositionZ() - GetFloorZ() < hoverHeight) + UpdateHeight(GetPositionZ() + hoverHeight); + } + else + { + RemoveUnitMovementFlag(MOVEMENTFLAG_HOVER); + //! Dying creatures will MoveFall from setDeathState + if (hoverHeight && (!isDying() || GetTypeId() != TYPEID_UNIT)) + { + float newZ = std::max(GetFloorZ(), GetPositionZ() - hoverHeight); + UpdateAllowedPositionZ(GetPositionX(), GetPositionY(), newZ); + UpdateHeight(newZ); + } + } + + if (enable) + Movement::PacketSender(this, SMSG_SPLINE_MOVE_SET_HOVER, SMSG_MOVE_SET_HOVER).Send(); + else + Movement::PacketSender(this, SMSG_SPLINE_MOVE_UNSET_HOVER, SMSG_MOVE_UNSET_HOVER).Send(); + + if (IsAlive()) + { + if (IsGravityDisabled() || IsHovering()) + SetPlayHoverAnim(true); + else + SetPlayHoverAnim(false); + } + else if (IsPlayer()) // To update player who dies while flying/hovering + SetPlayHoverAnim(false, false); + + return true; +} + +void Unit::SendSetVehicleRecId(uint32 vehicleId) +{ + WorldPackets::Vehicle::SetVehicleRecID setVehicleRec; + setVehicleRec.VehicleGUID = GetGUID(); + setVehicleRec.VehicleRecID = vehicleId; + SendMessageToSet(setVehicleRec.Write(), true); +} + +void Unit::SetPlayHoverAnim(bool enable, bool sendUpdate /*= true*/) +{ + if (IsPlayingHoverAnim() == enable) + return; + + m_updateFlag.PlayerHoverAnim = enable; + + if (!sendUpdate) + return; + + WorldPackets::Misc::SetPlayHoverAnim packet; + packet.UnitGUID = GetGUID(); + packet.PlayHoverAnim = enable; + SendMessageToSet(packet.Write(), true); +} + +bool Unit::IsSplineEnabled() const +{ + return movespline->Initialized() && !movespline->Finalized(); +} + +void Unit::BuildValuesUpdate(uint8 updateType, ByteBuffer* data, Player* target) const +{ + if (!target) + return; + + ByteBuffer fieldBuffer; + + uint32 valCount = m_valuesCount; + + uint32* flags = UnitUpdateFieldFlags; + uint32 visibleFlag = UF_FLAG_PUBLIC; + + if (target == this) + visibleFlag |= UF_FLAG_PRIVATE; + else if (GetTypeId() == TYPEID_PLAYER) + valCount = PLAYER_END_NOT_SELF; + + UpdateMaskPacketBuilder updateMask(valCount); + + Player* plr = GetCharmerOrOwnerPlayerOrPlayerItself(); + if (GetOwnerGUID() == target->GetGUID()) + visibleFlag |= UF_FLAG_OWNER; + + if (HasFlag(UNIT_DYNAMIC_FLAGS, 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; + + if (IsCreature()) + visibleFlag |= UF_FLAG_UNIT_ALL; + + Creature const* creature = ToCreature(); + for (uint16 index = 0; index < valCount; ++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) + { + uint32 appendValue = m_uint32Values[UNIT_NPC_FLAGS]; + + if (creature) + { + if (!target->CanSeeSpellClickOn(creature)) + appendValue &= ~UNIT_NPC_FLAG_SPELLCLICK; + + if (!creature->IsClassTrainerOf(target)) + appendValue &= ~UNIT_NPC_FLAG_TRAINER_CLASS; + } + + fieldBuffer << uint32(appendValue); + } + else if (index == UNIT_FIELD_AURASTATE) + { + // Check per caster aura states to not enable using a spell in client if specified aura is not by target + fieldBuffer << BuildAuraStateUpdateForTarget(target); + } + // 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_RESISTANCEBUFFMODSPOSITIVE && index <= (UNIT_FIELD_RESISTANCEBUFFMODSPOSITIVE + 6)) || + (index >= UNIT_FIELD_RESISTANCEBUFFMODSNEGATIVE && index <= (UNIT_FIELD_RESISTANCEBUFFMODSNEGATIVE + 6))) + { + fieldBuffer << uint32(m_floatValues[index]); + } + // Gamemasters should be always able to select units - remove not selectable flag + else if (index == UNIT_FIELD_FLAGS) + { + uint32 appendValue = m_uint32Values[UNIT_FIELD_FLAGS]; + if (target->IsGameMaster()) + appendValue &= ~UNIT_FLAG_NOT_SELECTABLE; + + fieldBuffer << uint32(appendValue); + } + // use modelid_a if not gm, _h if gm for CREATURE_FLAG_EXTRA_TRIGGER creatures + else if (index == UNIT_FIELD_DISPLAYID) + { + 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->flags_extra & CREATURE_FLAG_EXTRA_TRIGGER) + if (target->IsGameMaster()) + displayId = cinfo->GetFirstVisibleModel()->CreatureDisplayID; + } + + fieldBuffer << uint32(displayId); + } + // hide lootable animation for unallowed players + else if (index == UNIT_DYNAMIC_FLAGS) + { + 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; + + fieldBuffer << dynamicFlags; + } + // FG: pretend that OTHER players in own group are friendly ("blue") + else if (index == UNIT_FIELD_BYTES_2 || index == UNIT_FIELD_FACTIONTEMPLATE) + { + 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)) + { + if (index == UNIT_FIELD_BYTES_2) + // Allow targetting opposite faction in party when enabled in config + fieldBuffer << (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 !! + else + // pretend that all other HOSTILE players have own faction, to allow follow, heal, rezz (trade wont work) + fieldBuffer << uint32(target->GetFaction()); + } + else + fieldBuffer << m_uint32Values[index]; + } + else + fieldBuffer << m_uint32Values[index]; + } + else + { + // send in current format (float as float, uint32 as uint32) + fieldBuffer << m_uint32Values[index]; + } + } + } + + updateMask.AppendToPacket(data); + data->append(fieldBuffer); +} + +void Unit::DestroyForPlayer(Player* target, bool /*onDeath = false*/) const +{ + if (Battleground* bg = target->GetBattleground()) + { + if (bg->isArena()) + { + WorldPacket data(SMSG_ARENA_UNIT_DESTROYED, 8); + data << GetGUID(); + target->SendDirectMessage(&data); + } + } + + WorldObject::DestroyForPlayer(target); +} + +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 + 1) + itr = auras.begin(); + } + } + } + else if (diff < 0) + return false; + } + } + + return true; +} + +void Unit::Talk(std::string const& text, ChatMsg msgType, Language language, float textRange, WorldObject const* target) +{ + Trinity::CustomChatTextBuilder builder(this, msgType, text, language, target); + Trinity::LocalizedPacketDo localizer(builder); + Trinity::PlayerDistWorker > worker(this, textRange, localizer); + Cell::VisitWorldObjects(this, worker, textRange); +} + +void Unit::Say(std::string const& 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 const& 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 const& 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 const& 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); +} + +void Unit::Talk(uint32 textId, ChatMsg msgType, float textRange, WorldObject const* target) +{ + if (!sObjectMgr->GetBroadcastText(textId)) + { + TC_LOG_ERROR("entities.unit", "WorldObject::MonsterText: `broadcast_text` (ID: %u) was not found", textId); + return; + } + + Trinity::BroadcastTextBuilder builder(this, msgType, textId, getGender(), target); + Trinity::LocalizedPacketDo localizer(builder); + Trinity::PlayerDistWorker > worker(this, textRange, localizer); + Cell::VisitWorldObjects(this, worker, textRange); +} + +void Unit::Say(uint32 textId, WorldObject const* target /*= nullptr*/) +{ + Talk(textId, CHAT_MSG_MONSTER_SAY, sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_SAY), target); +} + +void Unit::Yell(uint32 textId, WorldObject const* target /*= nullptr*/) +{ + Talk(textId, CHAT_MSG_MONSTER_YELL, sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_YELL), target); +} + +void Unit::TextEmote(uint32 textId, WorldObject const* target /*= nullptr*/, bool isBossEmote /*= false*/) +{ + Talk(textId, isBossEmote ? CHAT_MSG_RAID_BOSS_EMOTE : CHAT_MSG_MONSTER_EMOTE, sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_TEXTEMOTE), target); +} + +void Unit::Whisper(uint32 textId, Player* target, bool isBossWhisper /*= false*/) +{ + if (!target) + return; + + BroadcastText const* bct = sObjectMgr->GetBroadcastText(textId); + if (!bct) + { + TC_LOG_ERROR("entities.unit", "WorldObject::MonsterWhisper: `broadcast_text` was not %u 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); +} + +// Returns collisionheight of the unit. If it is 0, it returns DEFAULT_COLLISION_HEIGHT. +float Unit::GetCollisionHeight() const +{ + float scaleMod = GetObjectScale(); // 99% sure about this + + if (IsMounted()) + { + if (CreatureDisplayInfoEntry const* mountDisplayInfo = sCreatureDisplayInfoStore.LookupEntry(GetUInt32Value(UNIT_FIELD_MOUNTDISPLAYID))) + { + if (CreatureModelDataEntry const* mountModelData = sCreatureModelDataStore.LookupEntry(mountDisplayInfo->ModelID)) + { + CreatureDisplayInfoEntry const* displayInfo = sCreatureDisplayInfoStore.AssertEntry(GetNativeDisplayId()); + CreatureModelDataEntry const* modelData = sCreatureModelDataStore.AssertEntry(displayInfo->ModelID); + float const collisionHeight = scaleMod * ((mountModelData->MountHeight * mountDisplayInfo->CreatureModelScale) + (modelData->CollisionHeight * modelData->ModelScale * displayInfo->CreatureModelScale * 0.5f)); + return collisionHeight == 0.0f ? DEFAULT_COLLISION_HEIGHT : collisionHeight; + } + } + } + + //! Dismounting case - use basic default model data + CreatureDisplayInfoEntry const* displayInfo = sCreatureDisplayInfoStore.AssertEntry(GetNativeDisplayId()); + CreatureModelDataEntry const* modelData = sCreatureModelDataStore.AssertEntry(displayInfo->ModelID); + + float const collisionHeight = scaleMod * modelData->CollisionHeight * modelData->ModelScale * displayInfo->CreatureModelScale; + return collisionHeight == 0.0f ? DEFAULT_COLLISION_HEIGHT : collisionHeight; +} + +SpellInfo const* Unit::GetCastSpellInfo(SpellInfo const* spellInfo, TriggerCastFlags& triggerFlag) const +{ + Unit::AuraEffectList const& swaps = GetAuraEffectsByType(SPELL_AURA_OVERRIDE_ACTIONBAR_SPELLS); + Unit::AuraEffectList const& swaps2 = GetAuraEffectsByType(SPELL_AURA_OVERRIDE_ACTIONBAR_SPELLS_TRIGGERED); + + for (AuraEffect const* auraEffect : swaps) + { + if (uint32(auraEffect->GetMiscValue()) == spellInfo->Id || auraEffect->IsAffectingSpell(spellInfo)) + { + if (SpellInfo const* newInfo = sSpellMgr->GetSpellInfo(auraEffect->GetAmount())) + { + if (auraEffect->GetSpellInfo()->HasAttribute(SPELL_ATTR8_IGNORE_SPELLCAST_OVERRIDE_COST)) + triggerFlag |= TRIGGERED_IGNORE_POWER_COST; + else + triggerFlag &= ~TRIGGERED_IGNORE_POWER_COST; + + return newInfo; + } + } + } + + for (AuraEffect const* auraEffect : swaps2) + { + if (uint32(auraEffect->GetMiscValue()) == spellInfo->Id || auraEffect->IsAffectingSpell(spellInfo)) + { + if (SpellInfo const* newInfo = sSpellMgr->GetSpellInfo(auraEffect->GetAmount())) + { + if (auraEffect->GetSpellInfo()->HasAttribute(SPELL_ATTR8_IGNORE_SPELLCAST_OVERRIDE_COST)) + triggerFlag |= TRIGGERED_IGNORE_POWER_COST; + else + triggerFlag &= ~TRIGGERED_IGNORE_POWER_COST; + + return newInfo; + } + } + } + + return spellInfo; +} + +bool Unit::CanExecutePendingSpellCastRequest(SpellInfo const* spellInfo) const +{ + // Generic and melee spells have to wait, channeled spells can be processed immediately. + if (!GetCurrentSpell(CURRENT_CHANNELED_SPELL) && HasUnitState(UNIT_STATE_CASTING)) + return false; + + // Waiting for the global cooldown to expire + if (GetSpellHistory()->GetRemainingGlobalCooldown(spellInfo) > 0) + return false; + + return true; +} + +void Unit::RequestSpellCast(std::unique_ptr castRequest, SpellInfo const* spellInfo) +{ + // We are overriding an already existing spell cast request so inform the client that the old cast is being replaced + if (_pendingSpellCastRequest) + CancelPendingCastRequest(); + + _pendingSpellCastRequest = std::move(castRequest); + + // If we can process the cast request right now, do it. + if (CanExecutePendingSpellCastRequest(spellInfo)) + ProcessPendingSpellCastRequest(); +} + +void Unit::CancelPendingCastRequest() +{ + if (!_pendingSpellCastRequest) + return; + + // We have to inform the client that the cast has been canceled to avoid the cast button to stay highlightened + if (IsPlayer()) + { + // @todo: convert to packet class + WorldPacket data(SMSG_CAST_FAILED, 1 + 4 + 1); + data << uint8(_pendingSpellCastRequest->CastRequest.CastID); + data << uint32(_pendingSpellCastRequest->CastRequest.SpellID); + data << uint8(SPELL_FAILED_DONT_REPORT); + ToPlayer()->SendDirectMessage(&data); + } + + _pendingSpellCastRequest = nullptr; +} + +// A spell can be queued up within 400 milliseconds before global cooldown expires or the cast finishes +static constexpr uint16 const SPELL_QUEUE_TIME_WINDOW = 400; + +bool Unit::CanRequestSpellCast(SpellInfo const* spellInfo) const +{ + if (GetSpellHistory()->GetRemainingGlobalCooldown(spellInfo) > SPELL_QUEUE_TIME_WINDOW) + return false; + + for (CurrentSpellTypes spellSlot : { CURRENT_MELEE_SPELL, CURRENT_GENERIC_SPELL }) + if (Spell const* spell = GetCurrentSpell(spellSlot)) + if (spell->GetRemainingCastTime() > SPELL_QUEUE_TIME_WINDOW) + return false; + + return true; +} + +void Unit::ProcessPendingSpellCastRequest() +{ + if (!_pendingSpellCastRequest) + return; + + // Sanity check. If the player requested an invalid spell cast, just skip the request. + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(_pendingSpellCastRequest->CastRequest.SpellID); + if (!spellInfo) + { + _pendingSpellCastRequest = nullptr; + return; + } + + // Waiting for ongoing casts to finish and the global cooldown to expire. + if (!CanExecutePendingSpellCastRequest(spellInfo)) + return; + + // Spell can be executed now. Check data and prepare cast. + Unit* caster = this; + + // Process packet provided targets + SpellCastTargets targets(caster, _pendingSpellCastRequest->CastRequest); + + // VIANO_HEAL_TARGET_DEBUG_BEGIN + // Temporary diagnostic for Holy Light (spell 635): + // verify which explicit unit target actually arrived from the client. + if (spellInfo->Id == 635) + { + Unit* unitTarget = targets.GetUnitTarget(); + + TC_LOG_INFO( + "scripts", + "[VianoHealDebug] spell=635 casterGuid=%s requestedTargetGuid=%s resolvedTargetGuid=%s resolvedTargetEntry=%u resolvedTargetType=%u", + caster->GetGUID().ToString().c_str(), + targets.GetUnitTargetGUID().ToString().c_str(), + unitTarget ? unitTarget->GetGUID().ToString().c_str() : "", + unitTarget ? unitTarget->GetEntry() : 0u, + unitTarget ? uint32(unitTarget->GetTypeId()) : 0u); + } + // VIANO_HEAL_TARGET_DEBUG_END + + // The spell cast has been requested by using an item. Handle the cast accordingly. + if (_pendingSpellCastRequest->CastItemData.has_value()) + { + ProcessItemCast(*_pendingSpellCastRequest, targets); + _pendingSpellCastRequest = nullptr; + return; + } + + if (caster->IsCreature() && !caster->ToCreature()->HasSpell(spellInfo->Id)) + { + // If the vehicle creature does not have the spell but it allows the passenger to cast own spells + // change caster to player and let him cast + + Unit* charmer = caster->GetCharmer(); + if (charmer && (!charmer->IsOnVehicle(this) || spellInfo->CheckVehicle(charmer) != SPELL_CAST_OK)) + return; + else if (charmer) + caster = charmer; + else + { + TC_LOG_ERROR("spells", "Creature %s tried to allow its charmer to cast spell %u but there is no charmer.", caster->GetGUID().ToString().c_str(), spellInfo->Id); + return; + } + } + + TriggerCastFlags triggerFlag = TRIGGERED_NONE; + + if (caster->GetTypeId() == TYPEID_PLAYER && !caster->ToPlayer()->HasActiveSpell(spellInfo->Id) && !spellInfo->HasAttribute(SPELL_ATTR8_SKIP_IS_KNOWN_CHECK) && + !caster->ToPlayer()->HasArchProject(static_cast(spellInfo->ResearchProjectId))) + { + bool allow = false; + + // allow casting of unknown spells for special lock cases + if (GameObject *go = targets.GetGOTarget()) + if (go->GetSpellForLock(caster->ToPlayer()) == spellInfo) + allow = true; + + // TODO: Preparation for #23204 + // allow casting of spells triggered by clientside periodic trigger auras + /* + if (caster->HasAuraTypeWithTriggerSpell(SPELL_AURA_PERIODIC_TRIGGER_SPELL_FROM_CLIENT, spellId)) + { + allow = true; + triggerFlag = TRIGGERED_FULL_MASK; + } + */ + + if (!allow) + { + _pendingSpellCastRequest = nullptr; + return; + } + } + + // Check possible spell cast overrides + spellInfo = caster->GetCastSpellInfo(spellInfo, triggerFlag); + + // can't use our own spells when we're in possession of another unit, + if (caster->isPossessing()) + { + _pendingSpellCastRequest = nullptr; + return; + } + + // Client is resending autoshot cast opcode when other spell is cast during shoot rotation + // Skip it to prevent "interrupt" message + // Also check targets! target may have changed and we need to interrupt current spell + if (spellInfo->IsAutoRepeatRangedSpell()) + { + if (Spell* spell = caster->GetCurrentSpell(CURRENT_AUTOREPEAT_SPELL)) + { + if (spell->m_spellInfo == spellInfo && spell->m_targets.GetUnitTargetGUID() == targets.GetUnitTargetGUID()) + { + _pendingSpellCastRequest.reset(); + return; + } + } + } + + // auto-selection buff level base at target level (in spellInfo) + // TODO: is this even necessary? client already seems to send correct rank for "standard" buffs + if (spellInfo->IsPositive()) + { + if (Unit* target = targets.GetUnitTarget()) + { + SpellInfo const* actualSpellInfo = spellInfo->GetAuraRankForLevel(target->getLevel()); + + // if rank not found then function return NULL but in explicit cast case original spell can be cast and later failed with appropriate error message + if (actualSpellInfo) + spellInfo = actualSpellInfo; + } + } + + if (!_pendingSpellCastRequest->CastRequest.Weight.empty()) + { + ArchData archaeologyCastData; + for (WorldPackets::Spells::SpellWeight const& weight : _pendingSpellCastRequest->CastRequest.Weight) + { + switch (weight.Type) + { + case 1: // Currency + archaeologyCastData.FragId = weight.ID; + archaeologyCastData.FragCount = weight.Quantity; + break; + case 2: // Item + archaeologyCastData.KeyId = weight.ID; + archaeologyCastData.KeyCount = weight.Quantity; + break; + default: + break; + } + } + + if (Player* player = caster->ToPlayer()) + player->SetArchData(archaeologyCastData); + } + + Spell* spell = new Spell(caster, spellInfo, triggerFlag); + spell->m_cast_count = _pendingSpellCastRequest->CastRequest.CastID; // set count of casts + spell->m_glyphIndex = _pendingSpellCastRequest->CastRequest.Misc; + spell->prepare(targets); + + _pendingSpellCastRequest.reset(); +} + +void Unit::ProcessItemCast(PendingSpellCastRequest const& castRequest, SpellCastTargets const& targets) +{ + Player* player = ToPlayer(); + if (!player || !castRequest.CastItemData.has_value()) + return; + + Item* item = player->GetUseableItemByPos(castRequest.CastItemData->BagSlot, castRequest.CastItemData->Slot); + if (!item) + { + player->SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, nullptr, nullptr); + return; + } + + if (item->GetGUID() != castRequest.CastItemData->CastItem) + { + player->SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, nullptr, nullptr); + return; + } + + ItemTemplate const* proto = item->GetTemplate(); + if (!proto) + { + player->SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, item, nullptr); + return; + } + + // some item classes can be used only in equipped state + if (proto->GetInventoryType() != INVTYPE_NON_EQUIP && !item->IsEquipped()) + { + player->SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, item, nullptr); + return; + } + + InventoryResult msg = player->CanUseItem(item); + if (msg != EQUIP_ERR_OK) + { + player->SendEquipError(msg, item, nullptr); + return; + } + + // only allow conjured consumable, bandage, poisons (all should have the 2^21 item flag set in DB) + if (proto->GetClass() == ITEM_CLASS_CONSUMABLE && !(proto->GetFlags() & ITEM_FLAG_IGNORE_DEFAULT_ARENA_RESTRICTIONS) && player->InArena()) + { + player->SendEquipError(EQUIP_ERR_NOT_DURING_ARENA_MATCH, item, nullptr); + return; + } + + // don't allow items banned in arena + if ((proto->GetFlags() & ITEM_FLAG_NOT_USEABLE_IN_ARENA) && player->InArena()) + { + player->SendEquipError(EQUIP_ERR_NOT_DURING_ARENA_MATCH, item, nullptr); + return; + } + + if (player->IsInCombat()) + { + for (ItemEffect const& effect : proto->Effects) + { + if (SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(effect.SpellID)) + { + if (!spellInfo->CanBeUsedInCombat()) + { + player->SendEquipError(EQUIP_ERR_NOT_IN_COMBAT, item, nullptr); + return; + } + } + } + } + + // check also BIND_ON_ACQUIRE and BIND_QUEST for .additem or .additemset case by GM (not binded at adding to inventory) + if (item->GetTemplate()->GetBonding() == BIND_ON_USE || item->GetTemplate()->GetBonding() == BIND_ON_ACQUIRE || item->GetTemplate()->GetBonding() == BIND_QUEST) + { + if (!item->IsSoulBound()) + { + item->SetState(ITEM_CHANGED, player); + item->SetBinding(true); + } + } + + player->RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::ItemUse); + + // Note: If script stop casting it must send appropriate data to client to prevent stuck item in gray state. + if (!sScriptMgr->OnItemUse(player, item, targets)) + { + // no script or script not process request by self + player->CastItemUseSpell(item, targets, castRequest.CastRequest.CastID, castRequest.CastRequest.Misc); + } +} + +void Unit::SetGameClientMovingMe(GameClient* gameClientMovingMe) +{ + _gameClientMovingMe = gameClientMovingMe; +} + +Stats& Unit::GetStats() const +{ + return *_stats; +} + +int32 Unit::GetStat(StatType statType) const +{ + if (statType < StatType::Strength) + return 0; + + return GetInt32Value(UNIT_FIELD_STAT0 + AsUnderlyingType(statType)); +} diff --git a/src/server/game/Entities/Unit/Unit.cpp.bak-viano-group-context-v1-20261001-134352 b/src/server/game/Entities/Unit/Unit.cpp.bak-viano-group-context-v1-20261001-134352 new file mode 100644 index 0000000000..e8e2a9446a --- /dev/null +++ b/src/server/game/Entities/Unit/Unit.cpp.bak-viano-group-context-v1-20261001-134352 @@ -0,0 +1,14281 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include "Unit.h" +#include "AbstractPursuer.h" +#include "Archaeology.h" +#include "Battlefield.h" +#include "BattlefieldMgr.h" +#include "Battleground.h" +#include "BattlegroundScore.h" +#include "CellImpl.h" +#include "CharacterCache.h" +#include "CharmInfo.h" +#include "Chat.h" +#include "ChatTextBuilder.h" +#include "ChatPackets.h" +#include "CombatLogPackets.h" +#include "CombatPackets.h" +#include "Common.h" +#include "ConditionMgr.h" +#include "Containers.h" +#include "CreatureAI.h" +#include "CreatureAIImpl.h" +#include "CreatureGroups.h" +#include "Formulas.h" +#include "GameClient.h" +#include "GameTime.h" +#include "GridNotifiersImpl.h" +#include "Group.h" +#include "InstanceSaveMgr.h" +#include "InstanceScript.h" +#include "Item.h" +#include "ListUtils.h" +#include "Log.h" +#include "LootMgr.h" +#include "LootPackets.h" +#include "MiscPackets.h" +#include "MotionMaster.h" +#include "MovementGenerator.h" +#include "MovementPackets.h" +#include "MovementPacketBuilder.h" +#include "MovementStructures.h" +#include "MoveSpline.h" +#include "MoveSplineInit.h" +#include "MovementPacketSender.h" +#include "ObjectAccessor.h" +#include "ObjectMgr.h" +#include "Opcodes.h" +#include "OutdoorPvP.h" +#include "PassiveAI.h" +#include "Pet.h" +#include "PetPackets.h" +#include "PetAI.h" +#include "PhasingHandler.h" +#include "Player.h" +#include "PlayerAI.h" +#include "QuestDef.h" +#include "ReputationMgr.h" +#include "ScheduledChangeAI.h" +#include "Spell.h" +#include "SpellAuraEffects.h" +#include "SpellAuras.h" +#include "SpellCastRequest.h" +#include "SpellHistory.h" +#include "SpellInfo.h" +#include "SpellMgr.h" +#include "SpellPackets.h" +#include "Stats.h" +#include "TemporarySummon.h" +#include "Totem.h" +#include "Transport.h" +#include "UnitAI.h" +#include "UpdateFieldFlags.h" +#include "Util.h" +#include "Vehicle.h" +#include "VehiclePackets.h" +#include "World.h" +#include "WorldPacket.h" +#include "WorldSession.h" +#include +#include + +float baseMoveSpeed[MAX_MOVE_TYPE] = +{ + 2.5f, // MOVE_WALK + 7.0f, // MOVE_RUN + 4.5f, // MOVE_RUN_BACK + 4.722222f, // MOVE_SWIM + 2.5f, // MOVE_SWIM_BACK + 3.141594f, // MOVE_TURN_RATE + 7.0f, // MOVE_FLIGHT + 4.5f, // MOVE_FLIGHT_BACK + 3.14f // MOVE_PITCH_RATE +}; + +float playerBaseMoveSpeed[MAX_MOVE_TYPE] = +{ + 2.5f, // MOVE_WALK + 7.0f, // MOVE_RUN + 4.5f, // MOVE_RUN_BACK + 4.722222f, // MOVE_SWIM + 2.5f, // MOVE_SWIM_BACK + 3.141594f, // MOVE_TURN_RATE + 7.0f, // MOVE_FLIGHT + 4.5f, // MOVE_FLIGHT_BACK + 3.14f // MOVE_PITCH_RATE +}; + +DamageInfo::DamageInfo(Unit* attacker, Unit* victim, uint32 damage, SpellInfo const* spellInfo, SpellSchoolMask schoolMask, DamageEffectType damageType, WeaponAttackType attackType) + : m_attacker(attacker), m_victim(victim), m_damage(damage), m_spellInfo(spellInfo), m_schoolMask(schoolMask), m_damageType(damageType), m_attackType(attackType), + m_absorb(0), m_resist(0), m_block(0), m_hitMask(PROC_HIT_NONE) +{ +} + +DamageInfo::DamageInfo(CalcDamageInfo const& dmgInfo) + : m_attacker(dmgInfo.Attacker), m_victim(dmgInfo.Target), m_damage(dmgInfo.Damage), m_spellInfo(nullptr), m_schoolMask(SpellSchoolMask(dmgInfo.DamageSchoolMask)), + m_damageType(DIRECT_DAMAGE), m_attackType(dmgInfo.AttackType), m_absorb(dmgInfo.Absorb), m_resist(dmgInfo.Resist), m_block(dmgInfo.Blocked), m_hitMask(PROC_HIT_NONE) +{ + switch (dmgInfo.TargetState) + { + case VICTIMSTATE_IS_IMMUNE: + m_hitMask |= PROC_HIT_IMMUNE; + break; + case VICTIMSTATE_BLOCKS: + m_hitMask |= PROC_HIT_FULL_BLOCK; + break; + } + + if (dmgInfo.HitInfo & (HITINFO_PARTIAL_ABSORB | HITINFO_FULL_ABSORB)) + m_hitMask |= PROC_HIT_ABSORB; + + if (dmgInfo.HitInfo & HITINFO_FULL_RESIST) + m_hitMask |= PROC_HIT_FULL_RESIST; + + if (m_block) + m_hitMask |= PROC_HIT_BLOCK; + + bool const damageNullified = (dmgInfo.HitInfo & (HITINFO_FULL_ABSORB | HITINFO_FULL_RESIST)) != 0 || + (m_hitMask & (PROC_HIT_IMMUNE | PROC_HIT_FULL_BLOCK)) != 0; + switch (dmgInfo.HitOutCome) + { + case MELEE_HIT_MISS: + m_hitMask |= PROC_HIT_MISS; + break; + case MELEE_HIT_DODGE: + m_hitMask |= PROC_HIT_DODGE; + break; + case MELEE_HIT_PARRY: + m_hitMask |= PROC_HIT_PARRY; + break; + case MELEE_HIT_EVADE: + m_hitMask |= PROC_HIT_EVADE; + break; + case MELEE_HIT_BLOCK: + case MELEE_HIT_CRUSHING: + case MELEE_HIT_GLANCING: + case MELEE_HIT_NORMAL: + if (!damageNullified) + m_hitMask |= PROC_HIT_NORMAL; + break; + case MELEE_HIT_CRIT: + if (!damageNullified) + m_hitMask |= PROC_HIT_CRITICAL; + break; + } +} + +DamageInfo::DamageInfo(SpellNonMeleeDamage const& spellNonMeleeDamage, DamageEffectType damageType, WeaponAttackType attackType, ProcFlagsHit hitMask) + : m_attacker(spellNonMeleeDamage.attacker), m_victim(spellNonMeleeDamage.target), m_damage(spellNonMeleeDamage.damage), + m_spellInfo(sSpellMgr->GetSpellInfo(spellNonMeleeDamage.SpellID)), m_schoolMask(SpellSchoolMask(spellNonMeleeDamage.schoolMask)), m_damageType(damageType), + m_attackType(attackType), m_absorb(spellNonMeleeDamage.absorb), m_resist(spellNonMeleeDamage.resist), m_block(spellNonMeleeDamage.blocked), m_hitMask(hitMask) +{ + if (spellNonMeleeDamage.blocked) + m_hitMask |= PROC_HIT_BLOCK; + if (spellNonMeleeDamage.absorb) + m_hitMask |= PROC_HIT_ABSORB; +} + +void DamageInfo::ModifyDamage(int32 amount) +{ + amount = std::max(amount, -static_cast(GetDamage())); + m_damage += amount; +} + +void DamageInfo::AbsorbDamage(uint32 amount) +{ + amount = std::min(amount, GetDamage()); + m_absorb += amount; + m_damage -= amount; + m_hitMask |= PROC_HIT_ABSORB; +} + +void DamageInfo::ResistDamage(uint32 amount) +{ + amount = std::min(amount, GetDamage()); + m_resist += amount; + m_damage -= amount; + if (!m_damage) + { + m_hitMask |= PROC_HIT_FULL_RESIST; + m_hitMask &= ~(PROC_HIT_NORMAL | PROC_HIT_CRITICAL); + } +} + +void DamageInfo::BlockDamage(uint32 amount) +{ + amount = std::min(amount, GetDamage()); + m_block += amount; + m_damage -= amount; + m_hitMask |= PROC_HIT_BLOCK; + if (!m_damage) + { + m_hitMask |= PROC_HIT_FULL_BLOCK; + m_hitMask &= ~(PROC_HIT_NORMAL | PROC_HIT_CRITICAL); + } +} + +ProcFlagsHit DamageInfo::GetHitMask() const +{ + return m_hitMask; +} + +HealInfo::HealInfo(Unit* healer, Unit* target, uint32 heal, SpellInfo const* spellInfo, SpellSchoolMask schoolMask) + : _healer(healer), _target(target), _heal(heal), _effectiveHeal(0), _absorb(0), _spellInfo(spellInfo), _schoolMask(schoolMask), _hitMask(0) +{ +} + +void HealInfo::AbsorbHeal(uint32 amount) +{ + amount = std::min(amount, GetHeal()); + _absorb += amount; + _heal -= amount; + amount = std::min(amount, GetEffectiveHeal()); + _effectiveHeal -= amount; + _hitMask |= PROC_HIT_ABSORB; +} + +uint32 HealInfo::GetHitMask() const +{ + return _hitMask; +} + +ProcEventInfo::ProcEventInfo(Unit* actor, Unit* actionTarget, Unit* procTarget, + ProcFlags typeMask, ProcFlagsSpellType spellTypeMask, + ProcFlagsSpellPhase spellPhaseMask, ProcFlagsHit hitMask, + Spell* spell, DamageInfo* damageInfo, + HealInfo* healInfo) : + _actor(actor), _actionTarget(actionTarget), _procTarget(procTarget), + _typeMask(typeMask), _spellTypeMask(spellTypeMask), + _spellPhaseMask(spellPhaseMask), _hitMask(hitMask), _spell(spell), + _damageInfo(damageInfo), _healInfo(healInfo) +{ } + +SpellInfo const* ProcEventInfo::GetSpellInfo() const +{ + if (_spell) + return _spell->GetSpellInfo(); + if (_damageInfo) + return _damageInfo->GetSpellInfo(); + if (_healInfo) + return _healInfo->GetSpellInfo(); + return nullptr; +} + +SpellSchoolMask ProcEventInfo::GetSchoolMask() const +{ + if (_spell) + return _spell->GetSpellInfo()->GetSchoolMask(); + if (_damageInfo) + return _damageInfo->GetSchoolMask(); + if (_healInfo) + return _healInfo->GetSchoolMask(); + return SPELL_SCHOOL_MASK_NONE; +} + +DispelableAura::DispelableAura(Aura* aura, int32 dispelChance, uint8 dispelCharges) : + _aura(aura), _chance(dispelChance), _charges(dispelCharges) +{ +} + +DispelableAura::~DispelableAura() = default; + +bool DispelableAura::RollDispel() const +{ + return roll_chance_i(_chance); +} + +Unit::Unit(bool isWorldObject) : + WorldObject(isWorldObject), m_lastSanctuaryTime(0), + LastCharmerGUID(), m_ControlledByPlayer(false), movespline(std::make_unique()), + m_procDeep(0), m_procChainLength(0), m_removedAurasCount(0), + m_interruptMask(SpellAuraInterruptFlags::None), m_interruptMask2(SpellAuraInterruptFlags2::None), + m_charmer(nullptr), m_charmed(nullptr), + i_motionMaster(std::make_unique(this)), m_vehicle(nullptr), + m_unitTypeMask(UNIT_MASK_NONE), m_isEngaged(false), m_combatManager(this), m_threatManager(this), + i_AI(nullptr), m_aiLocked(false), m_spellHistory(std::make_unique(this)), + _stats(std::make_unique(this)) +{ + m_objectType |= TYPEMASK_UNIT; + m_objectTypeId = TYPEID_UNIT; + + m_updateFlag.MovementUpdate = true; + + m_baseAttackSpeed = { }; + m_attackTimer = { }; + m_modAttackSpeedPct.fill(1.0f); + + m_extraAttacks = 0; + m_canDualWield = false; + + m_movementCounter = 0; + + m_state = 0; + m_deathState = ALIVE; + + m_currentSpells.fill(nullptr); + + m_auraUpdateIterator = m_ownedAuras.end(); + + m_transform = 0; + m_canModifyStats = false; + + 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] = BASE_MINDAMAGE; + m_weaponDamage[i][MAXDAMAGE] = BASE_MAXDAMAGE; + } + + m_attacking = nullptr; + m_modMeleeHitChance = 0.0f; + m_modRangedHitChance = 0.0f; + m_modSpellHitChance = 0.0f; + m_baseSpellCritChance = 5; + + m_speed_rate.fill(1.0f); + _gameClientMovingMe = nullptr; + + // remove aurastates allowing special moves + m_reactiveTimer = { }; + m_cleanupDone = false; + m_duringRemoveFromWorld = false; + + m_serverSideVisibility.SetValue(SERVERSIDE_VISIBILITY_GHOST, GHOST_VISIBILITY_ALIVE); + + _lastLiquid = nullptr; + + _powerBarId = 0; + + _powerUpdateTimer = GetPowerUpdateInterval(); + _healthRegenerationTimer = UNIT_HEALTH_REGENERATION_INTERVAL; + + _powerFraction.fill(0.0f); + _usedPowerTypes.fill(MAX_POWERS); + + _oldFactionId = 0; + _isWalkingBeforeCharm = false; +} + +//////////////////////////////////////////////////////////// +// Methods of class Unit +Unit::~Unit() +{ + // set current spells as deletable + for (size_t i = 0; i < m_currentSpells.size(); ++i) + { + if (m_currentSpells[i]) + { + m_currentSpells[i]->SetReferencedFromCurrent(false); + m_currentSpells[i] = nullptr; + } + } + + m_Events.KillAllEvents(true); + + _DeleteRemovedAuras(); + + ASSERT(!m_duringRemoveFromWorld); + ASSERT(!m_attacking); + ASSERT(m_attackers.empty()); + ASSERT(m_sharedVision.empty()); + ASSERT(m_Controlled.empty()); + ASSERT(m_appliedAuras.empty()); + ASSERT(m_ownedAuras.empty()); + ASSERT(m_removedAuras.empty()); + ASSERT(m_gameObj.empty()); + ASSERT(m_dynObj.empty()); + ASSERT(!_gameClientMovingMe || _gameClientMovingMe->GetBasePlayer() == this); +} + +void Unit::Update(uint32 p_time) +{ + // WARNING! Order of execution here is important, do not change. + // Spells must be processed with event system BEFORE they go to _UpdateSpells. + // Or else we may have some SPELL_STATE_FINISHED spells stalled in pointers, that is bad. + WorldObject::Update(p_time); + + CheckPendingMovementAcks(); + + if (!IsInWorld()) + return; + + // Update server controlled facing for standing units + if (movespline->Finalized() && !isMoving()) + { + // Prioritize tracking channeled spells above unit targets if they should both be present at the same time. + Spell const* channeledSpell = GetCurrentSpell(CURRENT_CHANNELED_SPELL); + if (channeledSpell && channeledSpell->GetSpellInfo()->HasAttribute(SPELL_ATTR1_TRACK_TARGET_IN_CHANNEL)) + { + ObjectGuid const channelGuid = GetChannelObjectGuid(); + if (!channelGuid.IsEmpty() && channelGuid != GetGUID()) + if (WorldObject const* objectTarget = ObjectAccessor::GetWorldObject(*this, channelGuid)) + SetOrientationTowards(objectTarget); + } + else if (IsCreature() && !HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_POSSESSED)) + { + ObjectGuid const targetGuid = GetTarget(); + if (!targetGuid.IsEmpty() && targetGuid != GetGUID()) + if (WorldObject const* objectTarget = ObjectAccessor::GetWorldObject(*this, targetGuid)) + SetOrientationTowards(objectTarget); + } + } + + _UpdateSpells(p_time); + ProcessPendingSpellCastRequest(); + + // If this is set during update SetCantProc(false) call is missing somewhere in the code + // Having this would prevent spells from being proced, so let's crash + ASSERT(!m_procDeep); + + m_combatManager.Update(p_time); + + auto spellPausesCombatTimer = [&](CurrentSpellTypes type) + { + return GetCurrentSpell(type) && GetCurrentSpell(type)->GetSpellInfo()->HasAttribute(SPELL_ATTR6_DELAY_COMBAT_TIMER_DURING_CAST); + }; + + if (!spellPausesCombatTimer(CURRENT_GENERIC_SPELL) && !spellPausesCombatTimer(CURRENT_CHANNELED_SPELL)) + { + if (uint32 base_att = getAttackTimer(BASE_ATTACK)) + setAttackTimer(BASE_ATTACK, (p_time >= base_att ? 0 : base_att - p_time)); + if (uint32 ranged_att = getAttackTimer(RANGED_ATTACK)) + setAttackTimer(RANGED_ATTACK, (p_time >= ranged_att ? 0 : ranged_att - p_time)); + if (uint32 off_att = getAttackTimer(OFF_ATTACK)) + setAttackTimer(OFF_ATTACK, (p_time >= off_att ? 0 : off_att - p_time)); + } + + // update abilities available only for fraction of time + UpdateReactives(p_time); + + if (IsAlive()) + { + ModifyAuraState(AURA_STATE_HEALTHLESS_20_PERCENT, HealthBelowPct(20)); + ModifyAuraState(AURA_STATE_HEALTHLESS_35_PERCENT, HealthBelowPct(35)); + ModifyAuraState(AURA_STATE_HEALTH_ABOVE_75_PERCENT, HealthAbovePct(75)); + } + + UpdateSplineMovement(p_time); + i_motionMaster->UpdateMotion(p_time); + + if (!i_AI && (GetTypeId() != TYPEID_PLAYER || IsCharmed())) + UpdateCharmAI(); + + // Wait with the aura interrupts until we have updated our movement generators and position6 + if (IsPlayer()) + InterruptMovementBasedAuras(); + else if (!movespline->Finalized()) + InterruptMovementBasedAuras(); + + // All position info based actions have been executed, reset info + _positionUpdateInfo.Reset(); + + // Update Health Regeneration + _healthRegenerationTimer -= p_time; + if (_healthRegenerationTimer <= 0) + { + RegenerateHealth(); + _healthRegenerationTimer += UNIT_HEALTH_REGENERATION_INTERVAL; + } + + if (HasScheduledAIChange() && (GetTypeId() != TYPEID_PLAYER || (IsCharmed() && GetCharmerGUID().IsCreature()))) + UpdateCharmAI(); + RefreshAI(); +} + +void Unit::Heartbeat() +{ + WorldObject::Heartbeat(); + + // SMSG_FLIGHT_SPLINE_SYNC for cyclic splines + SendFlightSplineSyncUpdate(); + + // Trigger heartbeat procs and generic aura behavior such as food emotes and invoking aura script hooks + TriggerAuraHeartbeat(); +} + +void Unit::TriggerAuraHeartbeat() +{ + for (auto const& [_, auraApplication] : m_appliedAuras) + auraApplication->GetBase()->Heartbeat(); + + Unit::ProcSkillsAndAuras(this, nullptr, PROC_FLAG_HEARTBEAT, PROC_FLAG_NONE, PROC_SPELL_TYPE_MASK_ALL, PROC_SPELL_PHASE_NONE, PROC_HIT_NONE, nullptr, nullptr, nullptr); +} + +bool Unit::haveOffhandWeapon() const +{ + if (Player const* player = ToPlayer()) + return player->GetWeaponForAttack(OFF_ATTACK, true) != nullptr; + + return CanDualWield(); +} + +void Unit::MonsterMoveWithSpeed(float x, float y, float z, float speed, bool generatePath, bool forceDestination) +{ + Movement::MoveSplineInit init(this); + init.MoveTo(x, y, z, generatePath, forceDestination); + init.SetVelocity(speed); + GetMotionMaster()->LaunchMoveSpline(std::move(init), 0, MOTION_SLOT_ACTIVE, POINT_MOTION_TYPE); +} + +void Unit::UpdateSplineMovement(uint32 t_diff) +{ + if (movespline->Finalized()) + return; + + movespline->updateState(t_diff); + bool arrived = movespline->Finalized(); + + if (arrived) + { + DisableSpline(); + if (Optional animTier = movespline->GetAnimation()) + SetAnimTier(*animTier); + } + + UpdateSplinePosition(); +} + +void Unit::UpdateSplinePosition() +{ + Movement::Location loc = movespline->ComputePosition(); + + if (movespline->onTransport) + { + Position& pos = m_movementInfo.transport.pos; + pos.m_positionX = loc.x; + pos.m_positionY = loc.y; + pos.m_positionZ = loc.z; + pos.SetOrientation(loc.orientation); + + if (TransportBase* transport = GetDirectTransport()) + transport->CalculatePassengerPosition(loc.x, loc.y, loc.z, &loc.orientation); + else + return; + } + + if (HasUnitState(UNIT_STATE_CANNOT_TURN)) + loc.orientation = GetOrientation(); + + UpdatePosition(loc.x, loc.y, loc.z, loc.orientation); +} + +void Unit::SendFlightSplineSyncUpdate() +{ + if (!movespline->isCyclic() || movespline->Finalized()) + return; + + WorldPackets::Movement::FlightSplineSync flightSplineSync; + flightSplineSync.Guid = GetGUID(); + flightSplineSync.SplineDist = float(movespline->timePassed()) / movespline->Duration(); + SendMessageToSet(flightSplineSync.Write(), true); +} + +void Unit::InterruptMovementBasedAuras() +{ + // TODO: Check if orientation transport offset changed instead of only global orientation + if (_positionUpdateInfo.Turned) + RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Turning); + + if (_positionUpdateInfo.Relocated && !GetVehicle()) + RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Moving); +} + +void Unit::DisableSpline() +{ + m_movementInfo.RemoveMovementFlag(MOVEMENTFLAG_FORWARD); + movespline->_Interrupt(); +} + +void Unit::resetAttackTimer(WeaponAttackType type) +{ + m_attackTimer[type] = uint32(GetBaseAttackTime(type) * m_modAttackSpeedPct[type]); +} + +bool Unit::IsWithinCombatRange(Unit const* obj, float dist2compare) const +{ + if (!obj || !IsInMap(obj) || !IsInPhase(obj)) + return false; + + float dx = GetPositionX() - obj->GetPositionX(); + float dy = GetPositionY() - obj->GetPositionY(); + float dz = GetPositionZ() - obj->GetPositionZ(); + float distsq = dx * dx + dy * dy + dz * dz; + + float sizefactor = GetCombatReach() + obj->GetCombatReach(); + float maxdist = dist2compare + sizefactor; + + return distsq < maxdist * maxdist; +} + +bool Unit::IsWithinMeleeRangeAt(Position const& pos, Unit const* obj) const +{ + if (!obj || !IsInMap(obj) || !IsInPhase(obj)) + return false; + + float dx = pos.GetPositionX() - obj->GetPositionX(); + float dy = pos.GetPositionY() - obj->GetPositionY(); + float dz = GetPositionZ() - obj->GetPositionZ(); + + float distsq = dx*dx + dy*dy + dz*dz; + + float maxdist = GetMeleeRange(obj); + + return distsq <= maxdist * maxdist; +} + +float Unit::GetMeleeRange(Unit const* target) const +{ + float range = GetCombatReach() + target->GetCombatReach() + 1.3333334f; + return std::max(range, NOMINAL_MELEE_RANGE); +} + +bool Unit::IsWithinBoundaryRadius(const Unit* obj) const +{ + if (!obj || !IsInMap(obj) || !IsInPhase(obj)) + return false; + + float objBoundaryRadius = std::max(obj->GetBoundaryRadius(), MIN_MELEE_REACH); + + return IsInDist(obj, objBoundaryRadius); +} + +AuraApplication* Unit::GetVisibleAura(uint8 slot) const +{ + VisibleAuraMap::const_iterator itr = m_visibleAuras.find(slot); + if (itr != m_visibleAuras.end()) + return itr->second; + return 0; +} + +void Unit::SetVisibleAura(uint8 slot, AuraApplication * aur) +{ + m_visibleAuras[slot]=aur; + UpdateAuraForGroup(slot); +} + +void Unit::RemoveVisibleAura(uint8 slot) +{ + m_visibleAuras.erase(slot); + UpdateAuraForGroup(slot); +} + +void Unit::UpdateInterruptMask() +{ + m_interruptMask = SpellAuraInterruptFlags::None; + m_interruptMask2 = SpellAuraInterruptFlags2::None; + for (AuraApplication const* aurApp : m_interruptableAuras) + { + m_interruptMask |= aurApp->GetBase()->GetSpellInfo()->AuraInterruptFlags; + m_interruptMask2 |= aurApp->GetBase()->GetSpellInfo()->AuraInterruptFlags2; + } + + if (Spell* spell = m_currentSpells[CURRENT_CHANNELED_SPELL]) + { + if (spell->getState() == SPELL_STATE_CHANNELING) + { + m_interruptMask |= spell->m_spellInfo->ChannelInterruptFlags; + m_interruptMask2 |= spell->m_spellInfo->ChannelInterruptFlags2; + } + } +} + +bool Unit::HasAuraTypeWithFamilyFlags(AuraType auraType, uint32 familyName, 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()->HasAuraInterruptFlag(SpellAuraInterruptFlags::Damage)) + return true; + return false; +} + +bool Unit::HasBreakableByDamageCrowdControlAura(Unit* excludeCasterChannel) const +{ + uint32 excludeAura = 0; + if (Spell* currentChanneledSpell = excludeCasterChannel ? excludeCasterChannel->GetCurrentSpell(CURRENT_CHANNELED_SPELL) : nullptr) + excludeAura = currentChanneledSpell->GetSpellInfo()->Id; //Avoid self interrupt of channeled Crowd Control spells like Seduction + + return ( HasBreakableByDamageAuraType(SPELL_AURA_MOD_CONFUSE, excludeAura) + || HasBreakableByDamageAuraType(SPELL_AURA_MOD_FEAR, excludeAura) + || HasBreakableByDamageAuraType(SPELL_AURA_MOD_STUN, excludeAura) + || HasBreakableByDamageAuraType(SPELL_AURA_MOD_ROOT, excludeAura) + || HasBreakableByDamageAuraType(SPELL_AURA_TRANSFORM, excludeAura)); +} + +/*static*/ void Unit::DealDamageMods(Unit const* victim, uint32& damage, uint32* absorb) +{ + if (!victim || !victim->IsAlive() || victim->HasUnitState(UNIT_STATE_IN_FLIGHT) || (victim->GetTypeId() == TYPEID_UNIT && victim->ToCreature()->IsEvadingAttacks())) + { + if (absorb) + *absorb += damage; + damage = 0; + } +} + +/*static*/ uint32 Unit::DealDamage(Unit* attacker, Unit* victim, uint32 damage, uint32 unmitigatedDamage, DamageEffectType damagetype, SpellSchoolMask damageSchoolMask, SpellInfo const* spellProto, bool durabilityLoss) +{ + // Sparring Checks + if (Creature* target = victim->ToCreature()) + { + if (attacker && attacker->IsCreature() && !attacker->IsCharmedOwnedByPlayerOrPlayer()) + { + if (target->GetNoNpcDamageBelowPctHealthValue() != 0.0f) + { + if (damage >= target->GetHealth()) // First check: if we have a sparring limit we will never allow creatures to kill the sparring victim + damage = target->GetHealth() - 1; + else if (target->GetHealthPct() <= target->GetNoNpcDamageBelowPctHealthValue()) // Second check: stop incomming damage when we have surpassed the health limit + damage = 0; + } + } + } + + if (UnitAI* victimAI = victim->GetAI()) + victimAI->DamageTaken(attacker, damage); + + if (UnitAI* attackerAI = attacker ? attacker->GetAI() : nullptr) + attackerAI->DamageDealt(victim, damage, damagetype); + + // Hook for OnDamage Event + sScriptMgr->OnDamage(attacker, victim, damage); + + if (victim->GetTypeId() == TYPEID_PLAYER) + { + // Signal to pets that their owner was attacked - except when DOT. + if (attacker != victim && damagetype != DOT) + { + for (Unit* controlled : victim->m_Controlled) + if (Creature* cControlled = controlled->ToCreature()) + if (CreatureAI* controlledAI = cControlled->AI()) + controlledAI->OwnerAttackedBy(attacker); + } + + if (victim->ToPlayer()->GetCommandStatus(CHEAT_GOD)) + return 0; + } + + if (damagetype != NODAMAGE) + { + // interrupting auras with SpellAuraInterruptFlags::Damage before checking !damage (absorbed damage breaks that type of auras) + if (spellProto) + { + if (!spellProto->HasAttribute(SPELL_ATTR4_REACTIVE_DAMAGE_PROC)) + victim->RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Damage, spellProto); + } + else + victim->RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Damage); + + // 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 share = CalculatePct(damage, (*i)->GetAmount()); + + /// @todo check packets if damage is done by victim, or by attacker of victim + Unit::DealDamageMods(shareDamageTarget, share, nullptr); + Unit::DealDamage(attacker, shareDamageTarget, share, 0, NODAMAGE, spell->GetSchoolMask(), spell, false); + } + } + + uint32 health = victim->GetHealth(); + + // duel ends when player has 1 or less hp + bool duel_hasEnded = false; + bool duel_wasMounted = false; + if (victim->GetTypeId() == TYPEID_PLAYER && victim->ToPlayer()->duel && damage >= (health-1)) + { + if (!attacker) + return 0; + + // prevent kill only if killed in duel and killed by opponent or opponent controlled creature + if (victim->ToPlayer()->duel->opponent == attacker->GetControllingPlayer()) + damage = health - 1; + + duel_hasEnded = true; + } + else if (victim->IsCreature() && victim != attacker && damage >= health && victim->ToCreature()->HasStaticFlag(CREATURE_STATIC_FLAG_UNKILLABLE)) + { + damage = health - 1; + } + else if (victim->IsVehicle() && damage >= (health-1) && victim->GetCharmer() && victim->GetCharmer()->GetTypeId() == TYPEID_PLAYER) + { + Player* victimRider = victim->GetCharmer()->ToPlayer(); + + if (victimRider && victimRider->duel && victimRider->duel->isMounted) + { + if (!attacker) + return 0; + + // prevent kill only if killed in duel and killed by opponent or opponent controlled creature + if (victimRider->duel->opponent == attacker->GetControllingPlayer()) + damage = health - 1; + + duel_wasMounted = true; + duel_hasEnded = true; + } + } + + if (attacker && attacker != victim) + { + if (Player* killer = attacker->ToPlayer()) + { + // in bg, count dmg if victim is also a player + if (victim->GetTypeId() == TYPEID_PLAYER && !(spellProto && spellProto->HasAttribute(SPELL_ATTR7_DO_NOT_COUNT_FOR_PVP_SCOREBOARD))) + if (Battleground* bg = killer->GetBattleground()) + bg->UpdatePlayerScore(killer, SCORE_DAMAGE_DONE, damage); + + killer->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_DAMAGE_DONE, health > damage ? damage : health, 0, 0, victim); + killer->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HIGHEST_HIT_DEALT, damage); + } + } + + if (victim->GetTypeId() == TYPEID_PLAYER) + victim->ToPlayer()->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HIGHEST_HIT_RECEIVED, damage); + else if (!victim->IsControlledByPlayer() || victim->IsVehicle()) + { + if (!victim->ToCreature()->hasLootRecipient()) + victim->ToCreature()->SetLootRecipient(attacker); + + if (!attacker || attacker->IsControlledByPlayer() || (attacker->ToTempSummon() && attacker->ToTempSummon()->GetSummoner() && attacker->ToTempSummon()->GetSummoner()->GetTypeId() == TYPEID_PLAYER)) + victim->ToCreature()->LowerPlayerDamageReq(health < damage ? health : damage); + } + + if (health <= damage) + { + if (victim->GetTypeId() == TYPEID_PLAYER && victim != attacker) + victim->ToPlayer()->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_TOTAL_DAMAGE_RECEIVED, health); + + Unit::Kill(attacker, victim, durabilityLoss); + } + else + { + if (victim->GetTypeId() == TYPEID_PLAYER) + victim->ToPlayer()->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_TOTAL_DAMAGE_RECEIVED, damage); + + victim->ModifyHealth(-(int32)damage); + + if (damagetype == DIRECT_DAMAGE || damagetype == SPELL_DIRECT_DAMAGE) + victim->RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::NonPeriodicDamage, spellProto); + + if (victim->GetTypeId() != TYPEID_PLAYER) + { + // Part of Evade mechanics. DoT's and Thorns / Retribution Aura do not contribute to this + if (damagetype != DOT && damage > 0 && !victim->GetOwnerGUID().IsPlayer() && (!spellProto || !spellProto->HasAura(SPELL_AURA_DAMAGE_SHIELD))) + victim->ToCreature()->SetLastDamagedTime(GameTime::GetGameTime() + MAX_AGGRO_RESET_TIME); + + if (attacker && (!spellProto || !spellProto->HasAttribute(SPELL_ATTR4_NO_HARMFUL_THREAT))) + victim->GetThreatManager().AddThreat(attacker, float(damage), 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->GetPowerType() == POWER_RAGE) + { + uint32 rageBaseReward = CalcDamageTakenRageGain(victim, std::max(damage, unmitigatedDamage)); + victim->RewardRage(rageBaseReward, false); + } + + if (attacker && attacker->GetTypeId() == TYPEID_PLAYER) + { + // 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 && damagetype != DOT) + { + if (victim != attacker && (!spellProto || !(spellProto->HasAttribute(SPELL_ATTR6_NO_PUSHBACK) || spellProto->HasAttribute(SPELL_ATTR7_DONT_CAUSE_SPELL_PUSHBACK) || spellProto->HasAttribute(SPELL_ATTR3_TREAT_AS_PERIODIC)))) + { + if (Spell* spell = victim->m_currentSpells[CURRENT_GENERIC_SPELL]) + { + if (spell->getState() == SPELL_STATE_PREPARING) + { + auto isCastInterrupted = [&]() + { + if ((victim->IsPlayer() && spell->m_spellInfo->InterruptFlags.HasFlag(SpellInterruptFlags::DamageCancelsPlayerOnly))) + return true; + + return false; + }; + + auto isCastDelayed = [&]() + { + if (!damage) + return false; + + if ((victim->IsPlayer() && spell->m_spellInfo->InterruptFlags.HasFlag(SpellInterruptFlags::DamagePushbackPlayerOnly))) + return true; + + return false; + }; + + if (isCastInterrupted()) + victim->InterruptNonMeleeSpells(false); + else if (isCastDelayed()) + spell->Delayed(); + } + } + + if (damage && victim->IsPlayer()) + if (Spell* spell = victim->m_currentSpells[CURRENT_CHANNELED_SPELL]) + if (spell->getState() == SPELL_STATE_CHANNELING && spell->m_spellInfo->HasChannelInterruptFlag(SpellAuraInterruptFlags::DamageChannelDuration)) + spell->DelayedChannel(); + } + } + + // last damage from duel opponent + if (duel_hasEnded) + { + Player* he = duel_wasMounted ? victim->GetCharmer()->ToPlayer() : victim->ToPlayer(); + + ASSERT(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); + } + } + + return damage; +} + +void Unit::CastStop(uint32 except_spellid) +{ + for (uint32 i = CURRENT_FIRST_NON_MELEE_SPELL; i < CURRENT_MAX_SPELL; i++) + if (m_currentSpells[i] && m_currentSpells[i]->m_spellInfo->Id != except_spellid) + InterruptSpell(CurrentSpellTypes(i), false); +} + +void Unit::CalculateSpellDamageTaken(SpellNonMeleeDamage* damageInfo, int32 damage, SpellInfo const* spellInfo, WeaponAttackType attackType, bool crit, bool blocked /*= false*/) +{ + if (damage < 0) + return; + + Unit* victim = damageInfo->target; + if (!victim || !victim->IsAlive()) + return; + + SpellSchoolMask damageSchoolMask = SpellSchoolMask(damageInfo->schoolMask); + + // Spells with SPELL_ATTR4_IGNORE_DAMAGE_TAKEN_MODIFIERS ignore resilience because their damage is based off another spell's damage. + if (!spellInfo->HasAttribute(SPELL_ATTR4_IGNORE_DAMAGE_TAKEN_MODIFIERS)) + { + if (Unit::IsDamageReducedByArmor(damageSchoolMask, spellInfo)) + damage = Unit::CalcArmorReducedDamage(this, victim, damage, spellInfo, attackType); + + // Per-school calc + switch (spellInfo->DmgClass) + { + // Melee and Ranged Spells + case SPELL_DAMAGE_CLASS_RANGED: + case SPELL_DAMAGE_CLASS_MELEE: + { + if (crit) + { + damageInfo->HitInfo |= SPELL_HIT_TYPE_CRIT; + + // Calculate crit bonus + uint32 crit_bonus = damage; + // Apply crit_damage bonus for melee spells + if (Player* modOwner = GetSpellModOwner()) + modOwner->ApplySpellMod(spellInfo, SpellModOp::CritDamageAndHealing, crit_bonus); + damage += crit_bonus; + + // 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 += (GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_CRIT_DAMAGE_BONUS, spellInfo->GetSchoolMask()) - 1.0f) * 100; + + if (critPctDamageMod != 0) + AddPct(damage, critPctDamageMod); + } + + // Spell weapon based damage CAN BE crit & blocked at same time + if (blocked) + { + // double blocked amount if block is critical + uint32 value = victim->GetBlockPercent(); + if (victim->IsBlockCritical()) + value *= 2; // double blocked percent + + damageInfo->blocked = CalculatePct(damage, value); + if (damage <= int32(damageInfo->blocked)) + { + damageInfo->blocked = uint32(damage); + damageInfo->fullBlock = true; + } + damage -= damageInfo->blocked; + } + + if (CanApplyResilience()) + Unit::ApplyResilience(victim, &damage); + break; + } + // Magical Attacks + case SPELL_DAMAGE_CLASS_NONE: + case SPELL_DAMAGE_CLASS_MAGIC: + { + // If crit add critical bonus + if (crit) + { + damageInfo->HitInfo |= SPELL_HIT_TYPE_CRIT; + damage = Unit::SpellCriticalDamageBonus(this, spellInfo, damage); + } + + if (CanApplyResilience()) + Unit::ApplyResilience(victim, &damage); + break; + + } + default: + break; + } + } + + // Script Hook For CalculateSpellDamageTaken -- Allow scripts to change the Damage post class mitigation calculations + sScriptMgr->ModifySpellDamageTaken(damageInfo->target, damageInfo->attacker, damage); + + // Calculate absorb resist + if (damage < 0) + damage = 0; + + damageInfo->damage = damage; + DamageInfo dmgInfo(*damageInfo, SPELL_DIRECT_DAMAGE, BASE_ATTACK, PROC_HIT_NONE); + Unit::CalcAbsorbResist(dmgInfo); + damageInfo->absorb = dmgInfo.GetAbsorb(); + damageInfo->resist = dmgInfo.GetResist(); + + if (damageInfo->absorb) + damageInfo->HitInfo |= (damageInfo->damage - damageInfo->absorb == 0 ? HITINFO_FULL_ABSORB : HITINFO_PARTIAL_ABSORB); + + if (damageInfo->resist) + damageInfo->HitInfo |= (damageInfo->damage - damageInfo->resist == 0 ? HITINFO_FULL_RESIST : HITINFO_PARTIAL_RESIST); + + damageInfo->damage = dmgInfo.GetDamage(); +} + +void Unit::DealSpellDamage(SpellNonMeleeDamage* damageInfo, bool durabilityLoss) +{ + if (!damageInfo) + return; + + Unit* victim = damageInfo->target; + if (!victim) + return; + + if (!victim->IsAlive() || victim->HasUnitState(UNIT_STATE_IN_FLIGHT) || (victim->GetTypeId() == TYPEID_UNIT && victim->ToCreature()->IsEvadingAttacks())) + return; + + SpellInfo const* spellProto = sSpellMgr->GetSpellInfo(damageInfo->SpellID); + if (spellProto == nullptr) + { + TC_LOG_DEBUG("entities.unit", "Unit::DealSpellDamage has wrong damageInfo->SpellID: %u", damageInfo->SpellID); + return; + } + + // Call default DealDamage + Unit::DealDamage(this, victim, damageInfo->damage, damageInfo->unmitigatedDamage, SPELL_DIRECT_DAMAGE, SpellSchoolMask(damageInfo->schoolMask), spellProto, durabilityLoss); +} + +/*static*/ uint32 Unit::CalcMeleeAttackRageGain(Unit const* attacker, Unit const* victim, WeaponAttackType attType) +{ + if (!attacker || !victim || (attType != BASE_ATTACK && attType != OFF_ATTACK)) + return 0; + + uint32 rage = uint32(attacker->GetBaseAttackTime(attType) / 1000.f * 6.5f); + if (attType == OFF_ATTACK) + rage /= 2; + + // Players below level 10 start with a 50% rage gain reduction that slowly diminishes + if (attacker->IsPlayer() && attacker->getLevel() < 10) + rage -= (rage / 2.f) * (1.0f - (attacker->getLevel() / 10.f)); + + rage *= 10; + + AddPct(rage, attacker->GetTotalAuraModifier(SPELL_AURA_MOD_RAGE_FROM_DAMAGE_DEALT)); + + return rage; +} + +// The minimum amount a damaging hit should award +constexpr uint32 MIN_RAGE_AMOUNT = 10; + +/*static*/ uint32 Unit::CalcDamageTakenRageGain(Unit const* victim, uint32 damage) +{ + if (!victim || damage == 0) + return 0; + + double multiplier = static_cast(damage) / victim->GetMaxHealth(); + + // This formula is based on sampling multiple sniff data at multiple level ranges + // There has never been any leak and the only info we have is that the amount is based on max HP and damage taken and that there is a hidden constant + return std::max(MIN_RAGE_AMOUNT, std::ceil(400 * multiplier)); +} + +/// @todo for melee need create structure as in +void Unit::CalculateMeleeDamage(Unit* victim, CalcDamageInfo* damageInfo, WeaponAttackType attackType /*= BASE_ATTACK*/) +{ + damageInfo->Attacker = this; + damageInfo->Target = victim; + + damageInfo->DamageSchoolMask = GetMeleeDamageSchoolMask(attackType); + damageInfo->Damage = 0; + damageInfo->OriginalDamage = 0; + damageInfo->Absorb = 0; + damageInfo->Resist = 0; + + damageInfo->Blocked = 0; + damageInfo->HitInfo = 0; + damageInfo->TargetState = 0; + + damageInfo->AttackType = attackType; + damageInfo->ProcAttacker = PROC_FLAG_NONE; + damageInfo->ProcVictim = PROC_FLAG_NONE; + damageInfo->UnmitigatedDamage = 0; + damageInfo->HitOutCome = MELEE_HIT_EVADE; + damageInfo->RageGained = 0; + + if (!victim) + return; + + if (!IsAlive() || !victim->IsAlive()) + return; + + // Select HitInfo/procAttacker/procVictim flag based on attack type + switch (attackType) + { + case BASE_ATTACK: + damageInfo->ProcAttacker = PROC_FLAG_DEAL_MELEE_SWING | PROC_FLAG_MAIN_HAND_WEAPON_SWING; + damageInfo->ProcVictim = PROC_FLAG_TAKE_MELEE_SWING; + break; + case OFF_ATTACK: + damageInfo->ProcAttacker = PROC_FLAG_DEAL_MELEE_SWING | PROC_FLAG_OFF_HAND_WEAPON_SWING; + damageInfo->ProcVictim = PROC_FLAG_TAKE_MELEE_SWING; + damageInfo->HitInfo = HITINFO_OFFHAND; + break; + default: + return; + } + + // Physical Immune check + if (damageInfo->Target->IsImmunedToDamage(SpellSchoolMask(damageInfo->DamageSchoolMask))) + { + damageInfo->HitInfo |= HITINFO_NORMALSWING; + damageInfo->TargetState = VICTIMSTATE_IS_IMMUNE; + + damageInfo->Damage = 0; + damageInfo->UnmitigatedDamage = 0; + return; + } + + uint32 damage = 0; + damage += CalculateDamage(damageInfo->AttackType, false, true); + // Add melee damage bonus + damage = MeleeDamageBonusDone(damageInfo->Target, damage, damageInfo->AttackType, DIRECT_DAMAGE, nullptr, MECHANIC_NONE, SpellSchoolMask(damageInfo->DamageSchoolMask)); + damage = damageInfo->Target->MeleeDamageBonusTaken(this, damage, damageInfo->AttackType, nullptr, SpellSchoolMask(damageInfo->DamageSchoolMask)); + + // Script Hook For CalculateMeleeDamage -- Allow scripts to change the Damage pre class mitigation calculations + sScriptMgr->ModifyMeleeDamage(damageInfo->Target, damageInfo->Attacker, damage); + + // Calculate armor reduction + if (Unit::IsDamageReducedByArmor((SpellSchoolMask)(damageInfo->DamageSchoolMask))) + damageInfo->Damage = Unit::CalcArmorReducedDamage(damageInfo->Attacker, damageInfo->Target, damage, nullptr, damageInfo->AttackType); + else + damageInfo->Damage = damage; + + // Store unmitigated damage to reward rage later + damageInfo->UnmitigatedDamage = damage; + + damageInfo->HitOutCome = RollMeleeOutcomeAgainst(damageInfo->Target, damageInfo->AttackType); + + switch (damageInfo->HitOutCome) + { + case MELEE_HIT_EVADE: + damageInfo->HitInfo |= HITINFO_MISS | HITINFO_SWINGNOHITSOUND; + damageInfo->TargetState = VICTIMSTATE_EVADES; + damageInfo->OriginalDamage = damageInfo->Damage; + + damageInfo->Damage = 0; + return; + case MELEE_HIT_MISS: + damageInfo->HitInfo |= HITINFO_MISS; + damageInfo->TargetState = VICTIMSTATE_INTACT; + damageInfo->OriginalDamage = damageInfo->Damage; + + damageInfo->Damage = 0; + damageInfo->UnmitigatedDamage = 0; + break; + case MELEE_HIT_NORMAL: + damageInfo->TargetState = VICTIMSTATE_HIT; + damageInfo->OriginalDamage = damageInfo->Damage; + break; + case MELEE_HIT_CRIT: + { + damageInfo->HitInfo |= HITINFO_CRITICALHIT; + damageInfo->TargetState = VICTIMSTATE_HIT; + + // Crit bonus calc + damageInfo->Damage *= 2; + 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->DamageSchoolMask) - 1.0f) * 100; + + if (mod != 0) + AddPct(damageInfo->Damage, mod); + + damageInfo->OriginalDamage = damageInfo->Damage; + break; + } + case MELEE_HIT_PARRY: + damageInfo->TargetState = VICTIMSTATE_PARRY; + damageInfo->OriginalDamage = damageInfo->Damage; + damageInfo->Damage = 0; + break; + case MELEE_HIT_DODGE: + damageInfo->TargetState = VICTIMSTATE_DODGE; + damageInfo->OriginalDamage = damageInfo->Damage; + damageInfo->Damage = 0; + break; + case MELEE_HIT_BLOCK: + damageInfo->TargetState = VICTIMSTATE_HIT; + damageInfo->HitInfo |= HITINFO_BLOCK; + // 30% damage blocked, double blocked amount if block is critical + damageInfo->Blocked = CalculatePct(damageInfo->Damage, damageInfo->Target->GetBlockPercent()); + if (damageInfo->Target->IsBlockCritical()) + damageInfo->Blocked *= 2; + + damageInfo->OriginalDamage = damageInfo->Damage; + damageInfo->Damage -= damageInfo->Blocked; + break; + case MELEE_HIT_GLANCING: + { + damageInfo->HitInfo |= HITINFO_GLANCING; + damageInfo->TargetState = VICTIMSTATE_HIT; + int32 leveldif = int32(victim->getLevel()) - int32(getLevel()); + if (leveldif > 3) + leveldif = 3; + + damageInfo->OriginalDamage = damageInfo->Damage; + float reducePercent = 1.f - leveldif * 0.1f; + damageInfo->Damage = uint32(reducePercent * damageInfo->Damage); + break; + } + case MELEE_HIT_CRUSHING: + damageInfo->HitInfo |= HITINFO_CRUSHING; + damageInfo->TargetState = VICTIMSTATE_HIT; + // 150% normal damage + damageInfo->Damage += (damageInfo->Damage / 2); + damageInfo->OriginalDamage = damageInfo->Damage; + break; + default: + break; + } + + // Always apply HITINFO_AFFECTS_VICTIM in case its not a miss + if (!(damageInfo->HitInfo & HITINFO_MISS)) + damageInfo->HitInfo |= HITINFO_AFFECTS_VICTIM; + + int32 resilienceReduction = damageInfo->Damage; + if (CanApplyResilience()) + Unit::ApplyResilience(victim, &resilienceReduction); + resilienceReduction = damageInfo->Damage - resilienceReduction; + damageInfo->Damage -= resilienceReduction; + + // Calculate absorb resist + if (int32(damageInfo->Damage) > 0) + { + damageInfo->ProcVictim |= PROC_FLAG_TAKE_ANY_DAMAGE; + // Calculate absorb & resists + DamageInfo dmgInfo(*damageInfo); + Unit::CalcAbsorbResist(dmgInfo); + damageInfo->Absorb = dmgInfo.GetAbsorb(); + damageInfo->Resist = dmgInfo.GetResist(); + + if (damageInfo->Absorb) + damageInfo->HitInfo |= (damageInfo->Damage - damageInfo->Absorb == 0 ? HITINFO_FULL_ABSORB : HITINFO_PARTIAL_ABSORB); + + if (damageInfo->Resist) + damageInfo->HitInfo |= (damageInfo->Damage - damageInfo->Resist == 0 ? HITINFO_FULL_RESIST : HITINFO_PARTIAL_RESIST); + + damageInfo->Damage = dmgInfo.GetDamage(); + } + else // Impossible get negative result but.... + damageInfo->Damage = 0; +} + +void Unit::DealMeleeDamage(CalcDamageInfo* damageInfo, bool durabilityLoss) +{ + Unit* victim = damageInfo->Target; + + if (!victim->IsAlive() || victim->HasUnitState(UNIT_STATE_IN_FLIGHT) || (victim->GetTypeId() == TYPEID_UNIT && victim->ToCreature()->IsEvadingAttacks())) + return; + + if (damageInfo->TargetState == VICTIMSTATE_PARRY && + (GetTypeId() != TYPEID_UNIT || (ToCreature()->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_NO_PARRY_HASTEN) == 0)) + { + // Get attack timers + float offtime = float(victim->getAttackTimer(OFF_ATTACK)); + float basetime = float(victim->getAttackTimer(BASE_ATTACK)); + // Reduce attack time + if (victim->haveOffhandWeapon() && offtime < basetime) + { + float percent20 = victim->GetBaseAttackTime(OFF_ATTACK) * 0.20f; + float percent60 = 3.0f * percent20; + if (offtime > percent20 && offtime <= percent60) + victim->setAttackTimer(OFF_ATTACK, uint32(percent20)); + else if (offtime > percent60) + { + offtime -= 2.0f * percent20; + victim->setAttackTimer(OFF_ATTACK, uint32(offtime)); + } + } + else + { + float percent20 = victim->GetBaseAttackTime(BASE_ATTACK) * 0.20f; + float percent60 = 3.0f * percent20; + if (basetime > percent20 && basetime <= percent60) + victim->setAttackTimer(BASE_ATTACK, uint32(percent20)); + else if (basetime > percent60) + { + basetime -= 2.0f * percent20; + victim->setAttackTimer(BASE_ATTACK, uint32(basetime)); + } + } + } + + // Call default DealDamage + Unit::DealDamage(this, victim, damageInfo->Damage, damageInfo->UnmitigatedDamage, DIRECT_DAMAGE, SpellSchoolMask(damageInfo->DamageSchoolMask), nullptr, durabilityLoss); + + // If this is a creature and it attacks from behind it has a probability to daze it's victim + if ((damageInfo->HitOutCome == MELEE_HIT_CRIT || damageInfo->HitOutCome == MELEE_HIT_CRUSHING || damageInfo->HitOutCome == MELEE_HIT_NORMAL || damageInfo->HitOutCome == MELEE_HIT_GLANCING) && + IsCreature() && !ToCreature()->IsControlledByPlayer() && !ToCreature()->HasStaticFlag(CREATURE_STATIC_FLAG_4_CANNOT_DAZE) && + !victim->HasInArc(float(M_PI), this) && (victim->IsPlayer() || (victim->IsCreature() && !victim->ToCreature()->isWorldBoss())) && !victim->IsVehicle()) + { + // 20% base chance + float chance = 20.0f; + + // there is a newbie protection, at level 10 just 7% base chance; assuming linear function + if (victim->getLevel() < 30) + chance = 0.65f * victim->getLevel() + 0.5f; + + uint32 const victimDefense = victim->GetMaxSkillValueForLevel(this); + uint32 const attackerMeleeSkill = GetMaxSkillValueForLevel(); + + chance *= attackerMeleeSkill / float(victimDefense) * 0.16f; + + // -probability is between 0% and 40% + RoundToInterval(chance, 0.0f, 40.0f); + if (roll_chance_f(chance)) + CastSpell(victim, 1604 /*SPELL_DAZED*/, true); + } + + if (GetTypeId() == TYPEID_PLAYER) + { + DamageInfo dmgInfo(*damageInfo); + ToPlayer()->CastItemCombatSpell(dmgInfo); + } + + // Do effect if any damage done to target + if (damageInfo->Damage) + { + // We're going to call functions which can modify content of the list during iteration over it's elements + // Let's copy the list so we can prevent iterator invalidation + AuraEffectList vDamageShieldsCopy(victim->GetAuraEffectsByType(SPELL_AURA_DAMAGE_SHIELD)); + for (AuraEffect const* aurEff : vDamageShieldsCopy) + { + SpellInfo const* spellInfo = aurEff->GetSpellInfo(); + + // Damage shield can be resisted... + SpellMissInfo missInfo = victim->SpellHitResult(this, spellInfo, false); + if (missInfo != SPELL_MISS_NONE) + { + victim->SendSpellMiss(this, spellInfo->Id, missInfo); + continue; + } + + // ...or immuned + if (IsImmunedToDamage(spellInfo)) + { + victim->SendSpellDamageImmune(this, spellInfo->Id); + continue; + } + + uint32 damage = aurEff->GetAmount(); + if (Unit* caster = aurEff->GetCaster()) + { + damage = caster->SpellDamageBonusDone(this, spellInfo, damage, SPELL_DIRECT_DAMAGE, aurEff->GetEffIndex()); + damage = SpellDamageBonusTaken(caster, spellInfo, damage, SPELL_DIRECT_DAMAGE); + } + + DamageInfo dmgInfo(this, victim, damage, spellInfo, spellInfo->GetSchoolMask(), SPELL_DIRECT_DAMAGE, BASE_ATTACK); + victim->CalcAbsorbResist(dmgInfo); + damage = dmgInfo.GetDamage(); + Unit::DealDamageMods(victim, damage, nullptr); + + /// @todo Move this to a packet handler + WorldPackets::CombatLog::SpellDamageShield damageShield; + damageShield.Attacker = victim->GetGUID(); + damageShield.Defender = GetGUID(); + damageShield.SpellID = spellInfo->Id; + damageShield.TotalDamage = damage; + damageShield.OverKill = std::max(int32(damage) - int32(GetHealth()), 0); + damageShield.SchoolMask = spellInfo->SchoolMask; + damageShield.LogAbsorbed = dmgInfo.GetAbsorb(); + victim->SendMessageToSet(damageShield.Write(), true); + + Unit::DealDamage(victim, this, damage, 0, SPELL_DIRECT_DAMAGE, spellInfo->GetSchoolMask(), spellInfo, true); + } + } +} + +void Unit::HandleEmoteCommand(uint32 emoteId) +{ + WorldPackets::Chat::Emote emote; + emote.Guid = GetGUID(); + emote.EmoteID = emoteId; + + SendMessageToSet(emote.Write(), true); +} + +/*static*/ bool Unit::IsDamageReducedByArmor(SpellSchoolMask schoolMask, SpellInfo const* spellInfo /*= nullptr*/) +{ + // only physical spells damage gets reduced by armor + if ((schoolMask & SPELL_SCHOOL_MASK_NORMAL) == 0) + return false; + + return !spellInfo || !spellInfo->HasAttribute(SPELL_ATTR0_CU_IGNORE_ARMOR); +} + +// Calculation taken from Paperdollframe.lua (15595) +static float GetArmorReduction(float armor, uint8 attackerLevel) +{ + float levelModifier = attackerLevel; + if (levelModifier > 80) + levelModifier = levelModifier + (4.5f * (levelModifier - 59)) + (20 * (levelModifier - 80)); + else if (levelModifier > 59) + levelModifier = levelModifier + (4.5f * (levelModifier - 59)); + + float temp = 0.1f * armor / (8.5f * levelModifier + 40); + temp = temp / (1 + temp); + + if (temp > 0.75f) + return 75.f; + + if (temp < 0.f) + return 0.f; + + return temp * 100; +} + +/*static*/ uint32 Unit::CalcArmorReducedDamage(Unit const* attacker, Unit* victim, const uint32 damage, SpellInfo const* spellInfo, WeaponAttackType /*attackType*/, uint8 attackerLevel /*= 0*/) +{ + float armor = float(victim->GetArmor()); + + if (attacker) + { + // bypass enemy armor by SPELL_AURA_BYPASS_ARMOR_FOR_CASTER + int32 armorBypassPct = victim->GetTotalAuraModifier(SPELL_AURA_BYPASS_ARMOR_FOR_CASTER, [&](AuraEffect const* aurEff) + { + return aurEff->GetCasterGUID() == attacker->GetGUID(); + }); + + armor = CalculatePct(armor, 100 - std::min(armorBypassPct, 100)); + + // Ignore enemy armor by SPELL_AURA_MOD_TARGET_RESISTANCE aura + armor += attacker->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_TARGET_RESISTANCE, SPELL_SCHOOL_MASK_NORMAL); + + if (spellInfo) + if (Player* modOwner = attacker->GetSpellModOwner()) + modOwner->ApplySpellMod(spellInfo, SpellModOp::TargetResistance, armor); + + for (AuraEffect const* aurEff : attacker->GetAuraEffectsByType(SPELL_AURA_MOD_IGNORE_TARGET_RESIST)) + { + if (aurEff->GetMiscValue() & SPELL_SCHOOL_MASK_NORMAL) + armor = std::floor(AddPct(armor, -aurEff->GetAmount())); + } + + if (armor < 0.0f) + armor = 0.0f; + } + + float armorReduction = 100.f - GetArmorReduction(armor, attacker ? attacker->getLevel() : attackerLevel); + return std::max(std::ceil(damage * armorReduction / 100.f), 0); +} + +/*static*/ uint32 Unit::CalcSpellResistedDamage(Unit const* attacker, Unit* victim, uint32 damage, SpellSchoolMask schoolMask, SpellInfo const* spellInfo) +{ + // Magic damage, check for resists + if (!(schoolMask & SPELL_SCHOOL_MASK_MAGIC)) + return 0; + + // Npcs can have holy resistance + if ((schoolMask & SPELL_SCHOOL_MASK_HOLY) && victim->GetTypeId() != TYPEID_UNIT) + return 0; + + float const averageResist = Unit::CalculateAverageResistReduction(attacker, schoolMask, victim, spellInfo); + + float discreteResistProbability[11] = { }; + if (averageResist <= 0.1f) + { + discreteResistProbability[0] = 1.0f - 7.5f * averageResist; + discreteResistProbability[1] = 5.0f * averageResist; + discreteResistProbability[2] = 2.5f * averageResist; + } + else + { + for (uint32 i = 0; i < 11; ++i) + discreteResistProbability[i] = std::max(0.5f - 2.5f * std::fabs(0.1f * i - averageResist), 0.0f); + } + + float roll = rand_norm(); + float probabilitySum = 0.0f; + + uint32 resistance = 0; + for (; resistance < 11; ++resistance) + if (roll < (probabilitySum += discreteResistProbability[resistance])) + break; + + float damageResisted = damage * resistance / 10.f; + if (damageResisted > 0.0f) // if any damage was resisted + { + if (attacker) + { + int32 ignoredResistance = attacker->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_IGNORE_TARGET_RESIST, schoolMask); + ignoredResistance = std::min(ignoredResistance, 100); + ApplyPct(damageResisted, 100 - ignoredResistance); + + // Spells with melee and magic school mask, decide whether resistance or armor absorb is higher + if (spellInfo && spellInfo->HasAttribute(SPELL_ATTR0_CU_SCHOOLMASK_NORMAL_WITH_MAGIC)) + { + uint32 damageAfterArmor = Unit::CalcArmorReducedDamage(attacker, victim, damage, spellInfo, spellInfo->GetAttackType()); + float armorReduction = damage - damageAfterArmor; + + // pick the lower one, the weakest resistance counts + damageResisted = std::min(damageResisted, armorReduction); + } + } + } + + damageResisted = std::max(damageResisted, 0.f); + return uint32(damageResisted); +} + +/*static*/ float Unit::CalculateAverageResistReduction(WorldObject const* caster, SpellSchoolMask schoolMask, Unit const* victim, SpellInfo const* spellInfo) +{ + float victimResistance = float(victim->GetResistance(schoolMask)); + if (caster) + { + // pets inherit 100% of masters penetration + if (Player const* player = caster->GetSpellModOwner()) + { + victimResistance += float(player->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_TARGET_RESISTANCE, schoolMask)); + victimResistance -= float(player->GetSpellPenetrationItemMod()); + } + else if (Unit const* unitCaster = caster->ToUnit()) + victimResistance += float(unitCaster->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_TARGET_RESISTANCE, schoolMask)); + } + + // holy resistance exists in pve and comes from level difference, ignore template values + if (schoolMask & SPELL_SCHOOL_MASK_HOLY) + victimResistance = 0.0f; + + // Chaos Bolt exception, ignore all target resistances (unknown attribute?) + if (spellInfo && spellInfo->SpellFamilyName == SPELLFAMILY_WARLOCK && spellInfo->SpellIconID == 3178) + victimResistance = 0.0f; + + victimResistance = std::max(victimResistance, 0.0f); + + // level-based resistance does not apply to binary spells, and cannot be overcome by spell penetration + // gameobject caster -- should it have level based resistance? + if (caster && caster->GetTypeId() != TYPEID_GAMEOBJECT && (!spellInfo || !spellInfo->HasAttribute(SPELL_ATTR0_CU_BINARY_SPELL))) + victimResistance += std::max((float(victim->getLevelForTarget(caster)) - float(caster->getLevelForTarget(victim))) * 5.0f, 0.0f); + + static uint32 const 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); +} + +/*static*/ void Unit::CalcAbsorbResist(DamageInfo& damageInfo) +{ + if (!damageInfo.GetVictim() || !damageInfo.GetVictim()->IsAlive() || !damageInfo.GetDamage()) + return; + + uint32 resistedDamage = Unit::CalcSpellResistedDamage(damageInfo.GetAttacker(), damageInfo.GetVictim(), damageInfo.GetDamage(), damageInfo.GetSchoolMask(), damageInfo.GetSpellInfo()); + damageInfo.ResistDamage(resistedDamage); + + // Ignore Absorption Auras + float auraAbsorbMod = 0.f; + if (Unit* attacker = damageInfo.GetAttacker()) + { + auraAbsorbMod = attacker->GetMaxPositiveAuraModifierByMiscMask(SPELL_AURA_MOD_TARGET_ABSORB_SCHOOL, damageInfo.GetSchoolMask()); + auraAbsorbMod = std::max(auraAbsorbMod, static_cast(attacker->GetMaxPositiveAuraModifier(SPELL_AURA_MOD_TARGET_ABILITY_ABSORB_SCHOOL, [&damageInfo](AuraEffect const* aurEff) -> bool + { + if (!(aurEff->GetMiscValue() & damageInfo.GetSchoolMask())) + return false; + + if (!aurEff->IsAffectingSpell(damageInfo.GetSpellInfo())) + return false; + + return true; + }))); + } + + RoundToInterval(auraAbsorbMod, 0.0f, 100.0f); + + int32 absorbIgnoringDamage = CalculatePct(damageInfo.GetDamage(), auraAbsorbMod); + + // 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(damageInfo.GetVictim()->GetAuraEffectsByType(SPELL_AURA_SCHOOL_ABSORB)); + vSchoolAbsorbCopy.sort(Trinity::AbsorbAuraOrderPred()); + + // absorb without mana cost + for (AuraEffectList::iterator itr = vSchoolAbsorbCopy.begin(); (itr != vSchoolAbsorbCopy.end()) && (damageInfo.GetDamage() > 0); ++itr) + { + AuraEffect* absorbAurEff = *itr; + // Check if aura was removed during iteration - we don't need to work on such auras + AuraApplication const* aurApp = absorbAurEff->GetBase()->GetApplicationOfTarget(damageInfo.GetVictim()->GetGUID()); + if (!aurApp) + continue; + if (!(absorbAurEff->GetMiscValue() & damageInfo.GetSchoolMask())) + continue; + + // get amount which can be still absorbed by the aura + int32 currentAbsorb = absorbAurEff->GetAmount(); + // aura with infinite absorb amount - let the scripts handle absorbtion amount, set here to 0 for safety + if (currentAbsorb < 0) + currentAbsorb = 0; + + if (!absorbAurEff->GetSpellInfo()->HasAttribute(SPELL_ATTR6_ABSORB_CANNOT_BE_IGNORE)) + damageInfo.ModifyDamage(-absorbIgnoringDamage); + + uint32 tempAbsorb = uint32(currentAbsorb); + + bool defaultPrevented = false; + + absorbAurEff->GetBase()->CallScriptEffectAbsorbHandlers(absorbAurEff, aurApp, damageInfo, tempAbsorb, defaultPrevented); + currentAbsorb = tempAbsorb; + + if (!defaultPrevented) + { + + // absorb must be smaller than the damage itself + currentAbsorb = RoundToInterval(currentAbsorb, 0, int32(damageInfo.GetDamage())); + + damageInfo.AbsorbDamage(currentAbsorb); + + tempAbsorb = currentAbsorb; + absorbAurEff->GetBase()->CallScriptEffectAfterAbsorbHandlers(absorbAurEff, aurApp, damageInfo, tempAbsorb); + + // Check if our aura is using amount to count damage + if (absorbAurEff->GetAmount() >= 0) + { + // Reduce shield amount + absorbAurEff->SetAmount(absorbAurEff->GetAmount() - currentAbsorb); + // Aura cannot absorb anything more - remove it + if (absorbAurEff->GetAmount() <= 0) + absorbAurEff->GetBase()->Remove(AuraRemoveFlags::ByEnemySpell); + } + } + + if (!absorbAurEff->GetSpellInfo()->HasAttribute(SPELL_ATTR6_ABSORB_CANNOT_BE_IGNORE)) + damageInfo.ModifyDamage(absorbIgnoringDamage); + } + + // absorb by mana cost + AuraEffectList vManaShieldCopy(damageInfo.GetVictim()->GetAuraEffectsByType(SPELL_AURA_MANA_SHIELD)); + for (AuraEffectList::const_iterator itr = vManaShieldCopy.begin(); (itr != vManaShieldCopy.end()) && (damageInfo.GetDamage() > 0); ++itr) + { + AuraEffect* absorbAurEff = *itr; + // Check if aura was removed during iteration - we don't need to work on such auras + AuraApplication const* aurApp = absorbAurEff->GetBase()->GetApplicationOfTarget(damageInfo.GetVictim()->GetGUID()); + if (!aurApp) + continue; + // check damage school mask + if (!(absorbAurEff->GetMiscValue() & damageInfo.GetSchoolMask())) + continue; + + // get amount which can be still absorbed by the aura + int32 currentAbsorb = absorbAurEff->GetAmount(); + // aura with infinite absorb amount - let the scripts handle absorbtion amount, set here to 0 for safety + if (currentAbsorb < 0) + currentAbsorb = 0; + + if (!absorbAurEff->GetSpellInfo()->HasAttribute(SPELL_ATTR6_ABSORB_CANNOT_BE_IGNORE)) + damageInfo.ModifyDamage(-absorbIgnoringDamage); + + uint32 tempAbsorb = currentAbsorb; + + bool defaultPrevented = false; + + absorbAurEff->GetBase()->CallScriptEffectManaShieldHandlers(absorbAurEff, aurApp, damageInfo, tempAbsorb, defaultPrevented); + currentAbsorb = tempAbsorb; + + if (!defaultPrevented) + { + + // absorb must be smaller than the damage itself + currentAbsorb = RoundToInterval(currentAbsorb, 0, int32(damageInfo.GetDamage())); + + int32 manaReduction = currentAbsorb; + + // lower absorb amount by talents + if (float manaMultiplier = absorbAurEff->GetSpellInfo()->Effects[absorbAurEff->GetEffIndex()].CalcValueMultiplier(absorbAurEff->GetCaster())) + manaReduction = int32(float(manaReduction) * manaMultiplier); + + int32 manaTaken = -damageInfo.GetVictim()->ModifyPower(POWER_MANA, -manaReduction); + + // take case when mana has ended up into account + currentAbsorb = currentAbsorb ? int32(float(currentAbsorb) * (float(manaTaken) / float(manaReduction))) : 0; + + damageInfo.AbsorbDamage(currentAbsorb); + + tempAbsorb = currentAbsorb; + absorbAurEff->GetBase()->CallScriptEffectAfterManaShieldHandlers(absorbAurEff, aurApp, damageInfo, tempAbsorb); + + // Check if our aura is using amount to count damage + if (absorbAurEff->GetAmount() >= 0) + { + absorbAurEff->SetAmount(absorbAurEff->GetAmount() - currentAbsorb); + if ((absorbAurEff->GetAmount() <= 0)) + absorbAurEff->GetBase()->Remove(AuraRemoveFlags::ByEnemySpell); + } + } + + if (!absorbAurEff->GetSpellInfo()->HasAttribute(SPELL_ATTR6_ABSORB_CANNOT_BE_IGNORE)) + damageInfo.ModifyDamage(absorbIgnoringDamage); + } + + // split damage auras - only when not damaging self + if (damageInfo.GetVictim() != damageInfo.GetAttacker()) + { + // We're going to call functions which can modify content of the list during iteration over it's elements + // Let's copy the list so we can prevent iterator invalidation + AuraEffectList vSplitDamagePctCopy(damageInfo.GetVictim()->GetAuraEffectsByType(SPELL_AURA_SPLIT_DAMAGE_PCT)); + for (AuraEffectList::iterator itr = vSplitDamagePctCopy.begin(); itr != vSplitDamagePctCopy.end() && damageInfo.GetDamage() > 0; ++itr) + { + // Check if aura was removed during iteration - we don't need to work on such auras + AuraApplication const* aurApp = (*itr)->GetBase()->GetApplicationOfTarget(damageInfo.GetVictim()->GetGUID()); + if (!aurApp) + continue; + + // check damage school mask + if (!((*itr)->GetMiscValue() & damageInfo.GetSchoolMask())) + continue; + + // Damage can be splitted only if aura has an alive caster + Unit* caster = (*itr)->GetCaster(); + if (!caster || (caster == damageInfo.GetVictim()) || !caster->IsInWorld() || !caster->IsAlive()) + continue; + + uint32 splitDamage = CalculatePct(damageInfo.GetDamage(), (*itr)->GetAmount()); + + (*itr)->GetBase()->CallScriptEffectSplitHandlers((*itr), aurApp, damageInfo, splitDamage); + + // absorb must be smaller than the damage itself + splitDamage = RoundToInterval(splitDamage, uint32(0), uint32(damageInfo.GetDamage())); + + damageInfo.AbsorbDamage(splitDamage); + + // check if caster is immune to damage + if (caster->IsImmunedToDamage(damageInfo.GetSchoolMask())) + { + damageInfo.GetVictim()->SendSpellMiss(caster, (*itr)->GetSpellInfo()->Id, SPELL_MISS_IMMUNE); + continue; + } + + uint32 split_absorb = 0; + Unit::DealDamageMods(caster, splitDamage, &split_absorb); + + if (Unit* attacker = damageInfo.GetAttacker()) + { + // Sparring Checks + if (Creature* target = damageInfo.GetVictim()->ToCreature()) + if (attacker->IsCreature() && !attacker->IsCharmedOwnedByPlayerOrPlayer()) + if (target->GetNoNpcDamageBelowPctHealthValue() != 0.0f) + if (target->GetHealthPct() <= target->GetNoNpcDamageBelowPctHealthValue()) + damageInfo.ModifyDamage(damageInfo.GetDamage() * -1); + + attacker->SendSpellNonMeleeDamageLog(caster, (*itr)->GetSpellInfo()->Id, splitDamage, damageInfo.GetSchoolMask(), split_absorb, 0, damageInfo.GetDamageType() == DOT, 0, false, true); + } + + Unit::DealDamage(damageInfo.GetAttacker(), caster, splitDamage, 0, DIRECT_DAMAGE, damageInfo.GetSchoolMask(), (*itr)->GetSpellInfo(), false); + + // break 'Fear' and similar auras + Unit::ProcSkillsAndAuras(damageInfo.GetAttacker(), caster, PROC_FLAG_NONE, PROC_FLAG_TAKE_HARMFUL_SPELL, PROC_SPELL_TYPE_DAMAGE, PROC_SPELL_PHASE_HIT, PROC_HIT_NONE, nullptr, &damageInfo, nullptr); + } + } +} + +/*static*/ void Unit::CalcHealAbsorb(HealInfo& healInfo) +{ + if (!healInfo.GetHeal()) + return; + + 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(AuraRemoveFlags::ByEnemySpell); + if (removedAuras + 1 < healInfo.GetTarget()->m_removedAurasCount) + i = vHealAbsorb.begin(); + } + } + } + + if (absorbAmount > 0) + healInfo.AbsorbHeal(absorbAmount); +} + +void Unit::AttackerStateUpdate(Unit* victim, WeaponAttackType attType, bool extra) +{ + if (HasUnitState(UNIT_STATE_CANNOT_AUTOATTACK) || HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PACIFIED)) + return; + + if (HasUnitState(UNIT_STATE_CASTING)) + { + Spell* channeledSpell = GetCurrentSpell(CURRENT_CHANNELED_SPELL); + if (!channeledSpell || !channeledSpell->GetSpellInfo()->HasAttribute(SPELL_ATTR5_ALLOW_ACTIONS_DURING_CHANNEL)) + return; + } + + if (!victim->IsAlive()) + return; + + if ((attType == BASE_ATTACK || attType == OFF_ATTACK) && !IsWithinLOSInMap(victim)) + return; + + AtTargetAttacked(victim, true); + RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Attacking); + + if (attType != BASE_ATTACK && attType != OFF_ATTACK) + return; // ignore ranged case + + // melee attack spell cast at main hand attack only - no normal melee dmg dealt + if (attType == BASE_ATTACK && m_currentSpells[CURRENT_MELEE_SPELL] && !extra) + m_currentSpells[CURRENT_MELEE_SPELL]->cast(); + else + { + // attack can be redirected to another target + victim = GetMeleeHitRedirectTarget(victim); + + AuraEffectList const& meleeAttackOverrides = GetAuraEffectsByType(SPELL_AURA_OVERRIDE_AUTOATTACK_WITH_MELEE_SPELL); + AuraEffect const* meleeAttackAuraEffect = nullptr; + uint32 meleeAttackSpellId = 0; + if (attType == BASE_ATTACK) + { + if (!meleeAttackOverrides.empty()) + { + meleeAttackAuraEffect = meleeAttackOverrides.front(); + meleeAttackSpellId = meleeAttackAuraEffect->GetSpellInfo()->Effects[meleeAttackAuraEffect->GetEffIndex()].TriggerSpell; + } + } + else + { + auto itr = std::find_if(meleeAttackOverrides.begin(), meleeAttackOverrides.end(), [&](AuraEffect const* aurEff) + { + return aurEff->GetAmount() != 0; + }); + + if (itr != meleeAttackOverrides.end()) + { + meleeAttackAuraEffect = *itr; + meleeAttackSpellId = meleeAttackAuraEffect->GetAmount(); + } + } + + if (!meleeAttackAuraEffect) + { + CalcDamageInfo damageInfo; + CalculateMeleeDamage(victim, &damageInfo, attType); + // Send log damage message to client + Unit::DealDamageMods(victim, damageInfo.Damage, &damageInfo.Absorb); + + // Sparring Checks + if (Creature* target = victim->ToCreature()) + if (IsCreature() && !IsCharmedOwnedByPlayerOrPlayer()) + if (target->GetNoNpcDamageBelowPctHealthValue() != 0.0f) + if (target->GetHealthPct() <= target->GetNoNpcDamageBelowPctHealthValue()) + damageInfo.HitInfo |= HITINFO_FAKE_DAMAGE; + + // Rage reward + if (this != victim && damageInfo.HitOutCome != MELEE_HIT_MISS && GetPowerType() == POWER_RAGE) + { + if (uint32 rageReward = CalcMeleeAttackRageGain(this, victim, attType)) + { + damageInfo.HitInfo |= HITINFO_RAGE_GAIN; + damageInfo.RageGained = RewardRage(rageReward, true); + } + } + + SendAttackStateUpdate(&damageInfo); + + DealMeleeDamage(&damageInfo, true); + + DamageInfo dmgInfo(damageInfo); + Unit::ProcSkillsAndAuras(damageInfo.Attacker, damageInfo.Target, damageInfo.ProcAttacker, damageInfo.ProcVictim, PROC_SPELL_TYPE_NONE, PROC_SPELL_PHASE_NONE, dmgInfo.GetHitMask(), nullptr, &dmgInfo, nullptr); + + TC_LOG_DEBUG("entities.unit", "AttackerStateUpdate: %s attacked %s for %u dmg, absorbed %u, blocked %u, resisted %u.", + GetGUID().ToString().c_str(), victim->GetGUID().ToString().c_str(), damageInfo.Damage, damageInfo.Absorb, damageInfo.Blocked, damageInfo.Resist); + } + else + { + CastSpell(victim, meleeAttackSpellId, meleeAttackAuraEffect); + + uint32 hitInfo = HITINFO_AFFECTS_VICTIM | HITINFO_NO_ANIMATION; + if (attType == OFF_ATTACK) + hitInfo |= HITINFO_OFFHAND; + + SendAttackStateUpdate(hitInfo, victim, 0, GetMeleeDamageSchoolMask(), 0, 0, 0, VICTIMSTATE_HIT, 0); + } + } +} + +void Unit::HandleProcExtraAttackFor(Unit* victim) +{ + while (m_extraAttacks) + { + AttackerStateUpdate(victim, BASE_ATTACK, true); + --m_extraAttacks; + } +} + +MeleeHitOutcome Unit::RollMeleeOutcomeAgainst(const Unit* victim, WeaponAttackType attType) const +{ + if (victim->IsCreature() && victim->ToCreature()->IsInEvadeMode()) + return MELEE_HIT_EVADE; + + int32 const attackerMaxSkillValueForLevel = GetMaxSkillValueForLevel(victim); + int32 const victimMaxSkillValueForLevel = victim->GetMaxSkillValueForLevel(this); + + // Miss chance based on melee + int32 miss_chance = int32(MeleeSpellMissChance(victim, attType) * 100.f); + + // Critical hit chance + int32 crit_chance = int32(GetUnitCriticalChanceAgainst(attType, victim) * 100.0f); + + // stunned target cannot dodge and this is check in GetUnitDodgeChance() (returned 0 in this case) + int32 dodge_chance = int32(GetUnitDodgeChance(attType, victim) * 100.0f); + int32 block_chance = int32(GetUnitBlockChance(victim) * 100.0f); + int32 parry_chance = int32(GetUnitParryChance(attType, victim) * 100.0f); + + + // melee attack table implementation + // outcome priority: + // 1. > 2. > 3. > 4. > 5. > 6. > 7. > 8. + // MISS > DODGE > PARRY > GLANCING > BLOCK > CRIT > CRUSHING > HIT + + int32 sum = 0, tmp = 0; + int32 roll = urand(0, 9999); + + float arcAngle = static_cast(M_PI); + if (victim->IsCreature() && victim->ToCreature()->HasStaticFlag(CREATURE_STATIC_FLAG_5_240_DEGREE_BACK_ARC)) + arcAngle -= static_cast(M_PI / 3); + + // check if attack comes from behind, nobody can parry or block if attacker is behind + bool canParryOrBlock = victim->HasInArc(arcAngle, this) || victim->HasAuraType(SPELL_AURA_IGNORE_HIT_DIRECTION); + + // only creatures can dodge if attacker is behind + bool canDodge = victim->GetTypeId() != TYPEID_PLAYER || canParryOrBlock; + + // if victim is casting or cc'd it can't avoid attacks + if (victim->IsNonMeleeSpellCast(false) || victim->HasUnitState(UNIT_STATE_CONTROLLED)) + { + canDodge = false; + canParryOrBlock = false; + } + + // 1. MISS + tmp = miss_chance; + if (tmp > 0 && roll < (sum += tmp)) + return MELEE_HIT_MISS; + + // always crit against a sitting target (except 0 crit chance) + if (victim->IsPlayer() && crit_chance > 0 && !victim->IsStandState()) + return MELEE_HIT_CRIT; + + // 2. DODGE + if (canDodge) + { + tmp = dodge_chance; + if (tmp > 0 // check if unit _can_ dodge + && roll < (sum += tmp)) + return MELEE_HIT_DODGE; + } + + // 3. PARRY + if (canParryOrBlock) + { + tmp = parry_chance; + if (tmp > 0 // check if unit _can_ parry + && roll < (sum += tmp)) + return MELEE_HIT_PARRY; + } + + // 4. GLANCING + // Max 40% chance to score a glancing blow against mobs of the same or higher level (only players and pets, not for ranged weapons). + if ((GetTypeId() == TYPEID_PLAYER || IsPet()) && + victim->GetTypeId() != TYPEID_PLAYER && !victim->IsPet() && + getLevel() <= victim->getLevelForTarget(this)) + { + // cap possible value (with bonuses > max skill) + int32 skill = attackerMaxSkillValueForLevel; + + // against boss-level targets - 24% chance of 25% average damage reduction (damage reduction range : 20-30%) + // against level 82 elites - 18% chance of 15% average damage reduction (damage reduction range : 10-20%) + tmp = 600 + (victimMaxSkillValueForLevel - skill) * 120; + tmp = std::min(tmp, 4000); + if (tmp > 0 && roll < (sum += tmp)) + return MELEE_HIT_GLANCING; + } + + // 5. BLOCK + if (canParryOrBlock) + { + tmp = block_chance; + if (tmp > 0 // check if unit _can_ block + && roll < (sum += tmp)) + return MELEE_HIT_BLOCK; + } + + // 6.CRIT + tmp = crit_chance; + if (tmp > 0 && roll < (sum += tmp)) + return MELEE_HIT_CRIT; + + // 7. CRUSHING + // mobs can score crushing blows if they're 4 or more levels above victim + if (getLevelForTarget(victim) >= victim->getLevelForTarget(this) + 4 && + // can be from by creature (if can) or from controlled player that considered as creature + !IsControlledByPlayer() && + !(IsCreature() && ToCreature()->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_NO_CRUSH)) + { + // when their weapon skill is 15 or more above victim's defense skill + tmp = victimMaxSkillValueForLevel; + // tmp = mob's level * 5 - player's current defense skill + tmp = attackerMaxSkillValueForLevel - tmp; + // minimum of 20 points diff (4 levels difference) + tmp = std::max(tmp, 20); + + // add 2% chance per lacking skill point, min. is 15% + tmp = tmp * 200 - 1500; + if (roll < (sum += tmp)) + return MELEE_HIT_CRUSHING; + } + + // 8. HIT + return MELEE_HIT_NORMAL; +} + +uint32 Unit::CalculateDamage(WeaponAttackType attType, bool normalized, bool addTotalPct) const +{ + float minDamage = 0.0f; + float maxDamage = 0.0f; + + if (normalized || !addTotalPct) + CalculateMinMaxDamage(attType, normalized, addTotalPct, minDamage, maxDamage); + 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)); +} + +void Unit::SendMeleeAttackStart(Unit* victim) +{ + WorldPackets::Combat::AttackStart packet; + packet.Attacker = GetGUID(); + packet.Victim = victim->GetGUID(); + SendMessageToSet(packet.Write(), IsPlayer()); +} + +void Unit::SendMeleeAttackStop(Unit* victim) +{ + SendMessageToSet(WorldPackets::Combat::SAttackStop(this, victim).Write(), IsPlayer()); + + if (victim) + TC_LOG_DEBUG("entities.unit", "%s %u stopped attacking %s %u", (GetTypeId() == TYPEID_PLAYER ? "Player" : "Creature"), GetGUID().GetCounter(), (victim->GetTypeId() == TYPEID_PLAYER ? "player" : "creature"), victim->GetGUID().GetCounter()); + else + TC_LOG_DEBUG("entities.unit", "%s %u stopped attacking", (GetTypeId() == TYPEID_PLAYER ? "Player" : "Creature"), GetGUID().GetCounter()); +} + +bool Unit::IsBlockCritical() const +{ + if (roll_chance_i(GetTotalAuraModifier(SPELL_AURA_MOD_BLOCK_CRIT_CHANCE))) + return true; + return false; +} + +int32 Unit::GetMechanicResistChance(SpellInfo const* spellInfo) const +{ + if (!spellInfo) + return 0; + + int32 resistMech = 0; + for (uint8 eff = 0; eff < MAX_SPELL_EFFECTS; ++eff) + { + if (!spellInfo->Effects[eff].IsEffect()) + break; + + int32 effectMech = spellInfo->GetEffectMechanic(eff); + if (effectMech) + { + int32 temp = GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_MECHANIC_RESISTANCE, effectMech); + if (resistMech < temp) + resistMech = temp; + } + } + return resistMech; +} + +bool Unit::CanUseAttackType(uint8 attacktype) const +{ + switch (attacktype) + { + case BASE_ATTACK: + return !HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_DISARMED); + case OFF_ATTACK: + return !HasFlag(UNIT_FIELD_FLAGS_2, UNIT_FLAG2_DISARM_OFFHAND); + case RANGED_ATTACK: + return !HasFlag(UNIT_FIELD_FLAGS_2, UNIT_FLAG2_DISARM_RANGED); + default: + return true; + } +} + +// Melee based spells hit result calculations +SpellMissInfo Unit::MeleeSpellHitResult(Unit* victim, SpellInfo const* spellInfo) const +{ + if (spellInfo->HasAttribute(SPELL_ATTR3_NO_AVOIDANCE)) + return SPELL_MISS_NONE; + + WeaponAttackType attType = BASE_ATTACK; + + // Check damage class instead of attack type to correctly handle judgements + // - they are meele, but can't be dodged/parried/deflected because of ranged dmg class + if (spellInfo->DmgClass == SPELL_DAMAGE_CLASS_RANGED) + attType = RANGED_ATTACK; + + uint32 roll = urand(0, 9999); + uint32 missChance = uint32(MeleeSpellMissChance(victim, attType, spellInfo) * 100.0f); + + // Roll miss + uint32 tmp = missChance; + if (roll < tmp) + return SPELL_MISS_MISS; + + // Chance resist mechanic + int32 resist_chance = victim->GetMechanicResistChance(spellInfo) * 100; + tmp += resist_chance; + if (roll < tmp) + return SPELL_MISS_RESIST; + + // Same spells cannot be parried/dodged + if (spellInfo->HasAttribute(SPELL_ATTR0_NO_ACTIVE_DEFENSE)) + return SPELL_MISS_NONE; + + bool canDodge = !spellInfo->HasAttribute(SPELL_ATTR7_NO_ATTACK_DODGE); + bool canParry = !spellInfo->HasAttribute(SPELL_ATTR7_NO_ATTACK_PARRY); + bool canBlock = !spellInfo->HasAttribute(SPELL_ATTR8_NO_ATTACK_BLOCK); + + // if victim is casting or cc'd it can't avoid attacks + bool canUseDefenseWhileChanneling = victim->GetCurrentSpell(CURRENT_CHANNELED_SPELL) ? victim->GetCurrentSpell(CURRENT_CHANNELED_SPELL)->GetSpellInfo()->HasAttribute(SPELL_ATTR10_ALLOW_DEFENSE_WHILE_CHANNELING) : false; + bool canUseDefenseWhileCasting = victim->GetCurrentSpell(CURRENT_GENERIC_SPELL) ? victim->GetCurrentSpell(CURRENT_GENERIC_SPELL)->GetSpellInfo()->HasAttribute(SPELL_ATTR10_ALLOW_DEFENSE_WHILE_CASTING) : false; + if ((victim->IsNonMeleeSpellCast(false) || victim->HasUnitState(UNIT_STATE_CONTROLLED)) && !canUseDefenseWhileChanneling && !canUseDefenseWhileCasting) + { + canDodge = false; + canParry = false; + canBlock = false; + } + + // Ranged attacks can only miss, resist and deflect and get blocked + if (attType == RANGED_ATTACK) + { + canDodge = false; + canParry = false; + + // only if in front + if (!victim->HasUnitState(UNIT_STATE_CONTROLLED) && (victim->HasInArc(float(M_PI), this) || victim->HasAuraType(SPELL_AURA_IGNORE_HIT_DIRECTION))) + { + int32 deflect_chance = victim->GetTotalAuraModifier(SPELL_AURA_DEFLECT_SPELLS) * 100; + tmp += deflect_chance; + if (roll < tmp) + return SPELL_MISS_DEFLECT; + } + } + + // Check for attack from behind + if (!victim->HasInArc(float(M_PI), this)) + { + if (!victim->HasAuraType(SPELL_AURA_IGNORE_HIT_DIRECTION)) + { + // Can't dodge from behind in PvP (but its possible in PvE) + if (victim->GetTypeId() == TYPEID_PLAYER) + canDodge = false; + // Can't parry or block + canParry = false; + canBlock = false; + } + else // Only deterrence as of 3.3.5 + { + if (spellInfo->HasAttribute(SPELL_ATTR0_CU_REQ_CASTER_BEHIND_TARGET)) + canParry = false; + } + } + + // Ignore combat result aura + AuraEffectList const& ignore = GetAuraEffectsByType(SPELL_AURA_IGNORE_COMBAT_RESULT); + for (AuraEffect const* aurEff : ignore) + { + if (!aurEff->IsAffectingSpell(spellInfo)) + continue; + + switch (aurEff->GetMiscValue()) + { + case MELEE_HIT_DODGE: + canDodge = false; + break; + case MELEE_HIT_BLOCK: + canBlock = false; + break; + case MELEE_HIT_PARRY: + canParry = false; + break; + default: + TC_LOG_DEBUG("entities.unit", "Spell %u SPELL_AURA_IGNORE_COMBAT_RESULT has unhandled state %d", aurEff->GetId(), aurEff->GetMiscValue()); + break; + } + } + + if (canDodge) + { + // Roll dodge + int32 dodgeChance = int32(GetUnitDodgeChance(attType, victim) * 100.0f); + if (dodgeChance < 0) + dodgeChance = 0; + + if (roll < (tmp += dodgeChance)) + return SPELL_MISS_DODGE; + } + + if (canParry) + { + // Roll parry + int32 parryChance = int32(GetUnitParryChance(attType, victim) * 100.0f); + if (parryChance < 0) + parryChance = 0; + + tmp += parryChance; + if (roll < tmp) + return SPELL_MISS_PARRY; + } + + if (canBlock) + { + int32 blockChance = int32(GetUnitBlockChance(victim) * 100.0f); + if (blockChance < 0) + blockChance = 0; + tmp += blockChance; + + if (roll < tmp) + return SPELL_MISS_BLOCK; + } + + return SPELL_MISS_NONE; +} + +float Unit::GetUnitDodgeChance(WeaponAttackType attType, Unit const* victim) const +{ + int32 attackerSkill = GetMaxSkillValueForLevel(nullptr); + int32 defenderSkill = victim->GetMaxSkillValueForLevel(nullptr); + int32 skillDifference = defenderSkill - attackerSkill; + + float chance = 0.0f; + + if (victim->IsPlayer()) + { + chance += victim->GetFloatValue(PLAYER_DODGE_PERCENTAGE); + chance += skillDifference * 0.04f; + } + else + { + if (!victim->IsTotem()) + { + chance += victim->GetTotalAuraModifier(SPELL_AURA_MOD_DODGE_PERCENT); + if (skillDifference <= 10) + chance = 5.f + skillDifference * 0.1f; + else + chance = 6.f + (skillDifference - 10) * 0.1f; + } + } + + // Reduce enemy dodge chance by SPELL_AURA_MOD_COMBAT_RESULT_CHANCE + chance += GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_COMBAT_RESULT_CHANCE, VICTIMSTATE_DODGE); + + // reduce dodge by SPELL_AURA_MOD_ENEMY_DODGE + chance += GetTotalAuraModifier(SPELL_AURA_MOD_ENEMY_DODGE); + + if (IsPlayer()) + chance -= ToPlayer()->GetExpertiseDodgeOrParryReduction(attType); + else + chance -= GetTotalAuraModifier(SPELL_AURA_MOD_EXPERTISE) / 4.0f; + + return std::clamp(chance, 0.f, 100.f); +} + +float Unit::GetUnitParryChance(WeaponAttackType attType, Unit const* victim) const +{ + int32 attackerSkill = GetMaxSkillValueForLevel(nullptr); + int32 defenderSkill = victim->GetMaxSkillValueForLevel(nullptr); + int32 skillDifference = defenderSkill - attackerSkill; + + float chance = 0.0f; + + if (Player const* playerVictim = victim->ToPlayer()) + { + if (playerVictim->CanParry()) + { + Item* tmpitem = playerVictim->GetWeaponForAttack(BASE_ATTACK, true); + if (!tmpitem) + tmpitem = playerVictim->GetWeaponForAttack(OFF_ATTACK, true); + + if (tmpitem) + chance = playerVictim->GetFloatValue(PLAYER_PARRY_PERCENTAGE); + + chance += skillDifference * 0.04f; + } + } + else + { + // Allow parries for creatures only if it's not a totem, does have a virtual item equipped and does not have CREATURE_FLAG_EXTRA_NO_PARRY + if (!victim->IsTotem() && (victim->GetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 0) || victim->GetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 1)) && + !(victim->ToCreature()->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_NO_PARRY)) + { + chance += victim->GetTotalAuraModifier(SPELL_AURA_MOD_PARRY_PERCENT); + if (skillDifference <= 10) + chance = 5.f + skillDifference * 0.1f; + else + chance = 6.f + (skillDifference - 10) * 0.1f; + } + } + + if (IsPlayer()) + chance -= ToPlayer()->GetExpertiseDodgeOrParryReduction(attType); + else + chance -= GetTotalAuraModifier(SPELL_AURA_MOD_EXPERTISE) / 4.0f; + + return std::max(chance, 0.0f); +} + +float Unit::GetMeleeMissChance(Unit const* victim) const +{ + float chance = 0.f; + + // As of patch 4.0.1, weapon skills have been deprecated so we can go for plain defense skill values + uint16 attackerSkill = GetMaxSkillValueForLevel(nullptr); + uint16 defenderSkill = victim->GetMaxSkillValueForLevel(nullptr); + int32 skillDifference = defenderSkill - attackerSkill; + + if (victim->IsPlayer()) + { + chance = 5.f; + if (skillDifference > 0) + chance += skillDifference * 0.04f; + else if (skillDifference < 0) + chance -= skillDifference * 0.02f; + } + else + { + if (skillDifference <= 10) + chance = 5.f + skillDifference * 0.1f; + else + chance = 6.f + (skillDifference - 10) * 0.4f; + } + + return std::clamp(chance, 0.f, 100.f); +} + +float Unit::GetUnitBlockChance(Unit const* victim) const +{ + float chance = 0.f; + + if (Player const* playerVictim = victim->ToPlayer()) + { + if (playerVictim->CanBlock()) + { + Item* tmpitem = playerVictim->GetUseableItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND); + if (tmpitem && !tmpitem->IsBroken()) + chance = playerVictim->GetFloatValue(PLAYER_BLOCK_PERCENTAGE); + } + } + else + { + if (!victim->IsTotem() && !(victim->ToCreature()->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_NO_BLOCK)) + { + chance = 5.0f; + chance += victim->GetTotalAuraModifier(SPELL_AURA_MOD_BLOCK_PERCENT); + } + } + + return std::max(chance, 0.0f); +} + +float Unit::GetUnitCriticalChanceDone(WeaponAttackType attackType) const +{ + float chance = 0.f; + if (GetTypeId() == TYPEID_PLAYER) + { + switch (attackType) + { + case BASE_ATTACK: + chance = GetFloatValue(PLAYER_CRIT_PERCENTAGE); + break; + case OFF_ATTACK: + chance = GetFloatValue(PLAYER_OFFHAND_CRIT_PERCENTAGE); + break; + case RANGED_ATTACK: + chance = GetFloatValue(PLAYER_RANGED_CRIT_PERCENTAGE); + break; + // Just for good manner + default: + chance = 0.0f; + break; + } + } + else + { + if (!(ToCreature()->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_NO_CRIT)) + { + chance = 5.0f; + chance += GetTotalAuraModifier(SPELL_AURA_MOD_WEAPON_CRIT_PERCENT); + chance += GetTotalAuraModifier(SPELL_AURA_MOD_CRIT_PCT); + } + } + + return chance; +} + +float Unit::GetUnitCriticalChanceTaken(WeaponAttackType attackType, float critDone) const +{ + float chance = critDone; + + // flat aura mods + if (attackType == RANGED_ATTACK) + chance += GetTotalAuraModifier(SPELL_AURA_MOD_ATTACKER_RANGED_CRIT_CHANCE); + else + chance += GetTotalAuraModifier(SPELL_AURA_MOD_ATTACKER_MELEE_CRIT_CHANCE); + + // applied after resilience + chance += GetTotalAuraModifier(SPELL_AURA_MOD_ATTACKER_SPELL_AND_WEAPON_CRIT_CHANCE); + + return std::max(chance, 0.f); +} + +float Unit::GetUnitCriticalChanceAgainst(WeaponAttackType attackType, Unit const* victim) const +{ + float chance = GetUnitCriticalChanceDone(attackType); + return victim->GetUnitCriticalChanceTaken(attackType, chance); +} + +void Unit::_DeleteRemovedAuras() +{ + while (!m_removedAuras.empty()) + { + delete m_removedAuras.front(); + m_removedAuras.pop_front(); + } + + m_removedAurasCount = 0; +} + +void Unit::_UpdateSpells(uint32 time) +{ + m_spellHistory->Update(); + + if (m_currentSpells[CURRENT_AUTOREPEAT_SPELL]) + _UpdateAutoRepeatSpell(); + + // remove finished spells from current pointers + for (Spell*& spell : m_currentSpells) + { + if (spell && spell->getState() == SPELL_STATE_FINISHED) + { + spell->SetReferencedFromCurrent(false); + spell = nullptr; // remove pointer + } + } + + // m_auraUpdateIterator can be updated in indirect called code at aura remove to skip next planned to update but removed auras + for (m_auraUpdateIterator = m_ownedAuras.begin(); m_auraUpdateIterator != m_ownedAuras.end();) + { + Aura* i_aura = m_auraUpdateIterator->second; + ++m_auraUpdateIterator; // need shift to next for allow update if need into aura update + i_aura->UpdateOwner(time, this); + } + + // remove expired auras - do that after updates(used in scripts?) + for (AuraMap::iterator i = m_ownedAuras.begin(); i != m_ownedAuras.end();) + { + if (i->second->IsExpired()) + RemoveOwnedAura(i, AuraRemoveFlags::Expired); + 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()) + { + GameObjectList::iterator itr; + for (itr = m_gameObj.begin(); itr != m_gameObj.end();) + { + if (!(*itr)->isSpawned()) + { + (*itr)->SetOwnerGUID(ObjectGuid::Empty); + (*itr)->SetRespawnTime(0); + (*itr)->Delete(); + m_gameObj.erase(itr++); + } + else + ++itr; + } + } +} + +void Unit::_UpdateAutoRepeatSpell() +{ + SpellInfo const* autoRepeatSpellInfo = m_currentSpells[CURRENT_AUTOREPEAT_SPELL]->m_spellInfo; + + // check "realtime" interrupts + // don't cancel spells which are affected by a SPELL_AURA_CAST_WHILE_WALKING effect + if ((isMoving() && m_currentSpells[CURRENT_AUTOREPEAT_SPELL]->CheckMovement() != SPELL_CAST_OK) || IsNonMeleeSpellCast(false, false, true, autoRepeatSpellInfo->Id == 75)) + { + // cancel wand shoot + if (autoRepeatSpellInfo->Id != 75) + InterruptSpell(CURRENT_AUTOREPEAT_SPELL); + return; + } + + // castroutine + if (isAttackReady(RANGED_ATTACK) && m_currentSpells[CURRENT_AUTOREPEAT_SPELL]->getState() != SPELL_STATE_PREPARING) + { + // Check if able to cast + SpellCastResult result = m_currentSpells[CURRENT_AUTOREPEAT_SPELL]->CheckCast(true); + if (result != SPELL_CAST_OK) + { + if (autoRepeatSpellInfo->Id != 75) + InterruptSpell(CURRENT_AUTOREPEAT_SPELL); + else if (GetTypeId() == TYPEID_PLAYER) + Spell::SendCastResult(ToPlayer(), autoRepeatSpellInfo, 1, result); + + return; + } + + // we want to shoot + Spell* spell = new Spell(this, autoRepeatSpellInfo, TRIGGERED_IGNORE_GCD); + spell->prepare(m_currentSpells[CURRENT_AUTOREPEAT_SPELL]->m_targets); + } +} + +void Unit::SetCurrentCastSpell(Spell* spell) +{ + ASSERT(spell); // nullptr may be never passed here, use InterruptSpell or InterruptNonMeleeSpells + + CurrentSpellTypes CSpellType = spell->GetCurrentContainer(); + + if (spell == m_currentSpells[CSpellType]) // avoid breaking self + return; + + // special breakage effects: + switch (CSpellType) + { + case CURRENT_GENERIC_SPELL: + { + // generic spells always break channeled not delayed spells + if (m_currentSpells[CURRENT_CHANNELED_SPELL] && !m_currentSpells[CURRENT_CHANNELED_SPELL]->GetSpellInfo()->HasAttribute(SPELL_ATTR5_ALLOW_ACTIONS_DURING_CHANNEL)) + InterruptSpell(CURRENT_CHANNELED_SPELL, false); + + // autorepeat breaking + if (m_currentSpells[CURRENT_AUTOREPEAT_SPELL]) + { + // break autorepeat if not Auto Shot + if (m_currentSpells[CURRENT_AUTOREPEAT_SPELL]->GetSpellInfo()->Id != 75) + InterruptSpell(CURRENT_AUTOREPEAT_SPELL); + } + if (spell->GetCastTime() > 0) + AddUnitState(UNIT_STATE_CASTING); + + break; + } + case CURRENT_CHANNELED_SPELL: + { + // channel spells always break generic non-delayed and any channeled spells + InterruptSpell(CURRENT_GENERIC_SPELL, false); + InterruptSpell(CURRENT_CHANNELED_SPELL); + + // it also does break autorepeat if not Auto Shot + if (m_currentSpells[CURRENT_AUTOREPEAT_SPELL] && + m_currentSpells[CURRENT_AUTOREPEAT_SPELL]->GetSpellInfo()->Id != 75) + InterruptSpell(CURRENT_AUTOREPEAT_SPELL); + AddUnitState(UNIT_STATE_CASTING); + + break; + } + case CURRENT_AUTOREPEAT_SPELL: + { + if (m_currentSpells[CSpellType] && m_currentSpells[CSpellType]->getState() == SPELL_STATE_IDLE) + m_currentSpells[CSpellType]->setState(SPELL_STATE_FINISHED); + + // only Auto Shoot does not break anything + if (spell->GetSpellInfo()->Id != 75) + { + // generic autorepeats break generic non-delayed and channeled non-delayed spells + InterruptSpell(CURRENT_GENERIC_SPELL, false); + InterruptSpell(CURRENT_CHANNELED_SPELL, false); + } + break; + } + default: + break; // other spell types don't break anything now + } + + // current spell (if it is still here) may be safely deleted now + if (m_currentSpells[CSpellType]) + m_currentSpells[CSpellType]->SetReferencedFromCurrent(false); + + // set new current spell + m_currentSpells[CSpellType] = spell; + spell->SetReferencedFromCurrent(true); + + spell->m_selfContainer = &(m_currentSpells[spell->GetCurrentContainer()]); +} + +void Unit::InterruptSpell(CurrentSpellTypes spellType, bool withDelayed, bool withInstant) +{ + Spell* spell = m_currentSpells[spellType]; + if (!spell) + return; + + if ((withDelayed || spell->getState() != SPELL_STATE_LAUNCHED) && + (withInstant || spell->GetCastTime() > 0 || spell->getState() == SPELL_STATE_CHANNELING)) + { + // for example, do not let self-stun aura interrupt itself + if (!spell->IsInterruptable()) + return; + + // send autorepeat cancel message for autorepeat spells + if (spellType == CURRENT_AUTOREPEAT_SPELL) + if (GetTypeId() == TYPEID_PLAYER) + ToPlayer()->SendAutoRepeatCancel(this); + + if (spell->getState() != SPELL_STATE_FINISHED) + spell->cancel(); + else + { + m_currentSpells[spellType] = nullptr; + spell->SetReferencedFromCurrent(false); + } + + if (GetTypeId() == TYPEID_UNIT && IsAIEnabled()) + ToCreature()->AI()->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_LAUNCHED)) + { + if (!skipInstant || m_currentSpells[CURRENT_GENERIC_SPELL]->GetCastTime()) + { + if (!isAutoshoot || !(m_currentSpells[CURRENT_GENERIC_SPELL]->m_spellInfo->HasAttribute(SPELL_ATTR2_DO_NOT_RESET_COMBAT_TIMERS))) + return true; + } + } + // channeled spells may be delayed, but they are still considered cast + if (!skipChanneled && m_currentSpells[CURRENT_CHANNELED_SPELL] && + (m_currentSpells[CURRENT_CHANNELED_SPELL]->getState() != SPELL_STATE_FINISHED)) + { + if (!isAutoshoot || !(m_currentSpells[CURRENT_CHANNELED_SPELL]->m_spellInfo->HasAttribute(SPELL_ATTR2_DO_NOT_RESET_COMBAT_TIMERS))) + return true; + } + // autorepeat spells may be finished or delayed, but they are still considered cast + if (!skipAutorepeat && m_currentSpells[CURRENT_AUTOREPEAT_SPELL]) + return true; + + return false; +} + +void Unit::InterruptNonMeleeSpells(bool withDelayed, uint32 spell_id, bool withInstant /* = true */) +{ + // generic spells are interrupted if they are not finished or delayed + if (m_currentSpells[CURRENT_GENERIC_SPELL] && (!spell_id || m_currentSpells[CURRENT_GENERIC_SPELL]->m_spellInfo->Id == spell_id)) + InterruptSpell(CURRENT_GENERIC_SPELL, withDelayed, withInstant); + + // autorepeat spells are interrupted if they are not finished or delayed + if (m_currentSpells[CURRENT_AUTOREPEAT_SPELL] && (!spell_id || m_currentSpells[CURRENT_AUTOREPEAT_SPELL]->m_spellInfo->Id == spell_id)) + InterruptSpell(CURRENT_AUTOREPEAT_SPELL, withDelayed, withInstant); + + // channeled spells are interrupted if they are not finished, even if they are delayed + if (m_currentSpells[CURRENT_CHANNELED_SPELL] && (!spell_id || m_currentSpells[CURRENT_CHANNELED_SPELL]->m_spellInfo->Id == spell_id)) + InterruptSpell(CURRENT_CHANNELED_SPELL, true, true); +} + +Spell* Unit::FindCurrentSpellBySpellId(uint32 spell_id) const +{ + for (Spell* spell : m_currentSpells) + if (spell && spell->m_spellInfo->Id == spell_id) + return spell; + return nullptr; +} + +int32 Unit::GetCurrentSpellCastTime(uint32 spell_id) const +{ + if (Spell const* spell = FindCurrentSpellBySpellId(spell_id)) + return spell->GetCastTime(); + return 0; +} + +bool Unit::IsMovementPreventedByCasting() const +{ + // can always move when not casting + if (!HasUnitState(UNIT_STATE_CASTING)) + return false; + + if (Spell* spell = m_currentSpells[CURRENT_GENERIC_SPELL]) + if (HasAuraTypeWithAffectMask(SPELL_AURA_CAST_WHILE_WALKING, spell->GetSpellInfo()) || spell->getState() == SPELL_STATE_FINISHED || + !spell->m_spellInfo->InterruptFlags.HasFlag(SpellInterruptFlags::Movement)) + return false; + + // channeled spells during channel stage (after the initial cast timer) allow movement with a specific spell attribute + if (Spell* spell = m_currentSpells[CURRENT_CHANNELED_SPELL]) + if (spell->getState() != SPELL_STATE_FINISHED && spell->IsChannelActive()) + if (spell->GetSpellInfo()->IsMoveAllowedChannel() || HasAuraTypeWithAffectMask(SPELL_AURA_CAST_WHILE_WALKING, spell->GetSpellInfo())) + 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 * float(M_PI) - arc, target); +} + +bool Unit::isInAccessiblePlaceFor(Creature const* c) const +{ + // Aquatic creatures are not allowed to leave liquids + if (!IsInWater() && c->IsAquatic()) + return false; + + // Underwater special case. Some creatures may not go below liquid surfaces + if (IsUnderWater() && c->CannotPenetrateWater()) + return false; + + // Water checks + if (IsInWater() && !c->CanEnterWater()) + return false; + + // Some creatures are tied to the ocean floor and cannot chase swimming targets. + if (!IsOnOceanFloor() && c->IsUnderWater() && c->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_CANT_SWIM)) + return false; + + return true; +} + +bool Unit::IsInWater() const +{ + return GetLiquidStatus() & (LIQUID_MAP_IN_WATER | LIQUID_MAP_UNDER_WATER); +} + +bool Unit::IsUnderWater() const +{ + return GetLiquidStatus() & LIQUID_MAP_UNDER_WATER; +} + +bool Unit::IsOnOceanFloor() const +{ + return GetLiquidStatus() & LIQUID_MAP_OCEAN_FLOOR; +} + +void Unit::ProcessPositionDataChanged(PositionFullTerrainStatus const& data) +{ + ZLiquidStatus oldLiquidStatus = GetLiquidStatus(); + WorldObject::ProcessPositionDataChanged(data); + ProcessTerrainStatusUpdate(oldLiquidStatus, data.liquidInfo); + + if (oldLiquidStatus != GetLiquidStatus()) + UpdateMountCapability(); +} + +void Unit::ProcessTerrainStatusUpdate(ZLiquidStatus /*oldLiquidStatus*/, Optional const& newLiquidData) +{ + if (!IsControlledByPlayer()) + return; + + // remove appropriate auras if we are swimming/not swimming respectively + if (IsInWater()) + RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::UnderWater); + else + RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::AboveWater); + + // liquid aura handling + LiquidTypeEntry const* curLiquid = nullptr; + if (IsInWater() && newLiquidData) + curLiquid = sLiquidTypeStore.LookupEntry(newLiquidData->entry); + if (curLiquid != _lastLiquid) + { + if (_lastLiquid && _lastLiquid->SpellID) + RemoveAurasDueToSpell(_lastLiquid->SpellID); + Player* player = GetCharmerOrOwnerPlayerOrPlayerItself(); + + // Set _lastLiquid before casting liquid spell to avoid infinite loops + _lastLiquid = curLiquid; + + if (curLiquid && curLiquid->SpellID && (!player || !player->IsGameMaster())) + CastSpell(this, curLiquid->SpellID, true); + } +} +void Unit::DeMorph() +{ + SetDisplayId(GetNativeDisplayId()); +} + +Aura* Unit::_TryStackingOrRefreshingExistingAura(AuraCreateInfo& createInfo) +{ + ASSERT(createInfo.CasterGUID || createInfo.Caster); + + // Check if these can stack anyway + if (!createInfo.CasterGUID && !createInfo.GetSpellInfo()->IsStackableOnOneSlotWithDifferentCasters()) + createInfo.CasterGUID = createInfo.Caster->GetGUID(); + + // world gameobjects can't own auras and they send empty casterguid + // checked on sniffs with spell 22247 + if (createInfo.CasterGUID.IsGameObject()) + { + createInfo.Caster = nullptr; + createInfo.CasterGUID.Clear(); + } + + // passive and Incanter's Absorption and auras with different type can stack with themselves any number of times + if (!createInfo.GetSpellInfo()->IsMultiSlotAura()) + { + // check if cast item changed + ObjectGuid castItemGUID = createInfo.CastItemGUID; + + // find current aura from spell and change it's stackamount, or refresh it's duration + if (Aura* foundAura = GetOwnedAura(createInfo.GetSpellInfo()->Id, createInfo.GetSpellInfo()->IsStackableOnOneSlotWithDifferentCasters() ? ObjectGuid::Empty : createInfo.CasterGUID, createInfo.GetSpellInfo()->HasAttribute(SPELL_ATTR0_CU_ENCHANT_PROC) ? castItemGUID : ObjectGuid::Empty)) + { + // effect masks do not match + // extremely rare case + // let's just recreate aura + if (createInfo.GetAuraEffectMask() != foundAura->GetEffectMask()) + return nullptr; + + // update basepoints with new values - effect amount will be recalculated in ModStackAmount + for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) + { + if (!foundAura->HasEffect(i)) + continue; + + int32 bp = foundAura->GetSpellInfo()->Effects[i].BasePoints; + if (createInfo.BaseAmount) + bp = *(createInfo.BaseAmount + i); + + 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, AuraRemoveFlags::ByDefault, createInfo.ResetPeriodicTimer); + return foundAura; + } + } + + return nullptr; +} + +void Unit::_AddAura(UnitAura* aura, Unit* caster) +{ + ASSERT(!m_cleanupDone); + m_ownedAuras.emplace(aura->GetId(), aura); + + _RemoveNoStackAurasDueToAura(aura, true); + + if (aura->IsRemoved()) + return; + + aura->SetIsSingleTarget(caster && aura->GetSpellInfo()->IsSingleTarget()); + if (aura->IsSingleTarget()) + { + ASSERT((IsInWorld() && !IsDuringRemoveFromWorld()) || aura->GetCasterGUID() == GetGUID()); + /* @HACK: Player is not in world during loading auras. + * Single target auras are not saved or loaded from database + * but may be created as a result of aura links. + */ + + std::vector aurasSharingLimit; + // remove other single target auras + for (Aura* scAura : caster->GetSingleCastAuras()) + if (scAura->IsSingleTargetWith(aura)) + aurasSharingLimit.push_back(scAura); + + // register single target aura + caster->GetSingleCastAuras().push_front(aura); + + uint32 maxOtherAuras = aura->GetSpellInfo()->MaxAffectedTargets - 1; + while (aurasSharingLimit.size() > maxOtherAuras) + { + aurasSharingLimit.back()->Remove(); + aurasSharingLimit.pop_back(); + } + } +} + +// creates aura application instance and registers it in lists +// aura application effects are handled separately to prevent aura list corruption +AuraApplication* Unit::_CreateAuraApplication(Aura* aura, uint8 effMask) +{ + // can't apply aura on unit which is going to be deleted - to not create a memory leak + ASSERT(!m_cleanupDone); + + // just return if the aura has been already removed + // this can happen if OnEffectHitTarget() script hook killed the unit or the aura owner (which can be different) + if (aura->IsRemoved()) + { + TC_LOG_ERROR("spells", "Unit::_CreateAuraApplication() called with a removed aura. Check if OnEffectHitTarget() is triggering any spell with apply aura effect (that's not allowed!)"); + return nullptr; + } + + // aura mustn't be already applied on target + ASSERT (!aura->IsAppliedOnTarget(GetGUID()) && "Unit::_CreateAuraApplication: aura musn't be applied on target"); + + SpellInfo const* aurSpellInfo = aura->GetSpellInfo(); + uint32 aurId = aurSpellInfo->Id; + + // ghost spell check, allow apply any auras at player loading in ghost mode (will be cleanup after load) + if (!IsAlive() && !aurSpellInfo->IsDeathPersistent() && + (GetTypeId() != TYPEID_PLAYER || !ToPlayer()->GetSession()->PlayerLoading())) + return nullptr; + + Unit* caster = aura->GetCaster(); + + AuraApplication * aurApp = new AuraApplication(this, caster, aura, effMask); + m_appliedAuras.insert(AuraApplicationMap::value_type(aurId, aurApp)); + + if (aurSpellInfo->HasAnyAuraInterruptFlag()) + { + m_interruptableAuras.push_front(aurApp); + AddInterruptMask(aurSpellInfo->AuraInterruptFlags, aurSpellInfo->AuraInterruptFlags2); + } + + if (AuraStateType aState = aura->GetSpellInfo()->GetAuraState()) + m_auraStateAuras.insert(AuraStateAurasMap::value_type(aState, aurApp)); + + aura->_ApplyForTarget(this, caster, aurApp); + return aurApp; +} + +void Unit::_ApplyAuraEffect(Aura* aura, uint8 effIndex) +{ + ASSERT(aura); + ASSERT(aura->HasEffect(effIndex)); + AuraApplication * aurApp = aura->GetApplicationOfTarget(GetGUID()); + ASSERT(aurApp); + if (!aurApp->GetEffectMask()) + _ApplyAura(aurApp, 1 << effIndex); + else + aurApp->_HandleEffect(effIndex, true); +} + +// handles effects of aura application +// should be done after registering aura in lists +void Unit::_ApplyAura(AuraApplication* aurApp, uint8 effMask) +{ + Aura* aura = aurApp->GetBase(); + + _RemoveNoStackAurasDueToAura(aura, false); + + if (aurApp->GetRemoveMode().HasAnyFlag()) + return; + + // Update target aura state flag + if (AuraStateType aState = aura->GetSpellInfo()->GetAuraState()) + ModifyAuraState(aState, true); + + if (aurApp->GetRemoveMode().HasAnyFlag()) + return; + + // Sitdown on apply aura req seated + if (aura->GetSpellInfo()->HasAuraInterruptFlag(SpellAuraInterruptFlags::Standing) && !IsSitState()) + SetStandState(UNIT_STAND_STATE_SIT); + + Unit* caster = aura->GetCaster(); + + if (aurApp->GetRemoveMode().HasAnyFlag()) + 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().HasAnyFlag()) + aurApp->_HandleEffect(i, true); + } + + if (Player* player = ToPlayer()) + { + if (sConditionMgr->IsSpellUsedInSpellClickConditions(aurApp->GetBase()->GetId())) + player->UpdateVisibleGameobjectsOrSpellClicks(); + + player->FailAchievementCriteria(AchievementCriteriaFailEvent::GainAura, aurApp->GetBase()->GetId()); + player->StartAchievementCriteria(AchievementCriteriaStartEvent::GainAura, aurApp->GetBase()->GetId()); + player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_BE_SPELL_TARGET2, aurApp->GetBase()->GetId()); + } +} + +// removes aura application from lists and unapplies effects +void Unit::_UnapplyAura(AuraApplicationMap::iterator& i, AuraRemoveFlags removeMode) +{ + AuraApplication * aurApp = i->second; + ASSERT(aurApp); + ASSERT(!aurApp->GetRemoveMode().HasAnyFlag()); + ASSERT(aurApp->GetTarget() == this); + + aurApp->SetRemoveMode(removeMode); + Aura* aura = aurApp->GetBase(); + TC_LOG_DEBUG("spells", "Aura %u now is remove mode %u", aura->GetId(), static_cast(removeMode)); + + // dead loop is killing the server probably + ASSERT(m_removedAurasCount < 0xFFFFFFFF); + + ++m_removedAurasCount; + + Unit* caster = aura->GetCaster(); + + // Remove all pointers from lists here to prevent possible pointer invalidation on spellcast/auraapply/auraremove + m_appliedAuras.erase(i); + + if (aura->GetSpellInfo()->HasAnyAuraInterruptFlag()) + { + Trinity::Containers::Lists::RemoveUnique(m_interruptableAuras, aurApp); + UpdateInterruptMask(); + } + + bool auraStateFound = false; + AuraStateType auraState = aura->GetSpellInfo()->GetAuraState(); + if (auraState) + { + bool canBreak = false; + // Get mask of all aurastates from remaining auras + for (AuraStateAurasMap::iterator itr = m_auraStateAuras.lower_bound(auraState); itr != m_auraStateAuras.upper_bound(auraState) && !(auraStateFound && canBreak);) + { + if (itr->second == aurApp) + { + m_auraStateAuras.erase(itr); + itr = m_auraStateAuras.lower_bound(auraState); + canBreak = true; + continue; + } + auraStateFound = true; + ++itr; + } + } + + aurApp->_Remove(); + aura->_UnapplyForTarget(this, caster, aurApp); + + // remove effects of the spell - needs to be done after removing aura from lists + for (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().HasFlag(AuraRemoveFlags::Expired) && GetTypeId() == TYPEID_UNIT && IsTotem()) + { + if (ToTotem()->GetSpell() == aura->GetId() && ToTotem()->GetTotemType() == TOTEM_PASSIVE) + ToTotem()->setDeathState(JUST_DIED); + } + + // Remove aurastates only if were not found + if (!auraStateFound) + ModifyAuraState(auraState, false); + + aura->HandleAuraSpecificMods(aurApp, caster, false, false); + + if (Player* player = ToPlayer()) + { + if (sConditionMgr->IsSpellUsedInSpellClickConditions(aurApp->GetBase()->GetId())) + player->UpdateVisibleGameobjectsOrSpellClicks(); + + player->FailAchievementCriteria(AchievementCriteriaFailEvent::LoseAura, aurApp->GetBase()->GetId()); + } + + i = m_appliedAuras.begin(); +} + +void Unit::_UnapplyAura(AuraApplication* aurApp, AuraRemoveFlags removeMode) +{ + // aura can be removed from unit only if it's applied on it, shouldn't happen + ASSERT(aurApp->GetBase()->GetApplicationOfTarget(GetGUID()) == aurApp); + + uint32 spellId = aurApp->GetBase()->GetId(); + AuraApplicationMapBoundsNonConst range = m_appliedAuras.equal_range(spellId); + + for (AuraApplicationMap::iterator iter = range.first; iter != range.second;) + { + if (iter->second == aurApp) + { + _UnapplyAura(iter, removeMode); + return; + } + else + ++iter; + } + ABORT(); +} + +void Unit::_RemoveNoStackAurasDueToAura(Aura* aura, bool owned) +{ + SpellInfo const* spellProto = aura->GetSpellInfo(); + + // passive spell special case (only non stackable with ranks) + if (spellProto->IsPassiveStackableWithRanks()) + return; + + if (!IsHighestExclusiveAura(aura)) + { + aura->Remove(); + return; + } + + if (owned) + RemoveOwnedAuras([aura](Aura const* ownedAura) { return !aura->CanStackWith(ownedAura); }, AuraRemoveFlags::ByDefault); + else + RemoveAppliedAuras([aura](AuraApplication const* appliedAura) { return !aura->CanStackWith(appliedAura->GetBase()); }, AuraRemoveFlags::ByDefault); +} + +void Unit::_RegisterAuraEffect(AuraEffect* aurEff, bool apply) +{ + if (apply) + { + m_modAuras[aurEff->GetAuraType()].push_front(aurEff); + if (Player* player = ToPlayer()) + { + player->StartAchievementCriteria(AchievementCriteriaStartEvent::GainAuraEffect, aurEff->GetAuraType()); + player->FailAchievementCriteria(AchievementCriteriaFailEvent::GainAuraEffect, aurEff->GetAuraType()); + } + } + else + Trinity::Containers::Lists::RemoveUnique(m_modAuras[aurEff->GetAuraType()], aurEff); +} + +// All aura base removes should go through this function! +void Unit::RemoveOwnedAura(AuraMap::iterator& i, AuraRemoveFlags removeMode) +{ + Aura* aura = i->second; + ASSERT(!aura->IsRemoved()); + + // if unit currently update aura list then make safe update iterator shift to next + if (m_auraUpdateIterator == i) + ++m_auraUpdateIterator; + + m_ownedAuras.erase(i); + m_removedAuras.push_front(aura); + + // Unregister single target aura + if (aura->IsSingleTarget()) + aura->UnregisterSingleTarget(); + + aura->_Remove(removeMode); + + i = m_ownedAuras.begin(); +} + +void Unit::RemoveOwnedAura(uint32 spellId, ObjectGuid casterGUID, uint8 reqEffMask, AuraRemoveFlags 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, AuraRemoveFlags removeMode) +{ + if (aura->IsRemoved()) + return; + + ASSERT(aura->GetOwner() == this); + + if (removeMode == AuraRemoveFlags::None) + { + TC_LOG_ERROR("spells", "Unit::RemoveOwnedAura() called with unallowed removeMode AuraRemoveFlags::None, spellId %u", aura->GetId()); + return; + } + + uint32 spellId = aura->GetId(); + AuraMapBoundsNonConst range = m_ownedAuras.equal_range(spellId); + + for (AuraMap::iterator itr = range.first; itr != range.second; ++itr) + { + if (itr->second == aura) + { + RemoveOwnedAura(itr, removeMode); + return; + } + } + + ABORT(); +} + +Aura* Unit::GetOwnedAura(uint32 spellId, ObjectGuid casterGUID, ObjectGuid itemCasterGUID, 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, AuraRemoveFlags mode) +{ + AuraApplication * aurApp = i->second; + // Do not remove aura which is already being removed + if (aurApp->GetRemoveMode().HasAnyFlag()) + 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, AuraRemoveFlags 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, AuraRemoveFlags 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().HasAnyFlag()) + { + // 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) + { + RemoveAura(iter, mode); + return; + } + else + ++iter; + } +} + +void Unit::RemoveAura(Aura* aura, AuraRemoveFlags mode) +{ + if (aura->IsRemoved()) + return; + if (AuraApplication * aurApp = aura->GetApplicationOfTarget(GetGUID())) + RemoveAura(aurApp, mode); +} + +void Unit::RemoveAppliedAuras(std::function const& check, AuraRemoveFlags removeMode /* = AuraRemoveFlags::ByDefault*/) +{ + for (AuraApplicationMap::iterator iter = m_appliedAuras.begin(); iter != m_appliedAuras.end();) + { + if (check(iter->second)) + { + RemoveAura(iter, removeMode); + continue; + } + ++iter; + } +} + +void Unit::RemoveOwnedAuras(std::function const& check, AuraRemoveFlags removeMode /* = AuraRemoveFlags::ByDefault*/) +{ + for (AuraMap::iterator iter = m_ownedAuras.begin(); iter != m_ownedAuras.end();) + { + if (check(iter->second)) + { + RemoveOwnedAura(iter, removeMode); + continue; + } + ++iter; + } +} + +void Unit::RemoveAppliedAuras(uint32 spellId, std::function const& check, AuraRemoveFlags removeMode /* = AuraRemoveFlags::ByDefault*/) +{ + for (AuraApplicationMap::iterator iter = m_appliedAuras.lower_bound(spellId); iter != m_appliedAuras.upper_bound(spellId);) + { + if (check(iter->second)) + { + RemoveAura(iter, removeMode); + iter = m_appliedAuras.lower_bound(spellId); + continue; + } + ++iter; + } +} + +void Unit::RemoveOwnedAuras(uint32 spellId, std::function const& check, AuraRemoveFlags removeMode /* = AuraRemoveFlags::ByDefault*/) +{ + for (AuraMap::iterator iter = m_ownedAuras.lower_bound(spellId); iter != m_ownedAuras.upper_bound(spellId);) + { + if (check(iter->second)) + { + RemoveOwnedAura(iter, removeMode); + iter = m_ownedAuras.lower_bound(spellId); + continue; + } + ++iter; + } +} + +void Unit::RemoveAurasByType(AuraType auraType, std::function const& check, AuraRemoveFlags removeMode /* = AuraRemoveFlags::ByDefault*/) +{ + for (AuraEffectList::iterator iter = m_modAuras[auraType].begin(); iter != m_modAuras[auraType].end();) + { + Aura* aura = (*iter)->GetBase(); + AuraApplication * aurApp = aura->GetApplicationOfTarget(GetGUID()); + ASSERT(aurApp); + + ++iter; + if (check(aurApp)) + { + uint32 removedAuras = m_removedAurasCount; + RemoveAura(aurApp, removeMode); + if (m_removedAurasCount > removedAuras + 1) + iter = m_modAuras[auraType].begin(); + } + } +} + +void Unit::RemoveAurasDueToSpell(uint32 spellId, ObjectGuid casterGUID, uint8 reqEffMask, AuraRemoveFlags 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, AuraRemoveFlags 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, WorldObject* dispeller, uint8 chargesRemoved/*= 1*/) +{ + AuraMapBoundsNonConst range = m_ownedAuras.equal_range(spellId); + for (AuraMap::iterator iter = range.first; iter != range.second;) + { + Aura* aura = iter->second; + if (aura->GetCasterGUID() == casterGUID) + { + DispelInfo dispelInfo(dispeller, dispellerSpellId, chargesRemoved); + + // Call OnDispel hook on AuraScript + aura->CallScriptDispel(&dispelInfo); + + if (aura->GetSpellInfo()->HasAttribute(SPELL_ATTR7_DISPEL_REMOVES_CHARGES)) + aura->ModCharges(-dispelInfo.GetRemovedCharges(), AuraRemoveFlags::ByEnemySpell); + else + aura->ModStackAmount(-dispelInfo.GetRemovedCharges(), AuraRemoveFlags::ByEnemySpell); + + // Call AfterDispel hook on AuraScript + aura->CallScriptAfterDispel(&dispelInfo); + + return; + } + else + ++iter; + } +} + +void Unit::RemoveAurasDueToSpellBySteal(uint32 spellId, ObjectGuid casterGUID, WorldObject* stealer, int32 stolenCharges /*= 1*/) +{ + AuraMapBoundsNonConst range = m_ownedAuras.equal_range(spellId); + for (AuraMap::iterator iter = range.first; iter != range.second;) + { + Aura* aura = iter->second; + if (aura->GetCasterGUID() == casterGUID) + { + 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<GetEffect(i)->CanBeRecalculated()) + recalculateMask |= (1<GetSpellInfo()->HasAttribute(SPELL_ATTR7_DISPEL_REMOVES_CHARGES); + // Cast duration to unsigned to prevent permanent aura's such as Righteous Fury being permanently added to caster + uint32 dur = std::min(2u * MINUTE * IN_MILLISECONDS, uint32(aura->GetDuration())); + + if (Unit* unitStealer = stealer->ToUnit()) + { + if (Aura* oldAura = unitStealer->GetAura(aura->GetId(), aura->GetCasterGUID())) + { + if (stealCharge) + oldAura->ModCharges(stolenCharges); + else + oldAura->ModStackAmount(stolenCharges); + oldAura->SetDuration(int32(dur)); + } + else + { + // single target state must be removed before aura creation to preserve existing single target aura + if (aura->IsSingleTarget()) + aura->UnregisterSingleTarget(); + + AuraCreateInfo createInfo(aura->GetSpellInfo(), effMask, stealer); + createInfo + .SetCasterGUID(aura->GetCasterGUID()) + .SetBaseAmount(baseDamage); + + if (Aura* newAura = Aura::TryRefreshStackOrCreate(createInfo)) + { + // created aura must not be single target aura,, so stealer won't loose it on recast + if (newAura->IsSingleTarget()) + { + newAura->UnregisterSingleTarget(); + // bring back single target aura status to the old aura + aura->SetIsSingleTarget(true); + caster->GetSingleCastAuras().push_front(aura); + } + // FIXME: using aura->GetMaxDuration() maybe not blizzlike but it fixes stealing of spells like Innervate + newAura->SetLoadedState(aura->GetMaxDuration(), int32(dur), stealCharge ? stolenCharges : aura->GetCharges(), stolenCharges, recalculateMask, aura->GetCritChance(), aura->CanApplyResilience(), &damage[0]); + newAura->ApplyForTargets(); + } + } + } + + if (stealCharge) + aura->ModCharges(-stolenCharges, AuraRemoveFlags::ByEnemySpell); + else + aura->ModStackAmount(-stolenCharges, AuraRemoveFlags::ByEnemySpell); + + return; + } + else + ++iter; + } +} + +void Unit::RemoveAurasDueToItemSpell(uint32 spellId, ObjectGuid castItemGuid) +{ + for (AuraApplicationMap::iterator iter = m_appliedAuras.lower_bound(spellId); iter != m_appliedAuras.upper_bound(spellId);) + { + if (iter->second->GetBase()->GetCastItemGUID() == castItemGuid) + { + RemoveAura(iter); + iter = m_appliedAuras.lower_bound(spellId); + } + else + ++iter; + } +} + +void Unit::RemoveAurasByType(AuraType auraType, ObjectGuid casterGUID, Aura* except, bool negative, bool positive) +{ + for (AuraEffectList::iterator iter = m_modAuras[auraType].begin(); iter != m_modAuras[auraType].end();) + { + Aura* aura = (*iter)->GetBase(); + AuraApplication * aurApp = aura->GetApplicationOfTarget(GetGUID()); + ASSERT(aurApp); + + ++iter; + if (aura != except && (!casterGUID || aura->GetCasterGUID() == casterGUID) + && ((negative && !aurApp->IsPositive()) || (positive && aurApp->IsPositive()))) + { + uint32 removedAuras = m_removedAurasCount; + RemoveAura(aurApp); + if (m_removedAurasCount > removedAuras + 1) + iter = m_modAuras[auraType].begin(); + } + } +} + +void Unit::RemoveAurasWithAttribute(uint32 flags) +{ + for (AuraApplicationMap::iterator iter = m_appliedAuras.begin(); iter != m_appliedAuras.end();) + { + SpellInfo const* spell = iter->second->GetBase()->GetSpellInfo(); + if (spell->Attributes & flags) + RemoveAura(iter); + else + ++iter; + } +} + +void Unit::RemoveNotOwnSingleTargetAuras(bool onPhaseChange /*= false*/) +{ + // single target auras from other casters + // Iterate m_ownedAuras - aura is marked as single target in Unit::AddAura (and pushed to m_ownedAuras). + // m_appliedAuras will NOT contain the aura before first Unit::Update after adding it to m_ownedAuras. + // Quickly removing such an aura will lead to it not being unregistered from caster's single cast auras container + // leading to assertion failures if the aura was cast on a player that can + // (and is changing map at the point where this function is called). + // Such situation occurs when player is logging in inside an instance and fails the entry check for any reason. + // The aura that was loaded from db (indirectly, via linked casts) gets removed before it has a chance + // to register in m_appliedAuras + for (AuraMap::iterator iter = m_ownedAuras.begin(); iter != m_ownedAuras.end();) + { + Aura const* aura = iter->second; + + if (aura->GetCasterGUID() != GetGUID() && aura->IsSingleTarget()) + { + if (!onPhaseChange) + RemoveOwnedAura(iter); + else + { + Unit* caster = aura->GetCaster(); + if (!caster || !caster->IsInPhase(this)) + RemoveOwnedAura(iter); + else + ++iter; + } + } + else + ++iter; + } + + // single target auras at other targets + AuraList& scAuras = GetSingleCastAuras(); + for (AuraList::iterator iter = scAuras.begin(); iter != scAuras.end();) + { + Aura* aura = *iter; + if (aura->GetUnitOwner() != this && (!onPhaseChange || !aura->GetUnitOwner()->IsInPhase(this))) + { + aura->Remove(); + iter = scAuras.begin(); + } + else + ++iter; + } +} + +template +bool IsInterruptFlagIgnoredForSpell(InterruptFlag /*flag*/, Unit const* /*unit*/, SpellInfo const* /*auraSpellInfo*/, SpellInfo const* /*interruptSource*/) +{ + return false; +} + +bool IsInterruptFlagIgnoredForSpell(SpellAuraInterruptFlags flag, Unit const* unit, SpellInfo const* auraSpellInfo, SpellInfo const* interruptSource) +{ + switch (flag) + { + case SpellAuraInterruptFlags::Moving: + return unit->HasAuraTypeWithAffectMask(SPELL_AURA_CAST_WHILE_WALKING, auraSpellInfo); + case SpellAuraInterruptFlags::Action: + case SpellAuraInterruptFlags::ActionDelayed: + if (interruptSource) + { + if (interruptSource->HasAttribute(SPELL_ATTR1_ALLOW_WHILE_STEALTHED) && auraSpellInfo->Dispel == DISPEL_STEALTH) + return true; + + if (interruptSource->HasAttribute(SPELL_ATTR2_ALLOW_WHILE_INVISIBLE) && auraSpellInfo->Dispel == DISPEL_INVISIBILITY) + return true; + } + break; + default: + break; + } + + return false; +} + +template +void Unit::RemoveAurasWithInterruptFlags(InterruptFlags flag, SpellInfo const* source) +{ + if (!HasInterruptFlag(flag)) + return; + + // interrupt auras + for (AuraApplicationList::iterator iter = m_interruptableAuras.begin(); iter != m_interruptableAuras.end();) + { + Aura* aura = (*iter)->GetBase(); + ++iter; + if (aura->GetSpellInfo()->HasAuraInterruptFlag(flag) + && (!source || aura->GetId() != source->Id) + && !IsInterruptFlagIgnoredForSpell(flag, this, aura->GetSpellInfo(), source)) + { + 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_CHANNELING + && spell->GetSpellInfo()->HasChannelInterruptFlag(flag) + && (!source || spell->GetSpellInfo()->Id != source->Id) + && !IsInterruptFlagIgnoredForSpell(flag, this, spell->GetSpellInfo(), source)) + InterruptNonMeleeSpells(false, 0, true); + + UpdateInterruptMask(); +} + +template TC_GAME_API void Unit::RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags flag, SpellInfo const* source); +template TC_GAME_API void Unit::RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags2 flag, SpellInfo const* source); + +void Unit::RemoveAurasWithFamily(SpellFamilyNames family, 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, AuraRemoveFlags::ByDefault, 0, true); + + RemoveAurasWithMechanic(1 << MECHANIC_ROOT, AuraRemoveFlags::ByDefault, 0, false); +} + +void Unit::RemoveAurasWithMechanic(uint32 mechanicMaskToRemove, AuraRemoveFlags removeMode, uint32 exceptSpellId, bool withEffectMechanics) +{ + std::vector aurasToUpdateTargets; + RemoveAppliedAuras([=, &aurasToUpdateTargets](AuraApplication const* aurApp) + { + Aura* aura = aurApp->GetBase(); + if (exceptSpellId && aura->GetId() == exceptSpellId) + return false; + + uint32 appliedMechanicMask = aura->GetSpellInfo()->GetSpellMechanicMaskByEffectMask(aurApp->GetEffectMask()); + if (!(appliedMechanicMask & mechanicMaskToRemove)) + return false; + + // spell mechanic matches required mask for removal + if ((1 << aura->GetSpellInfo()->Mechanic) & mechanicMaskToRemove || withEffectMechanics) + return true; + + // effect mechanic matches required mask for removal - don't remove, only update targets + aurasToUpdateTargets.push_back(aura); + return false; + }, removeMode); + + for (Aura* aura : aurasToUpdateTargets) + { + aura->UpdateTargetMap(aura->GetCaster()); + + // Fully remove the aura if all effects were removed + if (!aura->IsPassive() && aura->GetOwner() == this && !aura->GetApplicationOfTarget(GetGUID())) + aura->Remove(removeMode); + } +} + +void Unit::RemoveAurasByShapeShift() +{ + 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)) + { + RemoveAura(iter); + continue; + } + ++iter; + } +} + +void Unit::RemoveAreaAurasDueToLeaveWorld() +{ + // make sure that all area auras not applied on self are removed - prevent access to deleted pointer later + for (AuraMap::iterator iter = m_ownedAuras.begin(); iter != m_ownedAuras.end();) + { + Aura* aura = iter->second; + ++iter; + Aura::ApplicationMap const& appMap = aura->GetApplicationMap(); + for (Aura::ApplicationMap::const_iterator itr = appMap.begin(); itr!= appMap.end();) + { + AuraApplication * aurApp = itr->second; + ++itr; + Unit* target = aurApp->GetTarget(); + if (target == this) + continue; + target->RemoveAura(aurApp); + // things linked on aura remove may apply new area aura - so start from the beginning + iter = m_ownedAuras.begin(); + } + } + + // remove area auras owned by others + for (AuraApplicationMap::iterator iter = m_appliedAuras.begin(); iter != m_appliedAuras.end();) + { + if (iter->second->GetBase()->GetOwner() != this) + { + RemoveAura(iter); + } + else + ++iter; + } +} + +void Unit::RemoveAllAuras() +{ + // this may be a dead loop if some events on aura remove will continiously apply aura on remove + // we want to have all auras removed, so use your brain when linking events + for (uint32 counter = 0; !m_appliedAuras.empty() || !m_ownedAuras.empty(); counter++) + { + AuraApplicationMap::iterator aurAppIter; + for (aurAppIter = m_appliedAuras.begin(); aurAppIter != m_appliedAuras.end();) + _UnapplyAura(aurAppIter, AuraRemoveFlags::ByDefault); + + AuraMap::iterator aurIter; + for (aurIter = m_ownedAuras.begin(); aurIter != m_ownedAuras.end();) + RemoveOwnedAura(aurIter); + + const int maxIteration = 50; + // give this loop a few tries, if there are still auras then log as much information as possible + if (counter >= maxIteration) + { + std::stringstream sstr; + sstr << "Unit::RemoveAllAuras() iterated " << maxIteration << " times already but there are still " + << m_appliedAuras.size() << " m_appliedAuras and " << m_ownedAuras.size() << " m_ownedAuras.\n"; + + TC_LOG_ERROR("entities.unit", "%s", sstr.str().c_str()); + ASSERT(false); + + break; + } + } +} + +void Unit::RemoveArenaAuras() +{ + // in join, remove positive buffs, on end, remove negative + // used to remove positive visible auras in arenas + RemoveAppliedAuras([](AuraApplication const* aurApp) + { + Aura const* aura = aurApp->GetBase(); + return (!aura->GetSpellInfo()->HasAttribute(SPELL_ATTR4_ALLOW_ENTERING_ARENA) // don't remove stances, shadowform, pally/hunter auras + && !aura->IsPassive() // don't remove passive auras + && (aurApp->IsPositive() || !aura->GetSpellInfo()->HasAttribute(SPELL_ATTR3_ALLOW_AURA_WHILE_DEAD))) || // not negative death persistent auras + aura->GetSpellInfo()->HasAttribute(SPELL_ATTR5_REMOVE_ENTERING_ARENA); // special marker, always remove + }); +} + +void Unit::RemoveAurasOnEvade() +{ + if (IsCharmedOwnedByPlayerOrPlayer()) // if it is a player owned creature it should not remove the aura + return; + + // don't remove vehicle auras, passengers aren't supposed to drop off the vehicle + // don't remove clone caster on evade (to be verified) + auto evadeAuraCheck = [](Aura const* aura) + { + if (aura->HasEffectType(SPELL_AURA_CONTROL_VEHICLE)) + return false; + + if (aura->HasEffectType(SPELL_AURA_CLONE_CASTER)) + return false; + + if (aura->GetSpellInfo()->HasAttribute(SPELL_ATTR1_AURA_STAYS_AFTER_COMBAT)) + return false; + + return true; + }; + + auto evadeAuraApplicationCheck = [&evadeAuraCheck](AuraApplication const* aurApp) + { + return evadeAuraCheck(aurApp->GetBase()); + }; + + RemoveAppliedAuras(evadeAuraApplicationCheck); + RemoveOwnedAuras(evadeAuraCheck); +} + +void Unit::RemoveAllAurasOnDeath() +{ + // used just after dieing to remove all visible auras + // and disable the mods for the passive ones + for (AuraApplicationMap::iterator iter = m_appliedAuras.begin(); iter != m_appliedAuras.end();) + { + Aura const* aura = iter->second->GetBase(); + if (!aura->IsPassive() && !aura->IsDeathPersistent()) + _UnapplyAura(iter, AuraRemoveFlags::ByDeath); + else + ++iter; + } + + for (AuraMap::iterator iter = m_ownedAuras.begin(); iter != m_ownedAuras.end();) + { + Aura* aura = iter->second; + if (!aura->IsPassive() && !aura->IsDeathPersistent()) + RemoveOwnedAura(iter, AuraRemoveFlags::ByDeath); + 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, AuraRemoveFlags::ByDefault); + 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, AuraRemoveFlags::ByDefault); + 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, AuraRemoveFlags::ByDefault); + } + + for (AuraMap::iterator iter = m_ownedAuras.begin(); iter != m_ownedAuras.end();) + { + Aura* aura = iter->second; + if (aura->GetSpellInfo()->HasAura(type)) + ++iter; + else + RemoveOwnedAura(iter, AuraRemoveFlags::ByDefault); + } +} + +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, AuraRemoveFlags::ByDefault); + } + + 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, AuraRemoveFlags::ByDefault); + } +} + +void Unit::RemoveAllGroupBuffsFromCaster(ObjectGuid casterGUID) +{ + for (AuraMap::iterator iter = m_ownedAuras.begin(); iter != m_ownedAuras.end();) + { + Aura* aura = iter->second; + if (aura->GetCasterGUID() == casterGUID && aura->GetSpellInfo()->IsGroupBuff()) + { + RemoveOwnedAura(iter); + continue; + } + ++iter; + } +} + +void Unit::DelayOwnedAuras(uint32 spellId, ObjectGuid caster, int32 delaytime) +{ + AuraMapBoundsNonConst range = m_ownedAuras.equal_range(spellId); + for (; range.first != range.second; ++range.first) + { + Aura* aura = range.first->second; + if (!caster || aura->GetCasterGUID() == caster) + { + if (aura->GetDuration() < delaytime) + aura->SetDuration(0); + else + aura->SetDuration(aura->GetDuration() - delaytime); + + // update for out of range group members (on 1 slot use) + aura->SetNeedClientUpdateForTargets(); + } + } +} + +void Unit::_RemoveAllAuraStatMods() +{ + for (AuraApplicationMap::iterator i = m_appliedAuras.begin(); i != m_appliedAuras.end(); ++i) + (*i).second->GetBase()->HandleAllEffects(i->second, AURA_EFFECT_HANDLE_STAT, false); +} + +void Unit::_ApplyAllAuraStatMods() +{ + for (AuraApplicationMap::iterator i = m_appliedAuras.begin(); i != m_appliedAuras.end(); ++i) + (*i).second->GetBase()->HandleAllEffects(i->second, AURA_EFFECT_HANDLE_STAT, true); +} + +bool Unit::HasSingleCastAuraOfSpell(uint32 spellId) const +{ + AuraList const& scAuras = GetSingleCastAuras(); + for (Aura const* aura : scAuras) + if (aura->GetSpellInfo()->Id == spellId) + return true; + + return false; +} + +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 == uint32(name) && !spell->SpellFamilyFlags) + 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::GetDummyAuraEffect(SpellFamilyNames name, uint32 iconId, uint8 effIndex) const +{ + return GetAuraEffect(SPELL_AURA_DUMMY, name, iconId, effIndex); +} + +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(WorldObject const* caster, uint32 dispelMask, DispelChargesList& dispelList, bool isReflect /*= false*/) const +{ + // 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); + + AuraMap const& auras = GetOwnedAuras(); + for (auto itr = auras.begin(); itr != auras.end(); ++itr) + { + Aura* aura = itr->second; + AuraApplication const* aurApp = aura->GetApplicationOfTarget(GetGUID()); + if (!aurApp) + continue; + + // don't try to remove passive auras + if (aura->IsPassive()) + continue; + + if (aura->GetSpellInfo()->GetDispelMask() & dispelMask) + { + // do not remove positive auras if friendly target + // negative auras if non-friendly + // unless we're reflecting (dispeller eliminates one of it's benefitial buffs) + if (isReflect != (aurApp->IsPositive() == IsFriendlyTo(caster))) + continue; + + // 2.4.3 Patch Notes: "Dispel effects will no longer attempt to remove effects that have 100% dispel resistance." + int32 chance = aura->CalcDispelChance(this, !IsFriendlyTo(caster)); + if (!chance) + continue; + + // The charges / stack amounts don't count towards the total number of auras that can be dispelled. + // Ie: A dispel on a target with 5 stacks of Winters Chill and a Polymorph has 1 / (1 + 1) -> 50% chance to dispell + // Polymorph instead of 1 / (5 + 1) -> 16%. + bool const dispelCharges = aura->GetSpellInfo()->HasAttribute(SPELL_ATTR7_DISPEL_REMOVES_CHARGES); + uint8 charges = dispelCharges ? aura->GetCharges() : aura->GetStackAmount(); + if (charges > 0) + dispelList.emplace_back(aura, chance, charges); + } + } +} + +bool Unit::HasAuraEffect(uint32 spellId, uint8 effIndex, ObjectGuid caster) const +{ + AuraApplicationMapBounds range = m_appliedAuras.equal_range(spellId); + for (AuraApplicationMap::const_iterator itr = range.first; itr != range.second; ++itr) + { + if (itr->second->HasEffect(effIndex) + && (!caster || itr->second->GetBase()->GetCasterGUID() == caster)) + { + return true; + } + } + return false; +} + +uint32 Unit::GetAuraCount(uint32 spellId) const +{ + uint32 count = 0; + AuraApplicationMapBounds range = m_appliedAuras.equal_range(spellId); + + for (AuraApplicationMap::const_iterator itr = range.first; itr != range.second; ++itr) + { + if (itr->second->GetBase()->GetStackAmount() == 0) + ++count; + else + count += (uint32)itr->second->GetBase()->GetStackAmount(); + } + + return count; +} + +bool Unit::HasAura(uint32 spellId, ObjectGuid casterGUID, ObjectGuid itemCasterGUID, 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 +{ + for (AuraEffect const* eff : GetAuraEffectsByType(auraType)) + if (caster == eff->GetCasterGUID()) + return true; + return false; +} + +bool Unit::HasAuraTypeWithMiscvalue(AuraType auraType, int32 miscvalue) const +{ + AuraEffectList const& mTotalAuraList = GetAuraEffectsByType(auraType); + for (AuraEffectList::const_iterator i = mTotalAuraList.begin(); i != mTotalAuraList.end(); ++i) + if (miscvalue == (*i)->GetMiscValue()) + return true; + return false; +} + +bool Unit::HasAuraTypeWithAffectMask(AuraType auraType, SpellInfo const* affectedSpell) const +{ + AuraEffectList const& mTotalAuraList = GetAuraEffectsByType(auraType); + for (AuraEffectList::const_iterator i = mTotalAuraList.begin(); i != mTotalAuraList.end(); ++i) + if ((*i)->IsAffectingSpell(affectedSpell)) + return true; + return false; +} + +bool Unit::HasAuraTypeWithValue(AuraType auraType, int32 value) const +{ + AuraEffectList const& mTotalAuraList = GetAuraEffectsByType(auraType); + for (AuraEffectList::const_iterator i = mTotalAuraList.begin(); i != mTotalAuraList.end(); ++i) + if (value == (*i)->GetAmount()) + return true; + return false; +} + +template +bool Unit::HasNegativeAuraWithInterruptFlag(InterruptFlags flag, ObjectGuid guid) const +{ + if (!HasInterruptFlag(flag)) + return false; + + for (AuraApplicationList::const_iterator iter = m_interruptableAuras.begin(); iter != m_interruptableAuras.end(); ++iter) + { + if (!(*iter)->IsPositive() && (*iter)->GetBase()->GetSpellInfo()->HasAuraInterruptFlag(flag) && (!guid || (*iter)->GetBase()->GetCasterGUID() == guid)) + return true; + } + return false; +} + +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; +} + +bool Unit::HasStrongerAuraWithDR(SpellInfo const* auraSpellInfo, Unit* caster, bool triggered) const +{ + DiminishingGroup diminishGroup = auraSpellInfo->GetDiminishingReturnsGroupForSpell(triggered); + DiminishingLevels level = GetDiminishing(diminishGroup); + for (auto itr = m_appliedAuras.begin(); itr != m_appliedAuras.end(); ++itr) + { + SpellInfo const* spellInfo = itr->second->GetBase()->GetSpellInfo(); + if (spellInfo->GetDiminishingReturnsGroupForSpell(triggered) != diminishGroup) + continue; + + int32 existingDuration = itr->second->GetBase()->GetDuration(); + int32 newDuration = auraSpellInfo->GetMaxDuration(); + ApplyDiminishingToDuration(auraSpellInfo, triggered, newDuration, caster, level); + if (newDuration > 0 && newDuration < existingDuration) + return true; + } + + return false; +} + +AuraEffect* Unit::IsScriptOverriden(SpellInfo const* spell, int32 script) const +{ + AuraEffectList const& auras = GetAuraEffectsByType(SPELL_AURA_OVERRIDE_CLASS_SCRIPTS); + for (AuraEffectList::const_iterator i = auras.begin(); i != auras.end(); ++i) + { + if ((*i)->GetMiscValue() == script) + if ((*i)->IsAffectingSpell(spell)) + return (*i); + } + return nullptr; +} + +uint32 Unit::GetDiseasesByCaster(ObjectGuid casterGUID, bool remove) +{ + static const AuraType diseaseAuraTypes[] = + { + SPELL_AURA_PERIODIC_DAMAGE, // Frost Fever and Blood Plague + SPELL_AURA_LINKED // Crypt Fever and Ebon Plague + }; + + // Burning Blood - Blood DK T12 Bonus + // "Causes owning Death Knight's abilities to treat the target as if it had 2 diseases active." + if (HasAura(98957, casterGUID)) + return 2; + + uint32 diseases = 0; + for (AuraType aType : diseaseAuraTypes) + { + for (auto itr = m_modAuras[aType].begin(); itr != m_modAuras[aType].end();) + { + // Get auras with disease dispel type by caster + if ((*itr)->GetSpellInfo()->Dispel == DISPEL_DISEASE + && (*itr)->GetCasterGUID() == casterGUID) + { + ++diseases; + + if (remove) + { + RemoveAura((*itr)->GetId(), (*itr)->GetCasterGUID()); + itr = m_modAuras[aType].begin(); + continue; + } + } + ++itr; + } + } + return diseases; +} + +uint32 Unit::GetDoTsByCaster(ObjectGuid casterGUID) const +{ + static const AuraType diseaseAuraTypes[] = + { + SPELL_AURA_PERIODIC_DAMAGE, + SPELL_AURA_PERIODIC_DAMAGE_PERCENT, + SPELL_AURA_NONE + }; + + uint32 dots = 0; + for (AuraType const* itr = &diseaseAuraTypes[0]; itr && itr[0] != SPELL_AURA_NONE; ++itr) + { + Unit::AuraEffectList const& auras = GetAuraEffectsByType(*itr); + for (AuraEffectList::const_iterator i = auras.begin(); i != auras.end(); ++i) + { + // Get auras by caster + if ((*i)->GetCasterGUID() == casterGUID) + ++dots; + } + } + return dots; +} + +int32 Unit::GetTotalAuraModifier(AuraType auraType, std::function const& predicate) const +{ + AuraEffectList const& mTotalAuraList = GetAuraEffectsByType(auraType); + if (mTotalAuraList.empty()) + return 0; + + std::map sameEffectSpellGroup; + int32 modifier = 0; + + for (AuraEffect const* aurEff : mTotalAuraList) + { + if (predicate(aurEff)) + { + // Check if the Aura Effect has a the Same Effect Stack Rule and if so, use the highest amount of that SpellGroup + // If the Aura Effect does not have this Stack Rule, it returns false so we can add to the multiplier as usual + if (!sSpellMgr->AddSameEffectStackRuleSpellGroups(aurEff->GetSpellInfo(), static_cast(auraType), aurEff->GetAmount(), sameEffectSpellGroup)) + modifier += aurEff->GetAmount(); + } + } + + // Add the highest of the Same Effect Stack Rule SpellGroups to the accumulator + for (auto itr = sameEffectSpellGroup.begin(); itr != sameEffectSpellGroup.end(); ++itr) + modifier += itr->second; + + return modifier; +} + +float Unit::GetTotalAuraMultiplier(AuraType auraType, std::function const& predicate) const +{ + AuraEffectList const& mTotalAuraList = GetAuraEffectsByType(auraType); + if (mTotalAuraList.empty()) + return 1.0f; + + std::map sameEffectSpellGroup; + float multiplier = 1.0f; + + for (AuraEffect const* aurEff : mTotalAuraList) + { + if (predicate(aurEff)) + { + // Check if the Aura Effect has a the Same Effect Stack Rule and if so, use the highest amount of that SpellGroup + // If the Aura Effect does not have this Stack Rule, it returns false so we can add to the multiplier as usual + if (!sSpellMgr->AddSameEffectStackRuleSpellGroups(aurEff->GetSpellInfo(), static_cast(auraType), aurEff->GetAmount(), sameEffectSpellGroup)) + AddPct(multiplier, aurEff->GetAmount()); + } + } + + // Add the highest of the Same Effect Stack Rule SpellGroups to the multiplier + for (auto itr = sameEffectSpellGroup.begin(); itr != sameEffectSpellGroup.end(); ++itr) + AddPct(multiplier, itr->second); + + return multiplier; +} + +int32 Unit::GetMaxPositiveAuraModifier(AuraType auraType, std::function const& predicate) const +{ + AuraEffectList const& mTotalAuraList = GetAuraEffectsByType(auraType); + if (mTotalAuraList.empty()) + return 0; + + int32 modifier = 0; + for (AuraEffect const* aurEff : mTotalAuraList) + { + if (predicate(aurEff)) + modifier = std::max(modifier, aurEff->GetAmount()); + } + + return modifier; +} + +int32 Unit::GetMaxNegativeAuraModifier(AuraType auraType, std::function const& predicate) const +{ + AuraEffectList const& mTotalAuraList = GetAuraEffectsByType(auraType); + if (mTotalAuraList.empty()) + return 0; + + int32 modifier = 0; + for (AuraEffect const* aurEff : mTotalAuraList) + { + if (predicate(aurEff)) + modifier = std::min(modifier, aurEff->GetAmount()); + } + + return modifier; +} + +int32 Unit::GetTotalAuraModifier(AuraType auraType) const +{ + return GetTotalAuraModifier(auraType, [](AuraEffect const* /*aurEff*/) { return true; }); +} + +float Unit::GetTotalAuraMultiplier(AuraType auraType) const +{ + return GetTotalAuraMultiplier(auraType, [](AuraEffect const* /*aurEff*/) { return true; }); +} + +int32 Unit::GetMaxPositiveAuraModifier(AuraType auraType) const +{ + return GetMaxPositiveAuraModifier(auraType, [](AuraEffect const* /*aurEff*/) { return true; }); +} + +int32 Unit::GetMaxNegativeAuraModifier(AuraType auraType) const +{ + return GetMaxNegativeAuraModifier(auraType, [](AuraEffect const* /*aurEff*/) { return true; }); +} + +int32 Unit::GetTotalAuraModifierByMiscMask(AuraType auraType, uint32 miscMask) const +{ + return GetTotalAuraModifier(auraType, [miscMask](AuraEffect const* aurEff) -> bool + { + if ((aurEff->GetMiscValue() & miscMask) != 0) + return true; + return false; + }); +} + +float Unit::GetTotalAuraMultiplierByMiscMask(AuraType auraType, uint32 miscMask) const +{ + return GetTotalAuraMultiplier(auraType, [miscMask](AuraEffect const* aurEff) -> bool + { + if ((aurEff->GetMiscValue() & miscMask) != 0) + return true; + return false; + }); +} + +int32 Unit::GetMaxPositiveAuraModifierByMiscMask(AuraType auraType, uint32 miscMask, AuraEffect const* except /*= nullptr*/) const +{ + return GetMaxPositiveAuraModifier(auraType, [miscMask, except](AuraEffect const* aurEff) -> bool + { + if (except != aurEff && (aurEff->GetMiscValue() & miscMask) != 0) + return true; + return false; + }); +} + +int32 Unit::GetMaxNegativeAuraModifierByMiscMask(AuraType auraType, uint32 miscMask) const +{ + return GetMaxNegativeAuraModifier(auraType, [miscMask](AuraEffect const* aurEff) -> bool + { + if ((aurEff->GetMiscValue() & miscMask) != 0) + return true; + return false; + }); +} + +int32 Unit::GetTotalAuraModifierByMiscValue(AuraType auraType, int32 miscValue) const +{ + return GetTotalAuraModifier(auraType, [miscValue](AuraEffect const* aurEff) -> bool + { + if (aurEff->GetMiscValue() == miscValue) + return true; + return false; + }); +} + +float Unit::GetTotalAuraMultiplierByMiscValue(AuraType auraType, int32 miscValue) const +{ + return GetTotalAuraMultiplier(auraType, [miscValue](AuraEffect const* aurEff) -> bool + { + if (aurEff->GetMiscValue() == miscValue) + return true; + return false; + }); +} + +int32 Unit::GetMaxPositiveAuraModifierByMiscValue(AuraType auraType, int32 miscValue) const +{ + return GetMaxPositiveAuraModifier(auraType, [miscValue](AuraEffect const* aurEff) -> bool + { + if (aurEff->GetMiscValue() == miscValue) + return true; + return false; + }); +} + +int32 Unit::GetMaxNegativeAuraModifierByMiscValue(AuraType auraType, int32 miscValue) const +{ + return GetMaxNegativeAuraModifier(auraType, [miscValue](AuraEffect const* aurEff) -> bool + { + if (aurEff->GetMiscValue() == miscValue) + return true; + return false; + }); +} + +int32 Unit::GetTotalAuraModifierByAffectMask(AuraType auraType, SpellInfo const* affectedSpell) const +{ + return GetTotalAuraModifier(auraType, [affectedSpell](AuraEffect const* aurEff) -> bool + { + if (aurEff->IsAffectingSpell(affectedSpell)) + return true; + return false; + }); +} + +float Unit::GetTotalAuraMultiplierByAffectMask(AuraType auraType, SpellInfo const* affectedSpell) const +{ + return GetTotalAuraMultiplier(auraType, [affectedSpell](AuraEffect const* aurEff) -> bool + { + if (aurEff->IsAffectingSpell(affectedSpell)) + return true; + return false; + }); +} + +int32 Unit::GetMaxPositiveAuraModifierByAffectMask(AuraType auraType, SpellInfo const* affectedSpell) const +{ + return GetMaxPositiveAuraModifier(auraType, [affectedSpell](AuraEffect const* aurEff) -> bool + { + if (aurEff->IsAffectingSpell(affectedSpell)) + return true; + return false; + }); +} + +int32 Unit::GetMaxNegativeAuraModifierByAffectMask(AuraType auraType, SpellInfo const* affectedSpell) const +{ + return GetMaxNegativeAuraModifier(auraType, [affectedSpell](AuraEffect const* aurEff) -> bool + { + if (aurEff->IsAffectingSpell(affectedSpell)) + return true; + return false; + }); +} + +void Unit::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::_RegisterDynObject(DynamicObject* dynObj) +{ + m_dynObj.push_back(dynObj); + if (GetTypeId() == TYPEID_UNIT && IsAIEnabled()) + ToCreature()->AI()->JustRegisteredDynObject(dynObj); +} + +void Unit::_UnregisterDynObject(DynamicObject* dynObj) +{ + m_dynObj.remove(dynObj); + if (GetTypeId() == TYPEID_UNIT && IsAIEnabled()) + ToCreature()->AI()->JustUnregisteredDynObject(dynObj); +} + +DynamicObject* Unit::GetDynObject(uint32 spellId) const +{ + std::vector dynamicobjects = GetDynObjects(spellId); + return dynamicobjects.empty() ? nullptr : dynamicobjects.front(); +} + +std::vector Unit::GetDynObjects(uint32 spellId) const +{ + std::vector dynamicobjects; + for (DynObjectList::const_iterator i = m_dynObj.begin(); i != m_dynObj.end(); ++i) + if ((*i)->GetSpellId() == spellId) + dynamicobjects.push_back(*i); + + return dynamicobjects; +} + +void Unit::RemoveDynObject(uint32 spellId) +{ + if (m_dynObj.empty()) + return; + for (DynObjectList::iterator i = m_dynObj.begin(); i != m_dynObj.end();) + { + DynamicObject* dynObj = *i; + if (dynObj->GetSpellId() == spellId) + { + dynObj->Remove(); + i = m_dynObj.begin(); + } + else + ++i; + } +} + +void Unit::RemoveAllDynObjects() +{ + while (!m_dynObj.empty()) + m_dynObj.front()->Remove(); +} + +GameObject* Unit::GetGameObject(uint32 spellId) const +{ + std::vector gameobjects = GetGameObjects(spellId); + return gameobjects.empty() ? nullptr : gameobjects.front(); +} + +std::vector Unit::GetGameObjects(uint32 spellId) const +{ + std::vector gameobjects; + for (GameObjectList::const_iterator i = m_gameObj.begin(); i != m_gameObj.end(); ++i) + if ((*i)->GetSpellId() == spellId) + gameobjects.push_back(*i); + + return gameobjects; +} + +void Unit::AddGameObject(GameObject* gameObj) +{ + if (!gameObj || gameObj->GetOwnerGUID()) + return; + + m_gameObj.push_back(gameObj); + gameObj->SetOwnerGUID(GetGUID()); + + if (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) + GetSpellHistory()->StartCooldown(createBySpell, 0, nullptr, true); + } + + if (GetTypeId() == TYPEID_UNIT && ToCreature()->IsAIEnabled()) + ToCreature()->AI()->JustSummonedGameobject(gameObj); +} + +void Unit::RemoveGameObject(GameObject* gameObj, bool del) +{ + if (!gameObj || gameObj->GetOwnerGUID() != GetGUID()) + return; + + gameObj->SetOwnerGUID(ObjectGuid::Empty); + + for (uint8 i = 0; i < MAX_GAMEOBJECT_SLOT; ++i) + { + if (m_ObjectSlot[i] == gameObj->GetGUID()) + { + m_ObjectSlot[i].Clear(); + break; + } + } + + // GO created by some spell + if (uint32 spellid = gameObj->GetSpellId()) + { + RemoveAurasDueToSpell(spellid); + + SpellInfo const* createBySpell = sSpellMgr->GetSpellInfo(spellid); + // Need activate spell use for owner + if (createBySpell && createBySpell->IsCooldownStartedOnEvent()) + // note: item based cooldowns and cooldown spell mods with charges ignored (unknown existing cases) + GetSpellHistory()->SendCooldownEvent(createBySpell); + } + + m_gameObj.remove(gameObj); + + if (GetTypeId() == TYPEID_UNIT && ToCreature()->IsAIEnabled()) + ToCreature()->AI()->SummonedGameobjectDespawn(gameObj); + + if (del) + { + gameObj->SetRespawnTime(0); + gameObj->Delete(); + } +} + +void Unit::RemoveGameObject(uint32 spellid, bool del) +{ + if (m_gameObj.empty()) + return; + GameObjectList::iterator i, next; + for (i = m_gameObj.begin(); i != m_gameObj.end(); i = next) + { + next = i; + if (spellid == 0 || (*i)->GetSpellId() == spellid) + { + (*i)->SetOwnerGUID(ObjectGuid::Empty); + if (del) + { + (*i)->SetRespawnTime(0); + (*i)->Delete(); + } + + next = m_gameObj.erase(i); + } + else + ++next; + } +} + +void Unit::RemoveAllGameObjects() +{ + // remove references to unit + while (!m_gameObj.empty()) + { + GameObjectList::iterator i = m_gameObj.begin(); + (*i)->SetOwnerGUID(ObjectGuid::Empty); + (*i)->SetRespawnTime(0); + (*i)->Delete(); + m_gameObj.erase(i); + } +} + +void Unit::SendSpellNonMeleeDamageLog(SpellNonMeleeDamage* log) +{ + WorldPacket data(SMSG_SPELLNONMELEEDAMAGELOG, (16+4+4+4+1+4+4+1+1+4+4+1)); // we guess size + data << log->target->GetPackGUID(); + data << log->attacker->GetPackGUID(); + data << uint32(log->SpellID); + data << uint32(log->damage); // damage amount + int32 overkill = log->damage - log->target->GetHealth(); + data << uint32(overkill > 0 ? overkill : 0); // overkill + data << uint8 (log->schoolMask); // damage school + data << uint32(log->absorb); // AbsorbedDamage + data << uint32(log->resist); // resist + data << uint8 (log->periodicLog); // 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(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, uint32 spellID, uint32 damage, SpellSchoolMask damageSchoolMask, uint32 absorbedDamage, uint32 resist, bool isPeriodic, uint32 blocked, bool criticalHit, bool split) +{ + SpellNonMeleeDamage log(this, target, spellID, damageSchoolMask); + log.damage = damage - absorbedDamage - resist - blocked; + log.absorb = absorbedDamage; + log.resist = resist; + log.periodicLog = isPeriodic; + log.blocked = blocked; + log.HitInfo = 0; + if (criticalHit) + log.HitInfo |= SPELL_HIT_TYPE_CRIT; + if (split) + log.HitInfo |= SPELL_HIT_TYPE_SPLIT; + SendSpellNonMeleeDamageLog(&log); +} + +/*static*/ void Unit::ProcSkillsAndAuras(Unit* actor, Unit* actionTarget, ProcFlags typeMaskActor, ProcFlags typeMaskActionTarget, + ProcFlagsSpellType spellTypeMask, ProcFlagsSpellPhase spellPhaseMask, ProcFlagsHit hitMask, Spell* spell, DamageInfo* damageInfo, HealInfo* healInfo) +{ + static constexpr int32 ProcChainHardLimit = 10; + if (spell && spell->GetProcChainLength() >= ProcChainHardLimit) + { + TC_LOG_ERROR("spells.aura.effect", "Unit::ProcSkillsAndAuras: Possible infinite proc loop detected, current triggering spell %u", spell->GetSpellInfo()->Id); + return; + } + + WeaponAttackType attType = damageInfo ? damageInfo->GetAttackType() : BASE_ATTACK; + SpellInfo const* spellInfo = [&]() -> SpellInfo const* + { + if (spell) + return spell->GetSpellInfo(); + if (damageInfo) + return damageInfo->GetSpellInfo(); + if (healInfo) + return healInfo->GetSpellInfo(); + return nullptr; + }(); + + if (typeMaskActor && actor && !(spellInfo && spellInfo->HasAttribute(SPELL_ATTR3_SUPPRESS_CASTER_PROCS))) + actor->ProcSkillsAndReactives(false, actionTarget, typeMaskActor, hitMask, attType); + + if (typeMaskActionTarget && actionTarget && !(spellInfo && spellInfo->HasAttribute(SPELL_ATTR3_SUPPRESS_TARGET_PROCS))) + actionTarget->ProcSkillsAndReactives(true, actor, typeMaskActionTarget, hitMask, attType); + + if (actor) + actor->TriggerAurasProcOnEvent(actionTarget, typeMaskActor, typeMaskActionTarget, spellTypeMask, spellPhaseMask, hitMask, spell, damageInfo, healInfo); +} + +void Unit::SendPeriodicAuraLog(SpellPeriodicAuraLogInfo* pInfo) +{ + AuraEffect const* aura = pInfo->auraEff; + + WorldPackets::CombatLog::SpellPeriodicAuraLog periodicAuraLog; + periodicAuraLog.TargetGUID = GetGUID(); + periodicAuraLog.CasterGUID = aura->GetCasterGUID(); + periodicAuraLog.SpellID = aura->GetId(); + + WorldPackets::CombatLog::PeriodicAuraLogEffect& logEffect = periodicAuraLog.Entries.emplace_back(); + logEffect.Effect = aura->GetAuraType(); + logEffect.Amount = pInfo->damage; + logEffect.OverHealOrKill = pInfo->overDamage; + logEffect.Resisted = pInfo->resist; + logEffect.Crit = pInfo->critical; + + switch (logEffect.Effect) + { + case SPELL_AURA_PERIODIC_DAMAGE: + case SPELL_AURA_PERIODIC_DAMAGE_PERCENT: + logEffect.AbsorbedOrAmplitude = pInfo->absorb; + logEffect.SchoolMaskOrPower = aura->GetSpellInfo()->GetSchoolMask(); + break; + case SPELL_AURA_PERIODIC_HEAL: + case SPELL_AURA_OBS_MOD_HEALTH: + logEffect.AbsorbedOrAmplitude = pInfo->absorb; + break; + case SPELL_AURA_OBS_MOD_POWER: + case SPELL_AURA_PERIODIC_ENERGIZE: + logEffect.SchoolMaskOrPower = aura->GetMiscValue(); + break; + case SPELL_AURA_PERIODIC_MANA_LEECH: + logEffect.SchoolMaskOrPower = aura->GetMiscValue(); + logEffect.AbsorbedOrAmplitude = pInfo->multiplier; + break; + default: + TC_LOG_ERROR("entities.unit", "Unit::SendPeriodicAuraLog: unknown aura %u", uint32(aura->GetAuraType())); + return; + } + + SendMessageToSet(periodicAuraLog.Write(), true); +} + +void Unit::SendSpellDamageResist(Unit* target, uint32 spellId) +{ + WorldPacket data(SMSG_PROCRESIST, 8+8+4+1); + data << uint64(GetGUID()); + data << uint64(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 << uint64(GetGUID()); + data << uint64(target->GetGUID()); + data << uint32(spellId); + data << uint8(0); // bool - log format: 0-default, 1-debug + SendMessageToSet(&data, true); +} + +void Unit::SendAttackStateUpdate(CalcDamageInfo* damageInfo) +{ + WorldPackets::CombatLog::AttackerStateUpdate packet; + packet.HitInfo = damageInfo->HitInfo; + packet.AttackerGUID = damageInfo->Attacker->GetGUID(); + packet.VictimGUID = damageInfo->Target->GetGUID(); + packet.Damage = damageInfo->Damage; + int32 overkill = damageInfo->Damage - damageInfo->Target->GetHealth(); + packet.OverDamage = (overkill < 0 ? -1 : overkill); + + if (damageInfo->Damage) + { + packet.SubDmg.emplace(); + packet.SubDmg->SchoolMask = damageInfo->DamageSchoolMask; // School of sub damage + packet.SubDmg->FDamage = damageInfo->Damage; // sub damage + packet.SubDmg->Damage = damageInfo->Damage; // Sub Damage + packet.SubDmg->Absorbed = damageInfo->Absorb; + packet.SubDmg->Resisted = damageInfo->Resist; + } + + packet.VictimState = damageInfo->TargetState; + packet.BlockAmount = damageInfo->Blocked; + packet.RageGained = damageInfo->RageGained; + + SendMessageToSet(packet.Write(), 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.Damage = Damage - AbsorbDamage - Resist - BlockedAmount; + dmgInfo.DamageSchoolMask = damageSchoolMask; + dmgInfo.Absorb = AbsorbDamage; + dmgInfo.Resist = Resist; + dmgInfo.TargetState = TargetState; + dmgInfo.Blocked = BlockedAmount; + dmgInfo.RageGained = 0; + SendAttackStateUpdate(&dmgInfo); +} + +void Unit::SetPowerType(PowerType power) +{ + if (GetPowerType() == power) + return; + + SetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_POWER_TYPE, power); + + // Creatures can swap out their power type at index 0 so we do keep track of the new power type here + if (IsCreature()) + { + _usedPowerTypes[GetPowerIndex(power)] = power; + UpdatePowerRegeneration(power); + } + + if (GetTypeId() == TYPEID_PLAYER) + { + 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->GetTypeId() == TYPEID_PLAYER) && owner->ToPlayer()->GetGroup()) + owner->ToPlayer()->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_PET_POWER_TYPE); + } + } + + // Update max power + UpdateMaxPower(power); + + // Update current power + switch (power) + { + case POWER_MANA: // Keep the same (druid form switching...) + case POWER_ENERGY: + break; + case POWER_RAGE: // Reset to zero + SetPower(POWER_RAGE, 0); + break; + case POWER_FOCUS: // Make it full + SetFullPower(power); + break; + default: + break; + } +} + +PowerType Unit::CalculateDisplayPowerType() const +{ + PowerType displayPower = POWER_MANA; + switch (GetShapeshiftForm()) + { + case FORM_GHOUL: + case FORM_CAT: + displayPower = POWER_ENERGY; + break; + case FORM_BEAR: + displayPower = POWER_RAGE; + break; + case FORM_TRAVEL: + case FORM_GHOSTWOLF: + displayPower = POWER_MANA; + break; + default: + { + ChrClassesEntry const* cEntry = sChrClassesStore.LookupEntry(getClass()); + if (cEntry && cEntry->DisplayPower < MAX_POWERS) + displayPower = PowerType(cEntry->DisplayPower); + + if (Vehicle* vehicle = GetVehicleKit()) + { + if (PowerDisplayEntry const* powerDisplay = sPowerDisplayStore.LookupEntry(vehicle->GetVehicleInfo()->PowerDisplayID[0])) // To-do: 4.x has 3 power display id fields. + displayPower = PowerType(powerDisplay->ActualType); + else if (getClass() == CLASS_ROGUE) + displayPower = POWER_ENERGY; + } + else if (Pet const* pet = ToPet()) + { + if (pet->getPetType() == HUNTER_PET) // Hunter pets have focus + displayPower = POWER_FOCUS; + else if (pet->IsPetGhoul() || pet->IsRisenAlly()) // DK pets have energy + displayPower = POWER_ENERGY; + } + break; + } + } + + return displayPower; +} + +void Unit::UpdateDisplayPower() +{ + SetPowerType(CalculateDisplayPowerType()); +} + +void Unit::SetSheath(SheathState sheathed) +{ + SetByteValue(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_SHEATH_STATE, sheathed); + if (sheathed == SHEATH_STATE_UNARMED) + RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Sheathing); +} + +void Unit::_addAttacker(Unit* pAttacker) +{ + m_attackers.insert(pAttacker); +} + +void Unit::_removeAttacker(Unit* pAttacker) +{ + m_attackers.erase(pAttacker); +} + +Unit* Unit::getAttackerForHelper() const // If someone wants to help, who to give them +{ + if (!IsEngaged()) + return nullptr; + + if (Unit* victim = GetVictim()) + if ((!IsPet() && !IsCharmerOrSelfPlayer()) || IsInCombatWith(victim)) + return victim; + + CombatManager const& mgr = GetCombatManager(); + // pick arbitrary targets; our pvp combat > owner's pvp combat > our pve combat > owner's pve combat + Unit* owner = GetCharmerOrOwner(); + if (mgr.HasPvPCombat()) + return mgr.GetPvPCombatRefs().begin()->second->GetOther(this); + if (owner && (owner->GetCombatManager().HasPvPCombat())) + return owner->GetCombatManager().GetPvPCombatRefs().begin()->second->GetOther(owner); + if (mgr.HasPvECombat()) + return mgr.GetPvECombatRefs().begin()->second->GetOther(this); + if (owner && (owner->GetCombatManager().HasPvECombat())) + return owner->GetCombatManager().GetPvECombatRefs().begin()->second->GetOther(owner); + return nullptr; +} + +bool Unit::Attack(Unit* victim, bool meleeAttack) +{ + if (!victim || victim == this) + return false; + + // dead units can neither attack nor be attacked + if (!IsAlive() || !victim->IsInWorld() || !victim->IsAlive()) + return false; + + // player cannot attack in mount state + if (GetTypeId() == TYPEID_PLAYER && IsMounted()) + return false; + + Creature* creature = ToCreature(); + if (creature) + { + // creatures cannot attack while evading + if (creature->IsInEvadeMode()) + return false; + + if (!creature->CanMelee()) + meleeAttack = false; + } + + if (HasAuraType(SPELL_AURA_DISABLE_ATTACKING_EXCEPT_ABILITIES)) + return false; + + // nobody can attack GM in GM-mode + if (victim->GetTypeId() == TYPEID_PLAYER) + { + if (victim->ToPlayer()->IsGameMaster()) + return false; + } + else + { + if (victim->ToCreature()->IsEvadingAttacks()) + return false; + } + + if (m_attacking) + { + if (m_attacking == victim) + { + // switch to melee attack from ranged/magic + if (meleeAttack) + { + if (!HasUnitState(UNIT_STATE_MELEE_ATTACKING)) + { + AddUnitState(UNIT_STATE_MELEE_ATTACKING); + SendMeleeAttackStart(victim); + return true; + } + } + else if (HasUnitState(UNIT_STATE_MELEE_ATTACKING)) + { + ClearUnitState(UNIT_STATE_MELEE_ATTACKING); + SendMeleeAttackStop(victim); + return true; + } + return false; + } + + // switch target + InterruptSpell(CURRENT_MELEE_SPELL); + if (!meleeAttack) + ClearUnitState(UNIT_STATE_MELEE_ATTACKING); + } + + if (m_attacking) + m_attacking->_removeAttacker(this); + + m_attacking = victim; + m_attacking->_addAttacker(this); + + // Set our target + SetTarget(victim->GetGUID()); + + if (meleeAttack) + AddUnitState(UNIT_STATE_MELEE_ATTACKING); + + // set position before any AI calls/assistance + //if (GetTypeId() == TYPEID_UNIT) + // ToCreature()->SetCombatStartPosition(GetPositionX(), GetPositionY(), GetPositionZ()); + + if (creature && !IsControlledByPlayer()) + { + EngageWithTarget(victim); // ensure that anything we're attacking has threat + creature->CallAssistance(); + + // Remove emote state - will be restored on creature reset + SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_ONESHOT_NONE); + } + + // delay offhand weapon attack by 50% of the base attack time + if (haveOffhandWeapon() && GetTypeId() != TYPEID_PLAYER) + setAttackTimer(OFF_ATTACK, std::max(getAttackTimer(OFF_ATTACK), getAttackTimer(BASE_ATTACK) + uint32(CalculatePct(GetFloatValue(UNIT_FIELD_BASEATTACKTIME), 50)))); + + + if (meleeAttack) + SendMeleeAttackStart(victim); + + // Let the pet know we've started attacking someting. Handles melee attacks only + // Spells such as auto-shot and others handled in WorldSession::HandleCastSpellOpcode + if (GetTypeId() == TYPEID_PLAYER) + { + for (Unit* controlled : m_Controlled) + if (Creature* cControlled = controlled->ToCreature()) + if (CreatureAI* controlledAI = cControlled->AI()) + controlledAI->OwnerAttacked(victim); + } + + return true; +} + +bool Unit::AttackStop() +{ + if (!m_attacking) + return false; + + Unit* victim = m_attacking; + + m_attacking->_removeAttacker(this); + m_attacking = nullptr; + + // Clear our target + SetTarget(ObjectGuid::Empty); + + ClearUnitState(UNIT_STATE_MELEE_ATTACKING); + + InterruptSpell(CURRENT_MELEE_SPELL); + + // reset only at real combat stop + if (Creature* creature = ToCreature()) + creature->SetNoCallAssistance(false); + + SendMeleeAttackStop(victim); + + return true; +} + +void Unit::ValidateAttackersAndOwnTarget() +{ + // iterate attackers + UnitVector toRemove; + AttackerSet const& attackers = getAttackers(); + for (Unit* attacker : attackers) + if (!attacker->IsValidAttackTarget(this)) + toRemove.push_back(attacker); + + for (Unit* attacker : toRemove) + attacker->AttackStop(); + + // remove our own victim + if (Unit* victim = GetVictim()) + if (!IsValidAttackTarget(victim)) + AttackStop(); +} + +void Unit::CombatStop(bool includingCast, bool mutualPvP, bool (*unitFilter)(Unit const* otherUnit)) +{ + if (includingCast && IsNonMeleeSpellCast(false)) + InterruptNonMeleeSpells(false); + + AttackStop(); + + if (!unitFilter) + RemoveAllAttackers(); + else + { + std::vector attackersToRemove; + attackersToRemove.reserve(m_attackers.size()); + std::copy_if(m_attackers.begin(), m_attackers.end(), std::back_inserter(attackersToRemove), unitFilter); + + for (Unit* attacker : attackersToRemove) + attacker->AttackStop(); + } + if (GetTypeId() == TYPEID_PLAYER) + ToPlayer()->SendAttackSwingCancelAttack(); // melee and ranged forced attack cancel + + m_combatManager.EndAllPvECombat(unitFilter); + if (mutualPvP) + m_combatManager.EndAllPvPCombat(unitFilter); + else // vanish and brethren are weird + m_combatManager.SuppressPvPCombat(unitFilter); +} + +void Unit::CombatStopWithPets(bool includingCast) +{ + CombatStop(includingCast); + + for (Unit* minion : m_Controlled) + minion->CombatStop(includingCast); +} + +bool Unit::isAttackingPlayer() const +{ + if (HasUnitState(UNIT_STATE_ATTACK_PLAYER)) + return true; + + for (ControlList::const_iterator itr = m_Controlled.begin(); itr != m_Controlled.end(); ++itr) + if ((*itr)->isAttackingPlayer()) + return true; + + for (uint8 i = 0; i < MAX_SUMMON_SLOT; ++i) + if (m_SummonSlot[i]) + if (Creature* summon = GetMap()->GetCreature(m_SummonSlot[i])) + if (summon->isAttackingPlayer()) + return true; + + return false; +} + +void Unit::RemoveAllAttackers() +{ + while (!m_attackers.empty()) + { + AttackerSet::iterator iter = m_attackers.begin(); + if (!(*iter)->AttackStop()) + { + TC_LOG_ERROR("entities.unit", "WORLD: Unit has an attacker that isn't attacking it!"); + m_attackers.erase(iter); + } + } +} + +void Unit::ModifyAuraState(AuraStateType flag, bool apply) +{ + if (apply) + { + if (!HasFlag(UNIT_FIELD_AURASTATE, 1<<(flag-1))) + { + SetFlag(UNIT_FIELD_AURASTATE, 1<<(flag-1)); + if (GetTypeId() == TYPEID_PLAYER) + { + PlayerSpellMap const& sp_list = ToPlayer()->GetSpellMap(); + for (PlayerSpellMap::const_iterator itr = sp_list.begin(); itr != sp_list.end(); ++itr) + { + if (itr->second.state == PLAYERSPELL_REMOVED || itr->second.disabled) + continue; + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(itr->first); + if (!spellInfo || !spellInfo->IsPassive()) + continue; + if (spellInfo->CasterAuraState == uint32(flag)) + CastSpell(this, itr->first, true); + } + } + else if (Pet* pet = ToCreature()->ToPet()) + { + for (PetSpellMap::const_iterator itr = pet->m_spells.begin(); itr != pet->m_spells.end(); ++itr) + { + if (itr->second.state == PETSPELL_REMOVED) + continue; + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(itr->first); + if (!spellInfo || !spellInfo->IsPassive()) + continue; + if (spellInfo->CasterAuraState == uint32(flag)) + CastSpell(this, itr->first, true); + } + } + } + } + else + { + if (HasFlag(UNIT_FIELD_AURASTATE, 1<<(flag-1))) + { + RemoveFlag(UNIT_FIELD_AURASTATE, 1<<(flag-1)); + + Unit::AuraApplicationMap& tAuras = GetAppliedAuras(); + for (Unit::AuraApplicationMap::iterator itr = tAuras.begin(); itr != tAuras.end();) + { + SpellInfo const* spellProto = itr->second->GetBase()->GetSpellInfo(); + if (itr->second->GetBase()->GetCasterGUID() == GetGUID() && spellProto->CasterAuraState == uint32(flag) && (spellProto->IsPassive() || flag != AURA_STATE_ENRAGE)) + RemoveAura(itr); + else + ++itr; + } + } + } +} + +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 (AuraEffect const* aurEff : stateAuras) + if (aurEff->IsAffectingSpell(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; + + // 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(GetMapId()); + WorldPacket packet; + BuildValuesUpdateBlockForPlayer(&udata, player); + udata.BuildPacket(&packet); + player->SendDirectMessage(&packet); + + RemoveFieldNotifyFlag(UF_FLAG_OWNER); +} + +Player* Unit::GetControllingPlayer() const +{ + if (ObjectGuid guid = GetCharmerOrOwnerGUID()) + { + if (Unit* master = ObjectAccessor::GetUnit(*this, guid)) + return master->GetControllingPlayer(); + return nullptr; + } + else + return const_cast(ToPlayer()); +} + +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; + + TC_LOG_ERROR("entities.unit", "Unit::GetFirstMinion: Minion %s not exist.", pet_guid.ToString().c_str()); + 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; + + TC_LOG_FATAL("entities.unit", "Unit::GetGuardianPet: Guardian %s not exist.", pet_guid.ToString().c_str()); + const_cast(this)->SetPetGUID(ObjectGuid::Empty); + } + + return nullptr; +} + +void Unit::SetMinion(Minion* minion, bool apply) +{ + if (!minion) + { + TC_LOG_ERROR("entities.unit", "Unit::SetMinion: Unit %s tried to reference a non existing minion", GetGUID().ToString().c_str()); + return; + } + + TC_LOG_DEBUG("entities.unit", "SetMinion %u for %u, apply %u", minion->GetEntry(), GetEntry(), apply); + + if (apply) + { + if (minion->GetOwnerGUID()) + { + TC_LOG_FATAL("entities.unit", "SetMinion: Minion %u is not the minion of owner %u", minion->GetEntry(), GetEntry()); + return; + } + + minion->SetOwnerGUID(GetGUID()); + + m_Controlled.insert(minion); + + if (GetTypeId() == TYPEID_PLAYER) + { + minion->m_ControlledByPlayer = true; + minion->SetFlag(UNIT_FIELD_FLAGS, 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 (oldPet->IsPet()) + ((Pet*)oldPet)->Remove(PET_SAVE_DISMISS); + else + oldPet->UnSummon(); + SetPetGUID(minion->GetGUID()); + SetMinionGUID(ObjectGuid::Empty); + } + } + else + { + SetPetGUID(minion->GetGUID()); + SetMinionGUID(ObjectGuid::Empty); + } + } + + if (minion->HasUnitTypeMask(UNIT_MASK_CONTROLABLE_GUARDIAN)) + AddGuidValue(UNIT_FIELD_SUMMON, minion->GetGUID()); + + if (minion->m_Properties && SummonTitle(minion->m_Properties->Title) == SummonTitle::Companion) + SetCritterGUID(minion->GetGUID()); + + // PvP, FFAPvP + minion->SetByteValue(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PVP_FLAG, GetByteValue(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PVP_FLAG)); + + // 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()) + GetSpellHistory()->StartCooldown(spellInfo, 0, nullptr, true); + } + else + { + if (minion->GetOwnerGUID() != GetGUID()) + { + TC_LOG_FATAL("entities.unit", "SetMinion: Minion %u is not the minion of owner %u", minion->GetEntry(), GetEntry()); + return; + } + + if (m_Controlled.find(minion) != m_Controlled.end()) + m_Controlled.erase(minion); + + if (minion->m_Properties && SummonTitle(minion->m_Properties->Title) == SummonTitle::Companion) + if (GetCritterGUID() == minion->GetGUID()) + SetCritterGUID(ObjectGuid::Empty); + + if (minion->IsGuardianPet()) + { + if (GetPetGUID() == minion->GetGUID()) + SetPetGUID(ObjectGuid::Empty); + } + + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(minion->GetUInt32Value(UNIT_CREATED_BY_SPELL)); + // Remove infinity cooldown + if (spellInfo && (spellInfo->IsCooldownStartedOnEvent())) + GetSpellHistory()->SendCooldownEvent(spellInfo); + + //if (minion->HasUnitTypeMask(UNIT_MASK_GUARDIAN)) + { + if (RemoveGuidValue(UNIT_FIELD_SUMMON, minion->GetGUID())) + { + // Check if there is another minion + for (Unit const* controlled : m_Controlled) + { + // do not use this check, creature do not have charm guid + //if (GetCharmedGUID() == (*itr)->GetGUID()) + if (GetGUID() == controlled->GetCharmerGUID()) + continue; + + //ASSERT((*itr)->GetOwnerGUID() == GetGUID()); + if (controlled->GetOwnerOrCreatorGUID() != GetGUID()) + { + OutDebugInfo(); + controlled->OutDebugInfo(); + ABORT(); + } + ASSERT(controlled->IsCreature()); + + if (!controlled->HasUnitTypeMask(UNIT_MASK_CONTROLABLE_GUARDIAN)) + continue; + + if (AddGuidValue(UNIT_FIELD_SUMMON, controlled->GetGUID())) + { + // show another pet bar if there is no charm bar + if (IsPlayer() && !GetCharmedGUID()) + { + if (controlled->IsPet()) + ToPlayer()->PetSpellInitialize(); + else + ToPlayer()->CharmSpellInitialize(); + } + } + break; + } + } + } + } + UpdatePetCombatState(); +} + +void Unit::GetAllMinionsByEntry(std::list& Minions, uint32 entry) +{ + for (Unit::ControlList::iterator itr = m_Controlled.begin(); itr != m_Controlled.end();) + { + Unit* unit = *itr; + ++itr; + if (unit->GetEntry() == entry && unit->GetTypeId() == TYPEID_UNIT + && unit->IsSummon()) // minion, actually + Minions.push_back(unit->ToCreature()); + } +} + +void Unit::RemoveAllMinionsByEntry(uint32 entry) +{ + for (Unit::ControlList::iterator itr = m_Controlled.begin(); itr != m_Controlled.end();) + { + Unit* unit = *itr; + ++itr; + if (unit->GetEntry() == entry && unit->GetTypeId() == TYPEID_UNIT + && unit->IsSummon()) // minion, actually + unit->ToTempSummon()->UnSummon(); + // i think this is safe because i have never heard that a despawned minion will trigger a same minion + } +} + +void Unit::SetCharm(Unit* charm, bool apply) +{ + if (apply) + { + if (GetTypeId() == TYPEID_PLAYER) + { + ASSERT(AddGuidValue(UNIT_FIELD_CHARM, charm->GetGUID()), + "Player %s is trying to charm unit %u, but it already has a charmed unit %s", GetName().c_str(), charm->GetEntry(), GetCharmedGUID().ToString().c_str()); + m_charmed = charm; + + charm->m_ControlledByPlayer = true; + /// @todo maybe we can use this flag to check if controlled by player + charm->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED); + } + else + charm->m_ControlledByPlayer = false; + + // PvP, FFAPvP + charm->SetByteValue(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PVP_FLAG, GetByteValue(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PVP_FLAG)); + + ASSERT(charm->AddGuidValue(UNIT_FIELD_CHARMEDBY, GetGUID()), + "Unit %u is being charmed, but it already has a charmer %s", charm->GetEntry(), charm->GetCharmerGUID().ToString().c_str()); + charm->m_charmer = this; + + _isWalkingBeforeCharm = charm->IsWalking(); + if (_isWalkingBeforeCharm) + charm->SetWalk(false); + + m_Controlled.insert(charm); + } + else + { + charm->ClearUnitState(UNIT_STATE_CHARMED); + + if (GetTypeId() == TYPEID_PLAYER) + { + ASSERT(RemoveGuidValue(UNIT_FIELD_CHARM, charm->GetGUID()), + "Player %s is trying to uncharm unit %u, but it has another charmed unit %s", GetName().c_str(), charm->GetEntry(), GetCharmedGUID().ToString().c_str()); + m_charmed = nullptr; + } + + ASSERT(charm->RemoveGuidValue(UNIT_FIELD_CHARMEDBY, GetGUID()), + "Unit %u is being uncharmed, but it has another charmer %s", charm->GetEntry(), charm->GetCharmerGUID().ToString().c_str()); + charm->m_charmer = nullptr; + + if (charm->GetTypeId() == TYPEID_PLAYER) + { + charm->m_ControlledByPlayer = true; + charm->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED); + charm->ToPlayer()->UpdatePvPState(); + } + else if (Player* player = charm->GetCharmerOrOwnerPlayerOrPlayerItself()) + { + charm->m_ControlledByPlayer = true; + charm->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED); + charm->SetByteValue(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PVP_FLAG, player->GetByteValue(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PVP_FLAG)); + } + else + { + charm->m_ControlledByPlayer = false; + charm->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED); + charm->SetByteValue(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PVP_FLAG, 0); + } + + if (charm->IsWalking() != _isWalkingBeforeCharm) + charm->SetWalk(_isWalkingBeforeCharm); + + if (charm->GetTypeId() == TYPEID_PLAYER + || !charm->ToCreature()->HasUnitTypeMask(UNIT_MASK_MINION) + || charm->GetOwnerOrCreatorGUID() != GetGUID()) + { + m_Controlled.erase(charm); + } + } + UpdatePetCombatState(); +} + +/*static*/ void Unit::DealHeal(HealInfo& healInfo) +{ + int32 gain = 0; + Unit* healer = healInfo.GetHealer(); + Unit* victim = healInfo.GetTarget(); + uint32 addhealth = healInfo.GetHeal(); + + if (healer) + { + if (UnitAI* victimAI = victim->GetAI()) + victimAI->HealReceived(healer, addhealth); + + if (UnitAI* healerAI = healer->GetAI()) + healerAI->HealDone(victim, addhealth); + } + + if (addhealth) + gain = victim->ModifyHealth(int32(addhealth)); + + // Hook for OnHeal Event + sScriptMgr->OnHeal(healer, victim, (uint32&)gain); + + Unit* unit = healer; + if (healer && healer->GetTypeId() == TYPEID_UNIT && healer->IsTotem()) + unit = healer->GetOwner(); + + if (unit) + { + if (Player* player = unit->ToPlayer()) + { + if (!healInfo.GetSpellInfo() || !healInfo.GetSpellInfo()->HasAttribute(SPELL_ATTR7_DO_NOT_COUNT_FOR_PVP_SCOREBOARD)) + if (Battleground* bg = player->GetBattleground()) + bg->UpdatePlayerScore(player, SCORE_HEALING_DONE, gain); + + // use the actual gain, as the overheal shall not be counted, skip gain 0 (it ignored anyway in to criteria) + if (gain) + player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HEALING_DONE, gain, 0, 0, victim); + + player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HIGHEST_HEAL_CAST, addhealth); + } + } + + if (Player* player = victim->ToPlayer()) + { + player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_TOTAL_HEALING_RECEIVED, gain); + player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HIGHEST_HEALING_RECEIVED, addhealth); + } + + if (gain) + healInfo.SetEffectiveHeal(gain > 0 ? static_cast(gain) : 0UL); +} + +bool Unit::IsMagnet() const +{ + // Grounding Totem + if (GetUInt32Value(UNIT_CREATED_BY_SPELL) == 8177) /// @todo: find a more generic solution + return true; + + return false; +} + +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(AuraRemoveFlags::Expired); + return magnet; + } + } + return victim; +} + +Unit* Unit::GetFirstControlled() const +{ + // Sequence: charmed, pet, other guardians + Unit* unit = GetCharmed(); + if (!unit) + if (ObjectGuid guid = GetMinionGUID()) + unit = ObjectAccessor::GetUnit(*this, guid); + + return unit; +} + +void Unit::RemoveAllControlled() +{ + // possessed pet and vehicle + if (GetTypeId() == TYPEID_PLAYER) + ToPlayer()->StopCastingCharm(); + + while (!m_Controlled.empty()) + { + Unit* target = *m_Controlled.begin(); + m_Controlled.erase(m_Controlled.begin()); + if (target->GetCharmerGUID() == GetGUID()) + target->RemoveCharmAuras(); + else if (target->GetOwnerOrCreatorGUID() == GetGUID() && target->IsSummon()) + target->ToTempSummon()->UnSummon(); + else + TC_LOG_ERROR("entities.unit", "Unit %u is trying to release unit %u which is neither charmed nor owned by it", GetEntry(), target->GetEntry()); + } + if (GetPetGUID()) + TC_LOG_FATAL("entities.unit", "Unit %u is not able to release its pet %s", GetEntry(), GetPetGUID().ToString().c_str()); + if (GetMinionGUID()) + TC_LOG_FATAL("entities.unit", "Unit %u is not able to release its minion %s", GetEntry(), GetMinionGUID().ToString().c_str()); + if (GetCharmedGUID()) + TC_LOG_FATAL("entities.unit", "Unit %u is not able to release its charm %s", GetEntry(), GetCharmedGUID().ToString().c_str()); + if (!IsPet()) // pets don't use the flag for this + RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PET_IN_COMBAT); // m_controlled is now empty, so we know none of our minions are in combat +} + +bool Unit::isPossessedByPlayer() const +{ + return HasUnitState(UNIT_STATE_POSSESSED) && GetCharmerGUID().IsPlayer(); +} + +bool Unit::isPossessing(Unit* u) const +{ + return u->isPossessed() && GetCharmedGUID() == u->GetGUID(); +} + +bool Unit::isPossessing() const +{ + if (Unit* u = GetCharmed()) + return u->isPossessed(); + else + return false; +} + +Unit* Unit::GetCharmerOrSelf() const +{ + if (IsCharmed()) + return GetCharmer(); + else + return const_cast(this); +} + +Unit* Unit::GetNextRandomRaidMemberOrPet(float radius) +{ + Player* player = nullptr; + if (GetTypeId() == TYPEID_PLAYER) + player = ToPlayer(); + // Should we enable this also for charmed units? + else if (GetTypeId() == TYPEID_UNIT && IsPet()) + player = GetOwner()->ToPlayer(); + + if (!player) + return nullptr; + Group* group = player->GetGroup(); + // When there is no group check pet presence + if (!group) + { + // We are pet now, return owner + if (player != this) + return IsWithinDistInMap(player, radius) ? player : nullptr; + Unit* pet = GetGuardianPet(); + // No pet, no group, nothing to return + if (!pet) + return nullptr; + // We are owner now, return pet + return IsWithinDistInMap(pet, radius) ? pet : nullptr; + } + + std::vector nearMembers; + // reserve place for players and pets because resizing vector every unit push is unefficient (vector is reallocated then) + nearMembers.reserve(group->GetMembersCount() * 2); + + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + if (Player* Target = itr->GetSource()) + { + // IsHostileTo check duel and controlled by enemy + if (Target != this && IsWithinDistInMap(Target, radius) && Target->IsAlive() && !IsHostileTo(Target)) + nearMembers.push_back(Target); + + // Push player's pet to vector + if (Unit* pet = Target->GetGuardianPet()) + if (pet != this && IsWithinDistInMap(pet, radius) && pet->IsAlive() && !IsHostileTo(pet)) + nearMembers.push_back(pet); + } + + if (nearMembers.empty()) + return nullptr; + + uint32 randTarget = urand(0, nearMembers.size()-1); + return nearMembers[randTarget]; +} + +// only called in Player::SetSeer +// so move it to Player? +void Unit::AddPlayerToVision(Player* player) +{ + if (m_sharedVision.empty()) + { + setActive(true); + SetWorldObject(true); + } + m_sharedVision.push_back(player); +} + +// only called in Player::SetSeer +void Unit::RemovePlayerFromVision(Player* player) +{ + m_sharedVision.remove(player); + if (m_sharedVision.empty()) + { + setActive(false); + SetWorldObject(false); + } +} + +void Unit::RemoveBindSightAuras() +{ + RemoveAurasByType(SPELL_AURA_BIND_SIGHT); +} + +void Unit::RemoveCharmAuras() +{ + RemoveAurasByType(SPELL_AURA_MOD_CHARM); + RemoveAurasByType(SPELL_AURA_MOD_POSSESS); + RemoveAurasByType(SPELL_AURA_AOE_CHARM); +} + +void Unit::UnsummonAllTotems() +{ + for (uint8 i = 0; i < MAX_SUMMON_SLOT; ++i) + { + if (!m_SummonSlot[i]) + continue; + + if (Creature* OldTotem = GetMap()->GetCreature(m_SummonSlot[i])) + if (OldTotem->IsSummon()) + OldTotem->ToTempSummon()->UnSummon(); + } +} + +void Unit::SendHealSpellLog(HealInfo& healInfo, bool critical /*= false*/) +{ + // we guess size + WorldPacket data(SMSG_SPELLHEALLOG, 8 + 8 + 4 + 4 + 4 + 4 + 1 + 1); + data << healInfo.GetTarget()->GetPackGUID(); + data << healInfo.GetHealer()->GetPackGUID(); + data << uint32(healInfo.GetSpellInfo()->Id); + data << uint32(healInfo.GetHeal()); + data << uint32(healInfo.GetHeal() - healInfo.GetEffectiveHeal()); + 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 /*= false*/) +{ + // calculate heal absorb and reduce healing + Unit::CalcHealAbsorb(healInfo); + Unit::DealHeal(healInfo); + SendHealSpellLog(healInfo, critical); + return healInfo.GetEffectiveHeal(); +} + +void Unit::SendEnergizeSpellLog(Unit* victim, uint32 spellId, int32 damage, PowerType powerType) +{ + WorldPackets::CombatLog::SpellEnergizeLog packet; + packet.TargetGUID = victim->GetGUID(); + packet.CasterGUID = GetGUID(); + packet.SpellID = spellId; + packet.Amount = damage; + packet.Type = powerType; + + SendMessageToSet(packet.Write(), true); +} + +void Unit::EnergizeBySpell(Unit* victim, uint32 spellId, int32 damage, PowerType powerType) +{ + if (SpellInfo const* info = sSpellMgr->GetSpellInfo(spellId)) + EnergizeBySpell(victim, info, damage, powerType); +} + +void Unit::EnergizeBySpell(Unit* victim, SpellInfo const* spellInfo, int32 damage, PowerType powerType) +{ + victim->ModifyPower(powerType, damage, false); + victim->GetThreatManager().ForwardThreatForAssistingMe(this, float(damage) / 2, spellInfo, true); + SendEnergizeSpellLog(victim, spellInfo->Id, damage, powerType); +} + +int32 Unit::SpellDamageBonusDone(Unit* victim, SpellInfo const* spellProto, int32 pdamage, DamageEffectType damagetype, uint8 effIndex, uint32 stack /*= 1*/, Spell* spell /*= nullptr*/, AuraEffect const* aurEff /*= nullptr*/) const +{ + if (!spellProto || !victim) + return pdamage; + + int32 DoneTotal = 0; + float DoneTotalMod = 1.0f; + + auto callDamageScript = [&](int32& dmg, int32& flatMod, float& pctMod) + { + if (spell) + spell->CallScriptCalcDamageHandlers(victim, dmg, flatMod, pctMod); + else if (aurEff) + aurEff->GetBase()->CallScriptCalcDamageAndHealingHandlers(aurEff, aurEff->GetBase()->GetApplicationOfTarget(victim->GetGUID()), victim, dmg, flatMod, pctMod); + }; + + // Some spells don't benefit from done mods + if (damagetype == DIRECT_DAMAGE || spellProto->HasAttribute(SPELL_ATTR3_IGNORE_CASTER_MODIFIERS)) + { + callDamageScript(pdamage, DoneTotal, DoneTotalMod); + return int32(std::max(float(pdamage + DoneTotal) * DoneTotalMod, 0.0f)); + } + + // Totems get their bonus damage from their owner + if (IsCreature() && IsTotem()) + if (Unit* creator = ObjectAccessor::GetUnit(*this, GetCreatorGUID())) + return creator->SpellDamageBonusDone(victim, spellProto, pdamage, damagetype, effIndex, stack, spell, aurEff); + + DoneTotalMod = SpellDamagePctDone(victim, spellProto, damagetype); + + // done scripted mod (take it from owner) + Unit const* owner = GetOwner() ? GetOwner() : this; + DoneTotal += owner->GetTotalAuraModifier(SPELL_AURA_OVERRIDE_CLASS_SCRIPTS, [spellProto](AuraEffect const* aurEff) -> bool + { + if (!aurEff->IsAffectingSpell(spellProto)) + return false; + + switch (aurEff->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 + return true; + default: + break; + } + + return false; + }); + + // Some spells don't benefit from pct done mods + if (!spellProto->HasAttribute(SPELL_ATTR6_IGNORE_CASTER_DAMAGE_MODIFIERS)) + DoneTotal += GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_FLAT_SPELL_DAMAGE_VERSUS, victim->GetCreatureTypeMask()); + + // Done fixed damage bonus auras + int32 DoneAdvertisedBenefit = SpellBaseDamageBonusDone(spellProto->GetSchoolMask(), true); + // modify spell power by victim's SPELL_AURA_MOD_DAMAGE_TAKEN auras (eg Amplify/Dampen Magic) + DoneAdvertisedBenefit += GetTotalAuraModifier(SPELL_AURA_MOD_DAMAGE_TAKEN, [spellProto](AuraEffect const* aurEff) + { + if (spellProto->HasAttribute(SPELL_ATTR10_IGNORE_POSITIVE_DAMAGE_TAKEN_MODS) && aurEff->GetAmount() > 0) + return false; + + if ((aurEff->GetMiscValue() & spellProto->GetSchoolMask()) != 0) + return true; + + return false; + }); + + // Custom scripted damage + switch (spellProto->SpellFamilyName) + { + case SPELLFAMILY_HUNTER: + // Hunter Pet basic attacks (RAP * 0.4 * 0.2) + if (spellProto->SpellFamilyFlags[0] & 0x00080000) + DoneTotal += (owner->GetTotalAttackPowerValue(RANGED_ATTACK) * 0.4f) * 0.2f; + + // Kill Command + if (spellProto->SpellFamilyFlags[1] & 0x00000800) + DoneTotal += owner->GetTotalAttackPowerValue(RANGED_ATTACK) * 0.516f; + break; + case SPELLFAMILY_WARLOCK: + // Warlock Minions inherit 50% of their owner's spell power + if (IsMinion() && GetOwner()) + DoneAdvertisedBenefit = owner->SpellBaseDamageBonusDone(spellProto->GetSchoolMask(), true) * 0.5f; + break; + case SPELLFAMILY_MAGE: + // Mage Water Elementals inherit 40% of their owner's spell power + if (IsMinion() && GetOwner()) + DoneAdvertisedBenefit = owner->SpellBaseDamageBonusDone(spellProto->GetSchoolMask(), true) * 0.4f; + break; + default: + break; + } + + if (spellProto->BonusCoefficientFromAP > 0.0f) + { + float ApCoeffMod = spellProto->BonusCoefficientFromAP; + if (Player* modOwner = GetSpellModOwner()) + { + ApCoeffMod *= 100.0f; + modOwner->ApplySpellMod(spellProto, SpellModOp::BonusCoefficient, ApCoeffMod); + ApCoeffMod /= 100.0f; + } + + WeaponAttackType const attType = [&]() + { + if ((spellProto->IsRangedWeaponSpell() && spellProto->DmgClass != SPELL_DAMAGE_CLASS_MELEE)) + return RANGED_ATTACK; + + if (spellProto->HasAttribute(SPELL_ATTR3_REQUIRES_OFF_HAND_WEAPON) && !spellProto->HasAttribute(SPELL_ATTR3_REQUIRES_MAIN_HAND_WEAPON)) + return OFF_ATTACK; + + return BASE_ATTACK; + }(); + + float APbonus = float(victim->GetTotalAuraModifier(attType != RANGED_ATTACK ? SPELL_AURA_MELEE_ATTACK_POWER_ATTACKER_BONUS : SPELL_AURA_RANGED_ATTACK_POWER_ATTACKER_BONUS)); + APbonus += GetTotalAttackPowerValue(attType); + DoneTotal += int32(stack * ApCoeffMod * APbonus); + } + + // Default calculation + if (DoneAdvertisedBenefit) + { + float coeff = spellProto->Effects[effIndex].BonusMultiplier * spellProto->GetSpellScalingMultiplier(getLevel(), false); + if (Player* modOwner = GetSpellModOwner()) + { + coeff *= 100.0f; + modOwner->ApplySpellMod(spellProto, SpellModOp::BonusCoefficient, coeff); + coeff /= 100.0f; + } + + DoneTotal += int32(DoneAdvertisedBenefit * coeff * stack); + } + + callDamageScript(pdamage, DoneTotal, DoneTotalMod); + + float tmpDamage = float(pdamage + DoneTotal) * DoneTotalMod; + + // apply spellmod to Done damage (flat and pct) + if (Player* modOwner = GetSpellModOwner()) + modOwner->ApplySpellMod(spellProto, damagetype == DOT ? SpellModOp::PeriodicHealingAndDamage : SpellModOp::HealingAndDamage, tmpDamage); + + return int32(std::max(tmpDamage, 0.0f)); +} + +float Unit::SpellDamagePctDone(Unit* victim, SpellInfo const* spellProto, DamageEffectType damagetype) const +{ + if (!spellProto || !victim || damagetype == DIRECT_DAMAGE) + return 1.0f; + + // Some spells don't benefit from done mods + if (spellProto->HasAttribute(SPELL_ATTR3_IGNORE_CASTER_MODIFIERS)) + return 1.0f; + + // Some spells don't benefit from pct done mods + if (spellProto->HasAttribute(SPELL_ATTR6_IGNORE_CASTER_DAMAGE_MODIFIERS)) + return 1.0f; + + // For totems pct done mods are calculated when its calculation is run on the player in SpellDamageBonusDone. + if (GetTypeId() == TYPEID_UNIT && IsTotem()) + return 1.0f; + + // Done total percent damage auras + float DoneTotalMod = 1.0f; + + // Pet damage? + if (GetTypeId() == TYPEID_UNIT && !IsPet()) + DoneTotalMod *= ToCreature()->GetSpellDamageMod(ToCreature()->GetCreatureTemplate()->rank); + + float maxModDamagePercentSchool = 0.0f; + if (GetTypeId() == TYPEID_PLAYER) + { + for (uint32 i = 0; i < MAX_SPELL_SCHOOL; ++i) + if (spellProto->GetSchoolMask() & (1 << i)) + maxModDamagePercentSchool = std::max(maxModDamagePercentSchool, GetFloatValue(PLAYER_FIELD_MOD_DAMAGE_DONE_PCT + i)); + } + else + maxModDamagePercentSchool = GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_DAMAGE_PERCENT_DONE, spellProto->GetSchoolMask()); + + DoneTotalMod *= maxModDamagePercentSchool; + + uint32 creatureTypeMask = victim->GetCreatureTypeMask(); + + DoneTotalMod *= GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_DAMAGE_DONE_VERSUS, creatureTypeMask); + + // bonus against aurastate + DoneTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_DAMAGE_DONE_VERSUS_AURASTATE, [victim](AuraEffect const* aurEff) -> bool + { + if (victim->HasAuraState(static_cast(aurEff->GetMiscValue()))) + return true; + return false; + }); + + // Add SPELL_AURA_MOD_DAMAGE_DONE_FOR_MECHANIC percent bonus + AddPct(DoneTotalMod, GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_DAMAGE_DONE_FOR_MECHANIC, spellProto->Mechanic)); + + // done scripted mod (take it from owner) + Unit const* 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)->IsAffectingSpell(spellProto)) + continue; + + switch ((*i)->GetMiscValue()) + { + case 4920: // Molten Fury + case 4919: + { + if (victim->HasAuraState(AURA_STATE_HEALTHLESS_35_PERCENT, spellProto, this)) + AddPct(DoneTotalMod, (*i)->GetAmount()); + break; + } + case 6917: // Death's Embrace damage effect + case 6926: + case 6928: + { + // Health at 25% or less (25% stored at effect 2 of the spell) + if (victim->HealthBelowPct(CalculateSpellDamage(this, (*i)->GetSpellInfo(), EFFECT_2))) + AddPct(DoneTotalMod, (*i)->GetAmount()); + break; + } + case 6916: // Death's Embrace heal effect + case 6925: + case 6927: + if (HealthBelowPct(CalculateSpellDamage(this, (*i)->GetSpellInfo(), EFFECT_2))) + AddPct(DoneTotalMod, (*i)->GetAmount()); + break; + case 4992: // Soul Siphon + case 4993: + { + // EFFECT_0 contains the bonus amount per application, EFFECT_1 contains the maximum bonus amount + int32 bonusPerApplication = CalculateSpellDamage(this, (*i)->GetSpellInfo(), EFFECT_0); + int32 pctBonus = 0; + for (auto const& itr : victim->GetAppliedAuras()) + { + Aura const* aura = itr.second->GetBase(); + if (aura->GetCasterGUID() != GetGUID() || !aura->GetSpellInfo()->IsAbilityOfSkillType(SKILL_AFFLICTION)) + continue; + + pctBonus += bonusPerApplication; + } + + pctBonus = std::min(pctBonus, (*i)->GetAmount()); + AddPct(DoneTotalMod, pctBonus); + break; + } + case 5481: // Starfire Bonus + { + if (victim->GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_DRUID, 0x200002, 0, 0)) + AddPct(DoneTotalMod, (*i)->GetAmount()); + break; + } + } + } + + // Add SPELL_AURA_MOD_DAMAGE_FROM_MANA percent bonus + if (HasAuraType(SPELL_AURA_MOD_DAMAGE_FROM_MANA) && GetMaxPower(POWER_MANA)) + { + float totalBonus = owner->GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_DAMAGE_FROM_MANA, spellProto->GetSchoolMask()) - 1.f; + totalBonus *= ((float)GetPower(POWER_MANA) / GetMaxPower(POWER_MANA)); + if (totalBonus != 0.f) + DoneTotalMod += DoneTotalMod * totalBonus; + } + + // Custom scripted damage + switch (spellProto->SpellFamilyName) + { + case SPELLFAMILY_MAGE: + // Mastery: Frostburn + if (AuraEffect* aurEff = GetAuraEffect(76613, 0)) + if (victim->HasAuraState(AURA_STATE_FROZEN)) + if (aurEff->IsAffectingSpell(spellProto)) + AddPct(DoneTotalMod, aurEff->GetAmount()); + + // Ice Lance + if (spellProto->SpellIconID == 186) + { + if (victim->HasAuraState(AURA_STATE_FROZEN, spellProto, this) + || GetAuraEffect(SPELL_AURA_ABILITY_IGNORE_AURASTATE, SPELLFAMILY_MAGE, 0, 0, 0x0000000A, GetGUID())) + DoneTotalMod *= 2.0f; + } + + // Fingers of Frost + if (spellProto->SpellFamilyFlags[0] & 0x00020000) + if (GetAuraEffect(SPELL_AURA_ABILITY_IGNORE_AURASTATE, SPELLFAMILY_MAGE, 0, 0, 0x0000000A, GetGUID())) + AddPct(DoneTotalMod, 25.0f); + + // Torment the weak + if (spellProto->GetSchoolMask() & SPELL_SCHOOL_MASK_ARCANE) + { + if (victim->HasAuraWithMechanic((1<GetSpellInfo()->SpellIconID == 2215) + { + AddPct(DoneTotalMod, (*i)->GetAmount()); + break; + } + } + } + } + break; + case SPELLFAMILY_PRIEST: + // Smite + 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()); + } + break; + case SPELLFAMILY_DEATHKNIGHT: + // Sigil of the Vengeful Heart + if (spellProto->SpellFamilyFlags[0] & 0x2000) + if (AuraEffect* aurEff = GetAuraEffect(64962, EFFECT_1)) + DoneTotalMod += aurEff->GetAmount(); + break; + } + + return DoneTotalMod; +} + +int32 Unit::SpellDamageBonusTaken(Unit* caster, SpellInfo const* spellProto, int32 pdamage, DamageEffectType damagetype) const +{ + if (!spellProto || damagetype == DIRECT_DAMAGE) + return pdamage; + + float TakenTotalMod = 1.0f; + + // Mod damage from spell mechanic + if (uint32 mechanicMask = spellProto->GetAllEffectsMechanicMask()) + { + TakenTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_MECHANIC_DAMAGE_TAKEN_PERCENT, [mechanicMask, spellProto](AuraEffect const* aurEff) + { + if (spellProto->HasAttribute(SPELL_ATTR10_IGNORE_POSITIVE_DAMAGE_TAKEN_MODS) && aurEff->GetAmount() > 0) + return false; + + if (mechanicMask & uint32(1 << (aurEff->GetMiscValue()))) + return true; + return false; + }); + } + + //.. taken pct: dummy auras + AuraEffectList const& mDummyAuras = GetAuraEffectsByType(SPELL_AURA_DUMMY); + for (AuraEffectList::const_iterator i = mDummyAuras.begin(); i != mDummyAuras.end(); ++i) + { + switch ((*i)->GetSpellInfo()->SpellIconID) + { + // Cheat Death + case 2109: + if ((*i)->GetMiscValue() & SPELL_SCHOOL_MASK_NORMAL) + AddPct(TakenTotalMod, (*i)->GetAmount()); + break; + } + } + + // Spells with SPELL_ATTR4_IGNORE_DAMAGE_TAKEN_MODIFIERS should only benefit from mechanic damage mod auras. + if (!spellProto->HasAttribute(SPELL_ATTR4_IGNORE_DAMAGE_TAKEN_MODIFIERS)) + { + // from positive and negative SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN + // multiplicative bonus, for example Dispersion + Shadowform (0.10*0.85=0.085) + TakenTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN, [spellProto](AuraEffect const* aurEff) + { + if (spellProto->HasAttribute(SPELL_ATTR10_IGNORE_POSITIVE_DAMAGE_TAKEN_MODS) && aurEff->GetAmount() > 0) + return false; + + if ((aurEff->GetMiscValue() & spellProto->GetSchoolMask()) != 0) + return true; + + return false; + }); + + // From caster spells + if (caster) + { + TakenTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_DAMAGE_FROM_CASTER, [caster, spellProto](AuraEffect const* aurEff) + { + if (spellProto->HasAttribute(SPELL_ATTR10_IGNORE_POSITIVE_DAMAGE_TAKEN_MODS) && aurEff->GetAmount() > 0) + return false; + + if (aurEff->GetCasterGUID() == caster->GetGUID() && aurEff->IsAffectingSpell(spellProto)) + return true; + return false; + }); + } + } + + // Sanctified Wrath (bypass damage reduction) + if (caster && TakenTotalMod < 1.0f) + { + float damageReduction = 1.0f - TakenTotalMod; + Unit::AuraEffectList const& casterIgnoreResist = caster->GetAuraEffectsByType(SPELL_AURA_MOD_IGNORE_TARGET_RESIST); + for (AuraEffect const* aurEff : casterIgnoreResist) + { + if (!(aurEff->GetMiscValue() & spellProto->GetSchoolMask())) + continue; + + AddPct(damageReduction, -aurEff->GetAmount()); + } + + TakenTotalMod = 1.0f - damageReduction; + } + + float tmpDamage = pdamage * TakenTotalMod; + return int32(std::max(tmpDamage, 0.0f)); +} + +int32 Unit::SpellBaseDamageBonusDone(SpellSchoolMask schoolMask, bool withSpellPowerPctMod /*= false*/) const +{ + if (IsPlayer()) + { + float overrideSP = GetFloatValue(PLAYER_FIELD_OVERRIDE_SPELL_POWER_BY_AP_PCT); + if (overrideSP > 0.0f) + return int32(CalculatePct(GetTotalAttackPowerValue(BASE_ATTACK), overrideSP) + 0.5f); + } + + int32 DoneAdvertisedBenefit = GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_DAMAGE_DONE, schoolMask); + + if (Player const* player = ToPlayer()) + { + // Base value + DoneAdvertisedBenefit += player->GetBaseSpellPowerBonus(); + + // Check if we are ever using mana - PaperDollFrame.lua + if (GetPowerIndex(POWER_MANA) != MAX_POWERS) + DoneAdvertisedBenefit += std::max(0, int32(GetStat(StatType::Intellect)) - 10); // spellpower from intellect + + // Spell Power bonus from SPELL_AURA_MOD_SPELL_POWER_PCT + if (withSpellPowerPctMod) + DoneAdvertisedBenefit *= player->GetFloatValue(PLAYER_FIELD_MOD_SPELL_POWER_PCT); + + // Damage bonus from stats + AuraEffectList const& mDamageDoneOfStatPercent = GetAuraEffectsByType(SPELL_AURA_MOD_SPELL_DAMAGE_OF_STAT_PERCENT); + for (AuraEffect const* aurEff : mDamageDoneOfStatPercent) + { + if ((aurEff->GetMiscValue() & schoolMask) != 0) + { + // stat used stored in miscValueB for this aura + StatType const usedStat = static_cast(aurEff->GetMiscValueB()); + DoneAdvertisedBenefit += static_cast(CalculatePct(GetStat(usedStat), aurEff->GetAmount())); + } + } + + // ... and attack power + DoneAdvertisedBenefit += static_cast(CalculatePct(GetTotalAttackPowerValue(BASE_ATTACK), GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_SPELL_DAMAGE_OF_ATTACK_POWER, schoolMask))); + } + + return DoneAdvertisedBenefit; +} + +float Unit::SpellCritChanceDone(SpellInfo const* spellInfo, SpellSchoolMask schoolMask, WeaponAttackType attackType /*= BASE_ATTACK*/, bool isPeriodic /*= false*/) const +{ + //! Mobs can't crit with spells. (Except player controlled) + if (GetTypeId() == TYPEID_UNIT && !GetSpellModOwner()) + return 0.0f; + + // not critting spell + if (!isPeriodic && !spellInfo->HasAttribute(SPELL_ATTR0_CU_CAN_CRIT)) + return 0.0f; + + float crit_chance = 0.0f; + switch (spellInfo->DmgClass) + { + case SPELL_DAMAGE_CLASS_MAGIC: + { + if (schoolMask & SPELL_SCHOOL_MASK_NORMAL) + crit_chance = 0.0f; + // For other schools + else if (GetTypeId() == TYPEID_PLAYER) + crit_chance = GetFloatValue(PLAYER_SPELL_CRIT_PERCENTAGE1 + uint32(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: + { + crit_chance += GetUnitCriticalChanceDone(attackType); + crit_chance += GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_SPELL_CRIT_CHANCE_SCHOOL, schoolMask); + break; + } + 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(spellInfo, SpellModOp::CritChance, crit_chance); + + return std::max(crit_chance, 0.0f); +} + +float Unit::SpellCritChanceTaken(Unit const* caster, SpellInfo const* spellInfo, SpellSchoolMask schoolMask, float doneChance, WeaponAttackType attackType /*= BASE_ATTACK*/, bool isPeriodic /*= false*/) const +{ + // not critting spell + if (!isPeriodic && !spellInfo->HasAttribute(SPELL_ATTR0_CU_CAN_CRIT)) + return 0.0f; + + float crit_chance = doneChance; + switch (spellInfo->DmgClass) + { + case SPELL_DAMAGE_CLASS_MAGIC: + { + // taken + if (!spellInfo->IsPositive()) + { + // Modify critical chance by victim SPELL_AURA_MOD_ATTACKER_SPELL_CRIT_CHANCE + crit_chance += GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_ATTACKER_SPELL_CRIT_CHANCE, schoolMask); + + // Modify critical chance by victim SPELL_AURA_MOD_ATTACKER_SPELL_AND_WEAPON_CRIT_CHANCE + // applied after resilience + crit_chance += GetTotalAuraModifier(SPELL_AURA_MOD_ATTACKER_SPELL_AND_WEAPON_CRIT_CHANCE); + } + + // scripted (increase crit chance ... against ... target by x% + if (caster) + { + AuraEffectList const& mOverrideClassScript = caster->GetAuraEffectsByType(SPELL_AURA_OVERRIDE_CLASS_SCRIPTS); + for (AuraEffect const* aurEff : mOverrideClassScript) + { + if (!aurEff->IsAffectingSpell(spellInfo)) + continue; + + switch (aurEff->GetMiscValue()) + { + case 911: // Shatter + if (HasAuraState(AURA_STATE_FROZEN, spellInfo, caster)) + crit_chance *= (1 * aurEff->GetSpellInfo()->GetRank()); + else if (spellInfo->SpellFamilyFlags[0] & 0x00020000 || spellInfo->SpellFamilyFlags[1] & 0x00100000) + if (GetAuraEffect(SPELL_AURA_ABILITY_IGNORE_AURASTATE, SPELLFAMILY_MAGE, 0, 0, 0x0000000A, caster->GetGUID())) + crit_chance *= (1 * aurEff->GetSpellInfo()->GetRank()); + break; + case 57470: // Renewed Hope + case 57472: + if (HasAura(6788) || HasAura(47930)) + crit_chance += aurEff->GetAmount(); + break; + default: + break; + } + } + // Custom crit by class + switch (spellInfo->SpellFamilyName) + { + case SPELLFAMILY_MAGE: + // Glyph of Fire Blast + if (spellInfo->SpellFamilyFlags[0] == 0x2 && spellInfo->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_ROGUE: + // Shiv-applied poisons can't crit + if (caster->FindCurrentSpellBySpellId(5938)) + crit_chance = 0.0f; + break; + case SPELLFAMILY_PALADIN: + // Flash of light + if (spellInfo->SpellFamilyFlags[0] & 0x40000000) + { + // Sacred Shield + if (AuraEffect const* aura = GetAuraEffect(58597, 1, GetGUID())) + crit_chance += aura->GetAmount(); + break; + } + // Exorcism + else if (spellInfo->GetCategory() == 19) + { + if (GetCreatureTypeMask() & CREATURE_TYPEMASK_DEMON_OR_UNDEAD) + return 100.0f; + break; + } + + // Last Word + if (AuraEffect const* effect = caster->GetDummyAuraEffect(SPELLFAMILY_PALADIN, 2139, EFFECT_0)) + if (effect->IsAffectingSpell(spellInfo)) + if (HasAuraState(AURA_STATE_HEALTHLESS_35_PERCENT)) + crit_chance += effect->GetAmount(); + break; + case SPELLFAMILY_SHAMAN: + // Lava Burst + if ((spellInfo->SpellFamilyFlags[1] & 0x1000) != 0 || (spellInfo->SpellFamilyFlags[2] & 0x400000) != 0) + { + 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; + case SPELLFAMILY_WARLOCK: + // Searing Pain - Soulburn bonus + if (spellInfo->SpellFamilyFlags[0] & 0x00000100) + if (HasAura(74434, GetGUID())) + return 100.0f; + + // Improved Searing Pain + if (AuraEffect const* effect = caster->GetDummyAuraEffect(SPELLFAMILY_WARLOCK, 816, EFFECT_0)) + if (effect->IsAffectingSpell(spellInfo)) + if (GetHealthPct() <= 25.f) + crit_chance += effect->GetAmount(); + break; + } + + // Spell crit suppression + if (GetTypeId() == TYPEID_UNIT) + { + int32 const levelDiff = static_cast(getLevelForTarget(caster)) - caster->getLevel(); + if (levelDiff > 0) + crit_chance -= levelDiff * 0.7f; + } + } + break; + } + case SPELL_DAMAGE_CLASS_MELEE: + { + // Custom crit by class + if (caster) + { + switch (spellInfo->SpellFamilyName) + { + case SPELLFAMILY_DRUID: + // Rend and Tear - bonus crit chance for Ferocious Bite on bleeding targets + if (spellInfo->SpellFamilyFlags[0] & 0x00800000 + && spellInfo->SpellIconID == 1680 + && HasAuraState(AURA_STATE_BLEEDING)) + { + if (AuraEffect const* rendAndTear = caster->GetDummyAuraEffect(SPELLFAMILY_DRUID, 2859, 1)) + crit_chance += rendAndTear->GetAmount(); + break; + } + + // Predatory Strikes + if (spellInfo->SpellFamilyFlags[0] & 0x00010000) + if (AuraEffect const* critAuraEff = caster->GetDummyAuraEffect(SPELLFAMILY_DRUID, 1563, EFFECT_0)) + if (AuraEffect const* healthAuraEff = caster->GetDummyAuraEffect(SPELLFAMILY_DRUID, 1563, EFFECT_1)) + if (GetHealthPct() >= healthAuraEff->GetAmount()) + crit_chance += critAuraEff->GetAmount(); + break; + case SPELLFAMILY_HUNTER: + // Kill Command - Improved Kill Command + if (spellInfo->SpellFamilyFlags[1] & 0x00000800) + if (Player* player = caster->GetSpellModOwner()) + if (AuraEffect const* improvedKillCommand = player->GetDummyAuraEffect(SPELLFAMILY_HUNTER, 2221, EFFECT_0)) + crit_chance += improvedKillCommand->GetAmount(); + break; + } + } + [[fallthrough]]; // Calculate critical strike chance for both Ranged and Melee spells + } + case SPELL_DAMAGE_CLASS_RANGED: + if (caster) + { + switch (spellInfo->SpellFamilyName) + { + case SPELLFAMILY_HUNTER: + { + // Careful Aim + if (GetHealthPct() >= 90) + { + switch (spellInfo->Id) + { + case 56641: // Steady Shot + case 77767: // Cobra Shot + case 19434: // Aimed Shot + case 82928: // Aimed Shot! + if (AuraEffect* aurEff = caster->GetDummyAuraEffect(SPELLFAMILY_HUNTER, 2222, 0)) + crit_chance += aurEff->GetAmount(); + break; + default: + break; + } + } + break; + } + } + crit_chance = GetUnitCriticalChanceTaken(attackType, crit_chance); + } + break; + case SPELL_DAMAGE_CLASS_NONE: + default: + return 0.f; + } + + // for this types the bonus was already added in GetUnitCriticalChance, do not add twice + if (caster) + { + crit_chance += GetTotalAuraModifier(SPELL_AURA_MOD_CRIT_CHANCE_FOR_CASTER, [caster, spellInfo](AuraEffect const* aurEff) -> bool + { + if (aurEff->GetCasterGUID() == caster->GetGUID() && aurEff->IsAffectingSpell(spellInfo)) + return true; + return false; + }); + } + + return std::max(crit_chance, 0.0f); +} + +/*static*/ uint32 Unit::SpellCriticalDamageBonus(Unit const* caster, SpellInfo const* spellProto, uint32 damage, bool periodicDamage /*= false*/) +{ + // Calculate critical bonus + int32 crit_bonus = damage; + float crit_mod = 0.0f; + + switch (spellProto->DmgClass) + { + // Spells which are either marked as ranged or melee damage, will deal 100% additional damage + case SPELL_DAMAGE_CLASS_MELEE: + crit_bonus += damage; + break; + case SPELL_DAMAGE_CLASS_RANGED: + // Ranged spells which have magic periodic damage effect on the other hand, only deal 50% damage, just like spells + // This is a very specific case that is currently only known for Hunter spells, such as Serpent Sting and Black Arrow + if (periodicDamage && (spellProto->GetSchoolMask() & SPELL_SCHOOL_MASK_MAGIC) != 0) + crit_bonus += damage / 2; + else + crit_bonus += damage; + break; + default: + // Magic spells will simply deal 50% additional crit damage + crit_bonus += damage / 2; + break; + } + + if (caster) + { + crit_mod += (caster->GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_CRIT_DAMAGE_BONUS, spellProto->GetSchoolMask()) - 1.0f) * 100; + + if (crit_bonus != 0) + AddPct(crit_bonus, crit_mod); + + crit_bonus -= damage; + + if (damage > uint32(crit_bonus)) + { + // adds additional damage to critBonus (from talents) + if (Player* modOwner = caster->GetSpellModOwner()) + modOwner->ApplySpellMod(spellProto, SpellModOp::CritDamageAndHealing, crit_bonus); + } + + crit_bonus += damage; + } + + return crit_bonus; +} + +/*static*/ uint32 Unit::SpellCriticalHealingBonus(Unit const* caster, uint32 damage) +{ + // As of Patch 4.2.0 (2011-06-28): all healing spells now crit for 200% instead of 150% + damage *= 2; + + if (caster) + damage = int32(float(damage) * caster->GetTotalAuraMultiplier(SPELL_AURA_MOD_CRITICAL_HEALING_AMOUNT)); + + return damage; +} + +int32 Unit::SpellHealingBonusDone(Unit* victim, SpellInfo const* spellProto, int32 healamount, DamageEffectType damagetype, uint8 effIndex, uint32 stack /*= 1*/, Spell* spell /*= nullptr*/, AuraEffect const* aurEff /*= nullptr*/) const +{ + // For totems get healing bonus from owner (statue isn't totem in fact) + if (GetTypeId() == TYPEID_UNIT && IsTotem()) + if (Unit* creator = ObjectAccessor::GetUnit(*this, GetCreatorGUID())) + return creator->SpellHealingBonusDone(victim, spellProto, healamount, damagetype, effIndex, stack, spell, aurEff); + + // Some spells don't benefit from done mods + if (spellProto->HasAttribute(SPELL_ATTR3_IGNORE_CASTER_MODIFIERS)) + return healamount; + + // No bonus healing for potion spells + if (spellProto->SpellFamilyName == SPELLFAMILY_POTION) + return healamount; + + int32 DoneTotal = 0; + float DoneTotalMod = SpellHealingPctDone(victim, spellProto); + + // done scripted mod (take it from owner) + Unit const* owner = GetOwner() ? GetOwner() : this; + for (AuraEffect const* overrideClassScripts : owner->GetAuraEffectsByType(SPELL_AURA_OVERRIDE_CLASS_SCRIPTS)) + { + if (!overrideClassScripts->IsAffectingSpell(spellProto)) + continue; + + switch (overrideClassScripts->GetMiscValue()) + { + case 4415: // Increased Rejuvenation Healing + case 4953: + DoneTotal += overrideClassScripts->GetAmount(); + break; + default: + break; + } + } + + // Done fixed damage bonus auras + int32 DoneAdvertisedBenefit = SpellBaseHealingBonusDone(spellProto->GetSchoolMask(), true); + // modify spell power by victim's SPELL_AURA_MOD_HEALING auras (eg Amplify/Dampen Magic) + DoneAdvertisedBenefit += victim->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_HEALING, spellProto->GetSchoolMask()); + + // Pets just add their bonus damage to their spell damage + // note that their spell damage is just gain of their own auras + if (HasUnitTypeMask(UNIT_MASK_GUARDIAN)) + DoneAdvertisedBenefit += static_cast(this)->GetBonusDamage(); + + + // Check for table values + if (spellProto->BonusCoefficientFromAP > 0.0f) + { + WeaponAttackType const attType = (spellProto->IsRangedWeaponSpell() && spellProto->DmgClass != SPELL_DAMAGE_CLASS_MELEE) ? RANGED_ATTACK : BASE_ATTACK; + float APbonus = float(victim->GetTotalAuraModifier(attType == BASE_ATTACK ? SPELL_AURA_MELEE_ATTACK_POWER_ATTACKER_BONUS : SPELL_AURA_RANGED_ATTACK_POWER_ATTACKER_BONUS)); + APbonus += GetTotalAttackPowerValue(attType); + + DoneTotal += int32(spellProto->BonusCoefficientFromAP * stack * APbonus); + } + + // Default calculation + if (DoneAdvertisedBenefit) + { + float coeff = spellProto->Effects[effIndex].BonusMultiplier * spellProto->GetSpellScalingMultiplier(getLevel(), false); + if (Player* modOwner = GetSpellModOwner()) + { + coeff *= 100.0f; + modOwner->ApplySpellMod(spellProto, SpellModOp::BonusCoefficient, coeff); + coeff /= 100.0f; + } + + DoneTotal += int32(DoneAdvertisedBenefit * coeff * stack); + } + + 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; + } + if (spellProto->Effects[i].Effect == SPELL_EFFECT_HEALTH_LEECH) + DoneTotal = 0; + } + + if (spell) + spell->CallScriptCalcHealingHandlers(victim, healamount, DoneTotal, DoneTotalMod); + else if (aurEff) + aurEff->GetBase()->CallScriptCalcDamageAndHealingHandlers(aurEff, aurEff->GetBase()->GetApplicationOfTarget(victim->GetGUID()), victim, healamount, DoneTotal, DoneTotalMod); + + float heal = float(healamount + DoneTotal) * DoneTotalMod; + + + // apply spellmod to Done amount + if (Player* modOwner = GetSpellModOwner()) + modOwner->ApplySpellMod(spellProto, damagetype == DOT ? SpellModOp::PeriodicHealingAndDamage : SpellModOp::HealingAndDamage, heal); + + return int32(std::max(heal, 0.0f)); +} + +float Unit::SpellHealingPctDone(Unit* victim, SpellInfo const* spellProto) const +{ + // For totems pct done mods are calculated when its calculation is run on the player in SpellHealingBonusDone. + if (GetTypeId() == TYPEID_UNIT && IsTotem()) + return 1.0f; + + // No bonus healing for potion spells + if (spellProto->SpellFamilyName == SPELLFAMILY_POTION) + return 1.0f; + + float DoneTotalMod = 1.0f; + + // Healing done percent + if (IsPlayer()) + DoneTotalMod *= GetFloatValue(PLAYER_FIELD_MOD_HEALING_DONE_PCT); + else + DoneTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_HEALING_DONE_PERCENT); + + // done scripted mod (take it from owner) + Unit const* owner = GetOwner() ? GetOwner() : this; + for (AuraEffect const* overrideClassScripts : owner->GetAuraEffectsByType(SPELL_AURA_OVERRIDE_CLASS_SCRIPTS)) + { + if (!overrideClassScripts->IsAffectingSpell(spellProto)) + continue; + + switch (overrideClassScripts->GetMiscValue()) + { + case 21: // Test of Faith + case 6935: + case 6918: + if (victim->HealthBelowPct(50)) + AddPct(DoneTotalMod, overrideClassScripts->GetAmount()); + break; + case 8477: // Nourish Heal Boost + { + int32 modPercent = 0; + for (AuraEffect const* periodicHeal : victim->GetAuraEffectsByType(SPELL_AURA_PERIODIC_HEAL)) + { + if (periodicHeal->GetCasterGUID() != GetGUID() || periodicHeal->GetSpellInfo()->SpellFamilyName != SPELLFAMILY_DRUID) + continue; + + if (periodicHeal->GetSpellInfo()->SpellFamilyFlags.HasFlag(0x10 | 0x40, 0x10 | 0x4000000, 0)) + modPercent += overrideClassScripts->GetAmount() * periodicHeal->GetBase()->GetStackAmount(); + } + AddPct(DoneTotalMod, modPercent); + break; + } + default: + break; + } + } + + // Custom scripted damage + switch (spellProto->SpellFamilyName) + { + case SPELLFAMILY_SHAMAN: + { + // Nature's Blessing + if (AuraEffect const* blessing = GetDummyAuraEffect(SPELLFAMILY_SHAMAN, 2012, EFFECT_0)) + if (blessing->IsAffectingSpell(spellProto) && victim->HasAura(974, GetGUID())) + AddPct(DoneTotalMod, blessing->GetAmount()); + break; + } + default: + break; + } + + float healthPctDiff = 100.f - victim->GetHealthPct(); + for (AuraEffect const* healingDonePctVsTargetHealth : owner->GetAuraEffectsByType(SPELL_AURA_MOD_HEALING_DONE_PCT_VERSUS_TARGET_HEALTH)) + { + if (!healingDonePctVsTargetHealth->IsAffectingSpell(spellProto)) + continue; + + AddPct(DoneTotalMod, CalculatePct(float(healingDonePctVsTargetHealth->GetAmount()), healthPctDiff)); + } + + return DoneTotalMod; +} + +int32 Unit::SpellHealingBonusTaken(Unit* caster, SpellInfo const* spellProto, int32 healamount, DamageEffectType damagetype) const +{ + float TakenTotalMod = 1.0f; + + // Healing taken percent + if (IsPlayer()) + TakenTotalMod *= GetFloatValue(PLAYER_FIELD_MOD_HEALING_PCT); + else + { + float 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()); + + // Unused in Cataclysm (15595) + if (damagetype == DOT) + { + // Healing over time taken percent + float minval_hot = float(GetMaxNegativeAuraModifier(SPELL_AURA_259)); + if (minval_hot) + AddPct(TakenTotalMod, minval_hot); + + float maxval_hot = float(GetMaxPositiveAuraModifier(SPELL_AURA_259)); + if (maxval_hot) + AddPct(TakenTotalMod, maxval_hot); + } + + if (caster) + { + TakenTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_HEALING_RECEIVED, [caster, spellProto](AuraEffect const* aurEff) -> bool + { + if (caster->GetGUID() == aurEff->GetCasterGUID() && aurEff->IsAffectingSpell(spellProto)) + return true; + return false; + }); + } + + float heal = healamount * TakenTotalMod; + + return int32(std::max(heal, 0.0f)); +} + +int32 Unit::SpellBaseHealingBonusDone(SpellSchoolMask schoolMask, bool withSpellPowerPctMod /*= false*/) const +{ + if (IsPlayer()) + { + float overrideSP = GetFloatValue(PLAYER_FIELD_OVERRIDE_SPELL_POWER_BY_AP_PCT); + if (overrideSP > 0.0f) + return int32(CalculatePct(GetTotalAttackPowerValue(BASE_ATTACK), overrideSP) + 0.5f); + } + + int32 advertisedBenefit = GetTotalAuraModifier(SPELL_AURA_MOD_HEALING_DONE, [schoolMask](AuraEffect const* aurEff) -> bool + { + if (!aurEff->GetMiscValue() || (aurEff->GetMiscValue() & schoolMask) != 0) + return true; + return false; + }); + + // Healing bonus of spirit, intellect and strength + if (Player const* player = ToPlayer()) + { + // Base value + advertisedBenefit += player->GetBaseSpellPowerBonus(); + + // Check if we are ever using mana - PaperDollFrame.lua + if (GetPowerIndex(POWER_MANA) != MAX_POWERS) + advertisedBenefit += std::max(0, int32(GetStat(StatType::Intellect)) - 10); // spellpower from intellect + + // Spell Power bonus from SPELL_AURA_MOD_SPELL_POWER_PCT + if (withSpellPowerPctMod) + advertisedBenefit *= player->GetFloatValue(PLAYER_FIELD_MOD_SPELL_POWER_PCT); + + // 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) + StatType usedStat = StatType((*i)->GetSpellInfo()->Effects[(*i)->GetEffIndex()].MiscValue); + advertisedBenefit += int32(CalculatePct(GetStat(usedStat), (*i)->GetAmount())); + } + + // ... and attack power + advertisedBenefit += CalculatePct(GetTotalAttackPowerValue(BASE_ATTACK), GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_SPELL_HEALING_OF_ATTACK_POWER, schoolMask)); + } + return advertisedBenefit; +} + +bool Unit::IsImmunedToDamage(SpellSchoolMask schoolMask) const +{ + if (schoolMask == SPELL_SCHOOL_MASK_NONE) + return false; + + // If m_immuneToSchool type contain this school type, IMMUNE damage. + uint32 schoolImmunityMask = GetSchoolImmunityMask(); + if ((schoolImmunityMask & schoolMask) == schoolMask) // We need to be immune to all types + return true; + + // If m_immuneToDamage type contain magic, IMMUNE damage. + uint32 damageImmunityMask = GetDamageImmunityMask(); + if ((damageImmunityMask & schoolMask) == schoolMask) // We need to be immune to all types + return true; + + return false; +} + +bool Unit::IsImmunedToDamage(SpellInfo const* spellInfo) const +{ + if (!spellInfo) + return false; + + // for example 40175 + if (spellInfo->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES) && spellInfo->HasAttribute(SPELL_ATTR3_ALWAYS_HIT)) + return false; + + if (spellInfo->HasAttribute(SPELL_ATTR1_IMMUNITY_TO_HOSTILE_AND_FRIENDLY_EFFECTS) || spellInfo->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES)) + return false; + + if (uint32 schoolMask = spellInfo->GetSchoolMask()) + { + // If m_immuneToSchool type contain this school type, IMMUNE damage. + uint32 schoolImmunityMask = 0; + SpellImmuneContainer const& schoolList = m_spellImmune[IMMUNITY_SCHOOL]; + for (auto itr = schoolList.begin(); itr != schoolList.end(); ++itr) + if ((itr->first & schoolMask) && !spellInfo->CanPierceImmuneAura(sSpellMgr->GetSpellInfo(itr->second))) + schoolImmunityMask |= itr->first; + + // // We need to be immune to all types + if ((schoolImmunityMask & schoolMask) == schoolMask) + return true; + + // If m_immuneToDamage type contain magic, IMMUNE damage. + uint32 damageImmunityMask = GetDamageImmunityMask(); + if ((damageImmunityMask & schoolMask) == schoolMask) // We need to be immune to all types + return true; + } + + return false; +} + +bool Unit::IsImmunedToSpell(SpellInfo const* spellInfo, WorldObject const* caster, bool requireImmunityPurgesEffectAttribute /*= false*/) const +{ + if (!spellInfo) + return false; + + auto hasImmunity = [requireImmunityPurgesEffectAttribute](SpellImmuneContainer const& container, uint32 key) + { + Trinity::IteratorPair range = Trinity::Containers::MapEqualRange(container, key); + if (!requireImmunityPurgesEffectAttribute) + return range.begin() != range.end(); + + return std::any_of(range.begin(), range.end(), [](SpellImmuneContainer::value_type const& entry) + { + if (SpellInfo const* immunitySourceSpell = sSpellMgr->GetSpellInfo(entry.second)) + if (immunitySourceSpell->HasAttribute(SPELL_ATTR1_IMMUNITY_PURGES_EFFECT)) + return true; + + return false; + }); + }; + + // Single spell immunity. + SpellImmuneContainer const& idList = m_spellImmune[IMMUNITY_ID]; + if (hasImmunity(idList, spellInfo->Id)) + return true; + + if (spellInfo->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES)) + return false; + + if (uint32 dispel = spellInfo->Dispel) + { + SpellImmuneContainer const& dispelList = m_spellImmune[IMMUNITY_DISPEL]; + if (hasImmunity(dispelList, dispel)) + return true; + } + + // Spells that don't have effectMechanics. + if (uint32 mechanic = spellInfo->Mechanic) + { + SpellImmuneContainer const& mechanicList = m_spellImmune[IMMUNITY_MECHANIC]; + if (hasImmunity(mechanicList, mechanic)) + return true; + } + + bool immuneToAllEffects = true; + for (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; + + if (!IsImmunedToSpellEffect(spellInfo, i, caster, requireImmunityPurgesEffectAttribute)) + { + immuneToAllEffects = false; + break; + } + if (spellInfo->HasAttribute(SPELL_ATTR4_NO_PARTIAL_IMMUNITY)) + return true; + } + + if (immuneToAllEffects) //Return immune only if the target is immune to all spell effects. + return true; + + if (uint32 schoolMask = spellInfo->GetSchoolMask()) + { + uint32 schoolImmunityMask = 0; + SpellImmuneContainer const& schoolList = m_spellImmune[IMMUNITY_SCHOOL]; + for (auto itr = schoolList.begin(); itr != schoolList.end(); ++itr) + { + if ((itr->first & schoolMask) == 0) + continue; + + SpellInfo const* immuneSpellInfo = sSpellMgr->GetSpellInfo(itr->second); + if (requireImmunityPurgesEffectAttribute) + if (!immuneSpellInfo || !immuneSpellInfo->HasAttribute(SPELL_ATTR1_IMMUNITY_PURGES_EFFECT)) + continue; + + // Consider the school immune if any of these conditions are not satisfied. + // In case of no immuneSpellInfo, ignore that condition and check only the other conditions + if ((immuneSpellInfo && !immuneSpellInfo->IsPositive()) || !spellInfo->IsPositive() || !caster || !IsFriendlyTo(caster)) + if (!spellInfo->CanPierceImmuneAura(immuneSpellInfo)) + schoolImmunityMask |= itr->first; + } + + if ((schoolImmunityMask & schoolMask) == schoolMask) + return true; + } + + return false; +} + +uint32 Unit::GetSchoolImmunityMask() const +{ + uint32 mask = 0; + SpellImmuneContainer const& schoolList = m_spellImmune[IMMUNITY_SCHOOL]; + for (auto itr = schoolList.begin(); itr != schoolList.end(); ++itr) + mask |= itr->first; + + return mask; +} + +uint32 Unit::GetDamageImmunityMask() const +{ + uint32 mask = 0; + SpellImmuneContainer const& damageList = m_spellImmune[IMMUNITY_DAMAGE]; + for (auto itr = damageList.begin(); itr != damageList.end(); ++itr) + mask |= itr->first; + + return mask; +} + +uint32 Unit::GetMechanicImmunityMask() const +{ + uint32 mask = 0; + SpellImmuneContainer const& mechanicList = m_spellImmune[IMMUNITY_MECHANIC]; + for (auto itr = mechanicList.begin(); itr != mechanicList.end(); ++itr) + mask |= (1 << itr->first); + + return mask; +} + +bool Unit::IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index, WorldObject const* caster, + bool requireImmunityPurgesEffectAttribute /*= false*/) const +{ + if (!spellInfo || !spellInfo->Effects[index].IsEffect()) + return false; + + if (spellInfo->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES)) + return false; + + auto hasImmunity = [requireImmunityPurgesEffectAttribute](SpellImmuneContainer const& container, uint32 key) + { + Trinity::IteratorPair range = Trinity::Containers::MapEqualRange(container, key); + if (!requireImmunityPurgesEffectAttribute) + return range.begin() != range.end(); + + return std::any_of(range.begin(), range.end(), [](SpellImmuneContainer::value_type const& entry) + { + if (SpellInfo const* immunitySourceSpell = sSpellMgr->GetSpellInfo(entry.second)) + if (immunitySourceSpell->HasAttribute(SPELL_ATTR1_IMMUNITY_PURGES_EFFECT)) + return true; + + return false; + }); + }; + + + // If m_immuneToEffect type contain this effect type, IMMUNE effect. + uint32 effect = spellInfo->Effects[index].Effect; + SpellImmuneContainer const& effectList = m_spellImmune[IMMUNITY_EFFECT]; + if (hasImmunity(effectList, effect)) + return true; + + if (uint32 mechanic = spellInfo->Effects[index].Mechanic) + { + SpellImmuneContainer const& mechanicList = m_spellImmune[IMMUNITY_MECHANIC]; + if (hasImmunity(mechanicList, mechanic)) + return true; + } + + if (!spellInfo->HasAttribute(SPELL_ATTR3_ALWAYS_HIT)) + { + if (uint32 aura = spellInfo->Effects[index].ApplyAuraName) + { + SpellImmuneContainer const& list = m_spellImmune[IMMUNITY_STATE]; + if (hasImmunity(list, aura)) + return true; + + if (!spellInfo->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES)) + { + // Check for immune to application of harmful magical effects + AuraEffectList const& immuneAuraApply = GetAuraEffectsByType(SPELL_AURA_MOD_IMMUNE_AURA_APPLY_SCHOOL); + for (AuraEffectList::const_iterator iter = immuneAuraApply.begin(); iter != immuneAuraApply.end(); ++iter) + if (((*iter)->GetMiscValue() & spellInfo->GetSchoolMask()) && // Check school + ((caster && !IsFriendlyTo(caster)) || !spellInfo->IsPositiveEffect(index))) // Harmful + return true; + } + } + } + + return false; +} + +int32 Unit::MeleeDamageBonusDone(Unit* victim, int32 damage, WeaponAttackType attType, DamageEffectType damagetype, SpellInfo const* spellProto /*= nullptr*/, Mechanics mechanic /*= MECHANIC_NONE*/, SpellSchoolMask damageSchoolMask /*= SPELL_SCHOOL_MASK_NORMAL*/, Spell* spell /*= nullptr*/, AuraEffect const* aurEff /*= nullptr*/) +{ + if (!victim || damage == 0) + return 0; + + 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 const normalized = spellProto && spellProto->HasEffect(SPELL_EFFECT_NORMALIZED_WEAPON_DMG); + DoneFlatBenefit += int32(APbonus / 14.0f * GetAPMultiplier(attType, normalized)); + } + + // Done total percent damage auras + float DoneTotalMod = 1.0f; + + SpellSchoolMask schoolMask = spellProto ? spellProto->GetSchoolMask() : damageSchoolMask; + + if (!(schoolMask & SPELL_SCHOOL_MASK_NORMAL)) + { + // Some spells don't benefit from pct done mods + // mods for SPELL_SCHOOL_MASK_NORMAL are already factored in base melee damage calculation + if (!spellProto || !spellProto->HasAttribute(SPELL_ATTR6_IGNORE_CASTER_DAMAGE_MODIFIERS)) + { + float maxModDamagePercentSchool = 0.0f; + if (GetTypeId() == TYPEID_PLAYER) + { + for (uint32 i = SPELL_SCHOOL_HOLY; i < MAX_SPELL_SCHOOL; ++i) + if (schoolMask & (1 << i)) + maxModDamagePercentSchool = std::max(maxModDamagePercentSchool, GetFloatValue(PLAYER_FIELD_MOD_DAMAGE_DONE_PCT + i)); + } + else if (spellProto) + maxModDamagePercentSchool = GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_DAMAGE_PERCENT_DONE, spellProto->GetSchoolMask()); + + DoneTotalMod *= maxModDamagePercentSchool; + } + } + + DoneTotalMod *= GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_DAMAGE_DONE_VERSUS, creatureTypeMask); + + // bonus against aurastate + DoneTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_DAMAGE_DONE_VERSUS_AURASTATE, [victim](AuraEffect const* aurEff) -> bool + { + if (victim->HasAuraState(AuraStateType(aurEff->GetMiscValue()))) + return true; + return false; + }); + + // Add SPELL_AURA_MOD_DAMAGE_DONE_FOR_MECHANIC percent bonus + // Add SPELL_AURA_MOD_DAMAGE_DONE_FOR_MECHANIC percent bonus + if (mechanic != MECHANIC_NONE) + AddPct(DoneTotalMod, GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_DAMAGE_DONE_FOR_MECHANIC, mechanic)); + else if (spellProto && spellProto->Mechanic) + AddPct(DoneTotalMod, GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_DAMAGE_DONE_FOR_MECHANIC, spellProto->Mechanic)); + + if (!spellProto) + { + // Add SPELL_AURA_MOD_AUTOATTACK_DAMAGE percent bonus + AddPct(DoneTotalMod, GetTotalAuraModifier(SPELL_AURA_MOD_AUTOATTACK_DAMAGE)); + + // Add SPELL_AURA_MOD_MELEE_DAMAGE_FROM_CASTER percent bonus + // This aura name is a bit misleading but it's actually only suposed to increase melee auto attack damage + AddPct(DoneTotalMod, victim->GetTotalAuraMultiplier(SPELL_AURA_MOD_MELEE_DAMAGE_FROM_CASTER, [this](AuraEffect const* effect)->bool + { + return effect->GetCasterGUID() == GetGUID(); + })); + } + + if (spell) + spell->CallScriptCalcDamageHandlers(victim, damage, DoneFlatBenefit, DoneTotalMod); + else if (aurEff) + aurEff->GetBase()->CallScriptCalcDamageAndHealingHandlers(aurEff, aurEff->GetBase()->GetApplicationOfTarget(victim->GetGUID()), victim, damage, DoneFlatBenefit, DoneTotalMod); + + float damageF = float(damage + DoneFlatBenefit) * DoneTotalMod; + + // apply spellmod to Done damage + if (spellProto) + if (Player* modOwner = GetSpellModOwner()) + modOwner->ApplySpellMod(spellProto, damagetype == DOT ? SpellModOp::PeriodicHealingAndDamage : SpellModOp::HealingAndDamage, damageF); + + // bonus result can be negative + return int32(std::max(damageF, 0.0f)); +} + +int32 Unit::MeleeDamageBonusTaken(Unit* attacker, int32 pdamage, WeaponAttackType attType, SpellInfo const* spellProto /*= nullptr*/, SpellSchoolMask damageSchoolMask /*= SPELL_SCHOOL_MASK_NORMAL*/) +{ + if (pdamage == 0) + return 0; + + int32 TakenFlatBenefit = 0; + + uint32 meleeDamageSchoolMask = spellProto ? spellProto->GetSchoolMask() : attacker->GetMeleeDamageSchoolMask(); + + // ..taken + TakenFlatBenefit += GetTotalAuraModifier(SPELL_AURA_MOD_DAMAGE_TAKEN, [meleeDamageSchoolMask, spellProto](AuraEffect const* aurEff) + { + if (spellProto && spellProto->HasAttribute(SPELL_ATTR10_IGNORE_POSITIVE_DAMAGE_TAKEN_MODS) && aurEff->GetAmount() > 0) + return false; + + if ((aurEff->GetMiscValue() & meleeDamageSchoolMask) != 0) + return true; + + return false; + }); + + AuraType modDamageTaken = SPELL_AURA_MOD_RANGED_DAMAGE_TAKEN; + if (attType != RANGED_ATTACK) + modDamageTaken = SPELL_AURA_MOD_MELEE_DAMAGE_TAKEN; + + TakenFlatBenefit += GetTotalAuraModifier(modDamageTaken, [spellProto](AuraEffect const* aurEff) + { + if (spellProto && spellProto->HasAttribute(SPELL_ATTR10_IGNORE_POSITIVE_DAMAGE_TAKEN_MODS) && aurEff->GetAmount() > 0) + return false; + + return true; + }); + + if ((TakenFlatBenefit < 0) && (pdamage < -TakenFlatBenefit)) + return 0; + + // Taken total percent damage auras + float TakenTotalMod = 1.0f; + + // ..taken + TakenTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN, [meleeDamageSchoolMask, spellProto](AuraEffect const* aurEff) + { + if (spellProto && spellProto->HasAttribute(SPELL_ATTR10_IGNORE_POSITIVE_DAMAGE_TAKEN_MODS) && aurEff->GetAmount() > 0) + return false; + + if ((aurEff->GetMiscValue() & meleeDamageSchoolMask) != 0) + return true; + + return false; + }); + + // .. taken pct (special attacks) + if (spellProto) + { + // From caster spells + TakenTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_DAMAGE_FROM_CASTER, [attacker, spellProto](AuraEffect const* aurEff) -> bool + { + if (spellProto && spellProto->HasAttribute(SPELL_ATTR10_IGNORE_POSITIVE_DAMAGE_TAKEN_MODS) && aurEff->GetAmount() > 0) + return false; + + if (aurEff->GetCasterGUID() == attacker->GetGUID() && aurEff->IsAffectingSpell(spellProto)) + return true; + return false; + }); + + // 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, spellProto](AuraEffect const* aurEff) -> bool + { + if (spellProto && spellProto->HasAttribute(SPELL_ATTR10_IGNORE_POSITIVE_DAMAGE_TAKEN_MODS) && aurEff->GetAmount() > 0) + return false; + + if (mechanicMask & uint32(1 << (aurEff->GetMiscValue()))) + return true; + return false; + }); + } + } + + // .. taken pct: dummy auras + AuraEffectList const& mDummyAuras = GetAuraEffectsByType(SPELL_AURA_DUMMY); + for (AuraEffectList::const_iterator i = mDummyAuras.begin(); i != mDummyAuras.end(); ++i) + { + switch ((*i)->GetSpellInfo()->SpellIconID) + { + // Cheat Death + case 2109: + if ((*i)->GetMiscValue() & SPELL_SCHOOL_MASK_NORMAL) + { + if (Player* player = ToPlayer()) + { + float mod = player->GetRatingBonusValue(CR_RESILIENCE_PLAYER_DAMAGE_TAKEN) * (-8.0f); + AddPct(TakenTotalMod, std::max(mod, float((*i)->GetAmount()))); + } + } + break; + } + } + + + AuraType modDamageTakenPct = SPELL_AURA_MOD_RANGED_DAMAGE_TAKEN_PCT; + if (attType != RANGED_ATTACK) + modDamageTakenPct = SPELL_AURA_MOD_MELEE_DAMAGE_TAKEN_PCT; + + TakenTotalMod *= GetTotalAuraMultiplier(modDamageTakenPct, [spellProto](AuraEffect const* aurEff) + { + if (spellProto && spellProto->HasAttribute(SPELL_ATTR10_IGNORE_POSITIVE_DAMAGE_TAKEN_MODS) && aurEff->GetAmount() > 0) + return false; + + return true; + }); + + // Sanctified Wrath (bypass damage reduction) + if (TakenTotalMod < 1.0f) + { + SpellSchoolMask const attackSchoolMask = spellProto ? spellProto->GetSchoolMask() : damageSchoolMask; + + float damageReduction = 1.0f - TakenTotalMod; + Unit::AuraEffectList const& casterIgnoreResist = attacker->GetAuraEffectsByType(SPELL_AURA_MOD_IGNORE_TARGET_RESIST); + for (AuraEffect const* aurEff : casterIgnoreResist) + { + if (!(aurEff->GetMiscValue() & attackSchoolMask)) + continue; + + AddPct(damageReduction, -aurEff->GetAmount()); + } + + TakenTotalMod = 1.0f - damageReduction; + } + + return int32(std::max(std::ceil(static_cast(pdamage + TakenFlatBenefit) * TakenTotalMod), 0.0f)); +} + +void Unit::ApplySpellImmune(uint32 spellId, uint32 op, uint32 type, bool apply) +{ + if (apply) + m_spellImmune[op].emplace(type, spellId); + else + { + auto bounds = m_spellImmune[op].equal_range(type); + for (auto itr = bounds.first; itr != bounds.second;) + { + if (itr->second == spellId) + itr = m_spellImmune[op].erase(itr); + else + ++itr; + } + } +} + +float Unit::GetWeaponProcChance() const +{ + // normalized proc chance for weapon attack speed + // (odd formula...) + if (isAttackReady(BASE_ATTACK)) + return (GetBaseAttackTime(BASE_ATTACK) * 1.8f / 1000.0f); + else if (haveOffhandWeapon() && isAttackReady(OFF_ATTACK)) + return (GetBaseAttackTime(OFF_ATTACK) * 1.6f / 1000.0f); + return 0; +} + +float Unit::GetPPMProcChance(uint32 WeaponSpeed, float PPM, SpellInfo const* spellProto) const +{ + // proc per minute chance calculation + if (PPM <= 0) + return 0.0f; + + // Apply chance modifer aura + if (spellProto) + if (Player* modOwner = GetSpellModOwner()) + modOwner->ApplySpellMod(spellProto, SpellModOp::ProcFrequency, PPM); + + return std::floor((WeaponSpeed * PPM) / 600.0f); // result is chance in percents (probability = Speed_in_sec * (PPM / 60)) +} + +void Unit::Mount(uint32 mount, uint32 VehicleId, uint32 creatureEntry) +{ + if (mount) + SetUInt32Value(UNIT_FIELD_MOUNTDISPLAYID, mount); + + SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_MOUNT); + + if (Player* player = ToPlayer()) + { + // mount as a vehicle + if (VehicleId) + { + if (CreateVehicleKit(VehicleId, creatureEntry)) + { + player->SendOnCancelExpectedVehicleRideAura(); + + // 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->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_STUNNED); + else + player->UnsummonPetTemporaryIfAny(); + } + + // if we have charmed npc, stun him also (everywhere) + if (Unit* charm = player->GetCharmed()) + if (charm->GetTypeId() == TYPEID_UNIT) + charm->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_STUNNED); + + if (player->IsInWorld()) + player->SendMovementSetCollisionHeight(player->GetCollisionHeight(), UPDATE_COLLISION_HEIGHT_MOUNT); + } + + RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Mount); +} + +void Unit::Dismount() +{ + if (!IsMounted()) + return; + + SetUInt32Value(UNIT_FIELD_MOUNTDISPLAYID, 0); + RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_MOUNT); + + if (Player* thisPlayer = ToPlayer()) + thisPlayer->SendMovementSetCollisionHeight(thisPlayer->GetCollisionHeight(), UPDATE_COLLISION_HEIGHT_MOUNT); + + WorldPackets::Misc::Dismount packet; + packet.Guid = GetGUID(); + SendMessageToSet(packet.Write(), IsPlayer()); + + // dismount as a vehicle + if (GetTypeId() == TYPEID_PLAYER && GetVehicleKit()) + RemoveVehicleKit(); + + RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Dismount); + + // only resummon old pet if the player is already added to a map + // this prevents adding a pet to a not created map which would otherwise cause a crash + // (it could probably happen when logging in after a previous crash) + if (Player* player = ToPlayer()) + { + if (Pet* pPet = player->GetPet()) + { + if (pPet->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_STUNNED) && !pPet->HasUnitState(UNIT_STATE_STUNNED)) + pPet->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_STUNNED); + } + else + player->ResummonPetTemporaryUnSummonedIfAny(); + + // if we have charmed npc, remove stun also + if (Unit* charm = player->GetCharmed()) + if (charm->GetTypeId() == TYPEID_UNIT && charm->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_STUNNED) && !charm->HasUnitState(UNIT_STATE_STUNNED)) + charm->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_STUNNED); + } +} + +MountCapabilityEntry const* Unit::GetMountCapability(uint32 mountType) const +{ + if (!mountType) + return nullptr; + + MountTypeEntry const* mountTypeEntry = sMountTypeStore.LookupEntry(mountType); + if (!mountTypeEntry) + return nullptr; + + + uint32 areaId = GetAreaId(); + uint32 ridingSkill = 5000; + EnumFlag mountFlags = AreaMountFlags::None; + bool isUnderwater = false; + bool isInWater = false; + + if (GetTypeId() == TYPEID_PLAYER) + ridingSkill = ToPlayer()->GetSkillValue(SKILL_RIDING); + + if (HasAuraType(SPELL_AURA_MOUNT_RESTRICTIONS)) + { + for (AuraEffect const* auraEffect : GetAuraEffectsByType(SPELL_AURA_MOUNT_RESTRICTIONS)) + mountFlags |= AreaMountFlags(auraEffect->GetMiscValue()); + } + else if (AreaTableEntry const* areaTable = sAreaTableStore.LookupEntry(areaId)) + mountFlags = areaTable->GetMountFlags(); + + ZLiquidStatus liquidStatus = GetLiquidStatus(); + isUnderwater = (liquidStatus & LIQUID_MAP_UNDER_WATER) != 0; + isInWater = (liquidStatus & LIQUID_MAP_IN_WATER) != 0; + + for (uint32 i = MAX_MOUNT_CAPABILITIES; i > 0; --i) + { + MountCapabilityEntry const* mountCapability = sMountCapabilityStore.LookupEntry(mountTypeEntry->Capability[i - 1]); + if (!mountCapability) + continue; + + if (ridingSkill < mountCapability->ReqRidingSkill) + continue; + + if (!(mountCapability->Flags & MOUNT_CAPABIILTY_FLAG_IGNORE_RESTRICTIONS)) + { + if (mountCapability->Flags & MOUNT_CAPABILITY_FLAG_GROUND && !(mountFlags.HasFlag(AreaMountFlags::AllowGroundMounts))) + continue; + if (mountCapability->Flags & MOUNT_CAPABILITY_FLAG_FLYING && !(mountFlags.HasFlag(AreaMountFlags::AllowFlyingMounts))) + continue; + if (mountCapability->Flags & MOUNT_CAPABILITY_FLAG_FLOAT && !(mountFlags.HasFlag(AreaMountFlags::AllowSurfaceSwimmingMounts))) + continue; + if (mountCapability->Flags & MOUNT_CAPABILITY_FLAG_UNDERWATER && !(mountFlags.HasFlag(AreaMountFlags::AllowUnderwaterSwimmingMounts))) + continue; + } + + // Do not allow underwater restricted capabilities when not underwater + if (!(mountCapability->Flags & MOUNT_CAPABILITY_FLAG_UNDERWATER) && isUnderwater) + continue; + + // Do not allow water surface restricted capabilities when not on water surface + if (!(mountCapability->Flags & MOUNT_CAPABILITY_FLAG_FLOAT) && isInWater) + continue; + + // Do not allow liquid restricted mounts on ground or in air + if (!(mountCapability->Flags & (MOUNT_CAPABILITY_FLAG_GROUND | MOUNT_CAPABILITY_FLAG_FLYING)) && !isUnderwater && !isInWater) + continue; + + if (mountCapability->ReqMapID != -1 && + int32(GetMapId()) != mountCapability->ReqMapID && + GetMap()->GetEntry()->ParentMapID != mountCapability->ReqMapID) + continue; + + if (mountCapability->ReqAreaID && !DBCManager::IsInArea(areaId, mountCapability->ReqAreaID)) + continue; + + if (mountCapability->ReqSpellAuraID && !HasAura(mountCapability->ReqSpellAuraID)) + continue; + + if (mountCapability->ReqSpellKnownID && !HasSpell(mountCapability->ReqSpellKnownID)) + continue; + + return mountCapability; + } + + return nullptr; +} + +void Unit::UpdateMountCapability() +{ + AuraEffectList mounts = GetAuraEffectsByType(SPELL_AURA_MOUNTED); + for (AuraEffect* aurEff : mounts) + { + aurEff->RecalculateAmount(); + if (!aurEff->GetAmount()) + aurEff->GetBase()->Remove(); + else if (MountCapabilityEntry const* capability = sMountCapabilityStore.LookupEntry(aurEff->GetAmount())) // aura may get removed by interrupt flag, reapply + if (!HasAura(capability->ModSpellAuraID)) + CastSpell(this, capability->ModSpellAuraID, aurEff); + } +} + +bool Unit::IsServiceProvider() const +{ + return HasFlag(UNIT_NPC_FLAGS, + UNIT_NPC_FLAG_VENDOR | UNIT_NPC_FLAG_TRAINER | UNIT_NPC_FLAG_FLIGHTMASTER | + UNIT_NPC_FLAG_PETITIONER | UNIT_NPC_FLAG_BATTLEMASTER | UNIT_NPC_FLAG_BANKER | + UNIT_NPC_FLAG_INNKEEPER | UNIT_NPC_FLAG_SPIRITHEALER | + UNIT_NPC_FLAG_SPIRITGUIDE | UNIT_NPC_FLAG_TABARDDESIGNER | UNIT_NPC_FLAG_AUCTIONEER); +} + +void Unit::EngageWithTarget(Unit* enemy) +{ + if (!enemy) + return; + + if (CanHaveThreatList()) + m_threatManager.AddThreat(enemy, 0.0f, nullptr, true, true); + else + SetInCombatWith(enemy); +} + +void Unit::SetImmuneToAll(bool apply, bool keepCombat) +{ + if (apply) + { + SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC | UNIT_FLAG_IMMUNE_TO_NPC); + ValidateAttackersAndOwnTarget(); + if (!keepCombat) + m_combatManager.EndAllCombat(); + } + else + RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC | UNIT_FLAG_IMMUNE_TO_NPC); +} + +void Unit::SetImmuneToPC(bool apply, bool keepCombat) +{ + if (apply) + { + SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC); + ValidateAttackersAndOwnTarget(); + if (!keepCombat) + { + std::list toEnd; + for (auto const& pair : m_combatManager.GetPvECombatRefs()) + if (pair.second->GetOther(this)->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED)) + toEnd.push_back(pair.second); + for (auto const& pair : m_combatManager.GetPvPCombatRefs()) + if (pair.second->GetOther(this)->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED)) + toEnd.push_back(pair.second); + for (CombatReference* ref : toEnd) + ref->EndCombat(); + } + } + else + RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC); +} + +void Unit::SetImmuneToNPC(bool apply, bool keepCombat) +{ + if (apply) + { + SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_NPC); + ValidateAttackersAndOwnTarget(); + if (!keepCombat) + { + std::list toEnd; + for (auto const& pair : m_combatManager.GetPvECombatRefs()) + if (!pair.second->GetOther(this)->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED)) + toEnd.push_back(pair.second); + for (auto const& pair : m_combatManager.GetPvPCombatRefs()) + if (!pair.second->GetOther(this)->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED)) + toEnd.push_back(pair.second); + for (CombatReference* ref : toEnd) + ref->EndCombat(); + } + } + else + RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_NPC); +} + +bool Unit::IsThreatened() const +{ + return !m_threatManager.IsThreatListEmpty(); +} + +bool Unit::isTargetableForAttack(bool checkFakeDeath) const +{ + if (!IsAlive()) + return false; + + if (HasFlag(UNIT_FIELD_FLAGS, + UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NOT_SELECTABLE)) + return false; + + if (GetTypeId() == TYPEID_PLAYER && ToPlayer()->IsGameMaster()) + return false; + + return !HasUnitState(UNIT_STATE_UNATTACKABLE) && (!checkFakeDeath || !HasUnitState(UNIT_STATE_DIED)); +} + +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(PowerType power, int32 dVal, bool withPowerUpdate /*= true*/) +{ + int32 gain = 0; + + if (dVal == 0) + return 0; + + int32 curPower = GetPower(power); + + int32 val = dVal + curPower; + if (val <= GetMinPower(power)) + { + SetPower(power, GetMinPower(power), withPowerUpdate); + return -curPower; + } + + int32 maxPower = GetMaxPower(power); + + if (val < maxPower) + { + SetPower(power, val, withPowerUpdate); + gain = val - curPower; + } + else if (curPower != maxPower) + { + SetPower(power, maxPower, withPowerUpdate); + gain = maxPower - curPower; + } + + return gain; +} + +// Based on client function +float Unit::GetPowerRegen(PowerType powerType, bool isInCombat) const +{ + uint32 powerSlot = MAX_POWERS; + float totalRegeneration = 0.f; + double result = DBCManager::GetBasePowerRegen(powerType, isInCombat, _powerBarId); + float combatRegeneration = result; + + powerSlot = GetPowerIndex(powerType); + if (powerSlot != MAX_POWERS) + { + uint32 powerRegenIndex = isInCombat ? UNIT_FIELD_POWER_REGEN_INTERRUPTED_FLAT_MODIFIER : UNIT_FIELD_POWER_REGEN_FLAT_MODIFIER; + totalRegeneration = GetFloatValue(powerRegenIndex + powerSlot) + combatRegeneration; + result = totalRegeneration; + } + else + totalRegeneration = result; + + if (IsPlayer() && DBCManager::IsPowerTypeAffectedByHaste(powerType)) + { + float hasteRegen = GetFloatValue(PLAYER_FIELD_MOD_HASTE_REGEN); + if (G3D::fuzzyNe(hasteRegen, 0.f)) + result = totalRegeneration / hasteRegen; + } + + return result; +} + +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; + + return false; +} + +bool Unit::IsVisible() const +{ + return (m_serverSideVisibility.GetValue(SERVERSIDE_VISIBILITY_GM) > SEC_PLAYER) ? false : true; +} + +void Unit::SetVisible(bool x) +{ + if (!x) + m_serverSideVisibility.SetValue(SERVERSIDE_VISIBILITY_GM, SEC_GAMEMASTER); + else + m_serverSideVisibility.SetValue(SERVERSIDE_VISIBILITY_GM, SEC_PLAYER); + + UpdateObjectVisibility(); +} + +void Unit::UpdateSpeed(UnitMoveType mtype) +{ + int32 main_speed_mod = 0; + float stack_bonus = 1.0f; + float non_stack_bonus = 1.0f; + + switch (mtype) + { + // Only apply debuffs + case MOVE_FLIGHT_BACK: + case MOVE_RUN_BACK: + case MOVE_SWIM_BACK: + break; + case MOVE_WALK: + return; + case MOVE_RUN: + { + if (IsMounted()) // Use on mount auras + { + main_speed_mod = GetMaxPositiveAuraModifier(SPELL_AURA_MOD_INCREASE_MOUNTED_SPEED); + stack_bonus = GetTotalAuraMultiplier(SPELL_AURA_MOD_MOUNTED_SPEED_ALWAYS); + non_stack_bonus += GetMaxPositiveAuraModifier(SPELL_AURA_MOD_MOUNTED_SPEED_NOT_STACK) / 100.0f; + } + else + { + main_speed_mod = GetMaxPositiveAuraModifier(SPELL_AURA_MOD_INCREASE_SPEED); + stack_bonus = GetTotalAuraMultiplier(SPELL_AURA_MOD_SPEED_ALWAYS); + non_stack_bonus += GetMaxPositiveAuraModifier(SPELL_AURA_MOD_SPEED_NOT_STACK) / 100.0f; + } + break; + } + case MOVE_SWIM: + { + main_speed_mod = GetMaxPositiveAuraModifier(SPELL_AURA_MOD_INCREASE_SWIM_SPEED); + break; + } + case MOVE_FLIGHT: + { + if (GetTypeId() == TYPEID_UNIT && IsControlledByPlayer()) // not sure if good for pet + { + main_speed_mod = GetMaxPositiveAuraModifier(SPELL_AURA_MOD_INCREASE_VEHICLE_FLIGHT_SPEED); + stack_bonus = GetTotalAuraMultiplier(SPELL_AURA_MOD_VEHICLE_SPEED_ALWAYS); + + // for some spells this mod is applied on vehicle owner + int32 owner_speed_mod = 0; + + if (Unit* owner = GetCharmer()) + owner_speed_mod = owner->GetMaxPositiveAuraModifier(SPELL_AURA_MOD_INCREASE_VEHICLE_FLIGHT_SPEED); + + main_speed_mod = std::max(main_speed_mod, owner_speed_mod); + } + else if (IsMounted()) + { + main_speed_mod = GetMaxPositiveAuraModifier(SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED); + stack_bonus = GetTotalAuraMultiplier(SPELL_AURA_MOD_MOUNTED_FLIGHT_SPEED_ALWAYS); + } + else // Use not mount (shapeshift for example) auras (should stack) + main_speed_mod = GetTotalAuraModifier(SPELL_AURA_MOD_INCREASE_FLIGHT_SPEED) + GetTotalAuraModifier(SPELL_AURA_MOD_INCREASE_VEHICLE_FLIGHT_SPEED); + + non_stack_bonus += GetMaxPositiveAuraModifier(SPELL_AURA_MOD_FLIGHT_SPEED_NOT_STACK) / 100.0f; + + // Update speed for vehicle if available + if (GetTypeId() == TYPEID_PLAYER && GetVehicle()) + GetVehicleBase()->UpdateSpeed(MOVE_FLIGHT); + break; + } + default: + TC_LOG_ERROR("entities.unit", "Unit::UpdateSpeed: Unsupported move type (%d)", 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()) + { + CreatureMovementInfo const& movementInfo = ToCreature()->GetCreatureMovementInfo(); + speed *= movementInfo.HasRunSpeedOverriden ? (movementInfo.RunSpeed / baseMoveSpeed[MOVE_RUN]) : 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; + } + + // Apply strongest slow aura mod to speed + int32 slow = GetMaxNegativeAuraModifier(SPELL_AURA_MOD_DECREASE_SPEED); + if (slow) + AddPct(speed, slow); + + if (float minSpeedMod = (float)GetMaxPositiveAuraModifier(SPELL_AURA_MOD_MINIMUM_SPEED)) + { + float baseMinSpeed = 1.0f; + if (!GetOwnerOrCreatorGUID().IsPlayer() && !IsHunterPet() && IsCreature()) + { + CreatureMovementInfo const& movementInfo = ToCreature()->GetCreatureMovementInfo(); + baseMinSpeed = movementInfo.HasRunSpeedOverriden ? (movementInfo.RunSpeed / baseMoveSpeed[MOVE_RUN]) : ToCreature()->GetCreatureTemplate()->speed_run; + } + + float min_speed = CalculatePct(baseMinSpeed, minSpeedMod); + if (speed < min_speed) + speed = min_speed; + } + + SetSpeedRate(mtype, speed); +} + +float Unit::GetSpeed(UnitMoveType mtype) const +{ + return m_speed_rate[mtype]*(IsControlledByPlayer() ? playerBaseMoveSpeed[mtype] : baseMoveSpeed[mtype]); +} + +void Unit::SetSpeed(UnitMoveType mtype, float newValue) +{ + SetSpeedRate(mtype, newValue / (IsControlledByPlayer() ? playerBaseMoveSpeed[mtype] : baseMoveSpeed[mtype])); +} + +void Unit::SetSpeedRate(UnitMoveType mtype, float rate) +{ + if (rate < 0) + rate = 0.0f; + + // Update speed only on change + if (m_speed_rate[mtype] == rate) + return; + + + float newSpeedFlat = rate * (IsControlledByPlayer() ? playerBaseMoveSpeed[mtype] : baseMoveSpeed[mtype]); + if (IsMovedByClient() && IsInWorld()) + { + MovementPacketSender::SendSpeedChangeToMover(this, mtype, newSpeedFlat); + SetSpeedRateReal(mtype, rate); + } + else if (IsMovedByClient() && !IsInWorld()) // (1) + SetSpeedRateReal(mtype, rate); + else // <=> if(!IsMovedByPlayer()) + { + SetSpeedRateReal(mtype, rate); + MovementPacketSender::SendSpeedChangeToAll(this, mtype, newSpeedFlat); + } + + // explaination of (1): + // If the player is not in the world yet, it won't reply to the packets requiring an ack. And once the player is in the world, next time a movement + // packet which requires an ack is sent to the client (change of speed for example), the client is kicked from the + // server on the ground that it should have replied to the first packet first. That line is a hacky fix + // in the sense that it doesn't work like that in retail since buffs are applied only after the player has been + // initialized in the world. cf description of PR #18771 +} + +void Unit::SetSpeedRateReal(UnitMoveType mtype, float rate) +{ + if (!IsInCombat() && ToPlayer()) + if (Pet* pet = ToPlayer()->GetPet()) + pet->SetSpeedRate(mtype, rate); + + m_speed_rate[mtype] = rate; + PropagateSpeedChange(); +} + +void Unit::FollowTarget(Unit* target) +{ + if (!target) + { + TC_LOG_ERROR("entities.unit", "Unit::FollowTarget: Unit (%s) tried to follow a non-existant target.", GetGUID().ToString().c_str()); + return; + } + + // Determine follow configuration + bool joinFormation = false; // unit will follow its target in a generated formation shape and catches up to its target + bool catchUpToTarget = false; // unit will allign to the target speed and catches up to the target automatically + bool faceTarget = false; // unit will face its target with every spline + float distance = DEFAULT_FOLLOW_DISTANCE_PET; + + if (TempSummon* summon = ToTempSummon()) + { + if (SummonPropertiesEntry const* properties = summon->m_Properties) + { + // Allied summons, pet summons join a formation unless the following exceptions are being met. + if (properties->Control == SUMMON_CATEGORY_ALLY || properties->Control == SUMMON_CATEGORY_PET) + joinFormation = true; + + // Companion minipets will always be able to catch up to their target + if (properties->Slot == SUMMON_SLOT_MINIPET) + { + joinFormation = false; + catchUpToTarget = true; + faceTarget = true; + distance = DEFAULT_FOLLOW_DISTANCE; + } + + // Quest npcs follow their target outside of formations + if (properties->Slot == SUMMON_SLOT_QUEST) + { + joinFormation = false; + distance = DEFAULT_FOLLOW_DISTANCE; + } + } + + // Pets and minions alwys move in a formation of their target + if (summon->IsPet()) + joinFormation = true; + } + else if (IsCharmed()) + joinFormation = true; + + // Unit is already following its target + if (joinFormation && target->HasFormationFollower(this)) + return; + + GetMotionMaster()->MoveFollow(target, distance, DEFAULT_FOLLOW_ANGLE, joinFormation, catchUpToTarget, faceTarget); +} + +void Unit::RemoveFormationFollower(Unit* follower) +{ + for (FormationFollowerGUIDContainer::const_iterator itr = _formationFollowers.begin(); itr != _formationFollowers.end();) + { + ObjectGuid guid = *itr; + if (Unit* followingUnit = ObjectAccessor::GetUnit(*this, guid)) + { + if (followingUnit == follower) + itr = _formationFollowers.erase(itr); + else + ++itr; + } + else + itr = _formationFollowers.erase(itr); + } +} + +bool Unit::HasFormationFollower(Unit* follower) const +{ + for (ObjectGuid guid : _formationFollowers) + if (Unit* followingUnit = ObjectAccessor::GetUnit(*this, guid)) + if (followingUnit == follower) + return true; + + return false; +} + +void Unit::RemoveAllPursuers() +{ + for (uint8 i = 0; i < AsUnderlyingType(PursuingType::Max); ++i) + { + while (!_unitsPursuingMe[i].empty()) + (*_unitsPursuingMe[i].begin())->SetTarget(PursuingType(i), nullptr); + } +} + +void Unit::setDeathState(DeathState s) +{ + // Death state needs to be updated before RemoveAllAurasOnDeath() is called, to prevent entering combat + m_deathState = s; + + if (s != ALIVE && s != JUST_RESPAWNED) + { + CombatStop(); + ClearComboPointHolders(); // any combo points pointed to unit lost at it death + + if (IsNonMeleeSpellCast(false)) + InterruptNonMeleeSpells(false); + + ExitVehicle(); // Exit vehicle before calling RemoveAllControlled + // vehicles use special type of charm that is not removed by the next function + // triggering an assert + UnsummonAllTotems(); + RemoveAllControlled(); + RemoveAllAurasOnDeath(); + } + + if (s == JUST_DIED) + { + // remove aurastates allowing special moves + ClearAllReactives(); + ClearDiminishings(); + CancelPendingCastRequest(); + if (IsInWorld()) + { + // Only clear MotionMaster for entities that exists in world + // Avoids crashes in the following conditions : + // * Using 'call pet' on dead pets + // * Using 'call stabled pet' + // * Logging in with dead pets + GetMotionMaster()->Clear(false); + GetMotionMaster()->MoveIdle(); + } + StopMoving(); + DisableSpline(); + // without this when removing IncreaseMaxHealth aura player may stuck with 1 hp + // do not why since in IncreaseMaxHealth currenthealth is checked + SetHealth(0); + SetPower(GetPowerType(), 0); + SetUInt32Value(UNIT_NPC_EMOTESTATE, 0); + + // players in instance don't have ZoneScript, but they have InstanceScript + if (ZoneScript* zoneScript = GetZoneScript() ? GetZoneScript() : GetInstanceScript()) + zoneScript->OnUnitDeath(this); + } + else if (s == JUST_RESPAWNED) + RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SKINNABLE); // clear skinnable for creature and player (at battleground) +} + +//====================================================================== + +void Unit::AtEnterCombat() +{ + if (Spell* spell = m_currentSpells[CURRENT_GENERIC_SPELL]) + if (spell->getState() == SPELL_STATE_PREPARING + && spell->m_spellInfo->HasAttribute(SPELL_ATTR0_NOT_IN_COMBAT_ONLY_PEACEFUL) + && spell->m_spellInfo->InterruptFlags.HasFlag(SpellInterruptFlags::Combat) + && !spell->m_spellInfo->HasAura(SPELL_AURA_MOUNTED)) // for some reason mounts are not interrupted by entering combat even with both interrupt flag and attribute + InterruptSpell(CURRENT_GENERIC_SPELL, false, false); + + RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::EnteringCombat); + ProcSkillsAndAuras(this, nullptr, PROC_FLAG_ENTER_COMBAT, PROC_FLAG_NONE, PROC_SPELL_TYPE_MASK_ALL, PROC_SPELL_PHASE_NONE, PROC_HIT_NONE, nullptr, nullptr, nullptr); +} + +void Unit::AtExitCombat() +{ + RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::LeavingCombat); +} + +void Unit::AtTargetAttacked(Unit* target, bool canInitialAggro) +{ + if (!target->IsEngaged() && !canInitialAggro) + return; + target->EngageWithTarget(this); + if (Unit* targetOwner = target->GetCharmerOrOwner()) + targetOwner->EngageWithTarget(this); + + Player* myPlayerOwner = GetCharmerOrOwnerPlayerOrPlayerItself(); + Player* targetPlayerOwner = target->GetCharmerOrOwnerPlayerOrPlayerItself(); + if (myPlayerOwner && targetPlayerOwner && !(myPlayerOwner->duel && myPlayerOwner->duel->opponent == targetPlayerOwner)) + { + myPlayerOwner->UpdatePvP(true); + myPlayerOwner->SetContestedPvP(targetPlayerOwner); + myPlayerOwner->RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::PvPActive); + } +} + +void Unit::UpdatePetCombatState() +{ + ASSERT(!IsPet()); // player pets do not use UNIT_FLAG_PET_IN_COMBAT for this purpose - but player pets should also never have minions of their own to call this + + // UNIT_FLAG_PET_IN_COMBAT is set by default when the summoner is in combat by himself + if (IsInCombat()) + return; + + bool state = false; + for (Unit* minion : m_Controlled) + if (minion->IsInCombat()) + { + state = true; + break; + } + + if (state) + SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PET_IN_COMBAT); + else + RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PET_IN_COMBAT); +} + +//====================================================================== + +DiminishingLevels Unit::GetDiminishing(DiminishingGroup group) const +{ + DiminishingReturn const& diminish = m_Diminishing[group]; + if (!diminish.hitCount) + return DIMINISHING_LEVEL_1; + + // If last spell was cast more than 15 seconds ago - reset level + if (!diminish.stack && GetMSTimeDiffToNow(diminish.hitTime) > 15000) + return DIMINISHING_LEVEL_1; + + return DiminishingLevels(diminish.hitCount); +} + +void Unit::IncrDiminishing(SpellInfo const* auraSpellInfo, bool triggered) +{ + DiminishingGroup group = auraSpellInfo->GetDiminishingReturnsGroupForSpell(triggered); + uint32 currentLevel = GetDiminishing(group); + uint32 const maxLevel = auraSpellInfo->GetDiminishingReturnsMaxLevel(triggered); + + DiminishingReturn& diminish = m_Diminishing[group]; + if (currentLevel < maxLevel) + diminish.hitCount = currentLevel + 1; +} + +bool Unit::ApplyDiminishingToDuration(SpellInfo const* auraSpellInfo, bool triggered, int32& duration, WorldObject* caster, DiminishingLevels previousLevel) const +{ + DiminishingGroup const group = auraSpellInfo->GetDiminishingReturnsGroupForSpell(triggered); + if (duration == -1 || group == DIMINISHING_NONE) + return true; + + int32 const limitDuration = auraSpellInfo->GetDiminishingReturnsLimitDuration(triggered); + + // test pet/charm masters instead pets/charmeds + Unit const* targetOwner = GetCharmerOrOwner(); + Unit const* casterOwner = caster->GetCharmerOrOwner(); + + // 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; + WorldObject const* source = casterOwner ? casterOwner : caster; + + if (target->IsAffectedByDiminishingReturns() && source->GetTypeId() == TYPEID_PLAYER) + duration = limitDuration; + } + + float mod = 1.0f; + if (group == DIMINISHING_TAUNT) + { + if (GetTypeId() == TYPEID_UNIT && (ToCreature()->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_TAUNT_DIMINISH)) + { + DiminishingLevels diminish = previousLevel; + switch (diminish) + { + case DIMINISHING_LEVEL_1: break; + case DIMINISHING_LEVEL_2: mod = 0.65f; break; + case DIMINISHING_LEVEL_3: mod = 0.4225f; break; + case DIMINISHING_LEVEL_4: mod = 0.274625f; break; + case DIMINISHING_LEVEL_TAUNT_IMMUNE: mod = 0.0f; break; + default: break; + } + } + } + // Some diminishings applies to mobs too (for example, Stun) + else if (auraSpellInfo->GetDiminishingReturnsGroupType(triggered) == DRTYPE_ALL || + (auraSpellInfo->GetDiminishingReturnsGroupType(triggered) == DRTYPE_PLAYER && + (targetOwner ? targetOwner->IsAffectedByDiminishingReturns() : IsAffectedByDiminishingReturns()))) + { + DiminishingLevels diminish = previousLevel; + switch (diminish) + { + case DIMINISHING_LEVEL_1: break; + case DIMINISHING_LEVEL_2: mod = 0.5f; break; + case DIMINISHING_LEVEL_3: mod = 0.25f; break; + case DIMINISHING_LEVEL_IMMUNE: mod = 0.0f; break; + default: break; + } + } + + duration = int32(duration * mod); + return (duration != 0); +} + +void Unit::ApplyDiminishingAura(DiminishingGroup group, bool apply) +{ + // Checking for existing in the table + DiminishingReturn& diminish = m_Diminishing[group]; + + if (apply) + ++diminish.stack; + else if (diminish.stack) + { + --diminish.stack; + + // Remember time after last aura from group removed + if (!diminish.stack) + diminish.hitTime = getMSTime(); + } +} + +void Unit::ClearDiminishings() +{ + for (DiminishingReturn& dim : m_Diminishing) + dim.Clear(); +} + +uint32 Unit::GetCreatureType() const +{ + if (GetTypeId() == TYPEID_PLAYER) + { + ShapeshiftForm form = GetShapeshiftForm(); + SpellShapeshiftFormEntry const* ssEntry = sSpellShapeshiftFormStore.LookupEntry(form); + if (ssEntry && ssEntry->CreatureType > 0) + return ssEntry->CreatureType; + else + return CREATURE_TYPE_HUMANOID; + } + else + return ToCreature()->GetCreatureTemplate()->type; +} + +uint32 Unit::GetCreatureTypeMask() const +{ + uint32 creatureType = GetCreatureType(); + return (creatureType >= 1) ? (1 << (creatureType - 1)) : 0; +} + +void Unit::SetShapeshiftForm(ShapeshiftForm form) +{ + SetByteValue(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_SHAPESHIFT_FORM, form); +} + +bool Unit::IsInFeralForm() const +{ + ShapeshiftForm form = GetShapeshiftForm(); + return form == FORM_CAT || form == FORM_BEAR; +} + +bool Unit::IsInDisallowedMountForm() const +{ + if (ShapeshiftForm form = GetShapeshiftForm()) + { + SpellShapeshiftFormEntry const* shapeshift = sSpellShapeshiftFormStore.LookupEntry(form); + if (!shapeshift) + return true; + + if (!shapeshift->GetFlags().HasFlag(SpellShapeshiftFormFlags::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->Flags & 0x80)) + if (race && !(race->Flags & 0x4)) + return true; + + return false; +} + +/*####################################### +######## ######## +######## STAT SYSTEM ######## +######## ######## +#######################################*/ + +void Unit::HandleStatFlatModifier(UnitMods unitMod, UnitModifierFlatType modifierType, float amount, bool apply) +{ + if (unitMod >= UNIT_MOD_END || modifierType >= MODIFIER_TYPE_FLAT_END) + { + TC_LOG_ERROR("entities.unit", "ERROR in HandleStatFlatModifier(): non-existing UnitMods or wrong UnitModifierType!"); + return; + } + + if (!amount) + return; + + switch (modifierType) + { + case BASE_VALUE: + case TOTAL_VALUE: + m_auraFlatModifiersGroup[unitMod][modifierType] += apply ? amount : -amount; + break; + default: + break; + } + + UpdateUnitMod(unitMod); +} + +void Unit::ApplyStatPctModifier(UnitMods unitMod, UnitModifierPctType modifierType, float pct) +{ + if (unitMod >= UNIT_MOD_END || modifierType >= MODIFIER_TYPE_PCT_END) + { + TC_LOG_ERROR("entities.unit", "ERROR in ApplyStatPctModifier(): non-existing UnitMods or wrong UnitModifierType!"); + return; + } + + if (!pct) + return; + + switch (modifierType) + { + case BASE_PCT: + case TOTAL_PCT: + AddPct(m_auraPctModifiersGroup[unitMod][modifierType], pct); + break; + default: + break; + } + + UpdateUnitMod(unitMod); +} + +void Unit::SetStatFlatModifier(UnitMods unitMod, UnitModifierFlatType modifierType, float val) +{ + if (m_auraFlatModifiersGroup[unitMod][modifierType] == val) + return; + + m_auraFlatModifiersGroup[unitMod][modifierType] = val; + UpdateUnitMod(unitMod); +} + +void Unit::SetStatPctModifier(UnitMods unitMod, UnitModifierPctType modifierType, float val) +{ + if (m_auraPctModifiersGroup[unitMod][modifierType] == val) + return; + + m_auraPctModifiersGroup[unitMod][modifierType] = val; + UpdateUnitMod(unitMod); +} + +float Unit::GetFlatModifierValue(UnitMods unitMod, UnitModifierFlatType modifierType) const +{ + if (unitMod >= UNIT_MOD_END || modifierType >= MODIFIER_TYPE_FLAT_END) + { + TC_LOG_ERROR("entities.unit", "attempt to access non-existing modifier value from UnitMods!"); + return 0.0f; + } + + return m_auraFlatModifiersGroup[unitMod][modifierType]; +} + +float Unit::GetPctModifierValue(UnitMods unitMod, UnitModifierPctType modifierType) const +{ + if (unitMod >= UNIT_MOD_END || modifierType >= MODIFIER_TYPE_PCT_END) + { + TC_LOG_ERROR("entities.unit", "attempt to access non-existing modifier value from UnitMods!"); + return 0.0f; + } + + return m_auraPctModifiersGroup[unitMod][modifierType]; +} + +void Unit::UpdateUnitMod(UnitMods unitMod) +{ + if (!CanModifyStats()) + return; + + switch (unitMod) + { + case UNIT_MOD_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_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::GetTotalAuraModValue(UnitMods unitMod) const +{ + if (unitMod >= UNIT_MOD_END) + { + TC_LOG_ERROR("entities.unit", "attempt to access non-existing UnitMods in GetTotalAuraModValue()!"); + return 0.0f; + } + + float value = 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; +} + +PowerType 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_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) const +{ + if (attType == RANGED_ATTACK) + { + int32 ap = GetInt32Value(UNIT_FIELD_RANGED_ATTACK_POWER) + + GetInt32Value(UNIT_FIELD_RANGED_ATTACK_POWER_MOD_POS) + - GetInt32Value(UNIT_FIELD_RANGED_ATTACK_POWER_MOD_NEG); + + 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_MOD_POS) + - GetInt32Value(UNIT_FIELD_ATTACK_POWER_MOD_NEG); + + if (ap < 0) + return 0.0f; + return ap * (1.0f + GetFloatValue(UNIT_FIELD_ATTACK_POWER_MULTIPLIER)); + } +} + +float Unit::GetWeaponDamageRange(WeaponAttackType attType, WeaponDamageRange type) const +{ + if (attType == OFF_ATTACK && !haveOffhandWeapon()) + return 0.0f; + + return m_weaponDamage[attType][type]; +} + +bool Unit::CanFreeMove() const +{ + return !HasUnitState(UNIT_STATE_CONFUSED | UNIT_STATE_FLEEING | UNIT_STATE_IN_FLIGHT | + UNIT_STATE_ROOT | UNIT_STATE_STUNNED | UNIT_STATE_DISTRACTED) && GetOwnerOrCreatorGUID().IsEmpty(); +} + +void Unit::SetLevel(uint8 lvl) +{ + SetUInt32Value(UNIT_FIELD_LEVEL, lvl); + + if (Player* player = ToPlayer()) + { + // group update + if (player->GetGroup()) + player->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_LEVEL); + + sCharacterCache->UpdateCharacterLevel(GetGUID(), lvl); + } +} + +void Unit::SetHealth(uint32 val) +{ + if (getDeathState() == JUST_DIED || getDeathState() == CORPSE) + val = 0; + else if (GetTypeId() == TYPEID_PLAYER && getDeathState() == DEAD) + val = 1; + else + { + uint32 maxHealth = GetMaxHealth(); + if (maxHealth < val) + val = maxHealth; + } + + SetUInt32Value(UNIT_FIELD_HEALTH, val); + + // group update + if (Player* player = ToPlayer()) + { + if (player->GetGroup()) + player->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_CUR_HP); + } + else if (Pet* pet = ToCreature()->ToPet()) + { + if (pet->isControlled()) + { + Unit* owner = GetOwner(); + if (owner && (owner->GetTypeId() == TYPEID_PLAYER) && owner->ToPlayer()->GetGroup()) + owner->ToPlayer()->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 (GetTypeId() == TYPEID_PLAYER) + { + if (ToPlayer()->GetGroup()) + ToPlayer()->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_MAX_HP); + } + else if (Pet* pet = ToCreature()->ToPet()) + { + if (pet->isControlled()) + { + Unit* owner = GetOwner(); + if (owner && (owner->GetTypeId() == TYPEID_PLAYER) && owner->ToPlayer()->GetGroup()) + owner->ToPlayer()->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_PET_MAX_HP); + } + } + + if (val < health) + SetHealth(val); +} + +int32 Unit::GetPower(PowerType power) const +{ + uint32 powerIndex = GetPowerIndex(power); + if (powerIndex == MAX_POWERS) + return 0; + + return GetUInt32Value(UNIT_FIELD_POWER1 + powerIndex); +} + +int32 Unit::GetMaxPower(PowerType power) const +{ + uint32 powerIndex = GetPowerIndex(power); + if (powerIndex == MAX_POWERS) + return 0; + + return GetInt32Value(UNIT_FIELD_MAXPOWER1 + powerIndex); +} + +void Unit::SetPower(PowerType power, int32 val, bool withPowerUpdate /*= true*/) +{ + uint32 powerIndex = GetPowerIndex(power); + if (powerIndex == MAX_POWERS) + return; + + int32 maxPower = int32(GetMaxPower(power)); + if (maxPower < val) + val = maxPower; + + SetInt32Value(UNIT_FIELD_POWER1 + powerIndex, val); + + if (IsInWorld() && withPowerUpdate) + { + WorldPackets::Combat::PowerUpdate packet; + packet.Guid = GetGUID(); + packet.Powers.emplace_back(val, power); + SendMessageToSet(packet.Write(), IsPlayer()); + } + + // group update + if (Player* player = ToPlayer()) + { + 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->GetTypeId() == TYPEID_PLAYER) && owner->ToPlayer()->GetGroup()) + owner->ToPlayer()->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_PET_CUR_POWER); + } + } +} + +void Unit::SetMaxPower(PowerType power, int32 val) +{ + uint32 powerIndex = GetPowerIndex(power); + if (powerIndex == MAX_POWERS) + return; + + int32 cur_power = GetPower(power); + SetInt32Value(UNIT_FIELD_MAXPOWER1 + powerIndex, val); + + // group update + if (GetTypeId() == TYPEID_PLAYER) + { + if (ToPlayer()->GetGroup()) + ToPlayer()->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_MAX_POWER); + } + else if (Pet* pet = ToCreature()->ToPet()) + { + if (pet->isControlled()) + { + Unit* owner = GetOwner(); + if (owner && (owner->GetTypeId() == TYPEID_PLAYER) && owner->ToPlayer()->GetGroup()) + owner->ToPlayer()->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_PET_MAX_POWER); + } + } + + if (val < cur_power) + SetPower(power, val); +} + +void Unit::Regenerate(PowerType powerType, uint32 diff) +{ + uint32 maxValue = GetMaxPower(powerType); + if (!maxValue) + return; + + if (!HasFlag(UNIT_FIELD_FLAGS_2, UNIT_FLAG2_REGENERATE_POWER)) + return; + + uint32 curValue = GetPower(powerType); + + // Block power regeneration + if (HasAuraTypeWithValue(SPELL_AURA_PREVENT_REGENERATE_POWER, powerType) || HasAuraType(SPELL_AURA_INTERRUPT_REGEN)) + return; + + // Skip regeneration for power type we cannot have + uint32 powerIndex = GetPowerIndex(powerType); + if (powerIndex == MAX_POWERS) + return; + + float addValue = GetPowerRegen(powerType, IsInCombat()) * (diff / float(IN_MILLISECONDS)); + + // Apply config values + switch (powerType) + { + case POWER_MANA: + addValue *= sWorld->getRate(RATE_POWER_MANA); + break; + case POWER_RAGE: + addValue *= sWorld->getRate(RATE_POWER_RAGE_LOSS); + break; + case POWER_RUNIC_POWER: + addValue *= sWorld->getRate(RATE_POWER_RUNICPOWER_LOSS); + break; + case POWER_FOCUS: + addValue *= sWorld->getRate(RATE_POWER_FOCUS); + break; + case POWER_ENERGY: + addValue *= sWorld->getRate(RATE_POWER_ENERGY); + break; + default: + break; + } + + if (addValue < 0.0f) + { + if (curValue == 0) + return; + } + else if (addValue > 0.0f) + { + if (curValue == maxValue) + return; + } + else + return; + + addValue += _powerFraction[powerIndex]; + uint32 integerValue = uint32(std::fabs(addValue)); + + if (addValue < 0.0f) + { + if (curValue > integerValue) + { + curValue -= integerValue; + _powerFraction[powerIndex] = addValue + integerValue; + } + else + { + curValue = 0; + _powerFraction[powerIndex] = 0; + } + } + else + { + curValue += integerValue; + + if (curValue > maxValue) + { + curValue = maxValue; + _powerFraction[powerIndex] = 0; + } + else + _powerFraction[powerIndex] = addValue - integerValue; + } + + // Players update their powers in 2 seconds intervals, creatures in 1 second ones + if (_powerUpdateTimer <= 0) + SetPower(powerType, curValue); + else + UpdateUInt32Value(UNIT_FIELD_POWER1 + powerIndex, curValue); +} + +void Unit::RegisterPowerTypes() +{ + for (uint8 i = POWER_MANA; i < MAX_POWERS; ++i) + { + uint32 powerIndex = GetPowerIndex(PowerType(i)); + if (powerIndex == MAX_POWERS || powerIndex == MAX_POWERS_PER_CLASS) + continue; + + _usedPowerTypes[powerIndex] = static_cast(i); + } +} + +void Unit::AIUpdateTick(uint32 diff) +{ + if (UnitAI* ai = GetAI()) + { + m_aiLocked = true; + ai->UpdateAI(diff); + m_aiLocked = false; + } +} + +void Unit::PushAI(UnitAI* newAI) +{ + i_AIs.emplace(newAI); +} + +void Unit::SetAI(UnitAI* newAI) +{ + PushAI(newAI); + RefreshAI(); +} + +bool Unit::PopAI() +{ + if (!i_AIs.empty()) + { + i_AIs.pop(); + return true; + } + else + return false; +} + +void Unit::RefreshAI() +{ + ASSERT(!m_aiLocked, "Tried to change current AI during UpdateAI()"); + if (i_AIs.empty()) + i_AI = nullptr; + else + i_AI = i_AIs.top(); +} + +void Unit::ScheduleAIChange() +{ + bool const charmed = IsCharmed(); + + if (charmed) + PushAI(GetScheduledChangeAI()); + else + { + RestoreDisabledAI(); + PushAI(GetScheduledChangeAI()); //This could actually be PopAI() to get the previous AI but it's required atm to trigger UpdateCharmAI() + } +} + +void Unit::RestoreDisabledAI() +{ + // Keep popping the stack until we either reach the bottom or find a valid AI + while (PopAI()) + if (GetTopAI() && dynamic_cast(GetTopAI()) == nullptr) + return; +} + +UnitAI* Unit::GetScheduledChangeAI() +{ + if (Creature* creature = ToCreature()) + return new ScheduledChangeAI(creature); + else + return nullptr; +} + +bool Unit::HasScheduledAIChange() const +{ + if (UnitAI* ai = GetAI()) + return dynamic_cast(ai) != nullptr; + else + return true; +} + +void Unit::AddToWorld() +{ + WorldObject::AddToWorld(); + + RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::EnterWorld); +} + +void Unit::RemoveFromWorld() +{ + // cleanup + ASSERT(GetGUID()); + + if (IsInWorld()) + { + m_duringRemoveFromWorld = true; + if (UnitAI* ai = GetAI()) + ai->LeavingWorld(); + + if (IsVehicle()) + RemoveVehicleKit(true); + + RemoveCharmAuras(); + RemoveBindSightAuras(); + RemoveNotOwnSingleTargetAuras(); + RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::LeaveWorld); + + RemoveAllGameObjects(); + RemoveAllDynObjects(); + + ExitVehicle(); // Remove applied auras with SPELL_AURA_CONTROL_VEHICLE + UnsummonAllTotems(); + RemoveAllControlled(); + + RemoveAreaAurasDueToLeaveWorld(); + + // clear idle movement slot to finalize follow movement to unregister formation targets + GetMotionMaster()->Clear(MOTION_SLOT_IDLE); + RemoveAllPursuers(); + + if (IsCharmed()) + RemoveCharmedBy(nullptr); + + ASSERT(!GetCharmedGUID(), "Unit %u has charmed guid when removed from world", GetEntry()); + ASSERT(!GetCharmerGUID(), "Unit %u has charmer guid when removed from world", GetEntry()); + + if (Unit* owner = GetOwner()) + { + if (owner->m_Controlled.find(this) != owner->m_Controlled.end()) + { + TC_LOG_FATAL("entities.unit", "Unit %u is in controlled list of %u when removed from world", GetEntry(), owner->GetEntry()); + ABORT(); + } + } + + WorldObject::RemoveFromWorld(); + m_duringRemoveFromWorld = false; + } +} + +void Unit::CleanupBeforeRemoveFromMap(bool finalCleanup) +{ + // This needs to be before RemoveFromWorld to make GetCaster() return a valid pointer on aura removal + InterruptNonMeleeSpells(true); + + if (IsInWorld()) + RemoveFromWorld(); + else + { + // cleanup that must happen even if not in world + if (IsVehicle()) + RemoveVehicleKit(true); + } + + ASSERT(GetGUID()); + + // A unit may be in removelist and not in world, but it is still in grid + // and may have some references during delete + RemoveAllAuras(); + RemoveAllGameObjects(); + + if (finalCleanup) + m_cleanupDone = true; + + CombatStop(); + ClearComboPointHolders(); +} + +void Unit::CleanupsBeforeDelete(bool finalCleanup) +{ + CleanupBeforeRemoveFromMap(finalCleanup); + + WorldObject::CleanupsBeforeDelete(finalCleanup); +} + +void Unit::UpdateCharmAI() +{ + if (IsCharmed()) + { + UnitAI* newAI = nullptr; + if (IsPlayer()) + { + if (Unit* charmer = GetCharmer()) + { + // first, we check if the creature's own AI specifies an override playerai for its owned players + if (Creature* creatureCharmer = charmer->ToCreature()) + { + if (CreatureAI* charmerAI = creatureCharmer->AI()) + newAI = charmerAI->GetAIForCharmedPlayer(ToPlayer()); + } + else + TC_LOG_ERROR("misc", "Attempt to assign charm AI to player %s who is charmed by non-creature %s.", GetGUID().ToString().c_str(), GetCharmerGUID().ToString().c_str()); + } + if (!newAI) // otherwise, we default to the generic one + newAI = new SimpleCharmedPlayerAI(ToPlayer()); + } + else + { + ASSERT(IsCreature()); + if (isPossessed() || IsVehicle()) + newAI = new PossessedAI(ToCreature()); + else + newAI = new PetAI(ToCreature()); + } + + ASSERT(newAI); + SetAI(newAI); + newAI->OnCharmed(true); + } + else + { + RestoreDisabledAI(); + // Hack: this is required because we want to call OnCharmed(true) on the restored AI + RefreshAI(); + if (UnitAI* ai = GetAI()) + ai->OnCharmed(true); + } +} + +CharmInfo* Unit::InitCharmInfo() +{ + if (!m_charmInfo) + m_charmInfo = std::make_unique(this); + + return m_charmInfo.get(); +} + +void Unit::DeleteCharmInfo() +{ + if (!m_charmInfo) + return; + + m_charmInfo->RestoreState(); + m_charmInfo.reset(); +} + +bool Unit::isFrozen() const +{ + return HasAuraState(AURA_STATE_FROZEN); +} + +ProcFlagsHit createProcHitMask(SpellNonMeleeDamage* damageInfo, SpellMissInfo missCondition) +{ + ProcFlagsHit hitMask = PROC_HIT_NONE; + // Check victim state + if (missCondition != SPELL_MISS_NONE) + { + switch (missCondition) + { + case SPELL_MISS_MISS: + hitMask |= PROC_HIT_MISS; + break; + case SPELL_MISS_DODGE: + hitMask |= PROC_HIT_DODGE; + break; + case SPELL_MISS_PARRY: + hitMask |= PROC_HIT_PARRY; + break; + case SPELL_MISS_BLOCK: + // spells can't be partially blocked (it's damage can though) + hitMask |= PROC_HIT_BLOCK | PROC_HIT_FULL_BLOCK; + break; + case SPELL_MISS_EVADE: + hitMask |= PROC_HIT_EVADE; + break; + case SPELL_MISS_IMMUNE: + case SPELL_MISS_IMMUNE2: + hitMask |= PROC_HIT_IMMUNE; + break; + case SPELL_MISS_DEFLECT: + hitMask |= PROC_HIT_DEFLECT; + break; + case SPELL_MISS_ABSORB: + hitMask |= PROC_HIT_ABSORB; + break; + case SPELL_MISS_REFLECT: + hitMask |= PROC_HIT_REFLECT; + break; + case SPELL_MISS_RESIST: + hitMask |= PROC_HIT_FULL_RESIST; + break; + default: + break; + } + } + else + { + // On block + if (damageInfo->blocked) + { + hitMask |= PROC_HIT_BLOCK; + if (damageInfo->fullBlock) + hitMask |= PROC_HIT_FULL_BLOCK; + } + // On absorb + if (damageInfo->absorb) + hitMask |= PROC_HIT_ABSORB; + + // Don't set hit/crit hitMask if damage is nullified + bool const damageNullified = (damageInfo->HitInfo & (HITINFO_FULL_ABSORB | HITINFO_FULL_RESIST)) != 0 || (hitMask & PROC_HIT_FULL_BLOCK) != 0; + if (!damageNullified) + { + // On crit + if (damageInfo->HitInfo & SPELL_HIT_TYPE_CRIT) + hitMask |= PROC_HIT_CRITICAL; + else + hitMask |= PROC_HIT_NORMAL; + } + else if ((damageInfo->HitInfo & HITINFO_FULL_RESIST) != 0) + hitMask |= PROC_HIT_FULL_RESIST; + } + + return hitMask; +} + +void Unit::ProcSkillsAndReactives(bool isVictim, Unit* procTarget, ProcFlags typeMask, ProcFlagsHit hitMask, WeaponAttackType /*attType*/) +{ + // Player is loaded now - do not allow passive spell casts to proc + if (GetTypeId() == TYPEID_PLAYER && ToPlayer()->GetSession()->PlayerLoading()) + return; + + // For melee/ranged based attack need update skills and set some Aura states if victim present + if (typeMask & MELEE_BASED_TRIGGER_MASK && procTarget) + { + // If exist crit/parry/dodge/block need update aura state (for victim and attacker) + if (hitMask & (PROC_HIT_CRITICAL | PROC_HIT_PARRY | PROC_HIT_DODGE | PROC_HIT_BLOCK)) + { + // for victim + if (isVictim) + { + // Skip checks for Death Knights when in Blood Presence (Rune Strike) + if (getClass() == CLASS_DEATH_KNIGHT && hitMask & (PROC_HIT_DODGE | PROC_HIT_PARRY | PROC_HIT_BLOCK)) + if (GetAuraEffect(SPELL_AURA_MOD_BASE_RESISTANCE_PCT, SPELLFAMILY_DEATHKNIGHT, 0x800000, 0, 0, GetGUID())) + return; + + // if victim and dodge attack + if (hitMask & PROC_HIT_DODGE) + { + // Update AURA_STATE on dodge + if (getClass() != CLASS_ROGUE) // skip Rogue Riposte + { + ModifyAuraState(AURA_STATE_DEFENSE, true); + StartReactiveTimer(REACTIVE_DEFENSE); + } + } + // if victim and parry attack + if (hitMask & PROC_HIT_PARRY) + { + // For Hunters only Counterattack (skip Mongoose bite) + if (getClass() == CLASS_HUNTER) + { + 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 (hitMask & PROC_HIT_BLOCK) + { + ModifyAuraState(AURA_STATE_DEFENSE, true); + StartReactiveTimer(REACTIVE_DEFENSE); + } + } + else // For attacker + { + // Overpower on victim dodge + if ((hitMask & PROC_HIT_DODGE) && GetTypeId() == TYPEID_PLAYER && getClass() == CLASS_WARRIOR) + { + ToPlayer()->AddComboPoints(procTarget, 1); + StartReactiveTimer(REACTIVE_OVERPOWER); + } + } + } + } +} + +void Unit::GetProcAurasTriggeredOnEvent(AuraApplicationProcContainer& aurasTriggeringProc, AuraApplicationList* procAuras, ProcEventInfo& eventInfo) +{ + std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now(); + + auto processAuraApplication = [&](AuraApplication* aurApp) + { + if (uint32 procEffectMask = aurApp->GetBase()->GetProcEffectMask(aurApp, eventInfo, now)) + { + aurApp->GetBase()->PrepareProcToTrigger(aurApp, eventInfo, now); + aurasTriggeringProc.emplace_back(procEffectMask, aurApp); + } + else + { + if (aurApp->GetBase()->GetSpellInfo()->HasAttribute(SPELL_ATTR0_PROC_FAILURE_BURNS_CHARGE)) + { + if (SpellProcEntry const* procEntry = sSpellMgr->GetSpellProcEntry(aurApp->GetBase()->GetSpellInfo()->Id)) + { + aurApp->GetBase()->PrepareProcChargeDrop(procEntry, eventInfo); + aurasTriggeringProc.emplace_back(0, aurApp); + } + } + + if (aurApp->GetBase()->GetSpellInfo()->HasAttribute(SPELL_ATTR2_PROC_COOLDOWN_ON_FAILURE)) + if (SpellProcEntry const* procEntry = sSpellMgr->GetSpellProcEntry(aurApp->GetBase()->GetSpellInfo()->Id)) + aurApp->GetBase()->AddProcCooldown(procEntry, now); + } + }; + + // use provided list of auras which can proc + if (procAuras) + { + for (AuraApplication* aurApp : *procAuras) + { + ASSERT(aurApp->GetTarget() == this); + processAuraApplication(aurApp); + } + } + // or generate one on our own + else + { + for (AuraApplicationMap::iterator itr = GetAppliedAuras().begin(); itr != GetAppliedAuras().end(); ++itr) + processAuraApplication(itr->second); + } +} + +void Unit::TriggerAurasProcOnEvent(Unit* actionTarget, ProcFlags typeMaskActor, ProcFlags typeMaskActionTarget, ProcFlagsSpellType spellTypeMask, + ProcFlagsSpellPhase spellPhaseMask, ProcFlagsHit hitMask, Spell* spell, DamageInfo* damageInfo, HealInfo* healInfo) +{ + // prepare data for self trigger + ProcEventInfo myProcEventInfo(this, actionTarget, actionTarget, typeMaskActor, spellTypeMask, spellPhaseMask, hitMask, spell, damageInfo, healInfo); + if (typeMaskActor) + { + AuraApplicationProcContainer myAurasTriggeringProc; + GetProcAurasTriggeredOnEvent(myAurasTriggeringProc, nullptr, myProcEventInfo); + + // needed for example for Cobra Strikes, pet does the attack, but aura is on owner + if (Player* modOwner = GetSpellModOwner()) + { + if (modOwner != this && spell) + { + AuraApplicationList modAuras; + for (auto itr = modOwner->GetAppliedAuras().begin(); itr != modOwner->GetAppliedAuras().end(); ++itr) + { + if (spell->m_appliedMods.count(itr->second->GetBase()) != 0) + modAuras.push_front(itr->second); + } + modOwner->GetProcAurasTriggeredOnEvent(myAurasTriggeringProc, &modAuras, myProcEventInfo); + } + } + TriggerAurasProcOnEvent(myProcEventInfo, myAurasTriggeringProc); + } + + // prepare data for target trigger + ProcEventInfo targetProcEventInfo(this, actionTarget, this, typeMaskActionTarget, spellTypeMask, spellPhaseMask, hitMask, spell, damageInfo, healInfo); + if (typeMaskActionTarget && actionTarget) + { + AuraApplicationProcContainer targetAurasTriggeringProc; + actionTarget->GetProcAurasTriggeredOnEvent(targetAurasTriggeringProc, nullptr, targetProcEventInfo); + actionTarget->TriggerAurasProcOnEvent(targetProcEventInfo, targetAurasTriggeringProc); + } +} + +void Unit::TriggerAurasProcOnEvent(ProcEventInfo& eventInfo, AuraApplicationProcContainer& aurasTriggeringProc) +{ + Spell const* triggeringSpell = eventInfo.GetProcSpell(); + bool const disableProcs = triggeringSpell && triggeringSpell->IsProcDisabled(); + + int32 oldProcChainLength = std::exchange(m_procChainLength, std::max(m_procChainLength + 1, triggeringSpell ? triggeringSpell->GetProcChainLength() : 0)); + + if (disableProcs) + SetCantProc(true); + + for (auto const& [procEffectMask, aurApp] : aurasTriggeringProc) + { + if (aurApp->GetRemoveMode().HasAnyFlag()) + continue; + + aurApp->GetBase()->TriggerProcOnEvent(procEffectMask, aurApp, eventInfo); + } + + if (disableProcs) + SetCantProc(false); + + m_procChainLength = oldProcChainLength; +} + +///----------Pet responses methods----------------- +void Unit::SendPetActionFeedback(uint8 response, uint32 spellId /*=0*/) +{ + Unit* owner = GetOwner(); + if (!owner || owner->GetTypeId() != TYPEID_PLAYER) + return; + + WorldPackets::Pet::PetActionFeedback actionFeedback; + actionFeedback.Response = response; + actionFeedback.SpellID = spellId; + owner->ToPlayer()->SendDirectMessage(actionFeedback.Write()); +} + +void Unit::SendPetActionSound(int32 action) +{ + Unit* owner = GetOwner(); + if (!owner || owner->GetTypeId() != TYPEID_PLAYER) + return; + + WorldPackets::Pet::PetActionSound actionSound; + actionSound.UnitGUID = GetGUID(); + actionSound.Action = action; + owner->ToPlayer()->SendDirectMessage(actionSound.Write()); +} + +void Unit::SendPetAIReaction(ObjectGuid guid) +{ + Unit* owner = GetOwner(); + if (!owner || owner->GetTypeId() != TYPEID_PLAYER) + return; + + WorldPackets::Combat::AIReaction packet; + packet.UnitGUID = guid; + packet.Reaction = AI_REACTION_HOSTILE; + + owner->ToPlayer()->SendDirectMessage(packet.Write()); +} + +///----------End of Pet responses methods---------- + +void Unit::PropagateSpeedChange() +{ + GetMotionMaster()->PropagateSpeedChange(); +} + +void Unit::StopMoving() +{ + ClearUnitState(UNIT_STATE_MOVING); + + // not need send any packets if not in world or not moving + if (!IsInWorld() || movespline->Finalized()) + return; + + // Update position now since Stop does not start a new movement that can be updated later + if (movespline->HasStarted()) + UpdateSplinePosition(); + Movement::MoveSplineInit init(this); + init.Stop(); +} + +void Unit::PauseMovement(uint32 timer/* = 0*/, uint8 slot/* = 0*/, bool forced/* = true*/) +{ + if (IsInvalidMovementSlot(slot)) + return; + + if (MovementGenerator* movementGenerator = GetMotionMaster()->GetMotionSlot(MovementSlot(slot))) + movementGenerator->Pause(timer); + + if (forced && GetMotionMaster()->GetCurrentSlot() == MovementSlot(slot)) + StopMoving(); +} + +void Unit::ResumeMovement(uint32 timer/* = 0*/, uint8 slot/* = 0*/) +{ + if (IsInvalidMovementSlot(slot)) + return; + + if (MovementGenerator* movementGenerator = GetMotionMaster()->GetMotionSlot(MovementSlot(slot))) + movementGenerator->Resume(timer); +} + +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; +} + +void Unit::SetStandState(uint8 state) +{ + SetByteValue(UNIT_FIELD_BYTES_1, UNIT_BYTES_1_OFFSET_STAND_STATE, state); + + if (IsStandState()) + RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Standing); + + if (IsPlayer()) + { + WorldPackets::Misc::StandStateUpdate packet; + packet.State = state; + ToPlayer()->SendDirectMessage(packet.Write()); + } +} + +void Unit::SetAnimTier(AnimTier animTier, bool notifyClient /*= true*/) +{ + SetByteValue(UNIT_FIELD_BYTES_1, UNIT_BYTES_1_OFFSET_ANIM_TIER, AsUnderlyingType(animTier)); + + if (notifyClient) + { + WorldPackets::Misc::SetAnimTier setAnimTier; + setAnimTier.Unit = GetGUID(); + setAnimTier.Tier = AsUnderlyingType(animTier); + SendMessageToSet(setAnimTier.Write(), true); + } +} + +bool Unit::IsPolymorphed() const +{ + uint32 transformId = 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 = GetTypeId() == TYPEID_PLAYER ? 0.1 : 0.01; + SetObjectScale(std::max(scale, scaleMin)); +} + +void Unit::SetDisplayId(uint32 modelId, bool setNative /*= false*/) +{ + SetUInt32Value(UNIT_FIELD_DISPLAYID, modelId); + // Set Gender by modelId + if (CreatureModelInfo const* minfo = sObjectMgr->GetCreatureModelInfo(modelId)) + SetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_GENDER, minfo->gender); + + if (setNative) + SetUInt32Value(UNIT_FIELD_NATIVEDISPLAYID, modelId); +} + +void Unit::RestoreDisplayId() +{ + AuraEffect* handledAura = nullptr; + // try to receive model from transform auras + AuraEffectList const& transforms = GetAuraEffectsByType(SPELL_AURA_TRANSFORM); + if (!transforms.empty()) + { + // iterate over already applied transform auras - from newest to oldest + for (auto i = transforms.begin(); i != transforms.end(); ++i) + { + if (AuraApplication const* aurApp = (*i)->GetBase()->GetApplicationOfTarget(GetGUID())) + { + if (!handledAura) + handledAura = (*i); + // prefer negative auras + if (!aurApp->IsPositive()) + { + handledAura = (*i); + break; + } + } + } + } + + AuraEffectList const& shapeshiftAura = GetAuraEffectsByType(SPELL_AURA_MOD_SHAPESHIFT); + + // transform aura was found + if (handledAura) + { + handledAura->HandleEffect(this, AURA_EFFECT_HANDLE_SEND_FOR_CLIENT, true); + return; + } + 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; + } + } + + // no auras found - set modelid to default + SetDisplayId(GetNativeDisplayId()); +} + +void Unit::SendComboPoints() +{ + if (!IsPlayer()) + return; + + if (Unit* combotarget = ObjectAccessor::GetUnit(*this, m_comboTarget)) + { + WorldPacket data; + Player* movingMe = GetCharmerOrSelfPlayer(); + if (movingMe != this) + { + data.Initialize(SMSG_PET_UPDATE_COMBO_POINTS, movingMe->GetPackGUID().size() + combotarget->GetPackGUID().size() + 1); + data << movingMe->GetPackGUID(); + } + else + data.Initialize(SMSG_UPDATE_COMBO_POINTS, combotarget->GetPackGUID().size() + 1); + data << combotarget->GetPackGUID(); + data << uint8(m_comboPoints); + ToPlayer()->SendDirectMessage(&data); + } +} + +void Unit::AddComboPoints(Unit* target, int8 count, Spell* spell) +{ + if (!count) + return; + + int8* comboPoints = spell ? &spell->m_comboPointGain : &m_comboPoints; + + // without combo points lost (duration checked in aura) + RemoveAurasByType(SPELL_AURA_RETAIN_COMBO_POINTS); + + if (target->GetGUID() == m_comboTarget) + *comboPoints += count; + else + { + if (m_comboTarget) + if (Unit* target2 = ObjectAccessor::GetUnit(*this, m_comboTarget)) + target2->RemoveComboPointHolder(GetGUID()); + + // Spells will always add value to m_comboPoints eventualy, so it must be cleared first + if (spell) + m_comboPoints = 0; + + m_comboTarget = target->GetGUID(); + *comboPoints = count; + + target->AddComboPointHolder(GetGUID()); + } + + if (*comboPoints > 5) + *comboPoints = 5; + else if (*comboPoints < 0) + *comboPoints = 0; + + if (!spell) + SendComboPoints(); +} + +void Unit::GainSpellComboPoints(int8 count) +{ + if (!count) + return; + + m_comboPoints += count; + if (m_comboPoints > 5) m_comboPoints = 5; + else if (m_comboPoints < 0) m_comboPoints = 0; + + SendComboPoints(); +} + +void Unit::ClearComboPoints() +{ + if (!m_comboTarget) + return; + + // without combopoints lost (duration checked in aura) + RemoveAurasByType(SPELL_AURA_RETAIN_COMBO_POINTS); + + m_comboPoints = 0; + + SendComboPoints(); + + if (Unit* target = ObjectAccessor::GetUnit(*this, m_comboTarget)) + target->RemoveComboPointHolder(GetGUID()); + + m_comboTarget.Clear(); +} + +void Unit::ClearComboPointHolders() +{ + while (!m_ComboPointHolders.empty()) + { + ObjectGuid guid = *m_ComboPointHolders.begin(); + + Player* player = ObjectAccessor::GetPlayer(*this, guid); + if (player && player->GetComboTarget() == GetGUID()) // recheck for safe + player->ClearComboPoints(); // remove also guid from m_ComboPointHolders; + else + m_ComboPointHolders.erase(guid); // or remove manually + } +} + +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 (getClass() == CLASS_HUNTER && HasAuraState(AURA_STATE_HUNTER_PARRY)) + ModifyAuraState(AURA_STATE_HUNTER_PARRY, false); + if (getClass() == CLASS_WARRIOR && GetTypeId() == TYPEID_PLAYER) + ToPlayer()->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 (getClass() == CLASS_HUNTER && HasAuraState(AURA_STATE_HUNTER_PARRY)) + ModifyAuraState(AURA_STATE_HUNTER_PARRY, false); + break; + case REACTIVE_OVERPOWER: + if (getClass() == CLASS_WARRIOR && GetTypeId() == TYPEID_PLAYER) + ToPlayer()->ClearComboPoints(); + break; + } + } + else + { + m_reactiveTimer[reactive] -= p_time; + } + } +} + +Unit* Unit::SelectNearbyTarget(Unit* exclude, float dist) const +{ + std::list targets; + Trinity::AnyUnfriendlyUnitInObjectRangeCheck u_check(this, this, dist); + Trinity::UnitListSearcher searcher(this, targets, u_check); + Cell::VisitAllObjects(this, searcher, dist); + + // remove current target + if (GetVictim()) + targets.remove(GetVictim()); + + if (exclude) + targets.remove(exclude); + + // remove not LoS targets + for (std::list::iterator tIter = targets.begin(); tIter != targets.end();) + { + if (!IsWithinLOSInMap(*tIter) || (*tIter)->IsTotem() || (*tIter)->IsSpiritService() || (*tIter)->IsCritter()) + targets.erase(tIter++); + else + ++tIter; + } + + // no appropriate targets + if (targets.empty()) + return nullptr; + + // select random + return Trinity::Containers::SelectRandomContainerElement(targets); +} + +void ApplyPercentModFloatVar(float& var, float val, bool apply) +{ + var *= (apply ? (100.0f + val) / 100.0f : 100.0f / (100.0f + val)); +} + +uint32 Unit::GetBaseAttackTime(WeaponAttackType att) const +{ + return m_baseAttackSpeed[att]; +} + +void Unit::SetBaseAttackTime(WeaponAttackType att, uint32 val) +{ + m_baseAttackSpeed[att] = val; + UpdateAttackTimeField(att); +} + +void Unit::UpdateAttackTimeField(WeaponAttackType att) +{ + switch (att) + { + case BASE_ATTACK: + case OFF_ATTACK: + SetFloatValue(UNIT_FIELD_BASEATTACKTIME + AsUnderlyingType(att), uint32(m_baseAttackSpeed[att] * m_modAttackSpeedPct[att])); + break; + case RANGED_ATTACK: + SetFloatValue(UNIT_FIELD_RANGEDATTACKTIME, uint32(m_baseAttackSpeed[RANGED_ATTACK] * m_modAttackSpeedPct[RANGED_ATTACK])); + break; + default: + break; + } +} + +void Unit::ApplyAttackTimePercentMod(WeaponAttackType att, float val, bool apply) +{ + float amount = GetFloatValue(UNIT_FIELD_BASEATTACKTIME + AsUnderlyingType(att)); + float hasteAmount = GetTypeId() == TYPEID_PLAYER ? GetFloatValue(PLAYER_FIELD_MOD_HASTE) : 0.f; + float rangedHasteAmount = GetTypeId() == TYPEID_PLAYER ? GetFloatValue(PLAYER_FIELD_MOD_RANGED_HASTE) : 0.f; + + float remainingTimePct = float(m_attackTimer[att]) / (m_baseAttackSpeed[att] * m_modAttackSpeedPct[att]); + if (val > 0.f) + { + ApplyPercentModFloatVar(m_modAttackSpeedPct[att], val, !apply); + ApplyPercentModFloatVar(amount, val, !apply); + + if (GetTypeId() == TYPEID_PLAYER) + { + if (att == BASE_ATTACK) + ApplyPercentModFloatVar(hasteAmount, val, !apply); + else if (att == RANGED_ATTACK) + ApplyPercentModFloatVar(rangedHasteAmount, val, !apply); + } + } + else + { + ApplyPercentModFloatVar(m_modAttackSpeedPct[att], -val, apply); + ApplyPercentModFloatVar(amount, -val, apply); + + if (GetTypeId() == TYPEID_PLAYER) + { + if (att == BASE_ATTACK) + ApplyPercentModFloatVar(hasteAmount, -val, apply); + else if (att == RANGED_ATTACK) + ApplyPercentModFloatVar(rangedHasteAmount, -val, apply); + } + } + + SetFloatValue(UNIT_FIELD_BASEATTACKTIME + AsUnderlyingType(att), amount); + + if (GetTypeId() == TYPEID_PLAYER) + { + SetFloatValue(PLAYER_FIELD_MOD_HASTE, hasteAmount); + SetFloatValue(PLAYER_FIELD_MOD_RANGED_HASTE, rangedHasteAmount); + } + + UpdateAttackTimeField(att); + m_attackTimer[att] = uint32(m_baseAttackSpeed[att] * m_modAttackSpeedPct[att] * remainingTimePct); +} + +void Unit::ApplyHasteRegenMod(float val, bool apply) +{ + if (!IsPlayer()) + return; + + float amount = GetFloatValue(PLAYER_FIELD_MOD_HASTE_REGEN); + + if (val > 0.f) + ApplyPercentModFloatVar(amount, val, !apply); + else + ApplyPercentModFloatVar(amount, -val, apply); + + SetFloatValue(PLAYER_FIELD_MOD_HASTE_REGEN, amount); + + for (PowerType powerType : GetUsedPowerTypes()) + { + if (powerType == MAX_POWERS || GetPowerIndex(powerType) == MAX_POWERS) + continue; + + if (DBCManager::IsPowerTypeAffectedByHaste(powerType)) + UpdatePowerRegeneration(powerType); + } + + if (IsPlayer() && getClass() == CLASS_DEATH_KNIGHT) + UpdatePowerRegeneration(POWER_RUNE); +} + +void Unit::ApplyCastTimePercentMod(float val, bool apply, bool withCastHaste /*= true*/, bool withCastSpeed /*= true*/) +{ + float speedAmount = GetFloatValue(UNIT_MOD_CAST_SPEED); + float hasteAmount = GetFloatValue(UNIT_MOD_CAST_HASTE); + + if (val > 0.f) + { + if (withCastSpeed) + ApplyPercentModFloatVar(speedAmount, val, !apply); + if (withCastHaste) + ApplyPercentModFloatVar(hasteAmount, val, !apply); + } + else + { + if (withCastSpeed) + ApplyPercentModFloatVar(speedAmount, -val, apply); + if (withCastHaste) + ApplyPercentModFloatVar(hasteAmount, -val, apply); + } + + SetFloatValue(UNIT_MOD_CAST_SPEED, speedAmount); + SetFloatValue(UNIT_MOD_CAST_HASTE, hasteAmount); +} + +uint32 Unit::GetCastingTimeForBonus(SpellInfo const* spellProto, DamageEffectType damagetype, uint32 CastingTime) const +{ + // Not apply this to creature cast spells with casttime == 0 + if (CastingTime == 0 && GetTypeId() == TYPEID_UNIT && !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; + } + break; + default: + break; + } + + if (spellProto->Effects[i].IsTargetingArea()) + AreaEffect = true; + } + + // Combined Spells with Both Over Time and Direct Damage + if (overTime > 0 && CastingTime > 0 && DirectDamage) + { + // mainly for DoTs which are 3500 here otherwise + uint32 OriginalCastTime = spellProto->CalcCastTime(getLevel()); + 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 (GetTypeId() == TYPEID_UNIT && IsPet()) + { + Pet* pet = ((Pet*)this); + if (pet->isControlled()) + { + Unit* owner = GetOwner(); + if (owner && (owner->GetTypeId() == TYPEID_PLAYER) && owner->ToPlayer()->GetGroup()) + { + owner->ToPlayer()->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_PET_AURAS); + pet->SetAuraUpdateMaskForRaid(slot); + } + } + } +} + +void Unit::SetCantProc(bool apply) +{ + if (apply) + ++m_procDeep; + else + { + ASSERT(m_procDeep); + --m_procDeep; + } +} + +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(getLevel()); + // 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) const +{ + if (GetTypeId() != TYPEID_PLAYER || (IsInFeralForm() && !normalized)) + return GetBaseAttackTime(attType) / 1000.0f; + + Item* weapon = ToPlayer()->GetWeaponForAttack(attType, true); + if (!weapon) + return 2.0f; + + if (!normalized) + return weapon->GetTemplate()->GetDelay() / 1000.0f; + + switch (weapon->GetTemplate()->GetSubClass()) + { + case ITEM_SUBCLASS_WEAPON_AXE2: + case ITEM_SUBCLASS_WEAPON_MACE2: + case ITEM_SUBCLASS_WEAPON_POLEARM: + case ITEM_SUBCLASS_WEAPON_SWORD2: + case ITEM_SUBCLASS_WEAPON_STAFF: + case ITEM_SUBCLASS_WEAPON_FISHING_POLE: + return 3.3f; + case ITEM_SUBCLASS_WEAPON_BOW: + case ITEM_SUBCLASS_WEAPON_GUN: + case ITEM_SUBCLASS_WEAPON_CROSSBOW: + case ITEM_SUBCLASS_WEAPON_THROWN: + return 2.8f; + case ITEM_SUBCLASS_WEAPON_AXE: + case ITEM_SUBCLASS_WEAPON_MACE: + case ITEM_SUBCLASS_WEAPON_SWORD: + case ITEM_SUBCLASS_WEAPON_EXOTIC: + case ITEM_SUBCLASS_WEAPON_EXOTIC2: + case ITEM_SUBCLASS_WEAPON_FIST_WEAPON: + return 2.4f; + case ITEM_SUBCLASS_WEAPON_DAGGER: + return 1.7f; + default: + return weapon->GetTemplate()->GetDelay() / 1000.0f; + } +} + +Pet* Unit::CreateTamedPetFrom(Creature* creatureTarget, uint32 spell_id) +{ + if (GetTypeId() != TYPEID_PLAYER) + return nullptr; + + Pet* pet = new Pet(ToPlayer(), HUNTER_PET); + + if (!pet->CreateBaseAtCreature(creatureTarget)) + { + delete pet; + return nullptr; + } + + uint8 level = getLevel(); + + InitTamedPet(pet, level, spell_id); + + return pet; +} + +Pet* Unit::CreateTamedPetFrom(uint32 creatureEntry, uint32 spell_id) +{ + if (GetTypeId() != TYPEID_PLAYER) + return nullptr; + + CreatureTemplate const* creatureInfo = sObjectMgr->GetCreatureTemplate(creatureEntry); + if (!creatureInfo) + return nullptr; + + Pet* pet = new Pet(ToPlayer(), HUNTER_PET); + + if (!pet->CreateBaseAtCreatureInfo(creatureInfo, this) || !InitTamedPet(pet, getLevel(), spell_id)) + { + delete pet; + return nullptr; + } + + return pet; +} + +bool Unit::InitTamedPet(Pet* pet, uint8 level, uint32 spell_id) +{ + pet->SetCreatorGUID(GetGUID()); + pet->SetFaction(GetFaction()); + pet->SetUInt32Value(UNIT_CREATED_BY_SPELL, spell_id); + + if (GetTypeId() == TYPEID_PLAYER) + pet->SetUInt32Value(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED); + + if (!pet->InitStatsForLevel(level)) + { + TC_LOG_ERROR("entities.unit", "Pet::InitStatsForLevel() failed for creature (Entry: %u)!", pet->GetEntry()); + return false; + } + + PhasingHandler::InheritPhaseShift(pet, this); + + pet->GetCharmInfo()->SetPetNumber(sObjectMgr->GeneratePetNumber(), true); + // this enables pet details window (Shift+P) + pet->InitPetCreateSpells(); + //pet->InitLevelupSpellsForLevel(); + pet->SetFullHealth(); + pet->CastSpell(pet, 99289, true); // Energize + pet->SetReactState(REACT_ASSIST); + return true; +} + +void Unit::SendDurabilityLoss(Player* receiver, uint32 percent) +{ + WorldPackets::Misc::DurabilityDamageDeath packet; + packet.Percent = percent; + receiver->SendDirectMessage(packet.Write()); +} + +void Unit::PlayOneShotAnimKitId(uint16 animKitId) +{ + WorldPackets::Misc::PlayOneShotAnimKit packet; + packet.Unit = GetGUID(); + packet.AnimKitID = animKitId; + SendMessageToSet(packet.Write(), true); +} + +/*static*/ void Unit::Kill(Unit* attacker, Unit* victim, bool durabilityLoss /*= true*/) +{ + // Prevent killing unit twice (and giving reward from kill twice) + if (!victim->GetHealth()) + return; + + if (attacker && !attacker->IsInMap(victim)) + attacker = nullptr; + + // find player: owner of controlled `this` or `this` itself maybe + Player* player = nullptr; + if (attacker) + player = attacker->GetCharmerOrOwnerPlayerOrPlayerItself(); + + Creature* creature = victim->ToCreature(); + + bool isRewardAllowed = true; + if (creature) + { + isRewardAllowed = creature->IsDamageEnoughForLootingAndReward(); + if (!isRewardAllowed) + creature->SetLootRecipient(nullptr); + } + + if (isRewardAllowed && creature) + { + if (Player* lootRecipient = creature->GetLootRecipient()) + { + // Loot recipient can be in a different map + if (!creature->IsInMap(lootRecipient)) + { + if (Group* group = creature->GetLootRecipientGroup()) + { + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (!member || !creature->IsInMap(member)) + continue; + + player = member; + break; + } + } + } + else + player = creature->GetLootRecipient(); + } + } + + // Exploit fix + if (creature && creature->IsPet() && creature->GetOwnerOrCreatorGUID().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 << uint64(player->GetGUID()); // player with killing blow + data << uint64(victim->GetGUID()); // victim + + Player* looter = player; + Group* group = player->GetGroup(); + bool hasLooterGuid = false; + + if (group) + { + group->BroadcastPacket(&data, group->GetMemberGroup(player->GetGUID()) != 0); + + if (creature && !creature->IsLootDisabled()) + { + group->UpdateLooterGuid(creature, true); + if (group->GetLooterGuid()) + { + 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 && !creature->IsLootDisabled()) + { + WorldPackets::Loot::LootList lootList; + lootList.Owner = creature->GetGUID(); + lootList.RoundRobinWinner = player->GetGUID(); + + player->SendMessageToSet(lootList.Write(), false); + } + } + + // Generate loot before updating looter + if (creature && !creature->IsLootDisabled()) + { + Loot* loot = &creature->loot; + loot->clear(); + + if (uint32 lootid = creature->GetCreatureTemplate()->lootid) + loot->FillLoot(lootid, LootTemplates_Creature, looter, false, false, creature->GetLootMode()); + + if (creature->GetLootMode() > 0) + 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 (!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 (attacker && (attacker->IsPet() || attacker->IsTotem())) + { + // proc only once for victim + if (Unit* owner = attacker->GetOwner()) + Unit::ProcSkillsAndAuras(owner, victim, PROC_FLAG_KILL, PROC_FLAG_NONE, PROC_SPELL_TYPE_MASK_ALL, PROC_SPELL_PHASE_NONE, PROC_HIT_NONE, nullptr, nullptr, nullptr); + } + + if (!victim->IsCritter()) + Unit::ProcSkillsAndAuras(attacker, victim, PROC_FLAG_KILL, PROC_FLAG_NONE, PROC_SPELL_TYPE_MASK_ALL, PROC_SPELL_PHASE_NONE, PROC_HIT_NONE, nullptr, nullptr, nullptr); + + // Proc auras on death - must be before aura/combat remove + Unit::ProcSkillsAndAuras(victim, victim, PROC_FLAG_NONE, PROC_FLAG_DEATH, PROC_SPELL_TYPE_MASK_ALL, PROC_SPELL_PHASE_NONE, PROC_HIT_NONE, nullptr, nullptr, nullptr); + + // update get killing blow achievements, must be done before setDeathState to be able to require auras on target + // and before Spirit of Redemption as it also removes auras + if (attacker) + if (Player* killerPlayer = attacker->GetCharmerOrOwnerPlayerOrPlayerItself()) + killerPlayer->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_GET_KILLING_BLOWS, 1, 0, 0, victim); + + TC_LOG_DEBUG("entities.unit", "SET JUST_DIED"); + victim->setDeathState(JUST_DIED); + + // Inform pets (if any) when player kills target) + // MUST come after victim->setDeathState(JUST_DIED); or pet next target + // selection will get stuck on same target and break pet react state + if (player) + { + Pet* pet = player->GetPet(); + if (pet && pet->IsAlive() && pet->isControlled()) + { + if (pet->IsAIEnabled()) + pet->AI()->KilledUnit(victim); + else + TC_LOG_ERROR("entities.unit", "Pet doesn't have any AI in Unit::Kill(). %s", pet->GetGUID().ToString().c_str()); + } + } + + // 10% durability loss on death + if (Player* plrVictim = victim->ToPlayer()) + { + // remember victim PvP death for corpse type and corpse reclaim delay + // at original death (not at SpiritOfRedemtionTalent timeout) + plrVictim->SetPvPDeath(player != nullptr); + + // only if not player and not controlled by player pet. And not at BG + if ((durabilityLoss && !player && !victim->ToPlayer()->InBattleground()) || (player && sWorld->getBoolConfig(CONFIG_DURABILITY_LOSS_IN_PVP))) + { + double baseLoss = sWorld->getRate(RATE_DURABILITY_LOSS_ON_DEATH); + uint32 loss = uint32(baseLoss - (baseLoss * plrVictim->GetTotalAuraMultiplier(SPELL_AURA_MOD_DURABILITY_LOSS))); + TC_LOG_DEBUG("entities.unit", "We are dead, losing %u percent durability", loss); + // Durability loss is calculated more accurately again for each item in Player::DurabilityLoss + plrVictim->DurabilityLossAll(baseLoss, false); + // durability lost message + plrVictim->SendDurabilityLoss(plrVictim, loss); + } + // Call KilledUnit for creatures + if (attacker && attacker->IsCreature() && attacker->IsAIEnabled()) + attacker->ToCreature()->AI()->KilledUnit(victim); + + // last damage from non duel opponent or opponent controlled creature + if (plrVictim->duel) + { + plrVictim->duel->opponent->CombatStopWithPets(true); + plrVictim->CombatStopWithPets(true); + plrVictim->DuelComplete(DUEL_INTERRUPTED); + } + } + else // creature died + { + TC_LOG_DEBUG("entities.unit", "DealDamageNotPlayer"); + + if (creature && !creature->IsPet()) + { + // must be after setDeathState which resets dynamic flags + if (!creature->loot.isLooted()) + creature->SetFlag(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_LOOTABLE); + else + creature->AllLootRemovedFromCorpse(); + } + + // Call KilledUnit for creatures, this needs to be called after the lootable flag is set + if (attacker && attacker->IsCreature() && attacker->IsAIEnabled()) + attacker->ToCreature()->AI()->KilledUnit(victim); + + // Call creature just died function + if (CreatureAI* ai = creature->AI()) + ai->JustDied(attacker); + + if (TempSummon* summon = creature->ToTempSummon()) + if (Unit* summoner = summon->GetSummoner()) + if (summoner->ToCreature() && summoner->IsAIEnabled()) + summoner->ToCreature()->AI()->SummonedCreatureDies(creature, attacker); + + // Dungeon specific stuff, only applies to players killing creatures + if (creature->GetInstanceId()) + { + Map* instanceMap = creature->GetMap(); + /// @todo do instance binding anyway if the charmer/owner is offline + + if (instanceMap->IsDungeon() && ((attacker && attacker->GetCharmerOrOwnerPlayerOrPlayerItself()) || attacker == victim)) + { + if (instanceMap->IsRaidOrHeroicDungeon()) + { + if (creature->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_INSTANCE_BIND) + instanceMap->ToInstanceMap()->PermBindAllPlayers(); + } + else + { + // the reset time is set but not added to the scheduler + // until the players leave the instance + time_t resettime = GameTime::GetGameTime() + 2 * HOUR; + if (InstanceSave* save = sInstanceSaveMgr->GetInstanceSave(creature->GetInstanceId())) + if (save->GetResetTime() < resettime) + save->SetResetTime(resettime); + } + } + } + } + + // outdoor pvp things, do these after setting the death state, else the player activity notify won't work... doh... + // handle player kill only if not suicide (spirit of redemption for example) + if (player && attacker != victim) + { + if (OutdoorPvP* pvp = player->GetOutdoorPvP()) + pvp->HandleKill(player, victim); + + if (Battlefield* bf = sBattlefieldMgr->GetBattlefieldToZoneId(player->GetMap(), player->GetZoneId())) + bf->HandleKill(player, victim); + } + + //if (victim->GetTypeId() == TYPEID_PLAYER) + // if (OutdoorPvP* pvp = victim->ToPlayer()->GetOutdoorPvP()) + // pvp->HandlePlayerActivityChangedpVictim->ToPlayer(); + + // battleground things (do this at the end, so the death state flag will be properly set to handle in the bg->handlekill) + if (player && player->InBattleground()) + { + if (Battleground* bg = player->GetBattleground()) + { + if (Player* playerVictim = victim->ToPlayer()) + bg->HandleKillPlayer(playerVictim, player); + else + bg->HandleKillUnit(victim->ToCreature(), player); + } + } + + // achievement stuff + if (attacker && victim->GetTypeId() == TYPEID_PLAYER) + { + if (attacker->GetTypeId() == TYPEID_UNIT) + victim->ToPlayer()->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_KILLED_BY_CREATURE, attacker->GetEntry()); + else if (attacker->GetTypeId() == TYPEID_PLAYER && victim != attacker) + victim->ToPlayer()->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_KILLED_BY_PLAYER, 1, attacker->ToPlayer()->GetTeam()); + } + + // Hook for OnPVPKill Event + if (attacker) + { + if (Player* killerPlr = attacker->ToPlayer()) + { + if (Player* killedPlr = victim->ToPlayer()) + sScriptMgr->OnPVPKill(killerPlr, killedPlr); + else if (Creature* killedCre = victim->ToCreature()) + sScriptMgr->OnCreatureKill(killerPlr, killedCre); + } + else if (Creature* killerCre = attacker->ToCreature()) + { + if (Player* killed = victim->ToPlayer()) + sScriptMgr->OnPlayerKilledByCreature(killerCre, killed); + } + } +} + +void Unit::SetControlled(bool apply, UnitState state) +{ + if (apply) + { + if (HasUnitState(state)) + return; + + if (state & UNIT_STATE_CONTROLLED) + CastStop(); + + AddUnitState(state); + switch (state) + { + case UNIT_STATE_STUNNED: + SetStunned(true); + break; + case UNIT_STATE_ROOT: + if (!HasUnitState(UNIT_STATE_STUNNED)) + SetRooted(true); + break; + case UNIT_STATE_CONFUSED: + if (!HasUnitState(UNIT_STATE_STUNNED)) + { + ClearUnitState(UNIT_STATE_MELEE_ATTACKING); + SendMeleeAttackStop(); + // SendAutoRepeatCancel ? + SetConfused(true); + } + break; + case UNIT_STATE_FLEEING: + if (!HasUnitState(UNIT_STATE_STUNNED | UNIT_STATE_CONFUSED)) + { + ClearUnitState(UNIT_STATE_MELEE_ATTACKING); + SendMeleeAttackStop(); + // SendAutoRepeatCancel ? + SetFeared(true); + } + break; + default: + break; + } + } + else + { + switch (state) + { + case UNIT_STATE_STUNNED: + if (HasAuraType(SPELL_AURA_MOD_STUN)) + return; + + ClearUnitState(state); + SetStunned(false); + break; + case UNIT_STATE_ROOT: + if (HasAuraType(SPELL_AURA_MOD_ROOT) || GetVehicle() || (IsCreature() && ToCreature()->IsSessile())) + return; + + ClearUnitState(state); + SetRooted(false); + break; + case UNIT_STATE_CONFUSED: + if (HasAuraType(SPELL_AURA_MOD_CONFUSE)) + return; + + ClearUnitState(state); + SetConfused(false); + break; + case UNIT_STATE_FLEEING: + if (HasAuraType(SPELL_AURA_MOD_FEAR)) + return; + + ClearUnitState(state); + SetFeared(false); + break; + default: + return; + } + + ApplyControlStatesIfNeeded(); + } +} + +void Unit::ApplyControlStatesIfNeeded() +{ + // Unit States might have been already cleared but auras still present. I need to check with HasAuraType + if (HasUnitState(UNIT_STATE_STUNNED) || HasAuraType(SPELL_AURA_MOD_STUN)) + SetStunned(true); + + if (HasUnitState(UNIT_STATE_ROOT) || HasAuraType(SPELL_AURA_MOD_ROOT)) + SetRooted(true); + + if (HasUnitState(UNIT_STATE_CONFUSED) || HasAuraType(SPELL_AURA_MOD_CONFUSE)) + SetConfused(true); + + if (HasUnitState(UNIT_STATE_FLEEING) || HasAuraType(SPELL_AURA_MOD_FEAR)) + SetFeared(true); +} + +void Unit::SetStunned(bool apply) +{ + if (apply) + { + SetTarget(ObjectGuid::Empty); + SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_STUNNED); + + // Do not stop movement when the unit is affected by parabolic spline movement (knockbacks, pulls, scripted leaps etc.) + if (!movespline->isParabolic()) + StopMoving(); + + if (GetTypeId() == TYPEID_PLAYER) + SetStandState(UNIT_STAND_STATE_STAND); + SetRooted(true); + + CastStop(); + } + else + { + if (IsAlive() && GetVictim()) + SetTarget(EnsureVictim()->GetGUID()); + + // don't remove UNIT_FLAG_STUNNED for pet when owner is mounted (disabled pet's interface) + Unit* owner = GetCharmerOrOwner(); + if (!owner || owner->GetTypeId() != TYPEID_PLAYER || !owner->ToPlayer()->IsMounted()) + RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_STUNNED); + + if (!HasUnitState(UNIT_STATE_ROOT)) // prevent moving if it also has root effect + SetRooted(false); + } +} + +void Unit::SetRooted(bool apply, bool packetOnly /*= false*/) +{ + if (!packetOnly) + { + if (apply) + { + // MOVEMENTFLAG_ROOT cannot be used in conjunction with MOVEMENTFLAG_MASK_MOVING (tested 3.3.5a) + // this will freeze clients. That's why we remove MOVEMENTFLAG_MASK_MOVING before + // setting MOVEMENTFLAG_ROOT + RemoveUnitMovementFlag(MOVEMENTFLAG_MASK_MOVING); + AddUnitMovementFlag(MOVEMENTFLAG_ROOT); + + // Do not stop movement when the unit is affected by parabolic spline movement (knockbacks, pulls, scripted leaps etc.) + if (!movespline->isParabolic()) + StopMoving(); + } + else + RemoveUnitMovementFlag(MOVEMENTFLAG_ROOT); + } + + if (apply) + Movement::PacketSender(this, SMSG_SPLINE_MOVE_ROOT, SMSG_MOVE_ROOT, SMSG_MOVE_ROOT).Send(); + else + Movement::PacketSender(this, SMSG_SPLINE_MOVE_UNROOT, SMSG_MOVE_UNROOT, SMSG_MOVE_UNROOT).Send(); +} + +void Unit::SetFeared(bool apply) +{ + if (apply) + { + // block control to real player in control (eg charmer) + if (GetCharmerOrSelfPlayer()) + GetCharmerOrSelfPlayer()->SetClientControl(this, false); + + SetTarget(ObjectGuid::Empty); + + Unit* caster = nullptr; + Unit::AuraEffectList const& fearAuras = GetAuraEffectsByType(SPELL_AURA_MOD_FEAR); + if (!fearAuras.empty()) + caster = ObjectAccessor::GetUnit(*this, fearAuras.front()->GetCasterGUID()); + if (!caster) + caster = getAttackerForHelper(); + GetMotionMaster()->MoveFleeing(caster, fearAuras.empty() ? sWorld->getIntConfig(CONFIG_CREATURE_FAMILY_FLEE_DELAY) : 0); // caster == nullptr processed in MoveFleeing + } + else + { + if (IsAlive()) + { + if (GetMotionMaster()->GetCurrentMovementGeneratorType() == FLEEING_MOTION_TYPE) + GetMotionMaster()->MovementExpired(); + if (GetVictim()) + SetTarget(EnsureVictim()->GetGUID()); + } + + // allow control to real player in control (eg charmer) + if (GetCharmerOrSelfPlayer()) + GetCharmerOrSelfPlayer()->SetClientControl(this, true); + } +} + +void Unit::SetConfused(bool apply) +{ + if (apply) + { + // block control to real player in control (eg charmer) + if (GetCharmerOrSelfPlayer()) + GetCharmerOrSelfPlayer()->SetClientControl(this, false); + + SetTarget(ObjectGuid::Empty); + GetMotionMaster()->MoveConfused(); + } + else + { + if (IsAlive()) + { + if (GetMotionMaster()->GetCurrentMovementGeneratorType() == CONFUSED_MOTION_TYPE) + GetMotionMaster()->MovementExpired(); + if (GetVictim()) + SetTarget(EnsureVictim()->GetGUID()); + } + + // allow control to real player in control (eg charmer) + if (GetCharmerOrSelfPlayer()) + GetCharmerOrSelfPlayer()->SetClientControl(this, true); + } +} + +bool Unit::SetCharmedBy(Unit* charmer, CharmType type, AuraApplication const* aurApp) +{ + if (!charmer) + return false; + + // dismount players when charmed + if (GetTypeId() == TYPEID_PLAYER) + RemoveAurasByType(SPELL_AURA_MOUNTED); + + if (charmer->GetTypeId() == TYPEID_PLAYER) + charmer->RemoveAurasByType(SPELL_AURA_MOUNTED); + + ASSERT(type != CHARM_TYPE_POSSESS || charmer->GetTypeId() == TYPEID_PLAYER); + ASSERT((type == CHARM_TYPE_VEHICLE) == (GetVehicleKit() && GetVehicleKit()->IsControllableVehicle())); + + TC_LOG_DEBUG("entities.unit", "SetCharmedBy: charmer %u (GUID %u), charmed %u (GUID %u), type %u.", charmer->GetEntry(), charmer->GetGUID().GetCounter(), GetEntry(), GetGUID().GetCounter(), uint32(type)); + + if (this == charmer) + { + TC_LOG_FATAL("entities.unit", "Unit::SetCharmedBy: Unit %u (GUID %u) is trying to charm itself!", GetEntry(), GetGUID().GetCounter()); + return false; + } + + //if (HasUnitState(UNIT_STATE_UNATTACKABLE)) + // return false; + + if (GetTypeId() == TYPEID_PLAYER && ToPlayer()->GetTransport()) + { + TC_LOG_FATAL("entities.unit", "Unit::SetCharmedBy: Player on transport is trying to charm %u (GUID %u)", GetEntry(), GetGUID().GetCounter()); + return false; + } + + // Already charmed + if (GetCharmerGUID()) + { + TC_LOG_FATAL("entities.unit", "Unit::SetCharmedBy: %u (GUID %u) has already been charmed but %u (GUID %u) is trying to charm it!", GetEntry(), GetGUID().GetCounter(), charmer->GetEntry(), charmer->GetGUID().GetCounter()); + return false; + } + + CastStop(); + CombatStop(); /// @todo CombatStop(true) may cause crash (interrupt spells) + + Player* playerCharmer = charmer->ToPlayer(); + + // Charmer stop charming + if (playerCharmer) + { + playerCharmer->StopCastingCharm(); + playerCharmer->StopCastingBindSight(); + } + + // Charmed stop charming + if (GetTypeId() == TYPEID_PLAYER) + { + ToPlayer()->StopCastingCharm(); + ToPlayer()->StopCastingBindSight(); + } + + // StopCastingCharm may remove a possessed pet? + if (!IsInWorld()) + { + TC_LOG_FATAL("entities.unit", "Unit::SetCharmedBy: %u (GUID %u) is not in world but %u (GUID %u) is trying to charm it!", GetEntry(), GetGUID().GetCounter(), charmer->GetEntry(), charmer->GetGUID().GetCounter()); + 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().HasAnyFlag()) + { + // properly clean up charm changes up to this point to avoid leaving the unit in partially charmed state + SetFaction(_oldFactionId); + GetMotionMaster()->InitDefault(); + charmer->SetCharm(this, false); + return false; + } + + _oldFactionId = GetFaction(); + SetFaction(charmer->GetFaction()); + + // Set charmed + charmer->SetCharm(this, true); + + if (GetTypeId() == TYPEID_UNIT) + { + GetMotionMaster()->Clear(MOTION_SLOT_ACTIVE); + PauseMovement(0, MOTION_SLOT_IDLE); + StopMoving(); + } + else if (Player* player = ToPlayer()) + { + if (player->isAFK()) + player->ToggleAFK(); + + player->SetClientControl(this, false); + } + + // charm is set by aura, and aura effect remove handler was called during apply handler execution + // prevent undefined behaviour + if (aurApp && aurApp->GetRemoveMode().HasAnyFlag()) + return false; + + // Pets already have a properly initialized CharmInfo, don't overwrite it. + if (type != CHARM_TYPE_VEHICLE && !GetCharmInfo()) + { + if (CharmInfo* charmInfo = InitCharmInfo()) + { + if (type == CHARM_TYPE_POSSESS) + charmInfo->InitPossessCreateSpells(); + else + charmInfo->InitCharmCreateSpells(); + } + } + + if (playerCharmer) + { + switch (type) + { + case CHARM_TYPE_VEHICLE: + SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_POSSESSED); + playerCharmer->SetClientControl(this, true); + playerCharmer->VehicleSpellInitialize(); + break; + case CHARM_TYPE_POSSESS: + SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_POSSESSED); + charmer->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_REMOVE_CLIENT_CONTROL); + playerCharmer->SetClientControl(this, true); + playerCharmer->PossessSpellInitialize(); + AddUnitState(UNIT_STATE_POSSESSED); + break; + case CHARM_TYPE_CHARM: + if (GetTypeId() == TYPEID_UNIT && charmer->getClass() == CLASS_WARLOCK) + { + CreatureTemplate const* cinfo = ToCreature()->GetCreatureTemplate(); + if (cinfo && cinfo->type == CREATURE_TYPE_DEMON) + { + // to prevent client crash + SetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_CLASS, (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())); // cast can't be helped + } + } + playerCharmer->CharmSpellInitialize(); + break; + default: + case CHARM_TYPE_CONVERT: + break; + } + } + + AddUnitState(UNIT_STATE_CHARMED); + + if ((GetTypeId() != TYPEID_PLAYER) || (charmer->GetTypeId() != TYPEID_PLAYER)) + { + // AI will schedule its own change if appropriate + if (UnitAI* ai = GetAI()) + ai->OnCharmed(false); + else + ScheduleAIChange(); + } + return true; +} + +void Unit::RemoveCharmedBy(Unit* charmer) +{ + if (!IsCharmed()) + return; + + if (charmer) + ASSERT(charmer == GetCharmer()); + else + charmer = GetCharmer(); + + ASSERT(charmer); + + CharmType type; + if (HasUnitState(UNIT_STATE_POSSESSED)) + type = CHARM_TYPE_POSSESS; + else if (charmer && charmer->IsOnVehicle(this)) + type = CHARM_TYPE_VEHICLE; + else + type = CHARM_TYPE_CHARM; + + CastStop(); + AttackStop(); + + if (_oldFactionId) + { + SetFaction(_oldFactionId); + _oldFactionId = 0; + } + else + RestoreFaction(); + + GetMotionMaster()->InitDefault(); + + // Vehicle should not attack its passenger after he exists the seat + if (type != CHARM_TYPE_VEHICLE) + LastCharmerGUID = charmer->GetGUID(); + + ASSERT(type != CHARM_TYPE_POSSESS || charmer->GetTypeId() == TYPEID_PLAYER); + ASSERT(type != CHARM_TYPE_VEHICLE || (GetTypeId() == TYPEID_UNIT && IsVehicle())); + + charmer->SetCharm(this, false); + m_combatManager.RevalidateCombat(); + + Player* playerCharmer = charmer->ToPlayer(); + + if (playerCharmer) + { + switch (type) + { + case CHARM_TYPE_VEHICLE: + playerCharmer->SetClientControl(this, false); + playerCharmer->SetClientControl(charmer, true); + RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_POSSESSED); + break; + case CHARM_TYPE_POSSESS: + ClearUnitState(UNIT_STATE_POSSESSED); + playerCharmer->SetClientControl(this, false); + playerCharmer->SetClientControl(charmer, true); + charmer->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_REMOVE_CLIENT_CONTROL); + RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_POSSESSED); + break; + case CHARM_TYPE_CHARM: + if (GetTypeId() == TYPEID_UNIT && charmer->getClass() == CLASS_WARLOCK) + { + CreatureTemplate const* cinfo = ToCreature()->GetCreatureTemplate(); + if (cinfo && cinfo->type == CREATURE_TYPE_DEMON) + { + SetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_CLASS, uint8(cinfo->unit_class)); + if (GetCharmInfo()) + GetCharmInfo()->SetPetNumber(0, true); + else + TC_LOG_ERROR("entities.unit", "Aura::HandleModCharm: %s has a charm aura but no charm info!", GetGUID().ToString().c_str()); + } + } + break; + case CHARM_TYPE_CONVERT: + break; + } + } + + if (Player* player = ToPlayer()) + player->SetClientControl(this, true); + + EngageWithTarget(charmer); + + if (playerCharmer && this != charmer->GetFirstControlled()) + playerCharmer->SendRemoveControlBar(); + + // a guardian should always have charminfo + if (!IsGuardian()) + DeleteCharmInfo(); + + // reset confused movement for example + ApplyControlStatesIfNeeded(); + + if (GetTypeId() != TYPEID_PLAYER || charmer->GetTypeId() == TYPEID_UNIT) + { + if (UnitAI* charmedAI = GetAI()) + charmedAI->OnCharmed(false); // AI will potentially schedule a charm ai update + else + ScheduleAIChange(); + } +} + +void Unit::RestoreFaction() +{ + if (HasAuraType(SPELL_AURA_MOD_FACTION)) + { + SetFaction(GetAuraEffectsByType(SPELL_AURA_MOD_FACTION).front()->GetMiscValue()); + return; + } + + if (GetTypeId() == TYPEID_PLAYER) + ToPlayer()->SetFactionForRace(getRace()); + else + { + if (HasUnitTypeMask(UNIT_MASK_MINION)) + { + if (Unit* owner = GetOwner()) + { + SetFaction(owner->GetFaction()); + return; + } + } + + if (CreatureTemplate const* cinfo = ToCreature()->GetCreatureTemplate()) // normal creature + SetFaction(cinfo->faction); + } +} + +bool Unit::CreateVehicleKit(uint32 id, uint32 creatureEntry, bool loading /*= false*/) +{ + VehicleEntry const* vehInfo = sVehicleStore.LookupEntry(id); + if (!vehInfo) + return false; + + m_vehicleKit = std::make_unique(this, vehInfo, creatureEntry); + m_updateFlag.Vehicle = true; + m_unitTypeMask |= UNIT_MASK_VEHICLE; + + if (!loading) + SendSetVehicleRecId(id); + + return true; +} + +void Unit::RemoveVehicleKit(bool onRemoveFromWorld /*= false*/) +{ + if (!m_vehicleKit) + return; + + if (!onRemoveFromWorld) + SendSetVehicleRecId(0); + + m_vehicleKit->Uninstall(); + m_vehicleKit.reset(); + + m_updateFlag.Vehicle = false; + m_unitTypeMask &= ~UNIT_MASK_VEHICLE; + RemoveFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_SPELLCLICK | UNIT_NPC_FLAG_PLAYER_VEHICLE); +} + +bool Unit::IsOnVehicle(Unit const* vehicle) const +{ + return m_vehicle && m_vehicle == vehicle->GetVehicleKit(); +} + +Unit* Unit::GetVehicleBase() const +{ + return m_vehicle ? m_vehicle->GetBase() : nullptr; +} + +Creature* Unit::GetVehicleCreatureBase() const +{ + if (Unit* veh = GetVehicleBase()) + if (Creature* c = veh->ToCreature()) + return c; + + return nullptr; +} + +ObjectGuid Unit::GetTransGUID() const +{ + if (GetVehicle()) + return GetVehicleBase()->GetGUID(); + if (GetTransport()) + return GetTransport()->GetTransportGUID(); + + return ObjectGuid::Empty; +} + +TransportBase* Unit::GetDirectTransport() const +{ + if (Vehicle* veh = GetVehicle()) + return veh; + return GetTransport(); +} + +bool Unit::IsInPartyWith(Unit const* unit) const +{ + if (this == unit) + return true; + + Unit const* u1 = GetCharmerOrOwnerOrCreatorOrSelf(); + Unit const* u2 = unit->GetCharmerOrOwnerOrCreatorOrSelf(); + if (u1 == u2) + return true; + + if (u1->GetTypeId() == TYPEID_PLAYER && u2->GetTypeId() == TYPEID_PLAYER) + return u1->ToPlayer()->IsInSameGroupWith(u2->ToPlayer()); + else if ((u2->GetTypeId() == TYPEID_PLAYER && u1->GetTypeId() == TYPEID_UNIT && u1->ToCreature()->HasStaticFlag(CREATURE_STATIC_FLAG_4_TREAT_AS_RAID_UNIT_FOR_HELPFUL_SPELLS)) || + (u1->GetTypeId() == TYPEID_PLAYER && u2->GetTypeId() == TYPEID_UNIT && u2->ToCreature()->HasStaticFlag(CREATURE_STATIC_FLAG_4_TREAT_AS_RAID_UNIT_FOR_HELPFUL_SPELLS))) + return true; + + return u1->GetTypeId() == TYPEID_UNIT && u2->GetTypeId() == TYPEID_UNIT && u1->GetFaction() == u2->GetFaction(); +} + +bool Unit::IsInRaidWith(Unit const* unit) const +{ + if (this == unit) + return true; + + Unit const* u1 = GetCharmerOrOwnerOrCreatorOrSelf(); + Unit const* u2 = unit->GetCharmerOrOwnerOrCreatorOrSelf(); + if (u1 == u2) + return true; + + if (u1->GetTypeId() == TYPEID_PLAYER && u2->GetTypeId() == TYPEID_PLAYER) + return u1->ToPlayer()->IsInSameRaidWith(u2->ToPlayer()); + else if ((u2->GetTypeId() == TYPEID_PLAYER && u1->GetTypeId() == TYPEID_UNIT && u1->ToCreature()->HasStaticFlag(CREATURE_STATIC_FLAG_4_TREAT_AS_RAID_UNIT_FOR_HELPFUL_SPELLS)) || + (u1->GetTypeId() == TYPEID_PLAYER && u2->GetTypeId() == TYPEID_UNIT && u2->ToCreature()->HasStaticFlag(CREATURE_STATIC_FLAG_4_TREAT_AS_RAID_UNIT_FOR_HELPFUL_SPELLS))) + return true; + + return u1->GetTypeId() == TYPEID_UNIT && u2->GetTypeId() == TYPEID_UNIT && u1->GetFaction() == u2->GetFaction(); +} + +void Unit::GetPartyMembers(std::list &TagUnitMap) +{ + Unit* owner = GetCharmerOrOwnerOrSelf(); + Group* group = nullptr; + if (owner->GetTypeId() == TYPEID_PLAYER) + group = owner->ToPlayer()->GetGroup(); + + if (group) + { + uint8 subgroup = owner->ToPlayer()->GetSubGroup(); + + for (GroupReference* 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); + + if (Guardian* pet = Target->GetGuardianPet()) + if (pet->IsAlive()) + TagUnitMap.push_back(pet); + } + } + } + else + { + if ((owner == this || IsInMap(owner)) && owner->IsAlive()) + TagUnitMap.push_back(owner); + if (Guardian* pet = owner->GetGuardianPet()) + if ((pet == this || IsInMap(pet)) && pet->IsAlive()) + TagUnitMap.push_back(pet); + } +} + +bool Unit::IsContestedGuard() const +{ + if (FactionTemplateEntry const* entry = GetFactionTemplateEntry()) + return entry->IsHostileToPvpActivePlayers(); + + return false; +} + +void Unit::SetPvP(bool state) +{ + if (state) + SetByteFlag(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PVP_FLAG, UNIT_BYTE2_FLAG_PVP); + else + RemoveByteFlag(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PVP_FLAG, UNIT_BYTE2_FLAG_PVP); +} + +Aura* Unit::AddAura(uint32 spellId, Unit* target) +{ + if (!target) + return nullptr; + + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); + if (!spellInfo) + 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->IsAlive() && !spellInfo->IsPassive() && !spellInfo->HasAttribute(SPELL_ATTR2_ALLOW_DEAD_TARGET)) + return nullptr; + + if (target->IsImmunedToSpell(spellInfo, this)) + return nullptr; + + for (uint32 i = 0; i < MAX_SPELL_EFFECTS; ++i) + { + if (!(effMask & (1 << i))) + continue; + + if (target->IsImmunedToSpellEffect(spellInfo, i, this)) + effMask &= ~(1 << i); + } + + if (!effMask) + return nullptr; + + AuraCreateInfo createInfo(spellInfo, effMask, target); + createInfo.SetCaster(this); + + if (Aura* aura = Aura::TryRefreshStackOrCreate(createInfo)) + { + aura->ApplyForTargets(); + return aura; + } + return nullptr; +} + +void Unit::SetAuraStack(uint32 spellId, Unit* target, uint32 stack) +{ + Aura* aura = target->GetAura(spellId, GetGUID()); + if (!aura) + aura = AddAura(spellId, target); + if (aura && stack) + aura->SetStackAmount(stack); +} + +void Unit::SendPlaySpellVisualKit(uint32 id, uint32 type, uint32 duration) const +{ + if (!sSpellVisualKitStore.LookupEntry(id)) + return; + + WorldPackets::Spells::PlaySpellVisualKit packet; + packet.Unit = GetGUID(); + packet.KitRecID = id; // SpellVisualKit.dbc index + packet.KitType = type; + packet.Duration = duration; + SendMessageToSet(packet.Write(), IsPlayer()); +} + +void Unit::SendPlaySpellVisual(uint32 spellVisualId, Unit const* target /*= nullptr*/, Optional targetPosition /*= {}*/, float travelSpeed /*= 0.f*/, uint16 missReason /*= 0*/, uint16 reflectStatus /*= 0*/, bool speedAsTime /*= false*/) const +{ + if (!sSpellVisualStore.LookupEntry(spellVisualId)) + return; + + WorldPackets::Spells::PlaySpellVisual packet; + packet.SpellVisualID = spellVisualId; + packet.Source = GetGUID(); + if (target) + packet.Target = target->GetGUID(); + + if (targetPosition) + packet.TargetPosition = *targetPosition; + + packet.TravelSpeed = travelSpeed; + packet.MissReason = missReason; + packet.ReflectStatus = reflectStatus; + packet.SpeedAsTime = speedAsTime; + + SendMessageToSet(packet.Write(), true); +} + +void Unit::CancelSpellMissiles(uint32 spellId, bool reverseMissile /*= false*/) +{ + bool hasMissile = false; + for (auto& itr : m_Events.GetEvents()) + { + if (Spell const* spell = Spell::ExtractSpellFromEvent(itr.second)) + { + if (spell->GetSpellInfo()->Id == spellId) + { + if (!itr.second->IsAbortScheduled()) + { + itr.second->ScheduleAbort(); + hasMissile = true; + } + } + } + } + + if (hasMissile) + { + WorldPackets::Spells::MissileCancel packet; + packet.OwnerGUID = GetGUID(); + packet.SpellID = spellId; + packet.Reverse = reverseMissile; + SendMessageToSet(packet.Write(), false); + } +} + +bool Unit::CanApplyResilience() const +{ + return !IsVehicle() && GetOwnerGUID().IsPlayer(); +} + +/*static*/ void Unit::ApplyResilience(Unit const* victim, int32* damage) +{ + // player mounted on multi-passenger mount is also classified as vehicle + if (victim->IsVehicle() && victim->GetTypeId() != TYPEID_PLAYER) + return; + + Unit const* target = nullptr; + if (victim->GetTypeId() == TYPEID_PLAYER) + target = victim; + else // victim->GetTypeId() == TYPEID_UNIT + { + if (Unit* owner = victim->GetOwner()) + if (owner->GetTypeId() == TYPEID_PLAYER) + target = owner; + } + + if (!target) + return; + + *damage -= target->GetDamageReduction(*damage); +} + +int32 Unit::CalculateAOEAvoidance(int32 damage, uint32 schoolMask, bool npcCaster) const +{ + damage = int32(float(damage) * GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_AOE_DAMAGE_AVOIDANCE, schoolMask)); + if (npcCaster) + damage = int32(float(damage) * GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_CREATURE_AOE_DAMAGE_AVOIDANCE, schoolMask)); + + return damage; +} + +// Melee based spells can be miss, parry or dodge on this step +// Crit or block - determined on damage calculation phase! (and can be both in some time) +float Unit::MeleeSpellMissChance(Unit const* victim, WeaponAttackType attType, SpellInfo const* spellInfo /*= nullptr*/) const +{ + if (spellInfo && spellInfo->HasAttribute(SPELL_ATTR7_NO_ATTACK_MISS)) + return 0.f; + + // Calculate miss chance + float missChance = GetMeleeMissChance(victim); + + if (!spellInfo && haveOffhandWeapon()) + missChance += 19.f; + + // Spellmod from SpellModOp::HitChance + float resistMissChance = 100.0f; + if (spellInfo) + if (Player* modOwner = GetSpellModOwner()) + modOwner->ApplySpellMod(spellInfo, SpellModOp::HitChance, resistMissChance); + + missChance -= resistMissChance - 100.0f; + + if (attType == RANGED_ATTACK) + missChance -= m_modRangedHitChance; + else + missChance -= m_modMeleeHitChance; + + // miss chance from auras after calculating skill based miss + missChance -= GetTotalAuraModifier(SPELL_AURA_MOD_HIT_CHANCE); + if (attType == RANGED_ATTACK) + missChance -= victim->GetTotalAuraModifier(SPELL_AURA_MOD_ATTACKER_RANGED_HIT_CHANCE); + else + missChance -= victim->GetTotalAuraModifier(SPELL_AURA_MOD_ATTACKER_MELEE_HIT_CHANCE); + + return std::max(missChance, 0.f); +} + +void Unit::OnPhaseChange() +{ + if (!IsInWorld()) + return; + + // Inform player groups that a member's phase has changed so the next PartyMemberState packet will update the phasing accordingly + if (IsPlayer() && ToPlayer()->GetGroup()) + ToPlayer()->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_PHASE); +} + +void Unit::UpdateObjectVisibility(bool forced) +{ + if (!forced) + AddToNotify(NOTIFY_VISIBILITY_CHANGED); + else + { + WorldObject::UpdateObjectVisibility(true); + // call MoveInLineOfSight for nearby creatures + Trinity::AIRelocationNotifier notifier(*this); + Cell::VisitAllObjects(this, notifier, GetVisibilityRange()); + } +} + +void Unit::SendMoveKnockBack(Player* player, float speedXY, float speedZ, float vcos, float vsin) +{ + WorldPackets::Movement::MoveKnockBack moveKnockBack; + moveKnockBack.MoverGUID = GetGUID(); + moveKnockBack.SequenceIndex = m_movementCounter++; + moveKnockBack.Speeds.HorzSpeed = speedXY; + moveKnockBack.Speeds.VertSpeed = speedZ; + moveKnockBack.Direction = Position(vcos, vsin); + player->SendDirectMessage(moveKnockBack.Write()); +} + +void Unit::KnockbackFrom(float x, float y, float speedXY, float speedZ) +{ + if (IsMovedByServer()) + GetMotionMaster()->MoveKnockbackFrom(x, y, speedXY, speedZ); + else + { + float vcos, vsin; + GetSinCos(x, y, vsin, vcos); + SendMoveKnockBack(GetGameClientMovingMe()->GetBasePlayer(), speedXY, -speedZ, vcos, vsin); + + if (HasAuraType(SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED) || HasAuraType(SPELL_AURA_FLY)) + SetCanFly(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) const +{ + // Hardcoded cases + switch (spellId) + { + case 7090: // Bear Form + return 29414; + case 35200: // Roc Form + return 4877; + default: + break; + } + + if (GetTypeId() == TYPEID_PLAYER) + { + switch (form) + { + case FORM_CAT: + // Based on Hair color + if (getRace() == RACE_NIGHTELF) + { + uint8 hairColor = GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_HAIR_COLOR_ID); + switch (hairColor) + { + case 7: // Violet + case 8: + return 29405; + case 3: // Light Blue + return 29406; + case 0: // Green + case 1: // Light Green + case 2: // Dark Green + return 29407; + case 4: // White + return 29408; + default: // original - Dark Blue + return 892; + } + } + else if (getRace() == RACE_TROLL) + { + uint8 hairColor = GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_HAIR_COLOR_ID); + switch (hairColor) + { + case 0: // Red + case 1: + return 33668; + case 2: // Yellow + case 3: + return 33667; + case 4: // Blue + case 5: + case 6: + return 33666; + case 7: // Purple + case 10: + return 33665; + default: // original - white + return 33669; + } + } + else if (getRace() == RACE_WORGEN) + { + // Based on Skin color + uint8 skinColor = GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_SKIN_ID); + // Male + if (getGender() == GENDER_MALE) + { + switch (skinColor) + { + case 1: // Brown + return 33662; + case 2: // Black + case 7: + return 33661; + case 4: // yellow + return 33664; + case 3: // White + case 5: + return 33663; + default: // original - Gray + return 33660; + } + } + // Female + else + { + switch (skinColor) + { + case 5: // Brown + case 6: + return 33662; + case 7: // Black + case 8: + return 33661; + case 3: // yellow + case 4: + return 33664; + case 2: // White + return 33663; + default: // original - Gray + return 33660; + } + } + } + // Based on Skin color + else if (getRace() == RACE_TAUREN) + { + uint8 skinColor = GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_SKIN_ID); + // Male + if (getGender() == GENDER_MALE) + { + switch (skinColor) + { + case 12: // White + case 13: + case 14: + case 18: // Completly White + return 29409; + case 9: // Light Brown + case 10: + case 11: + return 29410; + case 6: // Brown + case 7: + case 8: + return 29411; + case 0: // Dark + case 1: + case 2: + case 3: // Dark Grey + case 4: + case 5: + return 29412; + default: // original - Grey + return 8571; + } + } + // Female + else + { + switch (skinColor) + { + case 10: // White + return 29409; + case 6: // Light Brown + case 7: + return 29410; + case 4: // Brown + case 5: + return 29411; + case 0: // Dark + case 1: + case 2: + case 3: + return 29412; + default: // original - Grey + return 8571; + } + } + } + else if (Player::TeamForRace(getRace()) == ALLIANCE) + return 892; + else + return 8571; + case FORM_BEAR: + // Based on Hair color + if (getRace() == RACE_NIGHTELF) + { + uint8 hairColor = GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_HAIR_COLOR_ID); + switch (hairColor) + { + case 0: // Green + case 1: // Light Green + case 2: // Dark Green + return 29413; // 29415? + case 6: // Dark Blue + return 29414; + case 4: // White + return 29416; + case 3: // Light Blue + return 29417; + default: // original - Violet + return 2281; + } + } + else if (getRace() == RACE_TROLL) + { + uint8 hairColor = GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_HAIR_COLOR_ID); + switch (hairColor) + { + case 0: // Red + case 1: + return 33657; + case 2: // Yellow + case 3: + return 33659; + case 7: // Purple + case 10: + return 33656; + case 8: // White + case 9: + case 11: + case 12: + return 33658; + default: // original - Blue + return 33655; + } + } + else if (getRace() == RACE_WORGEN) + { + // Based on Skin color + uint8 skinColor = GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_SKIN_ID); + // Male + if (getGender() == GENDER_MALE) + { + switch (skinColor) + { + case 1: // Brown + return 33653; + case 2: // Black + case 7: + return 33651; + case 4: // Yellow + return 33652; + case 3: // White + case 5: + return 33654; + default: // original - Gray + return 33650; + } + } + // Female + else + { + switch (skinColor) + { + case 5: // Brown + case 6: + return 33652; + case 7: // Black + case 8: + return 33651; + case 3: // yellow + case 4: + return 33654; + case 2: // White + return 33653; + default: // original - Gray + return 33650; + } + } + } + // Based on Skin color + else if (getRace() == RACE_TAUREN) + { + uint8 skinColor = GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_SKIN_ID); + // Male + if (getGender() == GENDER_MALE) + { + switch (skinColor) + { + case 0: // Dark (Black) + case 1: + case 2: + return 29418; + case 3: // White + case 4: + case 5: + case 12: + case 13: + case 14: + return 29419; + case 9: // Light Brown/Grey + case 10: + case 11: + case 15: + case 16: + case 17: + return 29420; + case 18: // Completly White + return 29421; + default: // original - Brown + return 2289; + } + } + // Female + else + { + switch (skinColor) + { + case 0: // Dark (Black) + case 1: + return 29418; + case 2: // White + case 3: + return 29419; + case 6: // Light Brown/Grey + case 7: + case 8: + case 9: + return 29420; + case 10: // Completly White + return 29421; + default: // original - Brown + return 2289; + } + } + } + else if (Player::TeamForRace(getRace()) == ALLIANCE) + return 2281; + else + return 2289; + case FORM_FLIGHT: + if (Player::TeamForRace(getRace()) == ALLIANCE) + return 20857; + return 20872; + case FORM_FLIGHT_EPIC: + if (Player::TeamForRace(getRace()) == ALLIANCE) + return (getRace() == RACE_WORGEN ? 37729 : 21243); + if (getRace() == RACE_TROLL) + return 37730; + return 21244; + case FORM_MOONKIN: + if (getRace() == RACE_TROLL) + return 37174; + if (getRace() == RACE_WORGEN) + return 37173; + return 15374; + case FORM_GHOSTWOLF: + if (HasAura(58135)) //! Glyph of Arctic Wolf + return 27312; + return 4613; + default: + break; + } + } + + uint32 modelid = 0; + SpellShapeshiftFormEntry const* formEntry = sSpellShapeshiftFormStore.LookupEntry(form); + if (formEntry && formEntry->CreatureDisplayID[0]) + { + // Take the alliance modelid as default + if (GetTypeId() != TYPEID_PLAYER) + return formEntry->CreatureDisplayID[0]; + else + { + if (Player::TeamForRace(getRace()) == ALLIANCE) + modelid = formEntry->CreatureDisplayID[0]; + else + modelid = formEntry->CreatureDisplayID[1]; + + // If the player is horde but there are no values for the horde modelid - take the alliance modelid + if (!modelid && Player::TeamForRace(getRace()) == HORDE) + modelid = formEntry->CreatureDisplayID[0]; + } + } + + return modelid; +} + +uint32 Unit::GetModelForTotem(PlayerTotemType totemType) +{ + switch (getRace()) + { + case RACE_ORC: + { + switch (totemType) + { + case SUMMON_TYPE_TOTEM_FIRE: // fire + return 30758; + case SUMMON_TYPE_TOTEM_EARTH: // earth + return 30757; + case SUMMON_TYPE_TOTEM_WATER: // water + return 30759; + case SUMMON_TYPE_TOTEM_AIR: // air + return 30756; + } + break; + } + case RACE_DWARF: + { + switch (totemType) + { + case SUMMON_TYPE_TOTEM_FIRE: // fire + return 30754; + case SUMMON_TYPE_TOTEM_EARTH: // earth + return 30753; + case SUMMON_TYPE_TOTEM_WATER: // water + return 30755; + case SUMMON_TYPE_TOTEM_AIR: // air + return 30736; + } + break; + } + case RACE_TROLL: + { + switch (totemType) + { + case SUMMON_TYPE_TOTEM_FIRE: // fire + return 30762; + case SUMMON_TYPE_TOTEM_EARTH: // earth + return 30761; + case SUMMON_TYPE_TOTEM_WATER: // water + return 30763; + case SUMMON_TYPE_TOTEM_AIR: // air + return 30760; + } + break; + } + case RACE_TAUREN: + { + switch (totemType) + { + case SUMMON_TYPE_TOTEM_FIRE: // fire + return 4589; + case SUMMON_TYPE_TOTEM_EARTH: // earth + return 4588; + case SUMMON_TYPE_TOTEM_WATER: // water + return 4587; + case SUMMON_TYPE_TOTEM_AIR: // air + return 4590; + } + break; + } + case RACE_DRAENEI: + { + switch (totemType) + { + case SUMMON_TYPE_TOTEM_FIRE: // fire + return 19074; + case SUMMON_TYPE_TOTEM_EARTH: // earth + return 19073; + case SUMMON_TYPE_TOTEM_WATER: // water + return 19075; + case SUMMON_TYPE_TOTEM_AIR: // air + return 19071; + } + break; + } + case RACE_GOBLIN: + { + switch (totemType) + { + case SUMMON_TYPE_TOTEM_FIRE: // fire + return 30783; + case SUMMON_TYPE_TOTEM_EARTH: // earth + return 30782; + case SUMMON_TYPE_TOTEM_WATER: // water + return 30784; + case SUMMON_TYPE_TOTEM_AIR: // air + return 30781; + } + break; + } + } + return 0; +} + +void Unit::JumpTo(float speedXY, float speedZ, bool forward, Optional dest) +{ + float angle = forward ? 0 : float(M_PI); + if (dest) + angle += GetRelativeAngle(*dest); + + if (GetTypeId() == TYPEID_UNIT) + GetMotionMaster()->MoveJumpTo(angle, speedXY, speedZ); + else + { + float vcos = std::cos(angle + GetOrientation()); + float vsin = std::sin(angle + GetOrientation()); + SendMoveKnockBack(ToPlayer(), speedXY, -speedZ, vcos, vsin); + } +} + +void Unit::JumpTo(WorldObject* obj, float speedZ, bool withOrientation) +{ + float x, y, z; + obj->GetContactPoint(this, x, y, z); + float speedXY = GetExactDist2d(x, y) * 10.0f / speedZ; + GetMotionMaster()->MoveJump(x, y, z, GetAngle(obj), speedXY, speedZ, EVENT_JUMP, withOrientation); +} + +bool Unit::HandleSpellClick(Unit* clicker, int8 seatId) +{ + bool result = false; + uint32 spellClickEntry = GetVehicleKit() ? GetVehicleKit()->GetCreatureEntry() : GetEntry(); + TriggerCastFlags const flags = GetVehicleKit() ? TRIGGERED_IGNORE_CASTER_MOUNTED_OR_ON_VEHICLE : TRIGGERED_NONE; + + 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 + if (!sConditionMgr->IsObjectMeetingSpellClickConditions(spellClickEntry, itr->second.spellId, clicker, this)) + 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) ? GetOwnerOrCreatorGUID() : clicker->GetGUID(); + + SpellInfo const* spellEntry = sSpellMgr->GetSpellInfo(itr->second.spellId); + // if (!spellEntry) should be checked at npc_spellclick load + + if (seatId > -1) + { + uint8 i = 0; + bool valid = false; + while (i < MAX_SPELL_EFFECTS && !valid) + { + if (spellEntry->Effects[i].ApplyAuraName == SPELL_AURA_CONTROL_VEHICLE) + { + valid = true; + break; + } + ++i; + } + + if (!valid) + { + TC_LOG_ERROR("sql.sql", "Spell %u specified in npc_spellclick_spells is not a valid vehicle enter aura!", itr->second.spellId); + continue; + } + + if (IsInMap(caster)) + { + CastSpellExtraArgs args(flags); + args.OriginalCaster = origCasterGUID; + args.AddSpellMod(SpellValueMod(SPELLVALUE_BASE_POINT0 + i), seatId + 1); + caster->CastSpell(target, itr->second.spellId, args); + } + else // This can happen during Player::_LoadAuras + { + int32 bp[MAX_SPELL_EFFECTS] = { }; + for (uint32 j = 0; j < MAX_SPELL_EFFECTS; ++j) + bp[j] = spellEntry->Effects[j].BasePoints; + + bp[i] = seatId; + + AuraCreateInfo createInfo(spellEntry, MAX_EFFECT_MASK, this); + createInfo + .SetCaster(clicker) + .SetBaseAmount(bp) + .SetCasterGUID(origCasterGUID); + + Aura::TryRefreshStackOrCreate(createInfo); + } + } + else + { + if (IsInMap(caster)) + caster->CastSpell(target, spellEntry->Id, CastSpellExtraArgs().SetOriginalCaster(origCasterGUID)); + else + { + AuraCreateInfo createInfo(spellEntry, MAX_EFFECT_MASK, this); + createInfo + .SetCaster(clicker) + .SetCasterGUID(origCasterGUID); + + Aura::TryRefreshStackOrCreate(createInfo); + } + } + + result = true; + } + + Creature* creature = ToCreature(); + if (creature && creature->IsAIEnabled()) + creature->AI()->OnSpellClick(clicker, result); + + return result; +} + +void Unit::EnterVehicle(Unit* base, int8 seatId) +{ + CastSpellExtraArgs args(TRIGGERED_IGNORE_CASTER_MOUNTED_OR_ON_VEHICLE); + args.AddSpellBP0(seatId + 1); + CastSpell(base, VEHICLE_SPELL_RIDE_HARDCODED, args); +} + +void Unit::_EnterVehicle(Vehicle* vehicle, int8 seatId, AuraApplication const* aurApp) +{ + // Must be called only from aura handler + ASSERT(aurApp); + + if (!IsAlive() || GetVehicleKit() == vehicle || vehicle->GetBase()->IsOnVehicle(this)) + return; + + if (m_vehicle) + { + if (m_vehicle != vehicle) + { + TC_LOG_DEBUG("entities.vehicle", "EnterVehicle: %u exit %u and enter %u.", GetEntry(), m_vehicle->GetBase()->GetEntry(), vehicle->GetBase()->GetEntry()); + ExitVehicle(); + } + else if (seatId >= 0 && seatId == GetTransSeat()) + return; + else + { + //Exit the current vehicle because unit will reenter in a new seat. + m_vehicle->GetBase()->RemoveAurasByType(SPELL_AURA_CONTROL_VEHICLE, GetGUID(), aurApp->GetBase()); + } + } + + if (aurApp->GetRemoveMode().HasAnyFlag()) + return; + + if (Player* player = ToPlayer()) + { + if (vehicle->GetBase()->GetTypeId() == TYPEID_PLAYER && player->IsInCombat()) + { + vehicle->GetBase()->RemoveAura(const_cast(aurApp)); + return; + } + + if (vehicle->GetBase()->GetTypeId() == TYPEID_UNIT) + { + // If a player entered a vehicle that is part of a formation, remove it from said formation + if (CreatureGroup* creatureGroup = vehicle->GetBase()->ToCreature()->GetFormation()) + creatureGroup->RemoveMember(vehicle->GetBase()->ToCreature()); + } + } + + // If vehicle flag for fixed position set (cannons), or if the following hardcoded units, then set state rooted + // 30236 | Argent Cannon + // 39759 | Tankbuster Cannon + if ((vehicle->GetVehicleInfo()->Flags & VEHICLE_FLAG_FIXED_POSITION) || vehicle->GetBase()->GetEntry() == 30236 || vehicle->GetBase()->GetEntry() == 39759) + SetControlled(true, UNIT_STATE_ROOT); + + ASSERT(!m_vehicle); + (void)vehicle->AddVehiclePassenger(this, seatId); +} + +void Unit::ChangeSeat(int8 seatId, bool next) +{ + if (!m_vehicle) + return; + + // Don't change if current and new seat are identical + if (seatId == GetTransSeat()) + return; + + SeatMap::const_iterator seat = (seatId < 0 ? m_vehicle->GetNextEmptySeat(GetTransSeat(), next) : m_vehicle->Seats.find(seatId)); + // The second part of the check will only return true if seatId >= 0. @Vehicle::GetNextEmptySeat makes sure of that. + if (seat == m_vehicle->Seats.end() || !seat->second.IsEmpty()) + return; + + AuraEffect* rideVehicleEffect = nullptr; + AuraEffectList const& vehicleAuras = m_vehicle->GetBase()->GetAuraEffectsByType(SPELL_AURA_CONTROL_VEHICLE); + for (AuraEffectList::const_iterator itr = vehicleAuras.begin(); itr != vehicleAuras.end(); ++itr) + { + if ((*itr)->GetCasterGUID() != GetGUID()) + continue; + + // Make sure there is only one ride vehicle aura on target cast by the unit changing seat + ASSERT(!rideVehicleEffect); + rideVehicleEffect = *itr; + } + + // Unit riding a vehicle must always have control vehicle aura on target + ASSERT(rideVehicleEffect); + + rideVehicleEffect->ChangeAmount(seat->first + 1); +} + +void Unit::ExitVehicle(Position const* /*exitPosition*/) +{ + //! This function can be called at upper level code to initialize an exit from the passenger's side. + if (!m_vehicle) + return; + + GetVehicleBase()->RemoveAurasByType(SPELL_AURA_CONTROL_VEHICLE, GetGUID()); + //! The following call would not even be executed successfully as the + //! SPELL_AURA_CONTROL_VEHICLE unapply handler already calls _ExitVehicle without + //! specifying an exitposition. The subsequent call below would return on if (!m_vehicle). + /*_ExitVehicle(exitPosition);*/ + //! To do: + //! We need to allow SPELL_AURA_CONTROL_VEHICLE unapply handlers in spellscripts + //! to specify exit coordinates and either store those per passenger, or we need to + //! init spline movement based on those coordinates in unapply handlers, and + //! relocate exiting passengers based on Unit::moveSpline data. Either way, + //! Coming Soon(TM) +} + +void Unit::_ExitVehicle(Position const* exitPosition) +{ + /// It's possible m_vehicle is nullptr, when this function is called indirectly from @VehicleJoinEvent::Abort. + /// In that case it was not possible to add the passenger to the vehicle. The vehicle aura has already been removed + /// from the target in the aforementioned function and we don't need to do anything else at this point. + if (!m_vehicle) + return; + + // This should be done before dismiss, because there may be some aura removal + VehicleSeatAddon const* seatAddon = m_vehicle->GetSeatAddonForSeatOfPassenger(this); + Vehicle* vehicle = m_vehicle->RemovePassenger(this); + + if (!vehicle) + { + TC_LOG_ERROR("entities.vehicle", "RemovePassenger() couldn't remove current unit from vehicle."); + return; + } + + Player* player = ToPlayer(); + + // If the player is on mounted duel and exits the mount, he should immediatly lose the duel + if (player && player->duel && player->duel->isMounted) + player->DuelComplete(DUEL_FLED); + + SetControlled(false, UNIT_STATE_ROOT); // SMSG_MOVE_FORCE_UNROOT, ~MOVEMENTFLAG_ROOT + + AddUnitState(UNIT_STATE_MOVE); + + if (player) + player->SetFallInformation(0, GetPositionZ()); + + Position pos; + // If we ask for a specific exit position, use that one. Otherwise allow scripts to modify it + if (exitPosition) + pos = *exitPosition; + else + { + // Set exit position to vehicle position and use the current orientation + pos = vehicle->GetBase()->GetPosition(); + pos.SetOrientation(GetOrientation()); + + // Change exit position based on seat entry + 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 }); + } + } + + float height = pos.GetPositionZ() + vehicle->GetBase()->GetCollisionHeight(); + + Movement::MoveSplineInit init(this); + + // Creatures without inhabit type air should begin falling after exiting the vehicle + if (GetTypeId() == TYPEID_UNIT && !CanFly() && height > GetMap()->GetWaterOrGroundLevel(GetPhaseShift(), pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ() + vehicle->GetBase()->GetCollisionHeight(), &height)) + init.SetFall(); + + init.MoveTo(pos.GetPositionX(), pos.GetPositionY(), height, false); + init.SetFacing(pos.GetOrientation()); + init.SetTransportExit(); + GetMotionMaster()->LaunchMoveSpline(std::move(init), EVENT_VEHICLE_EXIT, MOTION_SLOT_CONTROLLED); + + if (player) + player->ResummonPetTemporaryUnSummonedIfAny(); + + if (vehicle->GetBase()->HasUnitTypeMask(UNIT_MASK_MINION) && vehicle->GetBase()->GetTypeId() == TYPEID_UNIT) + if (((Minion*)vehicle->GetBase())->GetOwner() == this) + vehicle->GetBase()->ToCreature()->DespawnOrUnsummon(1); + + if (HasUnitTypeMask(UNIT_MASK_ACCESSORY)) + { + // Vehicle just died, we die too + if (vehicle->GetBase()->getDeathState() == JUST_DIED) + setDeathState(JUST_DIED); + // If for other reason we as minion are exiting the vehicle (ejected, master dismounted) - unsummon + else + ToTempSummon()->UnSummon(2000); // Approximation + } +} + +bool Unit::IsFalling() const +{ + return m_movementInfo.HasMovementFlag(MOVEMENTFLAG_FALLING | MOVEMENTFLAG_FALLING_FAR) || movespline->isFalling(); +} + +bool Unit::CanSwim() const +{ + // Mirror client behavior, if this method returns false then client will not use swimming animation and for players will apply gravity as if there was no water + if (HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_CANT_SWIM)) + return false; + if (HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED)) // is player + return true; + if (HasFlag(UNIT_FIELD_FLAGS_2, UNIT_FLAG2_AI_WILL_ONLY_SWIM_IF_TARGET_SWIMS)) + return false; + if (HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PET_IN_COMBAT)) + return true; + return HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_RENAME | UNIT_FLAG_CAN_SWIM); +} + +void Unit::NearTeleportTo(Position const& pos, bool casting /*= false*/) +{ + DisableSpline(); + if (GetTypeId() == TYPEID_PLAYER) + { + WorldLocation target(GetMapId(), pos); + ToPlayer()->TeleportTo(target, TELE_TO_NOT_LEAVE_TRANSPORT | TELE_TO_NOT_LEAVE_COMBAT | TELE_TO_NOT_UNSUMMON_PET | (casting ? TELE_TO_SPELL : TELE_TO_NONE)); + } + else + { + SendTeleportPacket(pos); + UpdatePosition(pos, true); + UpdateObjectVisibility(); + } +} + +void Unit::WriteMovementInfo(WorldPacket& data, Movement::ExtraMovementStatusElement* extras /*= nullptr*/, uint32* movementCounter /*= nullptr*/) +{ + MovementInfo const& mi = m_movementInfo; + + bool hasMovementFlags = GetUnitMovementFlags() != 0; + bool hasMovementFlags2 = GetExtraUnitMovementFlags() != 0; + bool hasTimestamp = true; + bool hasOrientation = !G3D::fuzzyEq(GetOrientation(), 0.0f); + bool hasTransportData = GetTransGUID() != 0; + bool hasSpline = IsSplineEnabled(); + + bool hasTransportTime2 = hasTransportData && m_movementInfo.transport.time2 != 0; + bool hasTransportVehicleId = hasTransportData && m_movementInfo.transport.vehicleId != 0; + bool hasPitch = HasUnitMovementFlag(MovementFlags(MOVEMENTFLAG_SWIMMING | MOVEMENTFLAG_FLYING)) || HasExtraUnitMovementFlag(MOVEMENTFLAG2_ALWAYS_ALLOW_PITCHING); + bool hasFallDirection = HasUnitMovementFlag(MOVEMENTFLAG_FALLING); + bool hasFallData = hasFallDirection || m_movementInfo.jump.fallTime != 0; + bool hasSplineElevation = HasUnitMovementFlag(MOVEMENTFLAG_SPLINE_ELEVATION); + + MovementStatusElements const* sequence = GetMovementStatusElementsSequence(static_cast(data.GetOpcode())); + if (!sequence) + { + TC_LOG_ERROR("network", "Unit::WriteMovementInfo: No movement sequence found for opcode %s", GetOpcodeNameForLogging(static_cast(data.GetOpcode())).c_str()); + return; + } + + ObjectGuid guid = GetGUID(); + ObjectGuid tguid = hasTransportData ? GetTransGUID() : ObjectGuid::Empty; + + for (; *sequence != MSEEnd; ++sequence) + { + MovementStatusElements const& element = *sequence; + + switch (element) + { + case MSEHasGuidByte0: + case MSEHasGuidByte1: + case MSEHasGuidByte2: + case MSEHasGuidByte3: + case MSEHasGuidByte4: + case MSEHasGuidByte5: + case MSEHasGuidByte6: + case MSEHasGuidByte7: + data.WriteBit(guid[element - MSEHasGuidByte0]); + break; + case MSEHasTransportGuidByte0: + case MSEHasTransportGuidByte1: + case MSEHasTransportGuidByte2: + case MSEHasTransportGuidByte3: + case MSEHasTransportGuidByte4: + case MSEHasTransportGuidByte5: + case MSEHasTransportGuidByte6: + case MSEHasTransportGuidByte7: + if (hasTransportData) + data.WriteBit(tguid[element - MSEHasTransportGuidByte0]); + break; + case MSEGuidByte0: + case MSEGuidByte1: + case MSEGuidByte2: + case MSEGuidByte3: + case MSEGuidByte4: + case MSEGuidByte5: + case MSEGuidByte6: + case MSEGuidByte7: + data.WriteByteSeq(guid[element - MSEGuidByte0]); + break; + case MSETransportGuidByte0: + case MSETransportGuidByte1: + case MSETransportGuidByte2: + case MSETransportGuidByte3: + case MSETransportGuidByte4: + case MSETransportGuidByte5: + case MSETransportGuidByte6: + case MSETransportGuidByte7: + if (hasTransportData) + data.WriteByteSeq(tguid[element - MSETransportGuidByte0]); + break; + case MSEHasMovementFlags: + data.WriteBit(!hasMovementFlags); + break; + case MSEHasMovementFlags2: + data.WriteBit(!hasMovementFlags2); + break; + case MSEHasTimestamp: + data.WriteBit(!hasTimestamp); + break; + case MSEHasOrientation: + data.WriteBit(!hasOrientation); + break; + case MSEHasTransportData: + data.WriteBit(hasTransportData); + break; + case MSEHasTransportTime2: + if (hasTransportData) + data.WriteBit(hasTransportTime2); + break; + case MSEHasVehicleId: + if (hasTransportData) + data.WriteBit(hasTransportVehicleId); + break; + case MSEHasPitch: + data.WriteBit(!hasPitch); + break; + case MSEHasFallData: + data.WriteBit(hasFallData); + break; + case MSEHasFallDirection: + if (hasFallData) + data.WriteBit(hasFallDirection); + break; + case MSEHasSplineElevation: + data.WriteBit(!hasSplineElevation); + break; + case MSEHasSpline: + data.WriteBit(hasSpline); + break; + case MSEHasHeightChangeFailed: + data.WriteBit(mi.HasHeightChangeFailed()); + break; + case MSEMovementFlags: + if (hasMovementFlags) + data.WriteBits(GetUnitMovementFlags(), 30); + break; + case MSEMovementFlags2: + if (hasMovementFlags2) + data.WriteBits(GetExtraUnitMovementFlags(), 12); + break; + case MSETimestamp: + if (hasTimestamp) + data << mi.time; + break; + case MSEPositionX: + data << GetPositionX(); + break; + case MSEPositionY: + data << GetPositionY(); + break; + case MSEPositionZ: + data << GetPositionZ(); + break; + case MSEOrientation: + if (hasOrientation) + data << GetOrientation(); + break; + case MSETransportPositionX: + if (hasTransportData) + data << GetTransOffsetX(); + break; + case MSETransportPositionY: + if (hasTransportData) + data << GetTransOffsetY(); + break; + case MSETransportPositionZ: + if (hasTransportData) + data << GetTransOffsetZ(); + break; + case MSETransportOrientation: + if (hasTransportData) + data << GetTransOffsetO(); + break; + case MSETransportSeat: + if (hasTransportData) + data << GetTransSeat(); + break; + case MSETransportTime: + if (hasTransportData) + data << GetTransTime(); + break; + case MSETransportTime2: + if (hasTransportData && hasTransportTime2) + data << mi.transport.time2; + break; + case MSETransportVehicleId: + if (hasTransportData && hasTransportVehicleId) + data << mi.transport.vehicleId; + break; + case MSEPitch: + if (hasPitch) + data << mi.pitch; + break; + case MSEFallTime: + if (hasFallData) + data << mi.jump.fallTime; + break; + case MSEFallVerticalSpeed: + if (hasFallData) + data << mi.jump.zspeed; + break; + case MSEFallCosAngle: + if (hasFallData && hasFallDirection) + data << mi.jump.cosAngle; + break; + case MSEFallSinAngle: + if (hasFallData && hasFallDirection) + data << mi.jump.sinAngle; + break; + case MSEFallHorizontalSpeed: + if (hasFallData && hasFallDirection) + data << mi.jump.xyspeed; + break; + case MSESplineElevation: + if (hasSplineElevation) + data << mi.splineElevation; + break; + case MSECounter: + data << uint32(movementCounter ? *movementCounter : 0); + break; + case MSEZeroBit: + data.WriteBit(0); + break; + case MSEOneBit: + data.WriteBit(1); + break; + case MSEFlushBits: + data.FlushBits(); + break; + case MSEExtraElement: + extras->WriteNextElement(data); + break; + default: + ASSERT(Movement::PrintInvalidSequenceElement(element, __FUNCTION__)); + break; + } + } +} + +void Unit::SendTeleportPacket(Position const& pos) +{ + // SMSG_MOVE_UPDATE_TELEPORT is sent to nearby players to signal the teleport + // MSG_MOVE_TELEPORT is sent to self in order to trigger MSG_MOVE_TELEPORT_ACK and update the position server side + if (IsMovedByClient()) + { + Player* playerMover = GetGameClientMovingMe()->GetBasePlayer(); + float x, y, z, o; + pos.GetPosition(x, y, z, o); + if (TransportBase* transportBase = GetDirectTransport()) + transportBase->CalculatePassengerOffset(x, y, z, &o); + + WorldPackets::Movement::MoveTeleport moveTeleport; + moveTeleport.MoverGUID = GetGUID(); + moveTeleport.Pos = Position(x, y, z); + if (GetTransGUID()) + moveTeleport.TransportGUID = GetTransGUID(); + moveTeleport.Facing = o; + moveTeleport.SequenceIndex = m_movementCounter++; + playerMover->SendDirectMessage(moveTeleport.Write()); + } + else + { + // This is the only packet sent for creatures which contains MovementInfo structure + // we do not update m_movementInfo for creatures so it needs to be done manually here + WorldPackets::Movement::MoveUpdateTeleport moveUpdateTeleport; + moveUpdateTeleport.Status = &m_movementInfo; + moveUpdateTeleport.Status->guid = GetGUID(); + moveUpdateTeleport.Status->pos.Relocate(pos); + moveUpdateTeleport.Status->time = GameTime::GetGameTimeMS(); + SendMessageToSet(moveUpdateTeleport.Write(), false); + } +} + +bool Unit::UpdatePosition(float x, float y, float z, float orientation, bool teleport) +{ + // prevent crash when a bad coord is sent by the client + if (!Trinity::IsValidMapCoord(x, y, z, orientation)) + { + TC_LOG_DEBUG("entities.unit", "Unit::UpdatePosition(%f, %f, %f) .. bad coordinates!", x, y, z); + return false; + } + + // Check if angular distance changed + bool const turn = G3D::fuzzyGt(M_PI - fabs(fabs(GetOrientation() - orientation) - M_PI), 0.0f); + + // G3D::fuzzyEq won't help here, in some cases magnitudes differ by a little more than G3D::eps, but should be considered equal + bool const relocated = (teleport || + std::fabs(GetPositionX() - x) > 0.001f || + std::fabs(GetPositionY() - y) > 0.001f || + std::fabs(GetPositionZ() - z) > 0.001f); + + if (relocated) + { + // move and update visible state if need + if (GetTypeId() == TYPEID_PLAYER) + GetMap()->PlayerRelocation(ToPlayer(), x, y, z, orientation); + else + GetMap()->CreatureRelocation(ToCreature(), x, y, z, orientation); + } + else if (turn) + UpdateOrientation(orientation); + + _positionUpdateInfo.Relocated = relocated; + _positionUpdateInfo.Turned = turn; + + return (relocated || turn); +} + +bool Unit::UpdatePosition(Position const& pos, bool teleport) +{ + return UpdatePosition(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), pos.GetOrientation(), teleport); +} + +//! Only server-side orientation update, does not broadcast to client +void Unit::UpdateOrientation(float orientation) +{ + SetOrientation(orientation); + if (IsVehicle()) + GetVehicleKit()->RelocatePassengers(); +} + +//! Only server-side height update, does not broadcast to client +void Unit::UpdateHeight(float newZ) +{ + Relocate(GetPositionX(), GetPositionY(), newZ); + if (IsVehicle()) + GetVehicleKit()->RelocatePassengers(); +} + +void Unit::ClearPendingMovementChangeForType(MovementChangeType changeType) +{ + m_pendingMovementChanges.erase(changeType); +} + +void Unit::AssignPendingMovementChange(MovementChangeType changeType, PlayerMovementPendingChange&& newChange) +{ + m_pendingMovementChanges[changeType] = newChange; +} + +PlayerMovementPendingChange const* Unit::GetPendingMovementChange(MovementChangeType changeType) const +{ + return Trinity::Containers::MapGetValuePtr(m_pendingMovementChanges, changeType); +} + +void Unit::CheckPendingMovementAcks() +{ + if (sWorld->getIntConfig(CONFIG_PENDING_MOVE_CHANGES_TIMEOUT) == 0) + return; + + if (!HasPendingMovementChange()) + return; + + for (std::pair const& pendingChange : m_pendingMovementChanges) + { + if (GameTime::GetGameTimeMS() > pendingChange.second.time + sWorld->getIntConfig(CONFIG_PENDING_MOVE_CHANGES_TIMEOUT)) + { + /* + when players are teleported from one corner of a map to an other (example: from Dragonblight to the entrance of Naxxramas, both in the same map: Northend), + is it done through what is called a 'near' teleport. A near teleport always involve teleporting a player from one point to an other in the same map, even if + the distance is huge. When that distance is big enough, a loading screen appears on the client side. During that time, the client loads the surrounding zone + of the new location (and everything it contains). The problem is that, as long as the client hasn't finished loading the new zone, it will NOT ack the near + teleport. So if the server sends a near teleport order at a certain time and the client takes 20s to load the new zone (let's imagine a very slow computer), + even with zero latency, the server will receive an ack from the client only after 20s. + + For this reason and because the current implementation is simple (you dear reader, feel free to improve it if you can), we will just ignore checking for + near teleport acks (for now. @todo). + */ + if (pendingChange.second.movementChangeType == MovementChangeType::TELEPORT) + return; + + GameClient* controller = GetGameClientMovingMe(); + controller->GetWorldSession()->KickPlayer(); + TC_LOG_INFO("cheat", "Unit::CheckPendingMovementAcks: Player GUID: %s took too long to acknowledge a movement change. He was therefore kicked.", controller->GetBasePlayer()->GetGUID().ToString().c_str()); + break; + } + } +} + +void Unit::PurgeAndApplyPendingMovementChanges(bool informObservers /* = true */) +{ + for (std::pair const& pendingChange : m_pendingMovementChanges) + { + float speedFlat = pendingChange.second.newValue; + MovementChangeType changeType = pendingChange.second.movementChangeType; + Optional moveType; + switch (changeType) + { + case MovementChangeType::SPEED_CHANGE_WALK: moveType = MOVE_WALK; break; + case MovementChangeType::SPEED_CHANGE_RUN: moveType = MOVE_RUN; break; + case MovementChangeType::SPEED_CHANGE_RUN_BACK: moveType = MOVE_RUN_BACK; break; + case MovementChangeType::SPEED_CHANGE_SWIM: moveType = MOVE_SWIM; break; + case MovementChangeType::SPEED_CHANGE_SWIM_BACK: moveType = MOVE_SWIM_BACK; break; + case MovementChangeType::RATE_CHANGE_TURN: moveType = MOVE_TURN_RATE; break; + case MovementChangeType::SPEED_CHANGE_FLIGHT_SPEED: moveType = MOVE_FLIGHT; break; + case MovementChangeType::SPEED_CHANGE_FLIGHT_BACK_SPEED: moveType = MOVE_FLIGHT_BACK; break; + case MovementChangeType::RATE_CHANGE_PITCH: moveType = MOVE_PITCH_RATE; break; + case MovementChangeType::GRAVITY_DISABLE: + Unit::SetDisableGravity(pendingChange.second.apply); + break; + case MovementChangeType::SET_CAN_FLY: + Unit::SetCanFly(pendingChange.second.apply); + break; + case MovementChangeType::SET_CAN_TRANSITION_BETWEEN_SWIM_AND_FLY: + Unit::SetCanTransitionBetweenSwimAndFly(pendingChange.second.apply); + break; + case MovementChangeType::SET_COLLISION_HGT: + break; + default: + ASSERT(false); + return; + } + + if (moveType.has_value()) + { + float newSpeedRate = speedFlat / (IsControlledByPlayer() ? playerBaseMoveSpeed[*moveType] : baseMoveSpeed[*moveType]); + SetSpeedRateReal(*moveType, newSpeedRate); + + if (informObservers) + MovementPacketSender::SendSpeedChangeToObservers(this, *moveType, speedFlat); + } + else if (informObservers) + { + switch (changeType) + { + case MovementChangeType::GRAVITY_DISABLE: + case MovementChangeType::SET_CAN_FLY: + case MovementChangeType::SET_CAN_TRANSITION_BETWEEN_SWIM_AND_FLY: + MovementPacketSender::SendMovementFlagChangeToObservers(this); + break; + default: + break; + } + } + } + + m_pendingMovementChanges.clear(); +} + +PlayerMovementPendingChange::PlayerMovementPendingChange() +{ + time = GameTime::GetGameTimeMS(); +} + +// baseRage means damage taken when attacker = false +int32 Unit::RewardRage(uint32 baseRage, bool asAttacker) +{ + float addRage = baseRage; + + addRage *= sWorld->getRate(RATE_POWER_RAGE_INCOME); + return ModifyPower(POWER_RAGE, static_cast(addRage), !asAttacker); +} + +void Unit::StopAttackFaction(uint32 faction_id) +{ + if (Unit* victim = GetVictim()) + { + if (victim->GetFactionTemplateEntry()->Faction == faction_id) + { + AttackStop(); + if (IsNonMeleeSpellCast(false)) + InterruptNonMeleeSpells(false); + + // melee and ranged forced attack cancel + if (GetTypeId() == TYPEID_PLAYER) + ToPlayer()->SendAttackSwingCancelAttack(); + } + } + + AttackerSet const& attackers = getAttackers(); + for (AttackerSet::const_iterator itr = attackers.begin(); itr != attackers.end();) + { + if ((*itr)->GetFactionTemplateEntry()->Faction == faction_id) + { + (*itr)->AttackStop(); + itr = attackers.begin(); + } + else + ++itr; + } + + std::vector refsToEnd; + for (auto const& pair : m_combatManager.GetPvECombatRefs()) + if (pair.second->GetOther(this)->GetFactionTemplateEntry()->Faction == faction_id) + refsToEnd.push_back(pair.second); + for (CombatReference* ref : refsToEnd) + ref->EndCombat(); + + for (Unit* minion : m_Controlled) + minion->StopAttackFaction(faction_id); +} + +void Unit::OutDebugInfo() const +{ + TC_LOG_ERROR("entities.unit", "Unit::OutDebugInfo"); + TC_LOG_DEBUG("entities.unit", "%s name %s", GetGUID().ToString().c_str(), GetName().c_str()); + TC_LOG_DEBUG("entities.unit", "Owner %s, Minion %s, Charmer %s, Charmed %s", GetOwnerOrCreatorGUID().ToString().c_str(), GetMinionGUID().ToString().c_str(), GetCharmerGUID().ToString().c_str(), GetCharmedGUID().ToString().c_str()); + TC_LOG_DEBUG("entities.unit", "In world %u, unit type mask %u", (uint32)(IsInWorld() ? 1 : 0), m_unitTypeMask); + if (IsInWorld()) + TC_LOG_DEBUG("entities.unit", "Mapid %u", GetMapId()); + + std::ostringstream o; + o << "Summon Slot: "; + for (uint32 i = 0; i < MAX_SUMMON_SLOT; ++i) + o << m_SummonSlot[i].ToString() << ", "; + + TC_LOG_DEBUG("entities.unit", "%s", o.str().c_str()); + o.str(""); + + o << "Controlled List: "; + for (ControlList::const_iterator itr = m_Controlled.begin(); itr != m_Controlled.end(); ++itr) + o << (*itr)->GetGUID().ToString() << ", "; + TC_LOG_DEBUG("entities.unit", "%s", o.str().c_str()); + o.str(""); + + o << "Aura List: "; + for (AuraApplicationMap::const_iterator itr = m_appliedAuras.begin(); itr != m_appliedAuras.end(); ++itr) + o << itr->first << ", "; + TC_LOG_DEBUG("entities.unit", "%s", o.str().c_str()); + o.str(""); + + if (IsVehicle()) + { + o << "Passenger List: "; + for (SeatMap::iterator itr = GetVehicleKit()->Seats.begin(); itr != GetVehicleKit()->Seats.end(); ++itr) + if (Unit* passenger = ObjectAccessor::GetUnit(*GetVehicleBase(), itr->second.Passenger.Guid)) + o << passenger->GetGUID().ToString() << ", "; + TC_LOG_DEBUG("entities.unit", "%s", o.str().c_str()); + } + + if (GetVehicle()) + TC_LOG_DEBUG("entities.unit", "On vehicle %u.", GetVehicleBase()->GetEntry()); +} + +void Unit::SendClearTarget() +{ + WorldPackets::Combat::BreakTarget packet; + packet.UnitGUID = GetGUID(); + SendMessageToSet(packet.Write(), false); +} + +uint32 Unit::GetResistance(SpellSchoolMask mask) const +{ + int32 resist = -1; + for (int32 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 CharmInfo::SetIsCommandAttack(bool val) +{ + _isCommandAttack = val; +} + +bool CharmInfo::IsCommandAttack() +{ + return _isCommandAttack; +} + +void CharmInfo::SetIsCommandFollow(bool val) +{ + _isCommandFollow = val; +} + +bool CharmInfo::IsCommandFollow() +{ + return _isCommandFollow; +} + +void CharmInfo::SaveStayPosition() +{ + //! At this point a new spline destination is enabled because of Unit::StopMoving() + G3D::Vector3 stayPos = _unit->movespline->FinalDestination(); + + if (_unit->movespline->onTransport) + if (TransportBase* transport = _unit->GetDirectTransport()) + transport->CalculatePassengerPosition(stayPos.x, stayPos.y, stayPos.z); + + _stayX = stayPos.x; + _stayY = stayPos.y; + _stayZ = stayPos.z; +} + +void CharmInfo::GetStayPosition(float &x, float &y, float &z) +{ + x = _stayX; + y = _stayY; + z = _stayZ; +} + +void CharmInfo::SetIsAtStay(bool val) +{ + _isAtStay = val; +} + +bool CharmInfo::IsAtStay() +{ + return _isAtStay; +} + +void CharmInfo::SetIsFollowing(bool val) +{ + _isFollowing = val; +} + +bool CharmInfo::IsFollowing() +{ + return _isFollowing; +} + +void CharmInfo::SetIsReturning(bool val) +{ + _isReturning = val; +} + +bool CharmInfo::IsReturning() +{ + return _isReturning; +} + +void Unit::SetOrientationTowards(WorldObject const* target) +{ + SetOrientation(GetAngle(target)); +} + +void Unit::SetFacingTo(float ori, bool force) +{ + // do not face when already moving + if (!force && (!IsStopped() || !movespline->Finalized())) + return; + + Movement::MoveSplineInit init(this); + init.MoveTo(GetPositionX(), GetPositionY(), GetPositionZ(), false); + if (GetTransport()) + init.DisableTransportPathTransformations(); // It makes no sense to target global orientation + init.SetFacing(ori); + init.Launch(); + UpdateSplineMovement(1); +} + +void Unit::SetFacingToObject(WorldObject const* object, bool force) +{ + // do not face when already moving + if (!force && (!IsStopped() || !movespline->Finalized())) + return; + + /// @todo figure out under what conditions creature will move towards object instead of facing it where it currently is. + Movement::MoveSplineInit init(this); + init.MoveTo(GetPositionX(), GetPositionY(), GetPositionZ(), false); + init.SetFacing(GetAngle(object)); // when on transport, GetAngle will still return global coordinates (and angle) that needs transforming + init.Launch(); + UpdateSplineMovement(1); +} + +void Unit::SetFacingToPoint(Position const& point, bool force) +{ + // do not face when already moving + if (!force && (!IsStopped() || !movespline->Finalized())) + return; + + /// @todo figure out under what conditions creature will move towards object instead of facing it where it currently is. + Movement::MoveSplineInit init(this); + init.MoveTo(GetPositionX(), GetPositionY(), GetPositionZ(), false); + if (GetTransport()) + init.DisableTransportPathTransformations(); // It makes no sense to target global orientation + init.SetFacing(point.GetPositionX(), point.GetPositionY(), point.GetPositionZ()); + init.Launch(); + UpdateSplineMovement(1); +} + +bool Unit::SetWalk(bool enable) +{ + if (enable == IsWalking()) + return false; + + if (enable) + AddUnitMovementFlag(MOVEMENTFLAG_WALKING); + else + RemoveUnitMovementFlag(MOVEMENTFLAG_WALKING); + + ///@ TODO: Find proper opcode for walk mode setting in player mind controlling a player case + if (enable) + Movement::PacketSender(this, SMSG_SPLINE_MOVE_SET_WALK_MODE, SMSG_SPLINE_MOVE_SET_WALK_MODE).Send(); + else + Movement::PacketSender(this, SMSG_SPLINE_MOVE_SET_RUN_MODE, SMSG_SPLINE_MOVE_SET_RUN_MODE).Send(); + + return true; +} + +bool Unit::SetDisableGravity(bool disable, bool /*updateAnimTier = true*/) +{ + if (disable == IsGravityDisabled()) + return false; + + if (disable) + { + AddUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY); + RemoveUnitMovementFlag(MOVEMENTFLAG_SWIMMING | MOVEMENTFLAG_SPLINE_ELEVATION); + SetFall(false); + } + else + { + RemoveUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY); + if (!HasUnitMovementFlag(MOVEMENTFLAG_CAN_FLY)) + SetFall(true); + } + + if (IsAlive()) + { + if (IsGravityDisabled() || IsHovering()) + SetPlayHoverAnim(true); + else + SetPlayHoverAnim(false); + } + else if (IsPlayer()) // To update player who dies while flying/hovering + SetPlayHoverAnim(false, false); + + return true; +} + +bool Unit::SetFall(bool enable) +{ + if (enable == HasUnitMovementFlag(MOVEMENTFLAG_FALLING)) + return false; + + if (enable) + { + AddUnitMovementFlag(MOVEMENTFLAG_FALLING); + m_movementInfo.SetFallTime(0); + } + else + RemoveUnitMovementFlag(MOVEMENTFLAG_FALLING | MOVEMENTFLAG_FALLING_FAR); + + return true; +} + +bool Unit::SetSwim(bool enable) +{ + if (enable == HasUnitMovementFlag(MOVEMENTFLAG_SWIMMING)) + return false; + + if (enable) + AddUnitMovementFlag(MOVEMENTFLAG_SWIMMING); + else + RemoveUnitMovementFlag(MOVEMENTFLAG_SWIMMING); + + if (enable) + Movement::PacketSender(this, SMSG_SPLINE_MOVE_START_SWIM, NULL_OPCODE).Send(); + else + Movement::PacketSender(this, SMSG_SPLINE_MOVE_STOP_SWIM, NULL_OPCODE).Send(); + + return true; +} + +bool Unit::SetCanFly(bool enable) +{ + if (enable == HasUnitMovementFlag(MOVEMENTFLAG_CAN_FLY)) + return false; + + if (enable) + { + AddUnitMovementFlag(MOVEMENTFLAG_CAN_FLY); + RemoveUnitMovementFlag(MOVEMENTFLAG_SWIMMING | MOVEMENTFLAG_SPLINE_ELEVATION); + SetFall(false); + } + else + { + if (IsFlying() && !IsGravityDisabled()) + SetFall(true); + RemoveUnitMovementFlag(MOVEMENTFLAG_CAN_FLY | MOVEMENTFLAG_MASK_MOVING_FLY); + } + + return true; +} + +bool Unit::SetCanTransitionBetweenSwimAndFly(bool enable) +{ + if (enable == HasExtraUnitMovementFlag(MOVEMENTFLAG2_CAN_SWIM_TO_FLY_TRANS)) + return false; + + if (enable) + AddExtraUnitMovementFlag(MOVEMENTFLAG2_CAN_SWIM_TO_FLY_TRANS); + else + RemoveExtraUnitMovementFlag(MOVEMENTFLAG2_CAN_SWIM_TO_FLY_TRANS); + + return true; +} + +bool Unit::SetWaterWalking(bool enable) +{ + if (enable == HasUnitMovementFlag(MOVEMENTFLAG_WATERWALKING)) + return false; + + if (enable) + AddUnitMovementFlag(MOVEMENTFLAG_WATERWALKING); + else + RemoveUnitMovementFlag(MOVEMENTFLAG_WATERWALKING); + + if (enable) + Movement::PacketSender(this, SMSG_SPLINE_MOVE_SET_WATER_WALK, SMSG_MOVE_WATER_WALK).Send(); + else + Movement::PacketSender(this, SMSG_SPLINE_MOVE_SET_LAND_WALK, SMSG_MOVE_LAND_WALK).Send(); + + return true; +} + +bool Unit::SetFeatherFall(bool enable) +{ + if (enable == HasUnitMovementFlag(MOVEMENTFLAG_FALLING_SLOW)) + return false; + + if (enable) + AddUnitMovementFlag(MOVEMENTFLAG_FALLING_SLOW); + else + RemoveUnitMovementFlag(MOVEMENTFLAG_FALLING_SLOW); + + if (enable) + Movement::PacketSender(this, SMSG_SPLINE_MOVE_SET_FEATHER_FALL, SMSG_MOVE_FEATHER_FALL).Send(); + else + Movement::PacketSender(this, SMSG_SPLINE_MOVE_SET_NORMAL_FALL, SMSG_MOVE_NORMAL_FALL).Send(); + + return true; +} + +bool Unit::SetHover(bool enable, bool /*updateAnimTier = true*/) +{ + if (enable == HasUnitMovementFlag(MOVEMENTFLAG_HOVER)) + return false; + + float hoverHeight = GetFloatValue(UNIT_FIELD_HOVERHEIGHT); + + if (enable) + { + //! No need to check height on ascent + AddUnitMovementFlag(MOVEMENTFLAG_HOVER); + if (hoverHeight && GetPositionZ() - GetFloorZ() < hoverHeight) + UpdateHeight(GetPositionZ() + hoverHeight); + } + else + { + RemoveUnitMovementFlag(MOVEMENTFLAG_HOVER); + //! Dying creatures will MoveFall from setDeathState + if (hoverHeight && (!isDying() || GetTypeId() != TYPEID_UNIT)) + { + float newZ = std::max(GetFloorZ(), GetPositionZ() - hoverHeight); + UpdateAllowedPositionZ(GetPositionX(), GetPositionY(), newZ); + UpdateHeight(newZ); + } + } + + if (enable) + Movement::PacketSender(this, SMSG_SPLINE_MOVE_SET_HOVER, SMSG_MOVE_SET_HOVER).Send(); + else + Movement::PacketSender(this, SMSG_SPLINE_MOVE_UNSET_HOVER, SMSG_MOVE_UNSET_HOVER).Send(); + + if (IsAlive()) + { + if (IsGravityDisabled() || IsHovering()) + SetPlayHoverAnim(true); + else + SetPlayHoverAnim(false); + } + else if (IsPlayer()) // To update player who dies while flying/hovering + SetPlayHoverAnim(false, false); + + return true; +} + +void Unit::SendSetVehicleRecId(uint32 vehicleId) +{ + WorldPackets::Vehicle::SetVehicleRecID setVehicleRec; + setVehicleRec.VehicleGUID = GetGUID(); + setVehicleRec.VehicleRecID = vehicleId; + SendMessageToSet(setVehicleRec.Write(), true); +} + +void Unit::SetPlayHoverAnim(bool enable, bool sendUpdate /*= true*/) +{ + if (IsPlayingHoverAnim() == enable) + return; + + m_updateFlag.PlayerHoverAnim = enable; + + if (!sendUpdate) + return; + + WorldPackets::Misc::SetPlayHoverAnim packet; + packet.UnitGUID = GetGUID(); + packet.PlayHoverAnim = enable; + SendMessageToSet(packet.Write(), true); +} + +bool Unit::IsSplineEnabled() const +{ + return movespline->Initialized() && !movespline->Finalized(); +} + +void Unit::BuildValuesUpdate(uint8 updateType, ByteBuffer* data, Player* target) const +{ + if (!target) + return; + + ByteBuffer fieldBuffer; + + uint32 valCount = m_valuesCount; + + uint32* flags = UnitUpdateFieldFlags; + uint32 visibleFlag = UF_FLAG_PUBLIC; + + if (target == this) + visibleFlag |= UF_FLAG_PRIVATE; + else if (GetTypeId() == TYPEID_PLAYER) + valCount = PLAYER_END_NOT_SELF; + + UpdateMaskPacketBuilder updateMask(valCount); + + Player* plr = GetCharmerOrOwnerPlayerOrPlayerItself(); + if (GetOwnerGUID() == target->GetGUID()) + visibleFlag |= UF_FLAG_OWNER; + + if (HasFlag(UNIT_DYNAMIC_FLAGS, 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; + + if (IsCreature()) + visibleFlag |= UF_FLAG_UNIT_ALL; + + Creature const* creature = ToCreature(); + for (uint16 index = 0; index < valCount; ++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) + { + uint32 appendValue = m_uint32Values[UNIT_NPC_FLAGS]; + + if (creature) + { + if (!target->CanSeeSpellClickOn(creature)) + appendValue &= ~UNIT_NPC_FLAG_SPELLCLICK; + + if (!creature->IsClassTrainerOf(target)) + appendValue &= ~UNIT_NPC_FLAG_TRAINER_CLASS; + } + + fieldBuffer << uint32(appendValue); + } + else if (index == UNIT_FIELD_AURASTATE) + { + // Check per caster aura states to not enable using a spell in client if specified aura is not by target + fieldBuffer << BuildAuraStateUpdateForTarget(target); + } + // 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_RESISTANCEBUFFMODSPOSITIVE && index <= (UNIT_FIELD_RESISTANCEBUFFMODSPOSITIVE + 6)) || + (index >= UNIT_FIELD_RESISTANCEBUFFMODSNEGATIVE && index <= (UNIT_FIELD_RESISTANCEBUFFMODSNEGATIVE + 6))) + { + fieldBuffer << uint32(m_floatValues[index]); + } + // Gamemasters should be always able to select units - remove not selectable flag + else if (index == UNIT_FIELD_FLAGS) + { + uint32 appendValue = m_uint32Values[UNIT_FIELD_FLAGS]; + if (target->IsGameMaster()) + appendValue &= ~UNIT_FLAG_NOT_SELECTABLE; + + fieldBuffer << uint32(appendValue); + } + // use modelid_a if not gm, _h if gm for CREATURE_FLAG_EXTRA_TRIGGER creatures + else if (index == UNIT_FIELD_DISPLAYID) + { + 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->flags_extra & CREATURE_FLAG_EXTRA_TRIGGER) + if (target->IsGameMaster()) + displayId = cinfo->GetFirstVisibleModel()->CreatureDisplayID; + } + + fieldBuffer << uint32(displayId); + } + // hide lootable animation for unallowed players + else if (index == UNIT_DYNAMIC_FLAGS) + { + 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; + + fieldBuffer << dynamicFlags; + } + // FG: pretend that OTHER players in own group are friendly ("blue") + else if (index == UNIT_FIELD_BYTES_2 || index == UNIT_FIELD_FACTIONTEMPLATE) + { + 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)) + { + if (index == UNIT_FIELD_BYTES_2) + // Allow targetting opposite faction in party when enabled in config + fieldBuffer << (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 !! + else + // pretend that all other HOSTILE players have own faction, to allow follow, heal, rezz (trade wont work) + fieldBuffer << uint32(target->GetFaction()); + } + else + fieldBuffer << m_uint32Values[index]; + } + else + fieldBuffer << m_uint32Values[index]; + } + else + { + // send in current format (float as float, uint32 as uint32) + fieldBuffer << m_uint32Values[index]; + } + } + } + + updateMask.AppendToPacket(data); + data->append(fieldBuffer); +} + +void Unit::DestroyForPlayer(Player* target, bool /*onDeath = false*/) const +{ + if (Battleground* bg = target->GetBattleground()) + { + if (bg->isArena()) + { + WorldPacket data(SMSG_ARENA_UNIT_DESTROYED, 8); + data << GetGUID(); + target->SendDirectMessage(&data); + } + } + + WorldObject::DestroyForPlayer(target); +} + +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 + 1) + itr = auras.begin(); + } + } + } + else if (diff < 0) + return false; + } + } + + return true; +} + +void Unit::Talk(std::string const& text, ChatMsg msgType, Language language, float textRange, WorldObject const* target) +{ + Trinity::CustomChatTextBuilder builder(this, msgType, text, language, target); + Trinity::LocalizedPacketDo localizer(builder); + Trinity::PlayerDistWorker > worker(this, textRange, localizer); + Cell::VisitWorldObjects(this, worker, textRange); +} + +void Unit::Say(std::string const& 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 const& 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 const& 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 const& 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); +} + +void Unit::Talk(uint32 textId, ChatMsg msgType, float textRange, WorldObject const* target) +{ + if (!sObjectMgr->GetBroadcastText(textId)) + { + TC_LOG_ERROR("entities.unit", "WorldObject::MonsterText: `broadcast_text` (ID: %u) was not found", textId); + return; + } + + Trinity::BroadcastTextBuilder builder(this, msgType, textId, getGender(), target); + Trinity::LocalizedPacketDo localizer(builder); + Trinity::PlayerDistWorker > worker(this, textRange, localizer); + Cell::VisitWorldObjects(this, worker, textRange); +} + +void Unit::Say(uint32 textId, WorldObject const* target /*= nullptr*/) +{ + Talk(textId, CHAT_MSG_MONSTER_SAY, sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_SAY), target); +} + +void Unit::Yell(uint32 textId, WorldObject const* target /*= nullptr*/) +{ + Talk(textId, CHAT_MSG_MONSTER_YELL, sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_YELL), target); +} + +void Unit::TextEmote(uint32 textId, WorldObject const* target /*= nullptr*/, bool isBossEmote /*= false*/) +{ + Talk(textId, isBossEmote ? CHAT_MSG_RAID_BOSS_EMOTE : CHAT_MSG_MONSTER_EMOTE, sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_TEXTEMOTE), target); +} + +void Unit::Whisper(uint32 textId, Player* target, bool isBossWhisper /*= false*/) +{ + if (!target) + return; + + BroadcastText const* bct = sObjectMgr->GetBroadcastText(textId); + if (!bct) + { + TC_LOG_ERROR("entities.unit", "WorldObject::MonsterWhisper: `broadcast_text` was not %u 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); +} + +// Returns collisionheight of the unit. If it is 0, it returns DEFAULT_COLLISION_HEIGHT. +float Unit::GetCollisionHeight() const +{ + float scaleMod = GetObjectScale(); // 99% sure about this + + if (IsMounted()) + { + if (CreatureDisplayInfoEntry const* mountDisplayInfo = sCreatureDisplayInfoStore.LookupEntry(GetUInt32Value(UNIT_FIELD_MOUNTDISPLAYID))) + { + if (CreatureModelDataEntry const* mountModelData = sCreatureModelDataStore.LookupEntry(mountDisplayInfo->ModelID)) + { + CreatureDisplayInfoEntry const* displayInfo = sCreatureDisplayInfoStore.AssertEntry(GetNativeDisplayId()); + CreatureModelDataEntry const* modelData = sCreatureModelDataStore.AssertEntry(displayInfo->ModelID); + float const collisionHeight = scaleMod * ((mountModelData->MountHeight * mountDisplayInfo->CreatureModelScale) + (modelData->CollisionHeight * modelData->ModelScale * displayInfo->CreatureModelScale * 0.5f)); + return collisionHeight == 0.0f ? DEFAULT_COLLISION_HEIGHT : collisionHeight; + } + } + } + + //! Dismounting case - use basic default model data + CreatureDisplayInfoEntry const* displayInfo = sCreatureDisplayInfoStore.AssertEntry(GetNativeDisplayId()); + CreatureModelDataEntry const* modelData = sCreatureModelDataStore.AssertEntry(displayInfo->ModelID); + + float const collisionHeight = scaleMod * modelData->CollisionHeight * modelData->ModelScale * displayInfo->CreatureModelScale; + return collisionHeight == 0.0f ? DEFAULT_COLLISION_HEIGHT : collisionHeight; +} + +SpellInfo const* Unit::GetCastSpellInfo(SpellInfo const* spellInfo, TriggerCastFlags& triggerFlag) const +{ + Unit::AuraEffectList const& swaps = GetAuraEffectsByType(SPELL_AURA_OVERRIDE_ACTIONBAR_SPELLS); + Unit::AuraEffectList const& swaps2 = GetAuraEffectsByType(SPELL_AURA_OVERRIDE_ACTIONBAR_SPELLS_TRIGGERED); + + for (AuraEffect const* auraEffect : swaps) + { + if (uint32(auraEffect->GetMiscValue()) == spellInfo->Id || auraEffect->IsAffectingSpell(spellInfo)) + { + if (SpellInfo const* newInfo = sSpellMgr->GetSpellInfo(auraEffect->GetAmount())) + { + if (auraEffect->GetSpellInfo()->HasAttribute(SPELL_ATTR8_IGNORE_SPELLCAST_OVERRIDE_COST)) + triggerFlag |= TRIGGERED_IGNORE_POWER_COST; + else + triggerFlag &= ~TRIGGERED_IGNORE_POWER_COST; + + return newInfo; + } + } + } + + for (AuraEffect const* auraEffect : swaps2) + { + if (uint32(auraEffect->GetMiscValue()) == spellInfo->Id || auraEffect->IsAffectingSpell(spellInfo)) + { + if (SpellInfo const* newInfo = sSpellMgr->GetSpellInfo(auraEffect->GetAmount())) + { + if (auraEffect->GetSpellInfo()->HasAttribute(SPELL_ATTR8_IGNORE_SPELLCAST_OVERRIDE_COST)) + triggerFlag |= TRIGGERED_IGNORE_POWER_COST; + else + triggerFlag &= ~TRIGGERED_IGNORE_POWER_COST; + + return newInfo; + } + } + } + + return spellInfo; +} + +bool Unit::CanExecutePendingSpellCastRequest(SpellInfo const* spellInfo) const +{ + // Generic and melee spells have to wait, channeled spells can be processed immediately. + if (!GetCurrentSpell(CURRENT_CHANNELED_SPELL) && HasUnitState(UNIT_STATE_CASTING)) + return false; + + // Waiting for the global cooldown to expire + if (GetSpellHistory()->GetRemainingGlobalCooldown(spellInfo) > 0) + return false; + + return true; +} + +void Unit::RequestSpellCast(std::unique_ptr castRequest, SpellInfo const* spellInfo) +{ + // We are overriding an already existing spell cast request so inform the client that the old cast is being replaced + if (_pendingSpellCastRequest) + CancelPendingCastRequest(); + + _pendingSpellCastRequest = std::move(castRequest); + + // If we can process the cast request right now, do it. + if (CanExecutePendingSpellCastRequest(spellInfo)) + ProcessPendingSpellCastRequest(); +} + +void Unit::CancelPendingCastRequest() +{ + if (!_pendingSpellCastRequest) + return; + + // We have to inform the client that the cast has been canceled to avoid the cast button to stay highlightened + if (IsPlayer()) + { + // @todo: convert to packet class + WorldPacket data(SMSG_CAST_FAILED, 1 + 4 + 1); + data << uint8(_pendingSpellCastRequest->CastRequest.CastID); + data << uint32(_pendingSpellCastRequest->CastRequest.SpellID); + data << uint8(SPELL_FAILED_DONT_REPORT); + ToPlayer()->SendDirectMessage(&data); + } + + _pendingSpellCastRequest = nullptr; +} + +// A spell can be queued up within 400 milliseconds before global cooldown expires or the cast finishes +static constexpr uint16 const SPELL_QUEUE_TIME_WINDOW = 400; + +bool Unit::CanRequestSpellCast(SpellInfo const* spellInfo) const +{ + if (GetSpellHistory()->GetRemainingGlobalCooldown(spellInfo) > SPELL_QUEUE_TIME_WINDOW) + return false; + + for (CurrentSpellTypes spellSlot : { CURRENT_MELEE_SPELL, CURRENT_GENERIC_SPELL }) + if (Spell const* spell = GetCurrentSpell(spellSlot)) + if (spell->GetRemainingCastTime() > SPELL_QUEUE_TIME_WINDOW) + return false; + + return true; +} + +void Unit::ProcessPendingSpellCastRequest() +{ + if (!_pendingSpellCastRequest) + return; + + // Sanity check. If the player requested an invalid spell cast, just skip the request. + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(_pendingSpellCastRequest->CastRequest.SpellID); + if (!spellInfo) + { + _pendingSpellCastRequest = nullptr; + return; + } + + // Waiting for ongoing casts to finish and the global cooldown to expire. + if (!CanExecutePendingSpellCastRequest(spellInfo)) + return; + + // Spell can be executed now. Check data and prepare cast. + Unit* caster = this; + + // Process packet provided targets + SpellCastTargets targets(caster, _pendingSpellCastRequest->CastRequest); + + + // The spell cast has been requested by using an item. Handle the cast accordingly. + if (_pendingSpellCastRequest->CastItemData.has_value()) + { + ProcessItemCast(*_pendingSpellCastRequest, targets); + _pendingSpellCastRequest = nullptr; + return; + } + + if (caster->IsCreature() && !caster->ToCreature()->HasSpell(spellInfo->Id)) + { + // If the vehicle creature does not have the spell but it allows the passenger to cast own spells + // change caster to player and let him cast + + Unit* charmer = caster->GetCharmer(); + if (charmer && (!charmer->IsOnVehicle(this) || spellInfo->CheckVehicle(charmer) != SPELL_CAST_OK)) + return; + else if (charmer) + caster = charmer; + else + { + TC_LOG_ERROR("spells", "Creature %s tried to allow its charmer to cast spell %u but there is no charmer.", caster->GetGUID().ToString().c_str(), spellInfo->Id); + return; + } + } + + TriggerCastFlags triggerFlag = TRIGGERED_NONE; + + if (caster->GetTypeId() == TYPEID_PLAYER && !caster->ToPlayer()->HasActiveSpell(spellInfo->Id) && !spellInfo->HasAttribute(SPELL_ATTR8_SKIP_IS_KNOWN_CHECK) && + !caster->ToPlayer()->HasArchProject(static_cast(spellInfo->ResearchProjectId))) + { + bool allow = false; + + // allow casting of unknown spells for special lock cases + if (GameObject *go = targets.GetGOTarget()) + if (go->GetSpellForLock(caster->ToPlayer()) == spellInfo) + allow = true; + + // TODO: Preparation for #23204 + // allow casting of spells triggered by clientside periodic trigger auras + /* + if (caster->HasAuraTypeWithTriggerSpell(SPELL_AURA_PERIODIC_TRIGGER_SPELL_FROM_CLIENT, spellId)) + { + allow = true; + triggerFlag = TRIGGERED_FULL_MASK; + } + */ + + if (!allow) + { + _pendingSpellCastRequest = nullptr; + return; + } + } + + // Check possible spell cast overrides + spellInfo = caster->GetCastSpellInfo(spellInfo, triggerFlag); + + // can't use our own spells when we're in possession of another unit, + if (caster->isPossessing()) + { + _pendingSpellCastRequest = nullptr; + return; + } + + // Client is resending autoshot cast opcode when other spell is cast during shoot rotation + // Skip it to prevent "interrupt" message + // Also check targets! target may have changed and we need to interrupt current spell + if (spellInfo->IsAutoRepeatRangedSpell()) + { + if (Spell* spell = caster->GetCurrentSpell(CURRENT_AUTOREPEAT_SPELL)) + { + if (spell->m_spellInfo == spellInfo && spell->m_targets.GetUnitTargetGUID() == targets.GetUnitTargetGUID()) + { + _pendingSpellCastRequest.reset(); + return; + } + } + } + + // auto-selection buff level base at target level (in spellInfo) + // TODO: is this even necessary? client already seems to send correct rank for "standard" buffs + if (spellInfo->IsPositive()) + { + if (Unit* target = targets.GetUnitTarget()) + { + SpellInfo const* actualSpellInfo = spellInfo->GetAuraRankForLevel(target->getLevel()); + + // if rank not found then function return NULL but in explicit cast case original spell can be cast and later failed with appropriate error message + if (actualSpellInfo) + spellInfo = actualSpellInfo; + } + } + + if (!_pendingSpellCastRequest->CastRequest.Weight.empty()) + { + ArchData archaeologyCastData; + for (WorldPackets::Spells::SpellWeight const& weight : _pendingSpellCastRequest->CastRequest.Weight) + { + switch (weight.Type) + { + case 1: // Currency + archaeologyCastData.FragId = weight.ID; + archaeologyCastData.FragCount = weight.Quantity; + break; + case 2: // Item + archaeologyCastData.KeyId = weight.ID; + archaeologyCastData.KeyCount = weight.Quantity; + break; + default: + break; + } + } + + if (Player* player = caster->ToPlayer()) + player->SetArchData(archaeologyCastData); + } + + Spell* spell = new Spell(caster, spellInfo, triggerFlag); + spell->m_cast_count = _pendingSpellCastRequest->CastRequest.CastID; // set count of casts + spell->m_glyphIndex = _pendingSpellCastRequest->CastRequest.Misc; + spell->prepare(targets); + + _pendingSpellCastRequest.reset(); +} + +void Unit::ProcessItemCast(PendingSpellCastRequest const& castRequest, SpellCastTargets const& targets) +{ + Player* player = ToPlayer(); + if (!player || !castRequest.CastItemData.has_value()) + return; + + Item* item = player->GetUseableItemByPos(castRequest.CastItemData->BagSlot, castRequest.CastItemData->Slot); + if (!item) + { + player->SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, nullptr, nullptr); + return; + } + + if (item->GetGUID() != castRequest.CastItemData->CastItem) + { + player->SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, nullptr, nullptr); + return; + } + + ItemTemplate const* proto = item->GetTemplate(); + if (!proto) + { + player->SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, item, nullptr); + return; + } + + // some item classes can be used only in equipped state + if (proto->GetInventoryType() != INVTYPE_NON_EQUIP && !item->IsEquipped()) + { + player->SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, item, nullptr); + return; + } + + InventoryResult msg = player->CanUseItem(item); + if (msg != EQUIP_ERR_OK) + { + player->SendEquipError(msg, item, nullptr); + return; + } + + // only allow conjured consumable, bandage, poisons (all should have the 2^21 item flag set in DB) + if (proto->GetClass() == ITEM_CLASS_CONSUMABLE && !(proto->GetFlags() & ITEM_FLAG_IGNORE_DEFAULT_ARENA_RESTRICTIONS) && player->InArena()) + { + player->SendEquipError(EQUIP_ERR_NOT_DURING_ARENA_MATCH, item, nullptr); + return; + } + + // don't allow items banned in arena + if ((proto->GetFlags() & ITEM_FLAG_NOT_USEABLE_IN_ARENA) && player->InArena()) + { + player->SendEquipError(EQUIP_ERR_NOT_DURING_ARENA_MATCH, item, nullptr); + return; + } + + if (player->IsInCombat()) + { + for (ItemEffect const& effect : proto->Effects) + { + if (SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(effect.SpellID)) + { + if (!spellInfo->CanBeUsedInCombat()) + { + player->SendEquipError(EQUIP_ERR_NOT_IN_COMBAT, item, nullptr); + return; + } + } + } + } + + // check also BIND_ON_ACQUIRE and BIND_QUEST for .additem or .additemset case by GM (not binded at adding to inventory) + if (item->GetTemplate()->GetBonding() == BIND_ON_USE || item->GetTemplate()->GetBonding() == BIND_ON_ACQUIRE || item->GetTemplate()->GetBonding() == BIND_QUEST) + { + if (!item->IsSoulBound()) + { + item->SetState(ITEM_CHANGED, player); + item->SetBinding(true); + } + } + + player->RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::ItemUse); + + // Note: If script stop casting it must send appropriate data to client to prevent stuck item in gray state. + if (!sScriptMgr->OnItemUse(player, item, targets)) + { + // no script or script not process request by self + player->CastItemUseSpell(item, targets, castRequest.CastRequest.CastID, castRequest.CastRequest.Misc); + } +} + +void Unit::SetGameClientMovingMe(GameClient* gameClientMovingMe) +{ + _gameClientMovingMe = gameClientMovingMe; +} + +Stats& Unit::GetStats() const +{ + return *_stats; +} + +int32 Unit::GetStat(StatType statType) const +{ + if (statType < StatType::Strength) + return 0; + + return GetInt32Value(UNIT_FIELD_STAT0 + AsUnderlyingType(statType)); +} diff --git a/src/server/game/Entities/Unit/Unit.cpp.bak-viano-heal-debug-20261001-005842 b/src/server/game/Entities/Unit/Unit.cpp.bak-viano-heal-debug-20261001-005842 new file mode 100644 index 0000000000..411082eb3e --- /dev/null +++ b/src/server/game/Entities/Unit/Unit.cpp.bak-viano-heal-debug-20261001-005842 @@ -0,0 +1,14280 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include "Unit.h" +#include "AbstractPursuer.h" +#include "Archaeology.h" +#include "Battlefield.h" +#include "BattlefieldMgr.h" +#include "Battleground.h" +#include "BattlegroundScore.h" +#include "CellImpl.h" +#include "CharacterCache.h" +#include "CharmInfo.h" +#include "Chat.h" +#include "ChatTextBuilder.h" +#include "ChatPackets.h" +#include "CombatLogPackets.h" +#include "CombatPackets.h" +#include "Common.h" +#include "ConditionMgr.h" +#include "Containers.h" +#include "CreatureAI.h" +#include "CreatureAIImpl.h" +#include "CreatureGroups.h" +#include "Formulas.h" +#include "GameClient.h" +#include "GameTime.h" +#include "GridNotifiersImpl.h" +#include "Group.h" +#include "InstanceSaveMgr.h" +#include "InstanceScript.h" +#include "Item.h" +#include "ListUtils.h" +#include "Log.h" +#include "LootMgr.h" +#include "LootPackets.h" +#include "MiscPackets.h" +#include "MotionMaster.h" +#include "MovementGenerator.h" +#include "MovementPackets.h" +#include "MovementPacketBuilder.h" +#include "MovementStructures.h" +#include "MoveSpline.h" +#include "MoveSplineInit.h" +#include "MovementPacketSender.h" +#include "ObjectAccessor.h" +#include "ObjectMgr.h" +#include "Opcodes.h" +#include "OutdoorPvP.h" +#include "PassiveAI.h" +#include "Pet.h" +#include "PetPackets.h" +#include "PetAI.h" +#include "PhasingHandler.h" +#include "Player.h" +#include "PlayerAI.h" +#include "QuestDef.h" +#include "ReputationMgr.h" +#include "ScheduledChangeAI.h" +#include "Spell.h" +#include "SpellAuraEffects.h" +#include "SpellAuras.h" +#include "SpellCastRequest.h" +#include "SpellHistory.h" +#include "SpellInfo.h" +#include "SpellMgr.h" +#include "SpellPackets.h" +#include "Stats.h" +#include "TemporarySummon.h" +#include "Totem.h" +#include "Transport.h" +#include "UnitAI.h" +#include "UpdateFieldFlags.h" +#include "Util.h" +#include "Vehicle.h" +#include "VehiclePackets.h" +#include "World.h" +#include "WorldPacket.h" +#include "WorldSession.h" +#include +#include + +float baseMoveSpeed[MAX_MOVE_TYPE] = +{ + 2.5f, // MOVE_WALK + 7.0f, // MOVE_RUN + 4.5f, // MOVE_RUN_BACK + 4.722222f, // MOVE_SWIM + 2.5f, // MOVE_SWIM_BACK + 3.141594f, // MOVE_TURN_RATE + 7.0f, // MOVE_FLIGHT + 4.5f, // MOVE_FLIGHT_BACK + 3.14f // MOVE_PITCH_RATE +}; + +float playerBaseMoveSpeed[MAX_MOVE_TYPE] = +{ + 2.5f, // MOVE_WALK + 7.0f, // MOVE_RUN + 4.5f, // MOVE_RUN_BACK + 4.722222f, // MOVE_SWIM + 2.5f, // MOVE_SWIM_BACK + 3.141594f, // MOVE_TURN_RATE + 7.0f, // MOVE_FLIGHT + 4.5f, // MOVE_FLIGHT_BACK + 3.14f // MOVE_PITCH_RATE +}; + +DamageInfo::DamageInfo(Unit* attacker, Unit* victim, uint32 damage, SpellInfo const* spellInfo, SpellSchoolMask schoolMask, DamageEffectType damageType, WeaponAttackType attackType) + : m_attacker(attacker), m_victim(victim), m_damage(damage), m_spellInfo(spellInfo), m_schoolMask(schoolMask), m_damageType(damageType), m_attackType(attackType), + m_absorb(0), m_resist(0), m_block(0), m_hitMask(PROC_HIT_NONE) +{ +} + +DamageInfo::DamageInfo(CalcDamageInfo const& dmgInfo) + : m_attacker(dmgInfo.Attacker), m_victim(dmgInfo.Target), m_damage(dmgInfo.Damage), m_spellInfo(nullptr), m_schoolMask(SpellSchoolMask(dmgInfo.DamageSchoolMask)), + m_damageType(DIRECT_DAMAGE), m_attackType(dmgInfo.AttackType), m_absorb(dmgInfo.Absorb), m_resist(dmgInfo.Resist), m_block(dmgInfo.Blocked), m_hitMask(PROC_HIT_NONE) +{ + switch (dmgInfo.TargetState) + { + case VICTIMSTATE_IS_IMMUNE: + m_hitMask |= PROC_HIT_IMMUNE; + break; + case VICTIMSTATE_BLOCKS: + m_hitMask |= PROC_HIT_FULL_BLOCK; + break; + } + + if (dmgInfo.HitInfo & (HITINFO_PARTIAL_ABSORB | HITINFO_FULL_ABSORB)) + m_hitMask |= PROC_HIT_ABSORB; + + if (dmgInfo.HitInfo & HITINFO_FULL_RESIST) + m_hitMask |= PROC_HIT_FULL_RESIST; + + if (m_block) + m_hitMask |= PROC_HIT_BLOCK; + + bool const damageNullified = (dmgInfo.HitInfo & (HITINFO_FULL_ABSORB | HITINFO_FULL_RESIST)) != 0 || + (m_hitMask & (PROC_HIT_IMMUNE | PROC_HIT_FULL_BLOCK)) != 0; + switch (dmgInfo.HitOutCome) + { + case MELEE_HIT_MISS: + m_hitMask |= PROC_HIT_MISS; + break; + case MELEE_HIT_DODGE: + m_hitMask |= PROC_HIT_DODGE; + break; + case MELEE_HIT_PARRY: + m_hitMask |= PROC_HIT_PARRY; + break; + case MELEE_HIT_EVADE: + m_hitMask |= PROC_HIT_EVADE; + break; + case MELEE_HIT_BLOCK: + case MELEE_HIT_CRUSHING: + case MELEE_HIT_GLANCING: + case MELEE_HIT_NORMAL: + if (!damageNullified) + m_hitMask |= PROC_HIT_NORMAL; + break; + case MELEE_HIT_CRIT: + if (!damageNullified) + m_hitMask |= PROC_HIT_CRITICAL; + break; + } +} + +DamageInfo::DamageInfo(SpellNonMeleeDamage const& spellNonMeleeDamage, DamageEffectType damageType, WeaponAttackType attackType, ProcFlagsHit hitMask) + : m_attacker(spellNonMeleeDamage.attacker), m_victim(spellNonMeleeDamage.target), m_damage(spellNonMeleeDamage.damage), + m_spellInfo(sSpellMgr->GetSpellInfo(spellNonMeleeDamage.SpellID)), m_schoolMask(SpellSchoolMask(spellNonMeleeDamage.schoolMask)), m_damageType(damageType), + m_attackType(attackType), m_absorb(spellNonMeleeDamage.absorb), m_resist(spellNonMeleeDamage.resist), m_block(spellNonMeleeDamage.blocked), m_hitMask(hitMask) +{ + if (spellNonMeleeDamage.blocked) + m_hitMask |= PROC_HIT_BLOCK; + if (spellNonMeleeDamage.absorb) + m_hitMask |= PROC_HIT_ABSORB; +} + +void DamageInfo::ModifyDamage(int32 amount) +{ + amount = std::max(amount, -static_cast(GetDamage())); + m_damage += amount; +} + +void DamageInfo::AbsorbDamage(uint32 amount) +{ + amount = std::min(amount, GetDamage()); + m_absorb += amount; + m_damage -= amount; + m_hitMask |= PROC_HIT_ABSORB; +} + +void DamageInfo::ResistDamage(uint32 amount) +{ + amount = std::min(amount, GetDamage()); + m_resist += amount; + m_damage -= amount; + if (!m_damage) + { + m_hitMask |= PROC_HIT_FULL_RESIST; + m_hitMask &= ~(PROC_HIT_NORMAL | PROC_HIT_CRITICAL); + } +} + +void DamageInfo::BlockDamage(uint32 amount) +{ + amount = std::min(amount, GetDamage()); + m_block += amount; + m_damage -= amount; + m_hitMask |= PROC_HIT_BLOCK; + if (!m_damage) + { + m_hitMask |= PROC_HIT_FULL_BLOCK; + m_hitMask &= ~(PROC_HIT_NORMAL | PROC_HIT_CRITICAL); + } +} + +ProcFlagsHit DamageInfo::GetHitMask() const +{ + return m_hitMask; +} + +HealInfo::HealInfo(Unit* healer, Unit* target, uint32 heal, SpellInfo const* spellInfo, SpellSchoolMask schoolMask) + : _healer(healer), _target(target), _heal(heal), _effectiveHeal(0), _absorb(0), _spellInfo(spellInfo), _schoolMask(schoolMask), _hitMask(0) +{ +} + +void HealInfo::AbsorbHeal(uint32 amount) +{ + amount = std::min(amount, GetHeal()); + _absorb += amount; + _heal -= amount; + amount = std::min(amount, GetEffectiveHeal()); + _effectiveHeal -= amount; + _hitMask |= PROC_HIT_ABSORB; +} + +uint32 HealInfo::GetHitMask() const +{ + return _hitMask; +} + +ProcEventInfo::ProcEventInfo(Unit* actor, Unit* actionTarget, Unit* procTarget, + ProcFlags typeMask, ProcFlagsSpellType spellTypeMask, + ProcFlagsSpellPhase spellPhaseMask, ProcFlagsHit hitMask, + Spell* spell, DamageInfo* damageInfo, + HealInfo* healInfo) : + _actor(actor), _actionTarget(actionTarget), _procTarget(procTarget), + _typeMask(typeMask), _spellTypeMask(spellTypeMask), + _spellPhaseMask(spellPhaseMask), _hitMask(hitMask), _spell(spell), + _damageInfo(damageInfo), _healInfo(healInfo) +{ } + +SpellInfo const* ProcEventInfo::GetSpellInfo() const +{ + if (_spell) + return _spell->GetSpellInfo(); + if (_damageInfo) + return _damageInfo->GetSpellInfo(); + if (_healInfo) + return _healInfo->GetSpellInfo(); + return nullptr; +} + +SpellSchoolMask ProcEventInfo::GetSchoolMask() const +{ + if (_spell) + return _spell->GetSpellInfo()->GetSchoolMask(); + if (_damageInfo) + return _damageInfo->GetSchoolMask(); + if (_healInfo) + return _healInfo->GetSchoolMask(); + return SPELL_SCHOOL_MASK_NONE; +} + +DispelableAura::DispelableAura(Aura* aura, int32 dispelChance, uint8 dispelCharges) : + _aura(aura), _chance(dispelChance), _charges(dispelCharges) +{ +} + +DispelableAura::~DispelableAura() = default; + +bool DispelableAura::RollDispel() const +{ + return roll_chance_i(_chance); +} + +Unit::Unit(bool isWorldObject) : + WorldObject(isWorldObject), m_lastSanctuaryTime(0), + LastCharmerGUID(), m_ControlledByPlayer(false), movespline(std::make_unique()), + m_procDeep(0), m_procChainLength(0), m_removedAurasCount(0), + m_interruptMask(SpellAuraInterruptFlags::None), m_interruptMask2(SpellAuraInterruptFlags2::None), + m_charmer(nullptr), m_charmed(nullptr), + i_motionMaster(std::make_unique(this)), m_vehicle(nullptr), + m_unitTypeMask(UNIT_MASK_NONE), m_isEngaged(false), m_combatManager(this), m_threatManager(this), + i_AI(nullptr), m_aiLocked(false), m_spellHistory(std::make_unique(this)), + _stats(std::make_unique(this)) +{ + m_objectType |= TYPEMASK_UNIT; + m_objectTypeId = TYPEID_UNIT; + + m_updateFlag.MovementUpdate = true; + + m_baseAttackSpeed = { }; + m_attackTimer = { }; + m_modAttackSpeedPct.fill(1.0f); + + m_extraAttacks = 0; + m_canDualWield = false; + + m_movementCounter = 0; + + m_state = 0; + m_deathState = ALIVE; + + m_currentSpells.fill(nullptr); + + m_auraUpdateIterator = m_ownedAuras.end(); + + m_transform = 0; + m_canModifyStats = false; + + 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] = BASE_MINDAMAGE; + m_weaponDamage[i][MAXDAMAGE] = BASE_MAXDAMAGE; + } + + m_attacking = nullptr; + m_modMeleeHitChance = 0.0f; + m_modRangedHitChance = 0.0f; + m_modSpellHitChance = 0.0f; + m_baseSpellCritChance = 5; + + m_speed_rate.fill(1.0f); + _gameClientMovingMe = nullptr; + + // remove aurastates allowing special moves + m_reactiveTimer = { }; + m_cleanupDone = false; + m_duringRemoveFromWorld = false; + + m_serverSideVisibility.SetValue(SERVERSIDE_VISIBILITY_GHOST, GHOST_VISIBILITY_ALIVE); + + _lastLiquid = nullptr; + + _powerBarId = 0; + + _powerUpdateTimer = GetPowerUpdateInterval(); + _healthRegenerationTimer = UNIT_HEALTH_REGENERATION_INTERVAL; + + _powerFraction.fill(0.0f); + _usedPowerTypes.fill(MAX_POWERS); + + _oldFactionId = 0; + _isWalkingBeforeCharm = false; +} + +//////////////////////////////////////////////////////////// +// Methods of class Unit +Unit::~Unit() +{ + // set current spells as deletable + for (size_t i = 0; i < m_currentSpells.size(); ++i) + { + if (m_currentSpells[i]) + { + m_currentSpells[i]->SetReferencedFromCurrent(false); + m_currentSpells[i] = nullptr; + } + } + + m_Events.KillAllEvents(true); + + _DeleteRemovedAuras(); + + ASSERT(!m_duringRemoveFromWorld); + ASSERT(!m_attacking); + ASSERT(m_attackers.empty()); + ASSERT(m_sharedVision.empty()); + ASSERT(m_Controlled.empty()); + ASSERT(m_appliedAuras.empty()); + ASSERT(m_ownedAuras.empty()); + ASSERT(m_removedAuras.empty()); + ASSERT(m_gameObj.empty()); + ASSERT(m_dynObj.empty()); + ASSERT(!_gameClientMovingMe || _gameClientMovingMe->GetBasePlayer() == this); +} + +void Unit::Update(uint32 p_time) +{ + // WARNING! Order of execution here is important, do not change. + // Spells must be processed with event system BEFORE they go to _UpdateSpells. + // Or else we may have some SPELL_STATE_FINISHED spells stalled in pointers, that is bad. + WorldObject::Update(p_time); + + CheckPendingMovementAcks(); + + if (!IsInWorld()) + return; + + // Update server controlled facing for standing units + if (movespline->Finalized() && !isMoving()) + { + // Prioritize tracking channeled spells above unit targets if they should both be present at the same time. + Spell const* channeledSpell = GetCurrentSpell(CURRENT_CHANNELED_SPELL); + if (channeledSpell && channeledSpell->GetSpellInfo()->HasAttribute(SPELL_ATTR1_TRACK_TARGET_IN_CHANNEL)) + { + ObjectGuid const channelGuid = GetChannelObjectGuid(); + if (!channelGuid.IsEmpty() && channelGuid != GetGUID()) + if (WorldObject const* objectTarget = ObjectAccessor::GetWorldObject(*this, channelGuid)) + SetOrientationTowards(objectTarget); + } + else if (IsCreature() && !HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_POSSESSED)) + { + ObjectGuid const targetGuid = GetTarget(); + if (!targetGuid.IsEmpty() && targetGuid != GetGUID()) + if (WorldObject const* objectTarget = ObjectAccessor::GetWorldObject(*this, targetGuid)) + SetOrientationTowards(objectTarget); + } + } + + _UpdateSpells(p_time); + ProcessPendingSpellCastRequest(); + + // If this is set during update SetCantProc(false) call is missing somewhere in the code + // Having this would prevent spells from being proced, so let's crash + ASSERT(!m_procDeep); + + m_combatManager.Update(p_time); + + auto spellPausesCombatTimer = [&](CurrentSpellTypes type) + { + return GetCurrentSpell(type) && GetCurrentSpell(type)->GetSpellInfo()->HasAttribute(SPELL_ATTR6_DELAY_COMBAT_TIMER_DURING_CAST); + }; + + if (!spellPausesCombatTimer(CURRENT_GENERIC_SPELL) && !spellPausesCombatTimer(CURRENT_CHANNELED_SPELL)) + { + if (uint32 base_att = getAttackTimer(BASE_ATTACK)) + setAttackTimer(BASE_ATTACK, (p_time >= base_att ? 0 : base_att - p_time)); + if (uint32 ranged_att = getAttackTimer(RANGED_ATTACK)) + setAttackTimer(RANGED_ATTACK, (p_time >= ranged_att ? 0 : ranged_att - p_time)); + if (uint32 off_att = getAttackTimer(OFF_ATTACK)) + setAttackTimer(OFF_ATTACK, (p_time >= off_att ? 0 : off_att - p_time)); + } + + // update abilities available only for fraction of time + UpdateReactives(p_time); + + if (IsAlive()) + { + ModifyAuraState(AURA_STATE_HEALTHLESS_20_PERCENT, HealthBelowPct(20)); + ModifyAuraState(AURA_STATE_HEALTHLESS_35_PERCENT, HealthBelowPct(35)); + ModifyAuraState(AURA_STATE_HEALTH_ABOVE_75_PERCENT, HealthAbovePct(75)); + } + + UpdateSplineMovement(p_time); + i_motionMaster->UpdateMotion(p_time); + + if (!i_AI && (GetTypeId() != TYPEID_PLAYER || IsCharmed())) + UpdateCharmAI(); + + // Wait with the aura interrupts until we have updated our movement generators and position6 + if (IsPlayer()) + InterruptMovementBasedAuras(); + else if (!movespline->Finalized()) + InterruptMovementBasedAuras(); + + // All position info based actions have been executed, reset info + _positionUpdateInfo.Reset(); + + // Update Health Regeneration + _healthRegenerationTimer -= p_time; + if (_healthRegenerationTimer <= 0) + { + RegenerateHealth(); + _healthRegenerationTimer += UNIT_HEALTH_REGENERATION_INTERVAL; + } + + if (HasScheduledAIChange() && (GetTypeId() != TYPEID_PLAYER || (IsCharmed() && GetCharmerGUID().IsCreature()))) + UpdateCharmAI(); + RefreshAI(); +} + +void Unit::Heartbeat() +{ + WorldObject::Heartbeat(); + + // SMSG_FLIGHT_SPLINE_SYNC for cyclic splines + SendFlightSplineSyncUpdate(); + + // Trigger heartbeat procs and generic aura behavior such as food emotes and invoking aura script hooks + TriggerAuraHeartbeat(); +} + +void Unit::TriggerAuraHeartbeat() +{ + for (auto const& [_, auraApplication] : m_appliedAuras) + auraApplication->GetBase()->Heartbeat(); + + Unit::ProcSkillsAndAuras(this, nullptr, PROC_FLAG_HEARTBEAT, PROC_FLAG_NONE, PROC_SPELL_TYPE_MASK_ALL, PROC_SPELL_PHASE_NONE, PROC_HIT_NONE, nullptr, nullptr, nullptr); +} + +bool Unit::haveOffhandWeapon() const +{ + if (Player const* player = ToPlayer()) + return player->GetWeaponForAttack(OFF_ATTACK, true) != nullptr; + + return CanDualWield(); +} + +void Unit::MonsterMoveWithSpeed(float x, float y, float z, float speed, bool generatePath, bool forceDestination) +{ + Movement::MoveSplineInit init(this); + init.MoveTo(x, y, z, generatePath, forceDestination); + init.SetVelocity(speed); + GetMotionMaster()->LaunchMoveSpline(std::move(init), 0, MOTION_SLOT_ACTIVE, POINT_MOTION_TYPE); +} + +void Unit::UpdateSplineMovement(uint32 t_diff) +{ + if (movespline->Finalized()) + return; + + movespline->updateState(t_diff); + bool arrived = movespline->Finalized(); + + if (arrived) + { + DisableSpline(); + if (Optional animTier = movespline->GetAnimation()) + SetAnimTier(*animTier); + } + + UpdateSplinePosition(); +} + +void Unit::UpdateSplinePosition() +{ + Movement::Location loc = movespline->ComputePosition(); + + if (movespline->onTransport) + { + Position& pos = m_movementInfo.transport.pos; + pos.m_positionX = loc.x; + pos.m_positionY = loc.y; + pos.m_positionZ = loc.z; + pos.SetOrientation(loc.orientation); + + if (TransportBase* transport = GetDirectTransport()) + transport->CalculatePassengerPosition(loc.x, loc.y, loc.z, &loc.orientation); + else + return; + } + + if (HasUnitState(UNIT_STATE_CANNOT_TURN)) + loc.orientation = GetOrientation(); + + UpdatePosition(loc.x, loc.y, loc.z, loc.orientation); +} + +void Unit::SendFlightSplineSyncUpdate() +{ + if (!movespline->isCyclic() || movespline->Finalized()) + return; + + WorldPackets::Movement::FlightSplineSync flightSplineSync; + flightSplineSync.Guid = GetGUID(); + flightSplineSync.SplineDist = float(movespline->timePassed()) / movespline->Duration(); + SendMessageToSet(flightSplineSync.Write(), true); +} + +void Unit::InterruptMovementBasedAuras() +{ + // TODO: Check if orientation transport offset changed instead of only global orientation + if (_positionUpdateInfo.Turned) + RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Turning); + + if (_positionUpdateInfo.Relocated && !GetVehicle()) + RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Moving); +} + +void Unit::DisableSpline() +{ + m_movementInfo.RemoveMovementFlag(MOVEMENTFLAG_FORWARD); + movespline->_Interrupt(); +} + +void Unit::resetAttackTimer(WeaponAttackType type) +{ + m_attackTimer[type] = uint32(GetBaseAttackTime(type) * m_modAttackSpeedPct[type]); +} + +bool Unit::IsWithinCombatRange(Unit const* obj, float dist2compare) const +{ + if (!obj || !IsInMap(obj) || !IsInPhase(obj)) + return false; + + float dx = GetPositionX() - obj->GetPositionX(); + float dy = GetPositionY() - obj->GetPositionY(); + float dz = GetPositionZ() - obj->GetPositionZ(); + float distsq = dx * dx + dy * dy + dz * dz; + + float sizefactor = GetCombatReach() + obj->GetCombatReach(); + float maxdist = dist2compare + sizefactor; + + return distsq < maxdist * maxdist; +} + +bool Unit::IsWithinMeleeRangeAt(Position const& pos, Unit const* obj) const +{ + if (!obj || !IsInMap(obj) || !IsInPhase(obj)) + return false; + + float dx = pos.GetPositionX() - obj->GetPositionX(); + float dy = pos.GetPositionY() - obj->GetPositionY(); + float dz = GetPositionZ() - obj->GetPositionZ(); + + float distsq = dx*dx + dy*dy + dz*dz; + + float maxdist = GetMeleeRange(obj); + + return distsq <= maxdist * maxdist; +} + +float Unit::GetMeleeRange(Unit const* target) const +{ + float range = GetCombatReach() + target->GetCombatReach() + 1.3333334f; + return std::max(range, NOMINAL_MELEE_RANGE); +} + +bool Unit::IsWithinBoundaryRadius(const Unit* obj) const +{ + if (!obj || !IsInMap(obj) || !IsInPhase(obj)) + return false; + + float objBoundaryRadius = std::max(obj->GetBoundaryRadius(), MIN_MELEE_REACH); + + return IsInDist(obj, objBoundaryRadius); +} + +AuraApplication* Unit::GetVisibleAura(uint8 slot) const +{ + VisibleAuraMap::const_iterator itr = m_visibleAuras.find(slot); + if (itr != m_visibleAuras.end()) + return itr->second; + return 0; +} + +void Unit::SetVisibleAura(uint8 slot, AuraApplication * aur) +{ + m_visibleAuras[slot]=aur; + UpdateAuraForGroup(slot); +} + +void Unit::RemoveVisibleAura(uint8 slot) +{ + m_visibleAuras.erase(slot); + UpdateAuraForGroup(slot); +} + +void Unit::UpdateInterruptMask() +{ + m_interruptMask = SpellAuraInterruptFlags::None; + m_interruptMask2 = SpellAuraInterruptFlags2::None; + for (AuraApplication const* aurApp : m_interruptableAuras) + { + m_interruptMask |= aurApp->GetBase()->GetSpellInfo()->AuraInterruptFlags; + m_interruptMask2 |= aurApp->GetBase()->GetSpellInfo()->AuraInterruptFlags2; + } + + if (Spell* spell = m_currentSpells[CURRENT_CHANNELED_SPELL]) + { + if (spell->getState() == SPELL_STATE_CHANNELING) + { + m_interruptMask |= spell->m_spellInfo->ChannelInterruptFlags; + m_interruptMask2 |= spell->m_spellInfo->ChannelInterruptFlags2; + } + } +} + +bool Unit::HasAuraTypeWithFamilyFlags(AuraType auraType, uint32 familyName, 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()->HasAuraInterruptFlag(SpellAuraInterruptFlags::Damage)) + return true; + return false; +} + +bool Unit::HasBreakableByDamageCrowdControlAura(Unit* excludeCasterChannel) const +{ + uint32 excludeAura = 0; + if (Spell* currentChanneledSpell = excludeCasterChannel ? excludeCasterChannel->GetCurrentSpell(CURRENT_CHANNELED_SPELL) : nullptr) + excludeAura = currentChanneledSpell->GetSpellInfo()->Id; //Avoid self interrupt of channeled Crowd Control spells like Seduction + + return ( HasBreakableByDamageAuraType(SPELL_AURA_MOD_CONFUSE, excludeAura) + || HasBreakableByDamageAuraType(SPELL_AURA_MOD_FEAR, excludeAura) + || HasBreakableByDamageAuraType(SPELL_AURA_MOD_STUN, excludeAura) + || HasBreakableByDamageAuraType(SPELL_AURA_MOD_ROOT, excludeAura) + || HasBreakableByDamageAuraType(SPELL_AURA_TRANSFORM, excludeAura)); +} + +/*static*/ void Unit::DealDamageMods(Unit const* victim, uint32& damage, uint32* absorb) +{ + if (!victim || !victim->IsAlive() || victim->HasUnitState(UNIT_STATE_IN_FLIGHT) || (victim->GetTypeId() == TYPEID_UNIT && victim->ToCreature()->IsEvadingAttacks())) + { + if (absorb) + *absorb += damage; + damage = 0; + } +} + +/*static*/ uint32 Unit::DealDamage(Unit* attacker, Unit* victim, uint32 damage, uint32 unmitigatedDamage, DamageEffectType damagetype, SpellSchoolMask damageSchoolMask, SpellInfo const* spellProto, bool durabilityLoss) +{ + // Sparring Checks + if (Creature* target = victim->ToCreature()) + { + if (attacker && attacker->IsCreature() && !attacker->IsCharmedOwnedByPlayerOrPlayer()) + { + if (target->GetNoNpcDamageBelowPctHealthValue() != 0.0f) + { + if (damage >= target->GetHealth()) // First check: if we have a sparring limit we will never allow creatures to kill the sparring victim + damage = target->GetHealth() - 1; + else if (target->GetHealthPct() <= target->GetNoNpcDamageBelowPctHealthValue()) // Second check: stop incomming damage when we have surpassed the health limit + damage = 0; + } + } + } + + if (UnitAI* victimAI = victim->GetAI()) + victimAI->DamageTaken(attacker, damage); + + if (UnitAI* attackerAI = attacker ? attacker->GetAI() : nullptr) + attackerAI->DamageDealt(victim, damage, damagetype); + + // Hook for OnDamage Event + sScriptMgr->OnDamage(attacker, victim, damage); + + if (victim->GetTypeId() == TYPEID_PLAYER) + { + // Signal to pets that their owner was attacked - except when DOT. + if (attacker != victim && damagetype != DOT) + { + for (Unit* controlled : victim->m_Controlled) + if (Creature* cControlled = controlled->ToCreature()) + if (CreatureAI* controlledAI = cControlled->AI()) + controlledAI->OwnerAttackedBy(attacker); + } + + if (victim->ToPlayer()->GetCommandStatus(CHEAT_GOD)) + return 0; + } + + if (damagetype != NODAMAGE) + { + // interrupting auras with SpellAuraInterruptFlags::Damage before checking !damage (absorbed damage breaks that type of auras) + if (spellProto) + { + if (!spellProto->HasAttribute(SPELL_ATTR4_REACTIVE_DAMAGE_PROC)) + victim->RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Damage, spellProto); + } + else + victim->RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Damage); + + // 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 share = CalculatePct(damage, (*i)->GetAmount()); + + /// @todo check packets if damage is done by victim, or by attacker of victim + Unit::DealDamageMods(shareDamageTarget, share, nullptr); + Unit::DealDamage(attacker, shareDamageTarget, share, 0, NODAMAGE, spell->GetSchoolMask(), spell, false); + } + } + + uint32 health = victim->GetHealth(); + + // duel ends when player has 1 or less hp + bool duel_hasEnded = false; + bool duel_wasMounted = false; + if (victim->GetTypeId() == TYPEID_PLAYER && victim->ToPlayer()->duel && damage >= (health-1)) + { + if (!attacker) + return 0; + + // prevent kill only if killed in duel and killed by opponent or opponent controlled creature + if (victim->ToPlayer()->duel->opponent == attacker->GetControllingPlayer()) + damage = health - 1; + + duel_hasEnded = true; + } + else if (victim->IsCreature() && victim != attacker && damage >= health && victim->ToCreature()->HasStaticFlag(CREATURE_STATIC_FLAG_UNKILLABLE)) + { + damage = health - 1; + } + else if (victim->IsVehicle() && damage >= (health-1) && victim->GetCharmer() && victim->GetCharmer()->GetTypeId() == TYPEID_PLAYER) + { + Player* victimRider = victim->GetCharmer()->ToPlayer(); + + if (victimRider && victimRider->duel && victimRider->duel->isMounted) + { + if (!attacker) + return 0; + + // prevent kill only if killed in duel and killed by opponent or opponent controlled creature + if (victimRider->duel->opponent == attacker->GetControllingPlayer()) + damage = health - 1; + + duel_wasMounted = true; + duel_hasEnded = true; + } + } + + if (attacker && attacker != victim) + { + if (Player* killer = attacker->ToPlayer()) + { + // in bg, count dmg if victim is also a player + if (victim->GetTypeId() == TYPEID_PLAYER && !(spellProto && spellProto->HasAttribute(SPELL_ATTR7_DO_NOT_COUNT_FOR_PVP_SCOREBOARD))) + if (Battleground* bg = killer->GetBattleground()) + bg->UpdatePlayerScore(killer, SCORE_DAMAGE_DONE, damage); + + killer->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_DAMAGE_DONE, health > damage ? damage : health, 0, 0, victim); + killer->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HIGHEST_HIT_DEALT, damage); + } + } + + if (victim->GetTypeId() == TYPEID_PLAYER) + victim->ToPlayer()->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HIGHEST_HIT_RECEIVED, damage); + else if (!victim->IsControlledByPlayer() || victim->IsVehicle()) + { + if (!victim->ToCreature()->hasLootRecipient()) + victim->ToCreature()->SetLootRecipient(attacker); + + if (!attacker || attacker->IsControlledByPlayer() || (attacker->ToTempSummon() && attacker->ToTempSummon()->GetSummoner() && attacker->ToTempSummon()->GetSummoner()->GetTypeId() == TYPEID_PLAYER)) + victim->ToCreature()->LowerPlayerDamageReq(health < damage ? health : damage); + } + + if (health <= damage) + { + if (victim->GetTypeId() == TYPEID_PLAYER && victim != attacker) + victim->ToPlayer()->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_TOTAL_DAMAGE_RECEIVED, health); + + Unit::Kill(attacker, victim, durabilityLoss); + } + else + { + if (victim->GetTypeId() == TYPEID_PLAYER) + victim->ToPlayer()->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_TOTAL_DAMAGE_RECEIVED, damage); + + victim->ModifyHealth(-(int32)damage); + + if (damagetype == DIRECT_DAMAGE || damagetype == SPELL_DIRECT_DAMAGE) + victim->RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::NonPeriodicDamage, spellProto); + + if (victim->GetTypeId() != TYPEID_PLAYER) + { + // Part of Evade mechanics. DoT's and Thorns / Retribution Aura do not contribute to this + if (damagetype != DOT && damage > 0 && !victim->GetOwnerGUID().IsPlayer() && (!spellProto || !spellProto->HasAura(SPELL_AURA_DAMAGE_SHIELD))) + victim->ToCreature()->SetLastDamagedTime(GameTime::GetGameTime() + MAX_AGGRO_RESET_TIME); + + if (attacker && (!spellProto || !spellProto->HasAttribute(SPELL_ATTR4_NO_HARMFUL_THREAT))) + victim->GetThreatManager().AddThreat(attacker, float(damage), 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->GetPowerType() == POWER_RAGE) + { + uint32 rageBaseReward = CalcDamageTakenRageGain(victim, std::max(damage, unmitigatedDamage)); + victim->RewardRage(rageBaseReward, false); + } + + if (attacker && attacker->GetTypeId() == TYPEID_PLAYER) + { + // 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 && damagetype != DOT) + { + if (victim != attacker && (!spellProto || !(spellProto->HasAttribute(SPELL_ATTR6_NO_PUSHBACK) || spellProto->HasAttribute(SPELL_ATTR7_DONT_CAUSE_SPELL_PUSHBACK) || spellProto->HasAttribute(SPELL_ATTR3_TREAT_AS_PERIODIC)))) + { + if (Spell* spell = victim->m_currentSpells[CURRENT_GENERIC_SPELL]) + { + if (spell->getState() == SPELL_STATE_PREPARING) + { + auto isCastInterrupted = [&]() + { + if ((victim->IsPlayer() && spell->m_spellInfo->InterruptFlags.HasFlag(SpellInterruptFlags::DamageCancelsPlayerOnly))) + return true; + + return false; + }; + + auto isCastDelayed = [&]() + { + if (!damage) + return false; + + if ((victim->IsPlayer() && spell->m_spellInfo->InterruptFlags.HasFlag(SpellInterruptFlags::DamagePushbackPlayerOnly))) + return true; + + return false; + }; + + if (isCastInterrupted()) + victim->InterruptNonMeleeSpells(false); + else if (isCastDelayed()) + spell->Delayed(); + } + } + + if (damage && victim->IsPlayer()) + if (Spell* spell = victim->m_currentSpells[CURRENT_CHANNELED_SPELL]) + if (spell->getState() == SPELL_STATE_CHANNELING && spell->m_spellInfo->HasChannelInterruptFlag(SpellAuraInterruptFlags::DamageChannelDuration)) + spell->DelayedChannel(); + } + } + + // last damage from duel opponent + if (duel_hasEnded) + { + Player* he = duel_wasMounted ? victim->GetCharmer()->ToPlayer() : victim->ToPlayer(); + + ASSERT(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); + } + } + + return damage; +} + +void Unit::CastStop(uint32 except_spellid) +{ + for (uint32 i = CURRENT_FIRST_NON_MELEE_SPELL; i < CURRENT_MAX_SPELL; i++) + if (m_currentSpells[i] && m_currentSpells[i]->m_spellInfo->Id != except_spellid) + InterruptSpell(CurrentSpellTypes(i), false); +} + +void Unit::CalculateSpellDamageTaken(SpellNonMeleeDamage* damageInfo, int32 damage, SpellInfo const* spellInfo, WeaponAttackType attackType, bool crit, bool blocked /*= false*/) +{ + if (damage < 0) + return; + + Unit* victim = damageInfo->target; + if (!victim || !victim->IsAlive()) + return; + + SpellSchoolMask damageSchoolMask = SpellSchoolMask(damageInfo->schoolMask); + + // Spells with SPELL_ATTR4_IGNORE_DAMAGE_TAKEN_MODIFIERS ignore resilience because their damage is based off another spell's damage. + if (!spellInfo->HasAttribute(SPELL_ATTR4_IGNORE_DAMAGE_TAKEN_MODIFIERS)) + { + if (Unit::IsDamageReducedByArmor(damageSchoolMask, spellInfo)) + damage = Unit::CalcArmorReducedDamage(this, victim, damage, spellInfo, attackType); + + // Per-school calc + switch (spellInfo->DmgClass) + { + // Melee and Ranged Spells + case SPELL_DAMAGE_CLASS_RANGED: + case SPELL_DAMAGE_CLASS_MELEE: + { + if (crit) + { + damageInfo->HitInfo |= SPELL_HIT_TYPE_CRIT; + + // Calculate crit bonus + uint32 crit_bonus = damage; + // Apply crit_damage bonus for melee spells + if (Player* modOwner = GetSpellModOwner()) + modOwner->ApplySpellMod(spellInfo, SpellModOp::CritDamageAndHealing, crit_bonus); + damage += crit_bonus; + + // 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 += (GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_CRIT_DAMAGE_BONUS, spellInfo->GetSchoolMask()) - 1.0f) * 100; + + if (critPctDamageMod != 0) + AddPct(damage, critPctDamageMod); + } + + // Spell weapon based damage CAN BE crit & blocked at same time + if (blocked) + { + // double blocked amount if block is critical + uint32 value = victim->GetBlockPercent(); + if (victim->IsBlockCritical()) + value *= 2; // double blocked percent + + damageInfo->blocked = CalculatePct(damage, value); + if (damage <= int32(damageInfo->blocked)) + { + damageInfo->blocked = uint32(damage); + damageInfo->fullBlock = true; + } + damage -= damageInfo->blocked; + } + + if (CanApplyResilience()) + Unit::ApplyResilience(victim, &damage); + break; + } + // Magical Attacks + case SPELL_DAMAGE_CLASS_NONE: + case SPELL_DAMAGE_CLASS_MAGIC: + { + // If crit add critical bonus + if (crit) + { + damageInfo->HitInfo |= SPELL_HIT_TYPE_CRIT; + damage = Unit::SpellCriticalDamageBonus(this, spellInfo, damage); + } + + if (CanApplyResilience()) + Unit::ApplyResilience(victim, &damage); + break; + + } + default: + break; + } + } + + // Script Hook For CalculateSpellDamageTaken -- Allow scripts to change the Damage post class mitigation calculations + sScriptMgr->ModifySpellDamageTaken(damageInfo->target, damageInfo->attacker, damage); + + // Calculate absorb resist + if (damage < 0) + damage = 0; + + damageInfo->damage = damage; + DamageInfo dmgInfo(*damageInfo, SPELL_DIRECT_DAMAGE, BASE_ATTACK, PROC_HIT_NONE); + Unit::CalcAbsorbResist(dmgInfo); + damageInfo->absorb = dmgInfo.GetAbsorb(); + damageInfo->resist = dmgInfo.GetResist(); + + if (damageInfo->absorb) + damageInfo->HitInfo |= (damageInfo->damage - damageInfo->absorb == 0 ? HITINFO_FULL_ABSORB : HITINFO_PARTIAL_ABSORB); + + if (damageInfo->resist) + damageInfo->HitInfo |= (damageInfo->damage - damageInfo->resist == 0 ? HITINFO_FULL_RESIST : HITINFO_PARTIAL_RESIST); + + damageInfo->damage = dmgInfo.GetDamage(); +} + +void Unit::DealSpellDamage(SpellNonMeleeDamage* damageInfo, bool durabilityLoss) +{ + if (!damageInfo) + return; + + Unit* victim = damageInfo->target; + if (!victim) + return; + + if (!victim->IsAlive() || victim->HasUnitState(UNIT_STATE_IN_FLIGHT) || (victim->GetTypeId() == TYPEID_UNIT && victim->ToCreature()->IsEvadingAttacks())) + return; + + SpellInfo const* spellProto = sSpellMgr->GetSpellInfo(damageInfo->SpellID); + if (spellProto == nullptr) + { + TC_LOG_DEBUG("entities.unit", "Unit::DealSpellDamage has wrong damageInfo->SpellID: %u", damageInfo->SpellID); + return; + } + + // Call default DealDamage + Unit::DealDamage(this, victim, damageInfo->damage, damageInfo->unmitigatedDamage, SPELL_DIRECT_DAMAGE, SpellSchoolMask(damageInfo->schoolMask), spellProto, durabilityLoss); +} + +/*static*/ uint32 Unit::CalcMeleeAttackRageGain(Unit const* attacker, Unit const* victim, WeaponAttackType attType) +{ + if (!attacker || !victim || (attType != BASE_ATTACK && attType != OFF_ATTACK)) + return 0; + + uint32 rage = uint32(attacker->GetBaseAttackTime(attType) / 1000.f * 6.5f); + if (attType == OFF_ATTACK) + rage /= 2; + + // Players below level 10 start with a 50% rage gain reduction that slowly diminishes + if (attacker->IsPlayer() && attacker->getLevel() < 10) + rage -= (rage / 2.f) * (1.0f - (attacker->getLevel() / 10.f)); + + rage *= 10; + + AddPct(rage, attacker->GetTotalAuraModifier(SPELL_AURA_MOD_RAGE_FROM_DAMAGE_DEALT)); + + return rage; +} + +// The minimum amount a damaging hit should award +constexpr uint32 MIN_RAGE_AMOUNT = 10; + +/*static*/ uint32 Unit::CalcDamageTakenRageGain(Unit const* victim, uint32 damage) +{ + if (!victim || damage == 0) + return 0; + + double multiplier = static_cast(damage) / victim->GetMaxHealth(); + + // This formula is based on sampling multiple sniff data at multiple level ranges + // There has never been any leak and the only info we have is that the amount is based on max HP and damage taken and that there is a hidden constant + return std::max(MIN_RAGE_AMOUNT, std::ceil(400 * multiplier)); +} + +/// @todo for melee need create structure as in +void Unit::CalculateMeleeDamage(Unit* victim, CalcDamageInfo* damageInfo, WeaponAttackType attackType /*= BASE_ATTACK*/) +{ + damageInfo->Attacker = this; + damageInfo->Target = victim; + + damageInfo->DamageSchoolMask = GetMeleeDamageSchoolMask(attackType); + damageInfo->Damage = 0; + damageInfo->OriginalDamage = 0; + damageInfo->Absorb = 0; + damageInfo->Resist = 0; + + damageInfo->Blocked = 0; + damageInfo->HitInfo = 0; + damageInfo->TargetState = 0; + + damageInfo->AttackType = attackType; + damageInfo->ProcAttacker = PROC_FLAG_NONE; + damageInfo->ProcVictim = PROC_FLAG_NONE; + damageInfo->UnmitigatedDamage = 0; + damageInfo->HitOutCome = MELEE_HIT_EVADE; + damageInfo->RageGained = 0; + + if (!victim) + return; + + if (!IsAlive() || !victim->IsAlive()) + return; + + // Select HitInfo/procAttacker/procVictim flag based on attack type + switch (attackType) + { + case BASE_ATTACK: + damageInfo->ProcAttacker = PROC_FLAG_DEAL_MELEE_SWING | PROC_FLAG_MAIN_HAND_WEAPON_SWING; + damageInfo->ProcVictim = PROC_FLAG_TAKE_MELEE_SWING; + break; + case OFF_ATTACK: + damageInfo->ProcAttacker = PROC_FLAG_DEAL_MELEE_SWING | PROC_FLAG_OFF_HAND_WEAPON_SWING; + damageInfo->ProcVictim = PROC_FLAG_TAKE_MELEE_SWING; + damageInfo->HitInfo = HITINFO_OFFHAND; + break; + default: + return; + } + + // Physical Immune check + if (damageInfo->Target->IsImmunedToDamage(SpellSchoolMask(damageInfo->DamageSchoolMask))) + { + damageInfo->HitInfo |= HITINFO_NORMALSWING; + damageInfo->TargetState = VICTIMSTATE_IS_IMMUNE; + + damageInfo->Damage = 0; + damageInfo->UnmitigatedDamage = 0; + return; + } + + uint32 damage = 0; + damage += CalculateDamage(damageInfo->AttackType, false, true); + // Add melee damage bonus + damage = MeleeDamageBonusDone(damageInfo->Target, damage, damageInfo->AttackType, DIRECT_DAMAGE, nullptr, MECHANIC_NONE, SpellSchoolMask(damageInfo->DamageSchoolMask)); + damage = damageInfo->Target->MeleeDamageBonusTaken(this, damage, damageInfo->AttackType, nullptr, SpellSchoolMask(damageInfo->DamageSchoolMask)); + + // Script Hook For CalculateMeleeDamage -- Allow scripts to change the Damage pre class mitigation calculations + sScriptMgr->ModifyMeleeDamage(damageInfo->Target, damageInfo->Attacker, damage); + + // Calculate armor reduction + if (Unit::IsDamageReducedByArmor((SpellSchoolMask)(damageInfo->DamageSchoolMask))) + damageInfo->Damage = Unit::CalcArmorReducedDamage(damageInfo->Attacker, damageInfo->Target, damage, nullptr, damageInfo->AttackType); + else + damageInfo->Damage = damage; + + // Store unmitigated damage to reward rage later + damageInfo->UnmitigatedDamage = damage; + + damageInfo->HitOutCome = RollMeleeOutcomeAgainst(damageInfo->Target, damageInfo->AttackType); + + switch (damageInfo->HitOutCome) + { + case MELEE_HIT_EVADE: + damageInfo->HitInfo |= HITINFO_MISS | HITINFO_SWINGNOHITSOUND; + damageInfo->TargetState = VICTIMSTATE_EVADES; + damageInfo->OriginalDamage = damageInfo->Damage; + + damageInfo->Damage = 0; + return; + case MELEE_HIT_MISS: + damageInfo->HitInfo |= HITINFO_MISS; + damageInfo->TargetState = VICTIMSTATE_INTACT; + damageInfo->OriginalDamage = damageInfo->Damage; + + damageInfo->Damage = 0; + damageInfo->UnmitigatedDamage = 0; + break; + case MELEE_HIT_NORMAL: + damageInfo->TargetState = VICTIMSTATE_HIT; + damageInfo->OriginalDamage = damageInfo->Damage; + break; + case MELEE_HIT_CRIT: + { + damageInfo->HitInfo |= HITINFO_CRITICALHIT; + damageInfo->TargetState = VICTIMSTATE_HIT; + + // Crit bonus calc + damageInfo->Damage *= 2; + 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->DamageSchoolMask) - 1.0f) * 100; + + if (mod != 0) + AddPct(damageInfo->Damage, mod); + + damageInfo->OriginalDamage = damageInfo->Damage; + break; + } + case MELEE_HIT_PARRY: + damageInfo->TargetState = VICTIMSTATE_PARRY; + damageInfo->OriginalDamage = damageInfo->Damage; + damageInfo->Damage = 0; + break; + case MELEE_HIT_DODGE: + damageInfo->TargetState = VICTIMSTATE_DODGE; + damageInfo->OriginalDamage = damageInfo->Damage; + damageInfo->Damage = 0; + break; + case MELEE_HIT_BLOCK: + damageInfo->TargetState = VICTIMSTATE_HIT; + damageInfo->HitInfo |= HITINFO_BLOCK; + // 30% damage blocked, double blocked amount if block is critical + damageInfo->Blocked = CalculatePct(damageInfo->Damage, damageInfo->Target->GetBlockPercent()); + if (damageInfo->Target->IsBlockCritical()) + damageInfo->Blocked *= 2; + + damageInfo->OriginalDamage = damageInfo->Damage; + damageInfo->Damage -= damageInfo->Blocked; + break; + case MELEE_HIT_GLANCING: + { + damageInfo->HitInfo |= HITINFO_GLANCING; + damageInfo->TargetState = VICTIMSTATE_HIT; + int32 leveldif = int32(victim->getLevel()) - int32(getLevel()); + if (leveldif > 3) + leveldif = 3; + + damageInfo->OriginalDamage = damageInfo->Damage; + float reducePercent = 1.f - leveldif * 0.1f; + damageInfo->Damage = uint32(reducePercent * damageInfo->Damage); + break; + } + case MELEE_HIT_CRUSHING: + damageInfo->HitInfo |= HITINFO_CRUSHING; + damageInfo->TargetState = VICTIMSTATE_HIT; + // 150% normal damage + damageInfo->Damage += (damageInfo->Damage / 2); + damageInfo->OriginalDamage = damageInfo->Damage; + break; + default: + break; + } + + // Always apply HITINFO_AFFECTS_VICTIM in case its not a miss + if (!(damageInfo->HitInfo & HITINFO_MISS)) + damageInfo->HitInfo |= HITINFO_AFFECTS_VICTIM; + + int32 resilienceReduction = damageInfo->Damage; + if (CanApplyResilience()) + Unit::ApplyResilience(victim, &resilienceReduction); + resilienceReduction = damageInfo->Damage - resilienceReduction; + damageInfo->Damage -= resilienceReduction; + + // Calculate absorb resist + if (int32(damageInfo->Damage) > 0) + { + damageInfo->ProcVictim |= PROC_FLAG_TAKE_ANY_DAMAGE; + // Calculate absorb & resists + DamageInfo dmgInfo(*damageInfo); + Unit::CalcAbsorbResist(dmgInfo); + damageInfo->Absorb = dmgInfo.GetAbsorb(); + damageInfo->Resist = dmgInfo.GetResist(); + + if (damageInfo->Absorb) + damageInfo->HitInfo |= (damageInfo->Damage - damageInfo->Absorb == 0 ? HITINFO_FULL_ABSORB : HITINFO_PARTIAL_ABSORB); + + if (damageInfo->Resist) + damageInfo->HitInfo |= (damageInfo->Damage - damageInfo->Resist == 0 ? HITINFO_FULL_RESIST : HITINFO_PARTIAL_RESIST); + + damageInfo->Damage = dmgInfo.GetDamage(); + } + else // Impossible get negative result but.... + damageInfo->Damage = 0; +} + +void Unit::DealMeleeDamage(CalcDamageInfo* damageInfo, bool durabilityLoss) +{ + Unit* victim = damageInfo->Target; + + if (!victim->IsAlive() || victim->HasUnitState(UNIT_STATE_IN_FLIGHT) || (victim->GetTypeId() == TYPEID_UNIT && victim->ToCreature()->IsEvadingAttacks())) + return; + + if (damageInfo->TargetState == VICTIMSTATE_PARRY && + (GetTypeId() != TYPEID_UNIT || (ToCreature()->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_NO_PARRY_HASTEN) == 0)) + { + // Get attack timers + float offtime = float(victim->getAttackTimer(OFF_ATTACK)); + float basetime = float(victim->getAttackTimer(BASE_ATTACK)); + // Reduce attack time + if (victim->haveOffhandWeapon() && offtime < basetime) + { + float percent20 = victim->GetBaseAttackTime(OFF_ATTACK) * 0.20f; + float percent60 = 3.0f * percent20; + if (offtime > percent20 && offtime <= percent60) + victim->setAttackTimer(OFF_ATTACK, uint32(percent20)); + else if (offtime > percent60) + { + offtime -= 2.0f * percent20; + victim->setAttackTimer(OFF_ATTACK, uint32(offtime)); + } + } + else + { + float percent20 = victim->GetBaseAttackTime(BASE_ATTACK) * 0.20f; + float percent60 = 3.0f * percent20; + if (basetime > percent20 && basetime <= percent60) + victim->setAttackTimer(BASE_ATTACK, uint32(percent20)); + else if (basetime > percent60) + { + basetime -= 2.0f * percent20; + victim->setAttackTimer(BASE_ATTACK, uint32(basetime)); + } + } + } + + // Call default DealDamage + Unit::DealDamage(this, victim, damageInfo->Damage, damageInfo->UnmitigatedDamage, DIRECT_DAMAGE, SpellSchoolMask(damageInfo->DamageSchoolMask), nullptr, durabilityLoss); + + // If this is a creature and it attacks from behind it has a probability to daze it's victim + if ((damageInfo->HitOutCome == MELEE_HIT_CRIT || damageInfo->HitOutCome == MELEE_HIT_CRUSHING || damageInfo->HitOutCome == MELEE_HIT_NORMAL || damageInfo->HitOutCome == MELEE_HIT_GLANCING) && + IsCreature() && !ToCreature()->IsControlledByPlayer() && !ToCreature()->HasStaticFlag(CREATURE_STATIC_FLAG_4_CANNOT_DAZE) && + !victim->HasInArc(float(M_PI), this) && (victim->IsPlayer() || (victim->IsCreature() && !victim->ToCreature()->isWorldBoss())) && !victim->IsVehicle()) + { + // 20% base chance + float chance = 20.0f; + + // there is a newbie protection, at level 10 just 7% base chance; assuming linear function + if (victim->getLevel() < 30) + chance = 0.65f * victim->getLevel() + 0.5f; + + uint32 const victimDefense = victim->GetMaxSkillValueForLevel(this); + uint32 const attackerMeleeSkill = GetMaxSkillValueForLevel(); + + chance *= attackerMeleeSkill / float(victimDefense) * 0.16f; + + // -probability is between 0% and 40% + RoundToInterval(chance, 0.0f, 40.0f); + if (roll_chance_f(chance)) + CastSpell(victim, 1604 /*SPELL_DAZED*/, true); + } + + if (GetTypeId() == TYPEID_PLAYER) + { + DamageInfo dmgInfo(*damageInfo); + ToPlayer()->CastItemCombatSpell(dmgInfo); + } + + // Do effect if any damage done to target + if (damageInfo->Damage) + { + // We're going to call functions which can modify content of the list during iteration over it's elements + // Let's copy the list so we can prevent iterator invalidation + AuraEffectList vDamageShieldsCopy(victim->GetAuraEffectsByType(SPELL_AURA_DAMAGE_SHIELD)); + for (AuraEffect const* aurEff : vDamageShieldsCopy) + { + SpellInfo const* spellInfo = aurEff->GetSpellInfo(); + + // Damage shield can be resisted... + SpellMissInfo missInfo = victim->SpellHitResult(this, spellInfo, false); + if (missInfo != SPELL_MISS_NONE) + { + victim->SendSpellMiss(this, spellInfo->Id, missInfo); + continue; + } + + // ...or immuned + if (IsImmunedToDamage(spellInfo)) + { + victim->SendSpellDamageImmune(this, spellInfo->Id); + continue; + } + + uint32 damage = aurEff->GetAmount(); + if (Unit* caster = aurEff->GetCaster()) + { + damage = caster->SpellDamageBonusDone(this, spellInfo, damage, SPELL_DIRECT_DAMAGE, aurEff->GetEffIndex()); + damage = SpellDamageBonusTaken(caster, spellInfo, damage, SPELL_DIRECT_DAMAGE); + } + + DamageInfo dmgInfo(this, victim, damage, spellInfo, spellInfo->GetSchoolMask(), SPELL_DIRECT_DAMAGE, BASE_ATTACK); + victim->CalcAbsorbResist(dmgInfo); + damage = dmgInfo.GetDamage(); + Unit::DealDamageMods(victim, damage, nullptr); + + /// @todo Move this to a packet handler + WorldPackets::CombatLog::SpellDamageShield damageShield; + damageShield.Attacker = victim->GetGUID(); + damageShield.Defender = GetGUID(); + damageShield.SpellID = spellInfo->Id; + damageShield.TotalDamage = damage; + damageShield.OverKill = std::max(int32(damage) - int32(GetHealth()), 0); + damageShield.SchoolMask = spellInfo->SchoolMask; + damageShield.LogAbsorbed = dmgInfo.GetAbsorb(); + victim->SendMessageToSet(damageShield.Write(), true); + + Unit::DealDamage(victim, this, damage, 0, SPELL_DIRECT_DAMAGE, spellInfo->GetSchoolMask(), spellInfo, true); + } + } +} + +void Unit::HandleEmoteCommand(uint32 emoteId) +{ + WorldPackets::Chat::Emote emote; + emote.Guid = GetGUID(); + emote.EmoteID = emoteId; + + SendMessageToSet(emote.Write(), true); +} + +/*static*/ bool Unit::IsDamageReducedByArmor(SpellSchoolMask schoolMask, SpellInfo const* spellInfo /*= nullptr*/) +{ + // only physical spells damage gets reduced by armor + if ((schoolMask & SPELL_SCHOOL_MASK_NORMAL) == 0) + return false; + + return !spellInfo || !spellInfo->HasAttribute(SPELL_ATTR0_CU_IGNORE_ARMOR); +} + +// Calculation taken from Paperdollframe.lua (15595) +static float GetArmorReduction(float armor, uint8 attackerLevel) +{ + float levelModifier = attackerLevel; + if (levelModifier > 80) + levelModifier = levelModifier + (4.5f * (levelModifier - 59)) + (20 * (levelModifier - 80)); + else if (levelModifier > 59) + levelModifier = levelModifier + (4.5f * (levelModifier - 59)); + + float temp = 0.1f * armor / (8.5f * levelModifier + 40); + temp = temp / (1 + temp); + + if (temp > 0.75f) + return 75.f; + + if (temp < 0.f) + return 0.f; + + return temp * 100; +} + +/*static*/ uint32 Unit::CalcArmorReducedDamage(Unit const* attacker, Unit* victim, const uint32 damage, SpellInfo const* spellInfo, WeaponAttackType /*attackType*/, uint8 attackerLevel /*= 0*/) +{ + float armor = float(victim->GetArmor()); + + if (attacker) + { + // bypass enemy armor by SPELL_AURA_BYPASS_ARMOR_FOR_CASTER + int32 armorBypassPct = victim->GetTotalAuraModifier(SPELL_AURA_BYPASS_ARMOR_FOR_CASTER, [&](AuraEffect const* aurEff) + { + return aurEff->GetCasterGUID() == attacker->GetGUID(); + }); + + armor = CalculatePct(armor, 100 - std::min(armorBypassPct, 100)); + + // Ignore enemy armor by SPELL_AURA_MOD_TARGET_RESISTANCE aura + armor += attacker->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_TARGET_RESISTANCE, SPELL_SCHOOL_MASK_NORMAL); + + if (spellInfo) + if (Player* modOwner = attacker->GetSpellModOwner()) + modOwner->ApplySpellMod(spellInfo, SpellModOp::TargetResistance, armor); + + for (AuraEffect const* aurEff : attacker->GetAuraEffectsByType(SPELL_AURA_MOD_IGNORE_TARGET_RESIST)) + { + if (aurEff->GetMiscValue() & SPELL_SCHOOL_MASK_NORMAL) + armor = std::floor(AddPct(armor, -aurEff->GetAmount())); + } + + if (armor < 0.0f) + armor = 0.0f; + } + + float armorReduction = 100.f - GetArmorReduction(armor, attacker ? attacker->getLevel() : attackerLevel); + return std::max(std::ceil(damage * armorReduction / 100.f), 0); +} + +/*static*/ uint32 Unit::CalcSpellResistedDamage(Unit const* attacker, Unit* victim, uint32 damage, SpellSchoolMask schoolMask, SpellInfo const* spellInfo) +{ + // Magic damage, check for resists + if (!(schoolMask & SPELL_SCHOOL_MASK_MAGIC)) + return 0; + + // Npcs can have holy resistance + if ((schoolMask & SPELL_SCHOOL_MASK_HOLY) && victim->GetTypeId() != TYPEID_UNIT) + return 0; + + float const averageResist = Unit::CalculateAverageResistReduction(attacker, schoolMask, victim, spellInfo); + + float discreteResistProbability[11] = { }; + if (averageResist <= 0.1f) + { + discreteResistProbability[0] = 1.0f - 7.5f * averageResist; + discreteResistProbability[1] = 5.0f * averageResist; + discreteResistProbability[2] = 2.5f * averageResist; + } + else + { + for (uint32 i = 0; i < 11; ++i) + discreteResistProbability[i] = std::max(0.5f - 2.5f * std::fabs(0.1f * i - averageResist), 0.0f); + } + + float roll = rand_norm(); + float probabilitySum = 0.0f; + + uint32 resistance = 0; + for (; resistance < 11; ++resistance) + if (roll < (probabilitySum += discreteResistProbability[resistance])) + break; + + float damageResisted = damage * resistance / 10.f; + if (damageResisted > 0.0f) // if any damage was resisted + { + if (attacker) + { + int32 ignoredResistance = attacker->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_IGNORE_TARGET_RESIST, schoolMask); + ignoredResistance = std::min(ignoredResistance, 100); + ApplyPct(damageResisted, 100 - ignoredResistance); + + // Spells with melee and magic school mask, decide whether resistance or armor absorb is higher + if (spellInfo && spellInfo->HasAttribute(SPELL_ATTR0_CU_SCHOOLMASK_NORMAL_WITH_MAGIC)) + { + uint32 damageAfterArmor = Unit::CalcArmorReducedDamage(attacker, victim, damage, spellInfo, spellInfo->GetAttackType()); + float armorReduction = damage - damageAfterArmor; + + // pick the lower one, the weakest resistance counts + damageResisted = std::min(damageResisted, armorReduction); + } + } + } + + damageResisted = std::max(damageResisted, 0.f); + return uint32(damageResisted); +} + +/*static*/ float Unit::CalculateAverageResistReduction(WorldObject const* caster, SpellSchoolMask schoolMask, Unit const* victim, SpellInfo const* spellInfo) +{ + float victimResistance = float(victim->GetResistance(schoolMask)); + if (caster) + { + // pets inherit 100% of masters penetration + if (Player const* player = caster->GetSpellModOwner()) + { + victimResistance += float(player->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_TARGET_RESISTANCE, schoolMask)); + victimResistance -= float(player->GetSpellPenetrationItemMod()); + } + else if (Unit const* unitCaster = caster->ToUnit()) + victimResistance += float(unitCaster->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_TARGET_RESISTANCE, schoolMask)); + } + + // holy resistance exists in pve and comes from level difference, ignore template values + if (schoolMask & SPELL_SCHOOL_MASK_HOLY) + victimResistance = 0.0f; + + // Chaos Bolt exception, ignore all target resistances (unknown attribute?) + if (spellInfo && spellInfo->SpellFamilyName == SPELLFAMILY_WARLOCK && spellInfo->SpellIconID == 3178) + victimResistance = 0.0f; + + victimResistance = std::max(victimResistance, 0.0f); + + // level-based resistance does not apply to binary spells, and cannot be overcome by spell penetration + // gameobject caster -- should it have level based resistance? + if (caster && caster->GetTypeId() != TYPEID_GAMEOBJECT && (!spellInfo || !spellInfo->HasAttribute(SPELL_ATTR0_CU_BINARY_SPELL))) + victimResistance += std::max((float(victim->getLevelForTarget(caster)) - float(caster->getLevelForTarget(victim))) * 5.0f, 0.0f); + + static uint32 const 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); +} + +/*static*/ void Unit::CalcAbsorbResist(DamageInfo& damageInfo) +{ + if (!damageInfo.GetVictim() || !damageInfo.GetVictim()->IsAlive() || !damageInfo.GetDamage()) + return; + + uint32 resistedDamage = Unit::CalcSpellResistedDamage(damageInfo.GetAttacker(), damageInfo.GetVictim(), damageInfo.GetDamage(), damageInfo.GetSchoolMask(), damageInfo.GetSpellInfo()); + damageInfo.ResistDamage(resistedDamage); + + // Ignore Absorption Auras + float auraAbsorbMod = 0.f; + if (Unit* attacker = damageInfo.GetAttacker()) + { + auraAbsorbMod = attacker->GetMaxPositiveAuraModifierByMiscMask(SPELL_AURA_MOD_TARGET_ABSORB_SCHOOL, damageInfo.GetSchoolMask()); + auraAbsorbMod = std::max(auraAbsorbMod, static_cast(attacker->GetMaxPositiveAuraModifier(SPELL_AURA_MOD_TARGET_ABILITY_ABSORB_SCHOOL, [&damageInfo](AuraEffect const* aurEff) -> bool + { + if (!(aurEff->GetMiscValue() & damageInfo.GetSchoolMask())) + return false; + + if (!aurEff->IsAffectingSpell(damageInfo.GetSpellInfo())) + return false; + + return true; + }))); + } + + RoundToInterval(auraAbsorbMod, 0.0f, 100.0f); + + int32 absorbIgnoringDamage = CalculatePct(damageInfo.GetDamage(), auraAbsorbMod); + + // 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(damageInfo.GetVictim()->GetAuraEffectsByType(SPELL_AURA_SCHOOL_ABSORB)); + vSchoolAbsorbCopy.sort(Trinity::AbsorbAuraOrderPred()); + + // absorb without mana cost + for (AuraEffectList::iterator itr = vSchoolAbsorbCopy.begin(); (itr != vSchoolAbsorbCopy.end()) && (damageInfo.GetDamage() > 0); ++itr) + { + AuraEffect* absorbAurEff = *itr; + // Check if aura was removed during iteration - we don't need to work on such auras + AuraApplication const* aurApp = absorbAurEff->GetBase()->GetApplicationOfTarget(damageInfo.GetVictim()->GetGUID()); + if (!aurApp) + continue; + if (!(absorbAurEff->GetMiscValue() & damageInfo.GetSchoolMask())) + continue; + + // get amount which can be still absorbed by the aura + int32 currentAbsorb = absorbAurEff->GetAmount(); + // aura with infinite absorb amount - let the scripts handle absorbtion amount, set here to 0 for safety + if (currentAbsorb < 0) + currentAbsorb = 0; + + if (!absorbAurEff->GetSpellInfo()->HasAttribute(SPELL_ATTR6_ABSORB_CANNOT_BE_IGNORE)) + damageInfo.ModifyDamage(-absorbIgnoringDamage); + + uint32 tempAbsorb = uint32(currentAbsorb); + + bool defaultPrevented = false; + + absorbAurEff->GetBase()->CallScriptEffectAbsorbHandlers(absorbAurEff, aurApp, damageInfo, tempAbsorb, defaultPrevented); + currentAbsorb = tempAbsorb; + + if (!defaultPrevented) + { + + // absorb must be smaller than the damage itself + currentAbsorb = RoundToInterval(currentAbsorb, 0, int32(damageInfo.GetDamage())); + + damageInfo.AbsorbDamage(currentAbsorb); + + tempAbsorb = currentAbsorb; + absorbAurEff->GetBase()->CallScriptEffectAfterAbsorbHandlers(absorbAurEff, aurApp, damageInfo, tempAbsorb); + + // Check if our aura is using amount to count damage + if (absorbAurEff->GetAmount() >= 0) + { + // Reduce shield amount + absorbAurEff->SetAmount(absorbAurEff->GetAmount() - currentAbsorb); + // Aura cannot absorb anything more - remove it + if (absorbAurEff->GetAmount() <= 0) + absorbAurEff->GetBase()->Remove(AuraRemoveFlags::ByEnemySpell); + } + } + + if (!absorbAurEff->GetSpellInfo()->HasAttribute(SPELL_ATTR6_ABSORB_CANNOT_BE_IGNORE)) + damageInfo.ModifyDamage(absorbIgnoringDamage); + } + + // absorb by mana cost + AuraEffectList vManaShieldCopy(damageInfo.GetVictim()->GetAuraEffectsByType(SPELL_AURA_MANA_SHIELD)); + for (AuraEffectList::const_iterator itr = vManaShieldCopy.begin(); (itr != vManaShieldCopy.end()) && (damageInfo.GetDamage() > 0); ++itr) + { + AuraEffect* absorbAurEff = *itr; + // Check if aura was removed during iteration - we don't need to work on such auras + AuraApplication const* aurApp = absorbAurEff->GetBase()->GetApplicationOfTarget(damageInfo.GetVictim()->GetGUID()); + if (!aurApp) + continue; + // check damage school mask + if (!(absorbAurEff->GetMiscValue() & damageInfo.GetSchoolMask())) + continue; + + // get amount which can be still absorbed by the aura + int32 currentAbsorb = absorbAurEff->GetAmount(); + // aura with infinite absorb amount - let the scripts handle absorbtion amount, set here to 0 for safety + if (currentAbsorb < 0) + currentAbsorb = 0; + + if (!absorbAurEff->GetSpellInfo()->HasAttribute(SPELL_ATTR6_ABSORB_CANNOT_BE_IGNORE)) + damageInfo.ModifyDamage(-absorbIgnoringDamage); + + uint32 tempAbsorb = currentAbsorb; + + bool defaultPrevented = false; + + absorbAurEff->GetBase()->CallScriptEffectManaShieldHandlers(absorbAurEff, aurApp, damageInfo, tempAbsorb, defaultPrevented); + currentAbsorb = tempAbsorb; + + if (!defaultPrevented) + { + + // absorb must be smaller than the damage itself + currentAbsorb = RoundToInterval(currentAbsorb, 0, int32(damageInfo.GetDamage())); + + int32 manaReduction = currentAbsorb; + + // lower absorb amount by talents + if (float manaMultiplier = absorbAurEff->GetSpellInfo()->Effects[absorbAurEff->GetEffIndex()].CalcValueMultiplier(absorbAurEff->GetCaster())) + manaReduction = int32(float(manaReduction) * manaMultiplier); + + int32 manaTaken = -damageInfo.GetVictim()->ModifyPower(POWER_MANA, -manaReduction); + + // take case when mana has ended up into account + currentAbsorb = currentAbsorb ? int32(float(currentAbsorb) * (float(manaTaken) / float(manaReduction))) : 0; + + damageInfo.AbsorbDamage(currentAbsorb); + + tempAbsorb = currentAbsorb; + absorbAurEff->GetBase()->CallScriptEffectAfterManaShieldHandlers(absorbAurEff, aurApp, damageInfo, tempAbsorb); + + // Check if our aura is using amount to count damage + if (absorbAurEff->GetAmount() >= 0) + { + absorbAurEff->SetAmount(absorbAurEff->GetAmount() - currentAbsorb); + if ((absorbAurEff->GetAmount() <= 0)) + absorbAurEff->GetBase()->Remove(AuraRemoveFlags::ByEnemySpell); + } + } + + if (!absorbAurEff->GetSpellInfo()->HasAttribute(SPELL_ATTR6_ABSORB_CANNOT_BE_IGNORE)) + damageInfo.ModifyDamage(absorbIgnoringDamage); + } + + // split damage auras - only when not damaging self + if (damageInfo.GetVictim() != damageInfo.GetAttacker()) + { + // We're going to call functions which can modify content of the list during iteration over it's elements + // Let's copy the list so we can prevent iterator invalidation + AuraEffectList vSplitDamagePctCopy(damageInfo.GetVictim()->GetAuraEffectsByType(SPELL_AURA_SPLIT_DAMAGE_PCT)); + for (AuraEffectList::iterator itr = vSplitDamagePctCopy.begin(); itr != vSplitDamagePctCopy.end() && damageInfo.GetDamage() > 0; ++itr) + { + // Check if aura was removed during iteration - we don't need to work on such auras + AuraApplication const* aurApp = (*itr)->GetBase()->GetApplicationOfTarget(damageInfo.GetVictim()->GetGUID()); + if (!aurApp) + continue; + + // check damage school mask + if (!((*itr)->GetMiscValue() & damageInfo.GetSchoolMask())) + continue; + + // Damage can be splitted only if aura has an alive caster + Unit* caster = (*itr)->GetCaster(); + if (!caster || (caster == damageInfo.GetVictim()) || !caster->IsInWorld() || !caster->IsAlive()) + continue; + + uint32 splitDamage = CalculatePct(damageInfo.GetDamage(), (*itr)->GetAmount()); + + (*itr)->GetBase()->CallScriptEffectSplitHandlers((*itr), aurApp, damageInfo, splitDamage); + + // absorb must be smaller than the damage itself + splitDamage = RoundToInterval(splitDamage, uint32(0), uint32(damageInfo.GetDamage())); + + damageInfo.AbsorbDamage(splitDamage); + + // check if caster is immune to damage + if (caster->IsImmunedToDamage(damageInfo.GetSchoolMask())) + { + damageInfo.GetVictim()->SendSpellMiss(caster, (*itr)->GetSpellInfo()->Id, SPELL_MISS_IMMUNE); + continue; + } + + uint32 split_absorb = 0; + Unit::DealDamageMods(caster, splitDamage, &split_absorb); + + if (Unit* attacker = damageInfo.GetAttacker()) + { + // Sparring Checks + if (Creature* target = damageInfo.GetVictim()->ToCreature()) + if (attacker->IsCreature() && !attacker->IsCharmedOwnedByPlayerOrPlayer()) + if (target->GetNoNpcDamageBelowPctHealthValue() != 0.0f) + if (target->GetHealthPct() <= target->GetNoNpcDamageBelowPctHealthValue()) + damageInfo.ModifyDamage(damageInfo.GetDamage() * -1); + + attacker->SendSpellNonMeleeDamageLog(caster, (*itr)->GetSpellInfo()->Id, splitDamage, damageInfo.GetSchoolMask(), split_absorb, 0, damageInfo.GetDamageType() == DOT, 0, false, true); + } + + Unit::DealDamage(damageInfo.GetAttacker(), caster, splitDamage, 0, DIRECT_DAMAGE, damageInfo.GetSchoolMask(), (*itr)->GetSpellInfo(), false); + + // break 'Fear' and similar auras + Unit::ProcSkillsAndAuras(damageInfo.GetAttacker(), caster, PROC_FLAG_NONE, PROC_FLAG_TAKE_HARMFUL_SPELL, PROC_SPELL_TYPE_DAMAGE, PROC_SPELL_PHASE_HIT, PROC_HIT_NONE, nullptr, &damageInfo, nullptr); + } + } +} + +/*static*/ void Unit::CalcHealAbsorb(HealInfo& healInfo) +{ + if (!healInfo.GetHeal()) + return; + + 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(AuraRemoveFlags::ByEnemySpell); + if (removedAuras + 1 < healInfo.GetTarget()->m_removedAurasCount) + i = vHealAbsorb.begin(); + } + } + } + + if (absorbAmount > 0) + healInfo.AbsorbHeal(absorbAmount); +} + +void Unit::AttackerStateUpdate(Unit* victim, WeaponAttackType attType, bool extra) +{ + if (HasUnitState(UNIT_STATE_CANNOT_AUTOATTACK) || HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PACIFIED)) + return; + + if (HasUnitState(UNIT_STATE_CASTING)) + { + Spell* channeledSpell = GetCurrentSpell(CURRENT_CHANNELED_SPELL); + if (!channeledSpell || !channeledSpell->GetSpellInfo()->HasAttribute(SPELL_ATTR5_ALLOW_ACTIONS_DURING_CHANNEL)) + return; + } + + if (!victim->IsAlive()) + return; + + if ((attType == BASE_ATTACK || attType == OFF_ATTACK) && !IsWithinLOSInMap(victim)) + return; + + AtTargetAttacked(victim, true); + RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Attacking); + + if (attType != BASE_ATTACK && attType != OFF_ATTACK) + return; // ignore ranged case + + // melee attack spell cast at main hand attack only - no normal melee dmg dealt + if (attType == BASE_ATTACK && m_currentSpells[CURRENT_MELEE_SPELL] && !extra) + m_currentSpells[CURRENT_MELEE_SPELL]->cast(); + else + { + // attack can be redirected to another target + victim = GetMeleeHitRedirectTarget(victim); + + AuraEffectList const& meleeAttackOverrides = GetAuraEffectsByType(SPELL_AURA_OVERRIDE_AUTOATTACK_WITH_MELEE_SPELL); + AuraEffect const* meleeAttackAuraEffect = nullptr; + uint32 meleeAttackSpellId = 0; + if (attType == BASE_ATTACK) + { + if (!meleeAttackOverrides.empty()) + { + meleeAttackAuraEffect = meleeAttackOverrides.front(); + meleeAttackSpellId = meleeAttackAuraEffect->GetSpellInfo()->Effects[meleeAttackAuraEffect->GetEffIndex()].TriggerSpell; + } + } + else + { + auto itr = std::find_if(meleeAttackOverrides.begin(), meleeAttackOverrides.end(), [&](AuraEffect const* aurEff) + { + return aurEff->GetAmount() != 0; + }); + + if (itr != meleeAttackOverrides.end()) + { + meleeAttackAuraEffect = *itr; + meleeAttackSpellId = meleeAttackAuraEffect->GetAmount(); + } + } + + if (!meleeAttackAuraEffect) + { + CalcDamageInfo damageInfo; + CalculateMeleeDamage(victim, &damageInfo, attType); + // Send log damage message to client + Unit::DealDamageMods(victim, damageInfo.Damage, &damageInfo.Absorb); + + // Sparring Checks + if (Creature* target = victim->ToCreature()) + if (IsCreature() && !IsCharmedOwnedByPlayerOrPlayer()) + if (target->GetNoNpcDamageBelowPctHealthValue() != 0.0f) + if (target->GetHealthPct() <= target->GetNoNpcDamageBelowPctHealthValue()) + damageInfo.HitInfo |= HITINFO_FAKE_DAMAGE; + + // Rage reward + if (this != victim && damageInfo.HitOutCome != MELEE_HIT_MISS && GetPowerType() == POWER_RAGE) + { + if (uint32 rageReward = CalcMeleeAttackRageGain(this, victim, attType)) + { + damageInfo.HitInfo |= HITINFO_RAGE_GAIN; + damageInfo.RageGained = RewardRage(rageReward, true); + } + } + + SendAttackStateUpdate(&damageInfo); + + DealMeleeDamage(&damageInfo, true); + + DamageInfo dmgInfo(damageInfo); + Unit::ProcSkillsAndAuras(damageInfo.Attacker, damageInfo.Target, damageInfo.ProcAttacker, damageInfo.ProcVictim, PROC_SPELL_TYPE_NONE, PROC_SPELL_PHASE_NONE, dmgInfo.GetHitMask(), nullptr, &dmgInfo, nullptr); + + TC_LOG_DEBUG("entities.unit", "AttackerStateUpdate: %s attacked %s for %u dmg, absorbed %u, blocked %u, resisted %u.", + GetGUID().ToString().c_str(), victim->GetGUID().ToString().c_str(), damageInfo.Damage, damageInfo.Absorb, damageInfo.Blocked, damageInfo.Resist); + } + else + { + CastSpell(victim, meleeAttackSpellId, meleeAttackAuraEffect); + + uint32 hitInfo = HITINFO_AFFECTS_VICTIM | HITINFO_NO_ANIMATION; + if (attType == OFF_ATTACK) + hitInfo |= HITINFO_OFFHAND; + + SendAttackStateUpdate(hitInfo, victim, 0, GetMeleeDamageSchoolMask(), 0, 0, 0, VICTIMSTATE_HIT, 0); + } + } +} + +void Unit::HandleProcExtraAttackFor(Unit* victim) +{ + while (m_extraAttacks) + { + AttackerStateUpdate(victim, BASE_ATTACK, true); + --m_extraAttacks; + } +} + +MeleeHitOutcome Unit::RollMeleeOutcomeAgainst(const Unit* victim, WeaponAttackType attType) const +{ + if (victim->IsCreature() && victim->ToCreature()->IsInEvadeMode()) + return MELEE_HIT_EVADE; + + int32 const attackerMaxSkillValueForLevel = GetMaxSkillValueForLevel(victim); + int32 const victimMaxSkillValueForLevel = victim->GetMaxSkillValueForLevel(this); + + // Miss chance based on melee + int32 miss_chance = int32(MeleeSpellMissChance(victim, attType) * 100.f); + + // Critical hit chance + int32 crit_chance = int32(GetUnitCriticalChanceAgainst(attType, victim) * 100.0f); + + // stunned target cannot dodge and this is check in GetUnitDodgeChance() (returned 0 in this case) + int32 dodge_chance = int32(GetUnitDodgeChance(attType, victim) * 100.0f); + int32 block_chance = int32(GetUnitBlockChance(victim) * 100.0f); + int32 parry_chance = int32(GetUnitParryChance(attType, victim) * 100.0f); + + + // melee attack table implementation + // outcome priority: + // 1. > 2. > 3. > 4. > 5. > 6. > 7. > 8. + // MISS > DODGE > PARRY > GLANCING > BLOCK > CRIT > CRUSHING > HIT + + int32 sum = 0, tmp = 0; + int32 roll = urand(0, 9999); + + float arcAngle = static_cast(M_PI); + if (victim->IsCreature() && victim->ToCreature()->HasStaticFlag(CREATURE_STATIC_FLAG_5_240_DEGREE_BACK_ARC)) + arcAngle -= static_cast(M_PI / 3); + + // check if attack comes from behind, nobody can parry or block if attacker is behind + bool canParryOrBlock = victim->HasInArc(arcAngle, this) || victim->HasAuraType(SPELL_AURA_IGNORE_HIT_DIRECTION); + + // only creatures can dodge if attacker is behind + bool canDodge = victim->GetTypeId() != TYPEID_PLAYER || canParryOrBlock; + + // if victim is casting or cc'd it can't avoid attacks + if (victim->IsNonMeleeSpellCast(false) || victim->HasUnitState(UNIT_STATE_CONTROLLED)) + { + canDodge = false; + canParryOrBlock = false; + } + + // 1. MISS + tmp = miss_chance; + if (tmp > 0 && roll < (sum += tmp)) + return MELEE_HIT_MISS; + + // always crit against a sitting target (except 0 crit chance) + if (victim->IsPlayer() && crit_chance > 0 && !victim->IsStandState()) + return MELEE_HIT_CRIT; + + // 2. DODGE + if (canDodge) + { + tmp = dodge_chance; + if (tmp > 0 // check if unit _can_ dodge + && roll < (sum += tmp)) + return MELEE_HIT_DODGE; + } + + // 3. PARRY + if (canParryOrBlock) + { + tmp = parry_chance; + if (tmp > 0 // check if unit _can_ parry + && roll < (sum += tmp)) + return MELEE_HIT_PARRY; + } + + // 4. GLANCING + // Max 40% chance to score a glancing blow against mobs of the same or higher level (only players and pets, not for ranged weapons). + if ((GetTypeId() == TYPEID_PLAYER || IsPet()) && + victim->GetTypeId() != TYPEID_PLAYER && !victim->IsPet() && + getLevel() <= victim->getLevelForTarget(this)) + { + // cap possible value (with bonuses > max skill) + int32 skill = attackerMaxSkillValueForLevel; + + // against boss-level targets - 24% chance of 25% average damage reduction (damage reduction range : 20-30%) + // against level 82 elites - 18% chance of 15% average damage reduction (damage reduction range : 10-20%) + tmp = 600 + (victimMaxSkillValueForLevel - skill) * 120; + tmp = std::min(tmp, 4000); + if (tmp > 0 && roll < (sum += tmp)) + return MELEE_HIT_GLANCING; + } + + // 5. BLOCK + if (canParryOrBlock) + { + tmp = block_chance; + if (tmp > 0 // check if unit _can_ block + && roll < (sum += tmp)) + return MELEE_HIT_BLOCK; + } + + // 6.CRIT + tmp = crit_chance; + if (tmp > 0 && roll < (sum += tmp)) + return MELEE_HIT_CRIT; + + // 7. CRUSHING + // mobs can score crushing blows if they're 4 or more levels above victim + if (getLevelForTarget(victim) >= victim->getLevelForTarget(this) + 4 && + // can be from by creature (if can) or from controlled player that considered as creature + !IsControlledByPlayer() && + !(IsCreature() && ToCreature()->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_NO_CRUSH)) + { + // when their weapon skill is 15 or more above victim's defense skill + tmp = victimMaxSkillValueForLevel; + // tmp = mob's level * 5 - player's current defense skill + tmp = attackerMaxSkillValueForLevel - tmp; + // minimum of 20 points diff (4 levels difference) + tmp = std::max(tmp, 20); + + // add 2% chance per lacking skill point, min. is 15% + tmp = tmp * 200 - 1500; + if (roll < (sum += tmp)) + return MELEE_HIT_CRUSHING; + } + + // 8. HIT + return MELEE_HIT_NORMAL; +} + +uint32 Unit::CalculateDamage(WeaponAttackType attType, bool normalized, bool addTotalPct) const +{ + float minDamage = 0.0f; + float maxDamage = 0.0f; + + if (normalized || !addTotalPct) + CalculateMinMaxDamage(attType, normalized, addTotalPct, minDamage, maxDamage); + 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)); +} + +void Unit::SendMeleeAttackStart(Unit* victim) +{ + WorldPackets::Combat::AttackStart packet; + packet.Attacker = GetGUID(); + packet.Victim = victim->GetGUID(); + SendMessageToSet(packet.Write(), IsPlayer()); +} + +void Unit::SendMeleeAttackStop(Unit* victim) +{ + SendMessageToSet(WorldPackets::Combat::SAttackStop(this, victim).Write(), IsPlayer()); + + if (victim) + TC_LOG_DEBUG("entities.unit", "%s %u stopped attacking %s %u", (GetTypeId() == TYPEID_PLAYER ? "Player" : "Creature"), GetGUID().GetCounter(), (victim->GetTypeId() == TYPEID_PLAYER ? "player" : "creature"), victim->GetGUID().GetCounter()); + else + TC_LOG_DEBUG("entities.unit", "%s %u stopped attacking", (GetTypeId() == TYPEID_PLAYER ? "Player" : "Creature"), GetGUID().GetCounter()); +} + +bool Unit::IsBlockCritical() const +{ + if (roll_chance_i(GetTotalAuraModifier(SPELL_AURA_MOD_BLOCK_CRIT_CHANCE))) + return true; + return false; +} + +int32 Unit::GetMechanicResistChance(SpellInfo const* spellInfo) const +{ + if (!spellInfo) + return 0; + + int32 resistMech = 0; + for (uint8 eff = 0; eff < MAX_SPELL_EFFECTS; ++eff) + { + if (!spellInfo->Effects[eff].IsEffect()) + break; + + int32 effectMech = spellInfo->GetEffectMechanic(eff); + if (effectMech) + { + int32 temp = GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_MECHANIC_RESISTANCE, effectMech); + if (resistMech < temp) + resistMech = temp; + } + } + return resistMech; +} + +bool Unit::CanUseAttackType(uint8 attacktype) const +{ + switch (attacktype) + { + case BASE_ATTACK: + return !HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_DISARMED); + case OFF_ATTACK: + return !HasFlag(UNIT_FIELD_FLAGS_2, UNIT_FLAG2_DISARM_OFFHAND); + case RANGED_ATTACK: + return !HasFlag(UNIT_FIELD_FLAGS_2, UNIT_FLAG2_DISARM_RANGED); + default: + return true; + } +} + +// Melee based spells hit result calculations +SpellMissInfo Unit::MeleeSpellHitResult(Unit* victim, SpellInfo const* spellInfo) const +{ + if (spellInfo->HasAttribute(SPELL_ATTR3_NO_AVOIDANCE)) + return SPELL_MISS_NONE; + + WeaponAttackType attType = BASE_ATTACK; + + // Check damage class instead of attack type to correctly handle judgements + // - they are meele, but can't be dodged/parried/deflected because of ranged dmg class + if (spellInfo->DmgClass == SPELL_DAMAGE_CLASS_RANGED) + attType = RANGED_ATTACK; + + uint32 roll = urand(0, 9999); + uint32 missChance = uint32(MeleeSpellMissChance(victim, attType, spellInfo) * 100.0f); + + // Roll miss + uint32 tmp = missChance; + if (roll < tmp) + return SPELL_MISS_MISS; + + // Chance resist mechanic + int32 resist_chance = victim->GetMechanicResistChance(spellInfo) * 100; + tmp += resist_chance; + if (roll < tmp) + return SPELL_MISS_RESIST; + + // Same spells cannot be parried/dodged + if (spellInfo->HasAttribute(SPELL_ATTR0_NO_ACTIVE_DEFENSE)) + return SPELL_MISS_NONE; + + bool canDodge = !spellInfo->HasAttribute(SPELL_ATTR7_NO_ATTACK_DODGE); + bool canParry = !spellInfo->HasAttribute(SPELL_ATTR7_NO_ATTACK_PARRY); + bool canBlock = !spellInfo->HasAttribute(SPELL_ATTR8_NO_ATTACK_BLOCK); + + // if victim is casting or cc'd it can't avoid attacks + bool canUseDefenseWhileChanneling = victim->GetCurrentSpell(CURRENT_CHANNELED_SPELL) ? victim->GetCurrentSpell(CURRENT_CHANNELED_SPELL)->GetSpellInfo()->HasAttribute(SPELL_ATTR10_ALLOW_DEFENSE_WHILE_CHANNELING) : false; + bool canUseDefenseWhileCasting = victim->GetCurrentSpell(CURRENT_GENERIC_SPELL) ? victim->GetCurrentSpell(CURRENT_GENERIC_SPELL)->GetSpellInfo()->HasAttribute(SPELL_ATTR10_ALLOW_DEFENSE_WHILE_CASTING) : false; + if ((victim->IsNonMeleeSpellCast(false) || victim->HasUnitState(UNIT_STATE_CONTROLLED)) && !canUseDefenseWhileChanneling && !canUseDefenseWhileCasting) + { + canDodge = false; + canParry = false; + canBlock = false; + } + + // Ranged attacks can only miss, resist and deflect and get blocked + if (attType == RANGED_ATTACK) + { + canDodge = false; + canParry = false; + + // only if in front + if (!victim->HasUnitState(UNIT_STATE_CONTROLLED) && (victim->HasInArc(float(M_PI), this) || victim->HasAuraType(SPELL_AURA_IGNORE_HIT_DIRECTION))) + { + int32 deflect_chance = victim->GetTotalAuraModifier(SPELL_AURA_DEFLECT_SPELLS) * 100; + tmp += deflect_chance; + if (roll < tmp) + return SPELL_MISS_DEFLECT; + } + } + + // Check for attack from behind + if (!victim->HasInArc(float(M_PI), this)) + { + if (!victim->HasAuraType(SPELL_AURA_IGNORE_HIT_DIRECTION)) + { + // Can't dodge from behind in PvP (but its possible in PvE) + if (victim->GetTypeId() == TYPEID_PLAYER) + canDodge = false; + // Can't parry or block + canParry = false; + canBlock = false; + } + else // Only deterrence as of 3.3.5 + { + if (spellInfo->HasAttribute(SPELL_ATTR0_CU_REQ_CASTER_BEHIND_TARGET)) + canParry = false; + } + } + + // Ignore combat result aura + AuraEffectList const& ignore = GetAuraEffectsByType(SPELL_AURA_IGNORE_COMBAT_RESULT); + for (AuraEffect const* aurEff : ignore) + { + if (!aurEff->IsAffectingSpell(spellInfo)) + continue; + + switch (aurEff->GetMiscValue()) + { + case MELEE_HIT_DODGE: + canDodge = false; + break; + case MELEE_HIT_BLOCK: + canBlock = false; + break; + case MELEE_HIT_PARRY: + canParry = false; + break; + default: + TC_LOG_DEBUG("entities.unit", "Spell %u SPELL_AURA_IGNORE_COMBAT_RESULT has unhandled state %d", aurEff->GetId(), aurEff->GetMiscValue()); + break; + } + } + + if (canDodge) + { + // Roll dodge + int32 dodgeChance = int32(GetUnitDodgeChance(attType, victim) * 100.0f); + if (dodgeChance < 0) + dodgeChance = 0; + + if (roll < (tmp += dodgeChance)) + return SPELL_MISS_DODGE; + } + + if (canParry) + { + // Roll parry + int32 parryChance = int32(GetUnitParryChance(attType, victim) * 100.0f); + if (parryChance < 0) + parryChance = 0; + + tmp += parryChance; + if (roll < tmp) + return SPELL_MISS_PARRY; + } + + if (canBlock) + { + int32 blockChance = int32(GetUnitBlockChance(victim) * 100.0f); + if (blockChance < 0) + blockChance = 0; + tmp += blockChance; + + if (roll < tmp) + return SPELL_MISS_BLOCK; + } + + return SPELL_MISS_NONE; +} + +float Unit::GetUnitDodgeChance(WeaponAttackType attType, Unit const* victim) const +{ + int32 attackerSkill = GetMaxSkillValueForLevel(nullptr); + int32 defenderSkill = victim->GetMaxSkillValueForLevel(nullptr); + int32 skillDifference = defenderSkill - attackerSkill; + + float chance = 0.0f; + + if (victim->IsPlayer()) + { + chance += victim->GetFloatValue(PLAYER_DODGE_PERCENTAGE); + chance += skillDifference * 0.04f; + } + else + { + if (!victim->IsTotem()) + { + chance += victim->GetTotalAuraModifier(SPELL_AURA_MOD_DODGE_PERCENT); + if (skillDifference <= 10) + chance = 5.f + skillDifference * 0.1f; + else + chance = 6.f + (skillDifference - 10) * 0.1f; + } + } + + // Reduce enemy dodge chance by SPELL_AURA_MOD_COMBAT_RESULT_CHANCE + chance += GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_COMBAT_RESULT_CHANCE, VICTIMSTATE_DODGE); + + // reduce dodge by SPELL_AURA_MOD_ENEMY_DODGE + chance += GetTotalAuraModifier(SPELL_AURA_MOD_ENEMY_DODGE); + + if (IsPlayer()) + chance -= ToPlayer()->GetExpertiseDodgeOrParryReduction(attType); + else + chance -= GetTotalAuraModifier(SPELL_AURA_MOD_EXPERTISE) / 4.0f; + + return std::clamp(chance, 0.f, 100.f); +} + +float Unit::GetUnitParryChance(WeaponAttackType attType, Unit const* victim) const +{ + int32 attackerSkill = GetMaxSkillValueForLevel(nullptr); + int32 defenderSkill = victim->GetMaxSkillValueForLevel(nullptr); + int32 skillDifference = defenderSkill - attackerSkill; + + float chance = 0.0f; + + if (Player const* playerVictim = victim->ToPlayer()) + { + if (playerVictim->CanParry()) + { + Item* tmpitem = playerVictim->GetWeaponForAttack(BASE_ATTACK, true); + if (!tmpitem) + tmpitem = playerVictim->GetWeaponForAttack(OFF_ATTACK, true); + + if (tmpitem) + chance = playerVictim->GetFloatValue(PLAYER_PARRY_PERCENTAGE); + + chance += skillDifference * 0.04f; + } + } + else + { + // Allow parries for creatures only if it's not a totem, does have a virtual item equipped and does not have CREATURE_FLAG_EXTRA_NO_PARRY + if (!victim->IsTotem() && (victim->GetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 0) || victim->GetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 1)) && + !(victim->ToCreature()->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_NO_PARRY)) + { + chance += victim->GetTotalAuraModifier(SPELL_AURA_MOD_PARRY_PERCENT); + if (skillDifference <= 10) + chance = 5.f + skillDifference * 0.1f; + else + chance = 6.f + (skillDifference - 10) * 0.1f; + } + } + + if (IsPlayer()) + chance -= ToPlayer()->GetExpertiseDodgeOrParryReduction(attType); + else + chance -= GetTotalAuraModifier(SPELL_AURA_MOD_EXPERTISE) / 4.0f; + + return std::max(chance, 0.0f); +} + +float Unit::GetMeleeMissChance(Unit const* victim) const +{ + float chance = 0.f; + + // As of patch 4.0.1, weapon skills have been deprecated so we can go for plain defense skill values + uint16 attackerSkill = GetMaxSkillValueForLevel(nullptr); + uint16 defenderSkill = victim->GetMaxSkillValueForLevel(nullptr); + int32 skillDifference = defenderSkill - attackerSkill; + + if (victim->IsPlayer()) + { + chance = 5.f; + if (skillDifference > 0) + chance += skillDifference * 0.04f; + else if (skillDifference < 0) + chance -= skillDifference * 0.02f; + } + else + { + if (skillDifference <= 10) + chance = 5.f + skillDifference * 0.1f; + else + chance = 6.f + (skillDifference - 10) * 0.4f; + } + + return std::clamp(chance, 0.f, 100.f); +} + +float Unit::GetUnitBlockChance(Unit const* victim) const +{ + float chance = 0.f; + + if (Player const* playerVictim = victim->ToPlayer()) + { + if (playerVictim->CanBlock()) + { + Item* tmpitem = playerVictim->GetUseableItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND); + if (tmpitem && !tmpitem->IsBroken()) + chance = playerVictim->GetFloatValue(PLAYER_BLOCK_PERCENTAGE); + } + } + else + { + if (!victim->IsTotem() && !(victim->ToCreature()->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_NO_BLOCK)) + { + chance = 5.0f; + chance += victim->GetTotalAuraModifier(SPELL_AURA_MOD_BLOCK_PERCENT); + } + } + + return std::max(chance, 0.0f); +} + +float Unit::GetUnitCriticalChanceDone(WeaponAttackType attackType) const +{ + float chance = 0.f; + if (GetTypeId() == TYPEID_PLAYER) + { + switch (attackType) + { + case BASE_ATTACK: + chance = GetFloatValue(PLAYER_CRIT_PERCENTAGE); + break; + case OFF_ATTACK: + chance = GetFloatValue(PLAYER_OFFHAND_CRIT_PERCENTAGE); + break; + case RANGED_ATTACK: + chance = GetFloatValue(PLAYER_RANGED_CRIT_PERCENTAGE); + break; + // Just for good manner + default: + chance = 0.0f; + break; + } + } + else + { + if (!(ToCreature()->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_NO_CRIT)) + { + chance = 5.0f; + chance += GetTotalAuraModifier(SPELL_AURA_MOD_WEAPON_CRIT_PERCENT); + chance += GetTotalAuraModifier(SPELL_AURA_MOD_CRIT_PCT); + } + } + + return chance; +} + +float Unit::GetUnitCriticalChanceTaken(WeaponAttackType attackType, float critDone) const +{ + float chance = critDone; + + // flat aura mods + if (attackType == RANGED_ATTACK) + chance += GetTotalAuraModifier(SPELL_AURA_MOD_ATTACKER_RANGED_CRIT_CHANCE); + else + chance += GetTotalAuraModifier(SPELL_AURA_MOD_ATTACKER_MELEE_CRIT_CHANCE); + + // applied after resilience + chance += GetTotalAuraModifier(SPELL_AURA_MOD_ATTACKER_SPELL_AND_WEAPON_CRIT_CHANCE); + + return std::max(chance, 0.f); +} + +float Unit::GetUnitCriticalChanceAgainst(WeaponAttackType attackType, Unit const* victim) const +{ + float chance = GetUnitCriticalChanceDone(attackType); + return victim->GetUnitCriticalChanceTaken(attackType, chance); +} + +void Unit::_DeleteRemovedAuras() +{ + while (!m_removedAuras.empty()) + { + delete m_removedAuras.front(); + m_removedAuras.pop_front(); + } + + m_removedAurasCount = 0; +} + +void Unit::_UpdateSpells(uint32 time) +{ + m_spellHistory->Update(); + + if (m_currentSpells[CURRENT_AUTOREPEAT_SPELL]) + _UpdateAutoRepeatSpell(); + + // remove finished spells from current pointers + for (Spell*& spell : m_currentSpells) + { + if (spell && spell->getState() == SPELL_STATE_FINISHED) + { + spell->SetReferencedFromCurrent(false); + spell = nullptr; // remove pointer + } + } + + // m_auraUpdateIterator can be updated in indirect called code at aura remove to skip next planned to update but removed auras + for (m_auraUpdateIterator = m_ownedAuras.begin(); m_auraUpdateIterator != m_ownedAuras.end();) + { + Aura* i_aura = m_auraUpdateIterator->second; + ++m_auraUpdateIterator; // need shift to next for allow update if need into aura update + i_aura->UpdateOwner(time, this); + } + + // remove expired auras - do that after updates(used in scripts?) + for (AuraMap::iterator i = m_ownedAuras.begin(); i != m_ownedAuras.end();) + { + if (i->second->IsExpired()) + RemoveOwnedAura(i, AuraRemoveFlags::Expired); + 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()) + { + GameObjectList::iterator itr; + for (itr = m_gameObj.begin(); itr != m_gameObj.end();) + { + if (!(*itr)->isSpawned()) + { + (*itr)->SetOwnerGUID(ObjectGuid::Empty); + (*itr)->SetRespawnTime(0); + (*itr)->Delete(); + m_gameObj.erase(itr++); + } + else + ++itr; + } + } +} + +void Unit::_UpdateAutoRepeatSpell() +{ + SpellInfo const* autoRepeatSpellInfo = m_currentSpells[CURRENT_AUTOREPEAT_SPELL]->m_spellInfo; + + // check "realtime" interrupts + // don't cancel spells which are affected by a SPELL_AURA_CAST_WHILE_WALKING effect + if ((isMoving() && m_currentSpells[CURRENT_AUTOREPEAT_SPELL]->CheckMovement() != SPELL_CAST_OK) || IsNonMeleeSpellCast(false, false, true, autoRepeatSpellInfo->Id == 75)) + { + // cancel wand shoot + if (autoRepeatSpellInfo->Id != 75) + InterruptSpell(CURRENT_AUTOREPEAT_SPELL); + return; + } + + // castroutine + if (isAttackReady(RANGED_ATTACK) && m_currentSpells[CURRENT_AUTOREPEAT_SPELL]->getState() != SPELL_STATE_PREPARING) + { + // Check if able to cast + SpellCastResult result = m_currentSpells[CURRENT_AUTOREPEAT_SPELL]->CheckCast(true); + if (result != SPELL_CAST_OK) + { + if (autoRepeatSpellInfo->Id != 75) + InterruptSpell(CURRENT_AUTOREPEAT_SPELL); + else if (GetTypeId() == TYPEID_PLAYER) + Spell::SendCastResult(ToPlayer(), autoRepeatSpellInfo, 1, result); + + return; + } + + // we want to shoot + Spell* spell = new Spell(this, autoRepeatSpellInfo, TRIGGERED_IGNORE_GCD); + spell->prepare(m_currentSpells[CURRENT_AUTOREPEAT_SPELL]->m_targets); + } +} + +void Unit::SetCurrentCastSpell(Spell* spell) +{ + ASSERT(spell); // nullptr may be never passed here, use InterruptSpell or InterruptNonMeleeSpells + + CurrentSpellTypes CSpellType = spell->GetCurrentContainer(); + + if (spell == m_currentSpells[CSpellType]) // avoid breaking self + return; + + // special breakage effects: + switch (CSpellType) + { + case CURRENT_GENERIC_SPELL: + { + // generic spells always break channeled not delayed spells + if (m_currentSpells[CURRENT_CHANNELED_SPELL] && !m_currentSpells[CURRENT_CHANNELED_SPELL]->GetSpellInfo()->HasAttribute(SPELL_ATTR5_ALLOW_ACTIONS_DURING_CHANNEL)) + InterruptSpell(CURRENT_CHANNELED_SPELL, false); + + // autorepeat breaking + if (m_currentSpells[CURRENT_AUTOREPEAT_SPELL]) + { + // break autorepeat if not Auto Shot + if (m_currentSpells[CURRENT_AUTOREPEAT_SPELL]->GetSpellInfo()->Id != 75) + InterruptSpell(CURRENT_AUTOREPEAT_SPELL); + } + if (spell->GetCastTime() > 0) + AddUnitState(UNIT_STATE_CASTING); + + break; + } + case CURRENT_CHANNELED_SPELL: + { + // channel spells always break generic non-delayed and any channeled spells + InterruptSpell(CURRENT_GENERIC_SPELL, false); + InterruptSpell(CURRENT_CHANNELED_SPELL); + + // it also does break autorepeat if not Auto Shot + if (m_currentSpells[CURRENT_AUTOREPEAT_SPELL] && + m_currentSpells[CURRENT_AUTOREPEAT_SPELL]->GetSpellInfo()->Id != 75) + InterruptSpell(CURRENT_AUTOREPEAT_SPELL); + AddUnitState(UNIT_STATE_CASTING); + + break; + } + case CURRENT_AUTOREPEAT_SPELL: + { + if (m_currentSpells[CSpellType] && m_currentSpells[CSpellType]->getState() == SPELL_STATE_IDLE) + m_currentSpells[CSpellType]->setState(SPELL_STATE_FINISHED); + + // only Auto Shoot does not break anything + if (spell->GetSpellInfo()->Id != 75) + { + // generic autorepeats break generic non-delayed and channeled non-delayed spells + InterruptSpell(CURRENT_GENERIC_SPELL, false); + InterruptSpell(CURRENT_CHANNELED_SPELL, false); + } + break; + } + default: + break; // other spell types don't break anything now + } + + // current spell (if it is still here) may be safely deleted now + if (m_currentSpells[CSpellType]) + m_currentSpells[CSpellType]->SetReferencedFromCurrent(false); + + // set new current spell + m_currentSpells[CSpellType] = spell; + spell->SetReferencedFromCurrent(true); + + spell->m_selfContainer = &(m_currentSpells[spell->GetCurrentContainer()]); +} + +void Unit::InterruptSpell(CurrentSpellTypes spellType, bool withDelayed, bool withInstant) +{ + Spell* spell = m_currentSpells[spellType]; + if (!spell) + return; + + if ((withDelayed || spell->getState() != SPELL_STATE_LAUNCHED) && + (withInstant || spell->GetCastTime() > 0 || spell->getState() == SPELL_STATE_CHANNELING)) + { + // for example, do not let self-stun aura interrupt itself + if (!spell->IsInterruptable()) + return; + + // send autorepeat cancel message for autorepeat spells + if (spellType == CURRENT_AUTOREPEAT_SPELL) + if (GetTypeId() == TYPEID_PLAYER) + ToPlayer()->SendAutoRepeatCancel(this); + + if (spell->getState() != SPELL_STATE_FINISHED) + spell->cancel(); + else + { + m_currentSpells[spellType] = nullptr; + spell->SetReferencedFromCurrent(false); + } + + if (GetTypeId() == TYPEID_UNIT && IsAIEnabled()) + ToCreature()->AI()->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_LAUNCHED)) + { + if (!skipInstant || m_currentSpells[CURRENT_GENERIC_SPELL]->GetCastTime()) + { + if (!isAutoshoot || !(m_currentSpells[CURRENT_GENERIC_SPELL]->m_spellInfo->HasAttribute(SPELL_ATTR2_DO_NOT_RESET_COMBAT_TIMERS))) + return true; + } + } + // channeled spells may be delayed, but they are still considered cast + if (!skipChanneled && m_currentSpells[CURRENT_CHANNELED_SPELL] && + (m_currentSpells[CURRENT_CHANNELED_SPELL]->getState() != SPELL_STATE_FINISHED)) + { + if (!isAutoshoot || !(m_currentSpells[CURRENT_CHANNELED_SPELL]->m_spellInfo->HasAttribute(SPELL_ATTR2_DO_NOT_RESET_COMBAT_TIMERS))) + return true; + } + // autorepeat spells may be finished or delayed, but they are still considered cast + if (!skipAutorepeat && m_currentSpells[CURRENT_AUTOREPEAT_SPELL]) + return true; + + return false; +} + +void Unit::InterruptNonMeleeSpells(bool withDelayed, uint32 spell_id, bool withInstant /* = true */) +{ + // generic spells are interrupted if they are not finished or delayed + if (m_currentSpells[CURRENT_GENERIC_SPELL] && (!spell_id || m_currentSpells[CURRENT_GENERIC_SPELL]->m_spellInfo->Id == spell_id)) + InterruptSpell(CURRENT_GENERIC_SPELL, withDelayed, withInstant); + + // autorepeat spells are interrupted if they are not finished or delayed + if (m_currentSpells[CURRENT_AUTOREPEAT_SPELL] && (!spell_id || m_currentSpells[CURRENT_AUTOREPEAT_SPELL]->m_spellInfo->Id == spell_id)) + InterruptSpell(CURRENT_AUTOREPEAT_SPELL, withDelayed, withInstant); + + // channeled spells are interrupted if they are not finished, even if they are delayed + if (m_currentSpells[CURRENT_CHANNELED_SPELL] && (!spell_id || m_currentSpells[CURRENT_CHANNELED_SPELL]->m_spellInfo->Id == spell_id)) + InterruptSpell(CURRENT_CHANNELED_SPELL, true, true); +} + +Spell* Unit::FindCurrentSpellBySpellId(uint32 spell_id) const +{ + for (Spell* spell : m_currentSpells) + if (spell && spell->m_spellInfo->Id == spell_id) + return spell; + return nullptr; +} + +int32 Unit::GetCurrentSpellCastTime(uint32 spell_id) const +{ + if (Spell const* spell = FindCurrentSpellBySpellId(spell_id)) + return spell->GetCastTime(); + return 0; +} + +bool Unit::IsMovementPreventedByCasting() const +{ + // can always move when not casting + if (!HasUnitState(UNIT_STATE_CASTING)) + return false; + + if (Spell* spell = m_currentSpells[CURRENT_GENERIC_SPELL]) + if (HasAuraTypeWithAffectMask(SPELL_AURA_CAST_WHILE_WALKING, spell->GetSpellInfo()) || spell->getState() == SPELL_STATE_FINISHED || + !spell->m_spellInfo->InterruptFlags.HasFlag(SpellInterruptFlags::Movement)) + return false; + + // channeled spells during channel stage (after the initial cast timer) allow movement with a specific spell attribute + if (Spell* spell = m_currentSpells[CURRENT_CHANNELED_SPELL]) + if (spell->getState() != SPELL_STATE_FINISHED && spell->IsChannelActive()) + if (spell->GetSpellInfo()->IsMoveAllowedChannel() || HasAuraTypeWithAffectMask(SPELL_AURA_CAST_WHILE_WALKING, spell->GetSpellInfo())) + 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 * float(M_PI) - arc, target); +} + +bool Unit::isInAccessiblePlaceFor(Creature const* c) const +{ + // Aquatic creatures are not allowed to leave liquids + if (!IsInWater() && c->IsAquatic()) + return false; + + // Underwater special case. Some creatures may not go below liquid surfaces + if (IsUnderWater() && c->CannotPenetrateWater()) + return false; + + // Water checks + if (IsInWater() && !c->CanEnterWater()) + return false; + + // Some creatures are tied to the ocean floor and cannot chase swimming targets. + if (!IsOnOceanFloor() && c->IsUnderWater() && c->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_CANT_SWIM)) + return false; + + return true; +} + +bool Unit::IsInWater() const +{ + return GetLiquidStatus() & (LIQUID_MAP_IN_WATER | LIQUID_MAP_UNDER_WATER); +} + +bool Unit::IsUnderWater() const +{ + return GetLiquidStatus() & LIQUID_MAP_UNDER_WATER; +} + +bool Unit::IsOnOceanFloor() const +{ + return GetLiquidStatus() & LIQUID_MAP_OCEAN_FLOOR; +} + +void Unit::ProcessPositionDataChanged(PositionFullTerrainStatus const& data) +{ + ZLiquidStatus oldLiquidStatus = GetLiquidStatus(); + WorldObject::ProcessPositionDataChanged(data); + ProcessTerrainStatusUpdate(oldLiquidStatus, data.liquidInfo); + + if (oldLiquidStatus != GetLiquidStatus()) + UpdateMountCapability(); +} + +void Unit::ProcessTerrainStatusUpdate(ZLiquidStatus /*oldLiquidStatus*/, Optional const& newLiquidData) +{ + if (!IsControlledByPlayer()) + return; + + // remove appropriate auras if we are swimming/not swimming respectively + if (IsInWater()) + RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::UnderWater); + else + RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::AboveWater); + + // liquid aura handling + LiquidTypeEntry const* curLiquid = nullptr; + if (IsInWater() && newLiquidData) + curLiquid = sLiquidTypeStore.LookupEntry(newLiquidData->entry); + if (curLiquid != _lastLiquid) + { + if (_lastLiquid && _lastLiquid->SpellID) + RemoveAurasDueToSpell(_lastLiquid->SpellID); + Player* player = GetCharmerOrOwnerPlayerOrPlayerItself(); + + // Set _lastLiquid before casting liquid spell to avoid infinite loops + _lastLiquid = curLiquid; + + if (curLiquid && curLiquid->SpellID && (!player || !player->IsGameMaster())) + CastSpell(this, curLiquid->SpellID, true); + } +} +void Unit::DeMorph() +{ + SetDisplayId(GetNativeDisplayId()); +} + +Aura* Unit::_TryStackingOrRefreshingExistingAura(AuraCreateInfo& createInfo) +{ + ASSERT(createInfo.CasterGUID || createInfo.Caster); + + // Check if these can stack anyway + if (!createInfo.CasterGUID && !createInfo.GetSpellInfo()->IsStackableOnOneSlotWithDifferentCasters()) + createInfo.CasterGUID = createInfo.Caster->GetGUID(); + + // world gameobjects can't own auras and they send empty casterguid + // checked on sniffs with spell 22247 + if (createInfo.CasterGUID.IsGameObject()) + { + createInfo.Caster = nullptr; + createInfo.CasterGUID.Clear(); + } + + // passive and Incanter's Absorption and auras with different type can stack with themselves any number of times + if (!createInfo.GetSpellInfo()->IsMultiSlotAura()) + { + // check if cast item changed + ObjectGuid castItemGUID = createInfo.CastItemGUID; + + // find current aura from spell and change it's stackamount, or refresh it's duration + if (Aura* foundAura = GetOwnedAura(createInfo.GetSpellInfo()->Id, createInfo.GetSpellInfo()->IsStackableOnOneSlotWithDifferentCasters() ? ObjectGuid::Empty : createInfo.CasterGUID, createInfo.GetSpellInfo()->HasAttribute(SPELL_ATTR0_CU_ENCHANT_PROC) ? castItemGUID : ObjectGuid::Empty)) + { + // effect masks do not match + // extremely rare case + // let's just recreate aura + if (createInfo.GetAuraEffectMask() != foundAura->GetEffectMask()) + return nullptr; + + // update basepoints with new values - effect amount will be recalculated in ModStackAmount + for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) + { + if (!foundAura->HasEffect(i)) + continue; + + int32 bp = foundAura->GetSpellInfo()->Effects[i].BasePoints; + if (createInfo.BaseAmount) + bp = *(createInfo.BaseAmount + i); + + 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, AuraRemoveFlags::ByDefault, createInfo.ResetPeriodicTimer); + return foundAura; + } + } + + return nullptr; +} + +void Unit::_AddAura(UnitAura* aura, Unit* caster) +{ + ASSERT(!m_cleanupDone); + m_ownedAuras.emplace(aura->GetId(), aura); + + _RemoveNoStackAurasDueToAura(aura, true); + + if (aura->IsRemoved()) + return; + + aura->SetIsSingleTarget(caster && aura->GetSpellInfo()->IsSingleTarget()); + if (aura->IsSingleTarget()) + { + ASSERT((IsInWorld() && !IsDuringRemoveFromWorld()) || aura->GetCasterGUID() == GetGUID()); + /* @HACK: Player is not in world during loading auras. + * Single target auras are not saved or loaded from database + * but may be created as a result of aura links. + */ + + std::vector aurasSharingLimit; + // remove other single target auras + for (Aura* scAura : caster->GetSingleCastAuras()) + if (scAura->IsSingleTargetWith(aura)) + aurasSharingLimit.push_back(scAura); + + // register single target aura + caster->GetSingleCastAuras().push_front(aura); + + uint32 maxOtherAuras = aura->GetSpellInfo()->MaxAffectedTargets - 1; + while (aurasSharingLimit.size() > maxOtherAuras) + { + aurasSharingLimit.back()->Remove(); + aurasSharingLimit.pop_back(); + } + } +} + +// creates aura application instance and registers it in lists +// aura application effects are handled separately to prevent aura list corruption +AuraApplication* Unit::_CreateAuraApplication(Aura* aura, uint8 effMask) +{ + // can't apply aura on unit which is going to be deleted - to not create a memory leak + ASSERT(!m_cleanupDone); + + // just return if the aura has been already removed + // this can happen if OnEffectHitTarget() script hook killed the unit or the aura owner (which can be different) + if (aura->IsRemoved()) + { + TC_LOG_ERROR("spells", "Unit::_CreateAuraApplication() called with a removed aura. Check if OnEffectHitTarget() is triggering any spell with apply aura effect (that's not allowed!)"); + return nullptr; + } + + // aura mustn't be already applied on target + ASSERT (!aura->IsAppliedOnTarget(GetGUID()) && "Unit::_CreateAuraApplication: aura musn't be applied on target"); + + SpellInfo const* aurSpellInfo = aura->GetSpellInfo(); + uint32 aurId = aurSpellInfo->Id; + + // ghost spell check, allow apply any auras at player loading in ghost mode (will be cleanup after load) + if (!IsAlive() && !aurSpellInfo->IsDeathPersistent() && + (GetTypeId() != TYPEID_PLAYER || !ToPlayer()->GetSession()->PlayerLoading())) + return nullptr; + + Unit* caster = aura->GetCaster(); + + AuraApplication * aurApp = new AuraApplication(this, caster, aura, effMask); + m_appliedAuras.insert(AuraApplicationMap::value_type(aurId, aurApp)); + + if (aurSpellInfo->HasAnyAuraInterruptFlag()) + { + m_interruptableAuras.push_front(aurApp); + AddInterruptMask(aurSpellInfo->AuraInterruptFlags, aurSpellInfo->AuraInterruptFlags2); + } + + if (AuraStateType aState = aura->GetSpellInfo()->GetAuraState()) + m_auraStateAuras.insert(AuraStateAurasMap::value_type(aState, aurApp)); + + aura->_ApplyForTarget(this, caster, aurApp); + return aurApp; +} + +void Unit::_ApplyAuraEffect(Aura* aura, uint8 effIndex) +{ + ASSERT(aura); + ASSERT(aura->HasEffect(effIndex)); + AuraApplication * aurApp = aura->GetApplicationOfTarget(GetGUID()); + ASSERT(aurApp); + if (!aurApp->GetEffectMask()) + _ApplyAura(aurApp, 1 << effIndex); + else + aurApp->_HandleEffect(effIndex, true); +} + +// handles effects of aura application +// should be done after registering aura in lists +void Unit::_ApplyAura(AuraApplication* aurApp, uint8 effMask) +{ + Aura* aura = aurApp->GetBase(); + + _RemoveNoStackAurasDueToAura(aura, false); + + if (aurApp->GetRemoveMode().HasAnyFlag()) + return; + + // Update target aura state flag + if (AuraStateType aState = aura->GetSpellInfo()->GetAuraState()) + ModifyAuraState(aState, true); + + if (aurApp->GetRemoveMode().HasAnyFlag()) + return; + + // Sitdown on apply aura req seated + if (aura->GetSpellInfo()->HasAuraInterruptFlag(SpellAuraInterruptFlags::Standing) && !IsSitState()) + SetStandState(UNIT_STAND_STATE_SIT); + + Unit* caster = aura->GetCaster(); + + if (aurApp->GetRemoveMode().HasAnyFlag()) + 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().HasAnyFlag()) + aurApp->_HandleEffect(i, true); + } + + if (Player* player = ToPlayer()) + { + if (sConditionMgr->IsSpellUsedInSpellClickConditions(aurApp->GetBase()->GetId())) + player->UpdateVisibleGameobjectsOrSpellClicks(); + + player->FailAchievementCriteria(AchievementCriteriaFailEvent::GainAura, aurApp->GetBase()->GetId()); + player->StartAchievementCriteria(AchievementCriteriaStartEvent::GainAura, aurApp->GetBase()->GetId()); + player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_BE_SPELL_TARGET2, aurApp->GetBase()->GetId()); + } +} + +// removes aura application from lists and unapplies effects +void Unit::_UnapplyAura(AuraApplicationMap::iterator& i, AuraRemoveFlags removeMode) +{ + AuraApplication * aurApp = i->second; + ASSERT(aurApp); + ASSERT(!aurApp->GetRemoveMode().HasAnyFlag()); + ASSERT(aurApp->GetTarget() == this); + + aurApp->SetRemoveMode(removeMode); + Aura* aura = aurApp->GetBase(); + TC_LOG_DEBUG("spells", "Aura %u now is remove mode %u", aura->GetId(), static_cast(removeMode)); + + // dead loop is killing the server probably + ASSERT(m_removedAurasCount < 0xFFFFFFFF); + + ++m_removedAurasCount; + + Unit* caster = aura->GetCaster(); + + // Remove all pointers from lists here to prevent possible pointer invalidation on spellcast/auraapply/auraremove + m_appliedAuras.erase(i); + + if (aura->GetSpellInfo()->HasAnyAuraInterruptFlag()) + { + Trinity::Containers::Lists::RemoveUnique(m_interruptableAuras, aurApp); + UpdateInterruptMask(); + } + + bool auraStateFound = false; + AuraStateType auraState = aura->GetSpellInfo()->GetAuraState(); + if (auraState) + { + bool canBreak = false; + // Get mask of all aurastates from remaining auras + for (AuraStateAurasMap::iterator itr = m_auraStateAuras.lower_bound(auraState); itr != m_auraStateAuras.upper_bound(auraState) && !(auraStateFound && canBreak);) + { + if (itr->second == aurApp) + { + m_auraStateAuras.erase(itr); + itr = m_auraStateAuras.lower_bound(auraState); + canBreak = true; + continue; + } + auraStateFound = true; + ++itr; + } + } + + aurApp->_Remove(); + aura->_UnapplyForTarget(this, caster, aurApp); + + // remove effects of the spell - needs to be done after removing aura from lists + for (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().HasFlag(AuraRemoveFlags::Expired) && GetTypeId() == TYPEID_UNIT && IsTotem()) + { + if (ToTotem()->GetSpell() == aura->GetId() && ToTotem()->GetTotemType() == TOTEM_PASSIVE) + ToTotem()->setDeathState(JUST_DIED); + } + + // Remove aurastates only if were not found + if (!auraStateFound) + ModifyAuraState(auraState, false); + + aura->HandleAuraSpecificMods(aurApp, caster, false, false); + + if (Player* player = ToPlayer()) + { + if (sConditionMgr->IsSpellUsedInSpellClickConditions(aurApp->GetBase()->GetId())) + player->UpdateVisibleGameobjectsOrSpellClicks(); + + player->FailAchievementCriteria(AchievementCriteriaFailEvent::LoseAura, aurApp->GetBase()->GetId()); + } + + i = m_appliedAuras.begin(); +} + +void Unit::_UnapplyAura(AuraApplication* aurApp, AuraRemoveFlags removeMode) +{ + // aura can be removed from unit only if it's applied on it, shouldn't happen + ASSERT(aurApp->GetBase()->GetApplicationOfTarget(GetGUID()) == aurApp); + + uint32 spellId = aurApp->GetBase()->GetId(); + AuraApplicationMapBoundsNonConst range = m_appliedAuras.equal_range(spellId); + + for (AuraApplicationMap::iterator iter = range.first; iter != range.second;) + { + if (iter->second == aurApp) + { + _UnapplyAura(iter, removeMode); + return; + } + else + ++iter; + } + ABORT(); +} + +void Unit::_RemoveNoStackAurasDueToAura(Aura* aura, bool owned) +{ + SpellInfo const* spellProto = aura->GetSpellInfo(); + + // passive spell special case (only non stackable with ranks) + if (spellProto->IsPassiveStackableWithRanks()) + return; + + if (!IsHighestExclusiveAura(aura)) + { + aura->Remove(); + return; + } + + if (owned) + RemoveOwnedAuras([aura](Aura const* ownedAura) { return !aura->CanStackWith(ownedAura); }, AuraRemoveFlags::ByDefault); + else + RemoveAppliedAuras([aura](AuraApplication const* appliedAura) { return !aura->CanStackWith(appliedAura->GetBase()); }, AuraRemoveFlags::ByDefault); +} + +void Unit::_RegisterAuraEffect(AuraEffect* aurEff, bool apply) +{ + if (apply) + { + m_modAuras[aurEff->GetAuraType()].push_front(aurEff); + if (Player* player = ToPlayer()) + { + player->StartAchievementCriteria(AchievementCriteriaStartEvent::GainAuraEffect, aurEff->GetAuraType()); + player->FailAchievementCriteria(AchievementCriteriaFailEvent::GainAuraEffect, aurEff->GetAuraType()); + } + } + else + Trinity::Containers::Lists::RemoveUnique(m_modAuras[aurEff->GetAuraType()], aurEff); +} + +// All aura base removes should go through this function! +void Unit::RemoveOwnedAura(AuraMap::iterator& i, AuraRemoveFlags removeMode) +{ + Aura* aura = i->second; + ASSERT(!aura->IsRemoved()); + + // if unit currently update aura list then make safe update iterator shift to next + if (m_auraUpdateIterator == i) + ++m_auraUpdateIterator; + + m_ownedAuras.erase(i); + m_removedAuras.push_front(aura); + + // Unregister single target aura + if (aura->IsSingleTarget()) + aura->UnregisterSingleTarget(); + + aura->_Remove(removeMode); + + i = m_ownedAuras.begin(); +} + +void Unit::RemoveOwnedAura(uint32 spellId, ObjectGuid casterGUID, uint8 reqEffMask, AuraRemoveFlags 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, AuraRemoveFlags removeMode) +{ + if (aura->IsRemoved()) + return; + + ASSERT(aura->GetOwner() == this); + + if (removeMode == AuraRemoveFlags::None) + { + TC_LOG_ERROR("spells", "Unit::RemoveOwnedAura() called with unallowed removeMode AuraRemoveFlags::None, spellId %u", aura->GetId()); + return; + } + + uint32 spellId = aura->GetId(); + AuraMapBoundsNonConst range = m_ownedAuras.equal_range(spellId); + + for (AuraMap::iterator itr = range.first; itr != range.second; ++itr) + { + if (itr->second == aura) + { + RemoveOwnedAura(itr, removeMode); + return; + } + } + + ABORT(); +} + +Aura* Unit::GetOwnedAura(uint32 spellId, ObjectGuid casterGUID, ObjectGuid itemCasterGUID, 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, AuraRemoveFlags mode) +{ + AuraApplication * aurApp = i->second; + // Do not remove aura which is already being removed + if (aurApp->GetRemoveMode().HasAnyFlag()) + 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, AuraRemoveFlags 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, AuraRemoveFlags 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().HasAnyFlag()) + { + // 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) + { + RemoveAura(iter, mode); + return; + } + else + ++iter; + } +} + +void Unit::RemoveAura(Aura* aura, AuraRemoveFlags mode) +{ + if (aura->IsRemoved()) + return; + if (AuraApplication * aurApp = aura->GetApplicationOfTarget(GetGUID())) + RemoveAura(aurApp, mode); +} + +void Unit::RemoveAppliedAuras(std::function const& check, AuraRemoveFlags removeMode /* = AuraRemoveFlags::ByDefault*/) +{ + for (AuraApplicationMap::iterator iter = m_appliedAuras.begin(); iter != m_appliedAuras.end();) + { + if (check(iter->second)) + { + RemoveAura(iter, removeMode); + continue; + } + ++iter; + } +} + +void Unit::RemoveOwnedAuras(std::function const& check, AuraRemoveFlags removeMode /* = AuraRemoveFlags::ByDefault*/) +{ + for (AuraMap::iterator iter = m_ownedAuras.begin(); iter != m_ownedAuras.end();) + { + if (check(iter->second)) + { + RemoveOwnedAura(iter, removeMode); + continue; + } + ++iter; + } +} + +void Unit::RemoveAppliedAuras(uint32 spellId, std::function const& check, AuraRemoveFlags removeMode /* = AuraRemoveFlags::ByDefault*/) +{ + for (AuraApplicationMap::iterator iter = m_appliedAuras.lower_bound(spellId); iter != m_appliedAuras.upper_bound(spellId);) + { + if (check(iter->second)) + { + RemoveAura(iter, removeMode); + iter = m_appliedAuras.lower_bound(spellId); + continue; + } + ++iter; + } +} + +void Unit::RemoveOwnedAuras(uint32 spellId, std::function const& check, AuraRemoveFlags removeMode /* = AuraRemoveFlags::ByDefault*/) +{ + for (AuraMap::iterator iter = m_ownedAuras.lower_bound(spellId); iter != m_ownedAuras.upper_bound(spellId);) + { + if (check(iter->second)) + { + RemoveOwnedAura(iter, removeMode); + iter = m_ownedAuras.lower_bound(spellId); + continue; + } + ++iter; + } +} + +void Unit::RemoveAurasByType(AuraType auraType, std::function const& check, AuraRemoveFlags removeMode /* = AuraRemoveFlags::ByDefault*/) +{ + for (AuraEffectList::iterator iter = m_modAuras[auraType].begin(); iter != m_modAuras[auraType].end();) + { + Aura* aura = (*iter)->GetBase(); + AuraApplication * aurApp = aura->GetApplicationOfTarget(GetGUID()); + ASSERT(aurApp); + + ++iter; + if (check(aurApp)) + { + uint32 removedAuras = m_removedAurasCount; + RemoveAura(aurApp, removeMode); + if (m_removedAurasCount > removedAuras + 1) + iter = m_modAuras[auraType].begin(); + } + } +} + +void Unit::RemoveAurasDueToSpell(uint32 spellId, ObjectGuid casterGUID, uint8 reqEffMask, AuraRemoveFlags 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, AuraRemoveFlags 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, WorldObject* dispeller, uint8 chargesRemoved/*= 1*/) +{ + AuraMapBoundsNonConst range = m_ownedAuras.equal_range(spellId); + for (AuraMap::iterator iter = range.first; iter != range.second;) + { + Aura* aura = iter->second; + if (aura->GetCasterGUID() == casterGUID) + { + DispelInfo dispelInfo(dispeller, dispellerSpellId, chargesRemoved); + + // Call OnDispel hook on AuraScript + aura->CallScriptDispel(&dispelInfo); + + if (aura->GetSpellInfo()->HasAttribute(SPELL_ATTR7_DISPEL_REMOVES_CHARGES)) + aura->ModCharges(-dispelInfo.GetRemovedCharges(), AuraRemoveFlags::ByEnemySpell); + else + aura->ModStackAmount(-dispelInfo.GetRemovedCharges(), AuraRemoveFlags::ByEnemySpell); + + // Call AfterDispel hook on AuraScript + aura->CallScriptAfterDispel(&dispelInfo); + + return; + } + else + ++iter; + } +} + +void Unit::RemoveAurasDueToSpellBySteal(uint32 spellId, ObjectGuid casterGUID, WorldObject* stealer, int32 stolenCharges /*= 1*/) +{ + AuraMapBoundsNonConst range = m_ownedAuras.equal_range(spellId); + for (AuraMap::iterator iter = range.first; iter != range.second;) + { + Aura* aura = iter->second; + if (aura->GetCasterGUID() == casterGUID) + { + 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<GetEffect(i)->CanBeRecalculated()) + recalculateMask |= (1<GetSpellInfo()->HasAttribute(SPELL_ATTR7_DISPEL_REMOVES_CHARGES); + // Cast duration to unsigned to prevent permanent aura's such as Righteous Fury being permanently added to caster + uint32 dur = std::min(2u * MINUTE * IN_MILLISECONDS, uint32(aura->GetDuration())); + + if (Unit* unitStealer = stealer->ToUnit()) + { + if (Aura* oldAura = unitStealer->GetAura(aura->GetId(), aura->GetCasterGUID())) + { + if (stealCharge) + oldAura->ModCharges(stolenCharges); + else + oldAura->ModStackAmount(stolenCharges); + oldAura->SetDuration(int32(dur)); + } + else + { + // single target state must be removed before aura creation to preserve existing single target aura + if (aura->IsSingleTarget()) + aura->UnregisterSingleTarget(); + + AuraCreateInfo createInfo(aura->GetSpellInfo(), effMask, stealer); + createInfo + .SetCasterGUID(aura->GetCasterGUID()) + .SetBaseAmount(baseDamage); + + if (Aura* newAura = Aura::TryRefreshStackOrCreate(createInfo)) + { + // created aura must not be single target aura,, so stealer won't loose it on recast + if (newAura->IsSingleTarget()) + { + newAura->UnregisterSingleTarget(); + // bring back single target aura status to the old aura + aura->SetIsSingleTarget(true); + caster->GetSingleCastAuras().push_front(aura); + } + // FIXME: using aura->GetMaxDuration() maybe not blizzlike but it fixes stealing of spells like Innervate + newAura->SetLoadedState(aura->GetMaxDuration(), int32(dur), stealCharge ? stolenCharges : aura->GetCharges(), stolenCharges, recalculateMask, aura->GetCritChance(), aura->CanApplyResilience(), &damage[0]); + newAura->ApplyForTargets(); + } + } + } + + if (stealCharge) + aura->ModCharges(-stolenCharges, AuraRemoveFlags::ByEnemySpell); + else + aura->ModStackAmount(-stolenCharges, AuraRemoveFlags::ByEnemySpell); + + return; + } + else + ++iter; + } +} + +void Unit::RemoveAurasDueToItemSpell(uint32 spellId, ObjectGuid castItemGuid) +{ + for (AuraApplicationMap::iterator iter = m_appliedAuras.lower_bound(spellId); iter != m_appliedAuras.upper_bound(spellId);) + { + if (iter->second->GetBase()->GetCastItemGUID() == castItemGuid) + { + RemoveAura(iter); + iter = m_appliedAuras.lower_bound(spellId); + } + else + ++iter; + } +} + +void Unit::RemoveAurasByType(AuraType auraType, ObjectGuid casterGUID, Aura* except, bool negative, bool positive) +{ + for (AuraEffectList::iterator iter = m_modAuras[auraType].begin(); iter != m_modAuras[auraType].end();) + { + Aura* aura = (*iter)->GetBase(); + AuraApplication * aurApp = aura->GetApplicationOfTarget(GetGUID()); + ASSERT(aurApp); + + ++iter; + if (aura != except && (!casterGUID || aura->GetCasterGUID() == casterGUID) + && ((negative && !aurApp->IsPositive()) || (positive && aurApp->IsPositive()))) + { + uint32 removedAuras = m_removedAurasCount; + RemoveAura(aurApp); + if (m_removedAurasCount > removedAuras + 1) + iter = m_modAuras[auraType].begin(); + } + } +} + +void Unit::RemoveAurasWithAttribute(uint32 flags) +{ + for (AuraApplicationMap::iterator iter = m_appliedAuras.begin(); iter != m_appliedAuras.end();) + { + SpellInfo const* spell = iter->second->GetBase()->GetSpellInfo(); + if (spell->Attributes & flags) + RemoveAura(iter); + else + ++iter; + } +} + +void Unit::RemoveNotOwnSingleTargetAuras(bool onPhaseChange /*= false*/) +{ + // single target auras from other casters + // Iterate m_ownedAuras - aura is marked as single target in Unit::AddAura (and pushed to m_ownedAuras). + // m_appliedAuras will NOT contain the aura before first Unit::Update after adding it to m_ownedAuras. + // Quickly removing such an aura will lead to it not being unregistered from caster's single cast auras container + // leading to assertion failures if the aura was cast on a player that can + // (and is changing map at the point where this function is called). + // Such situation occurs when player is logging in inside an instance and fails the entry check for any reason. + // The aura that was loaded from db (indirectly, via linked casts) gets removed before it has a chance + // to register in m_appliedAuras + for (AuraMap::iterator iter = m_ownedAuras.begin(); iter != m_ownedAuras.end();) + { + Aura const* aura = iter->second; + + if (aura->GetCasterGUID() != GetGUID() && aura->IsSingleTarget()) + { + if (!onPhaseChange) + RemoveOwnedAura(iter); + else + { + Unit* caster = aura->GetCaster(); + if (!caster || !caster->IsInPhase(this)) + RemoveOwnedAura(iter); + else + ++iter; + } + } + else + ++iter; + } + + // single target auras at other targets + AuraList& scAuras = GetSingleCastAuras(); + for (AuraList::iterator iter = scAuras.begin(); iter != scAuras.end();) + { + Aura* aura = *iter; + if (aura->GetUnitOwner() != this && (!onPhaseChange || !aura->GetUnitOwner()->IsInPhase(this))) + { + aura->Remove(); + iter = scAuras.begin(); + } + else + ++iter; + } +} + +template +bool IsInterruptFlagIgnoredForSpell(InterruptFlag /*flag*/, Unit const* /*unit*/, SpellInfo const* /*auraSpellInfo*/, SpellInfo const* /*interruptSource*/) +{ + return false; +} + +bool IsInterruptFlagIgnoredForSpell(SpellAuraInterruptFlags flag, Unit const* unit, SpellInfo const* auraSpellInfo, SpellInfo const* interruptSource) +{ + switch (flag) + { + case SpellAuraInterruptFlags::Moving: + return unit->HasAuraTypeWithAffectMask(SPELL_AURA_CAST_WHILE_WALKING, auraSpellInfo); + case SpellAuraInterruptFlags::Action: + case SpellAuraInterruptFlags::ActionDelayed: + if (interruptSource) + { + if (interruptSource->HasAttribute(SPELL_ATTR1_ALLOW_WHILE_STEALTHED) && auraSpellInfo->Dispel == DISPEL_STEALTH) + return true; + + if (interruptSource->HasAttribute(SPELL_ATTR2_ALLOW_WHILE_INVISIBLE) && auraSpellInfo->Dispel == DISPEL_INVISIBILITY) + return true; + } + break; + default: + break; + } + + return false; +} + +template +void Unit::RemoveAurasWithInterruptFlags(InterruptFlags flag, SpellInfo const* source) +{ + if (!HasInterruptFlag(flag)) + return; + + // interrupt auras + for (AuraApplicationList::iterator iter = m_interruptableAuras.begin(); iter != m_interruptableAuras.end();) + { + Aura* aura = (*iter)->GetBase(); + ++iter; + if (aura->GetSpellInfo()->HasAuraInterruptFlag(flag) + && (!source || aura->GetId() != source->Id) + && !IsInterruptFlagIgnoredForSpell(flag, this, aura->GetSpellInfo(), source)) + { + 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_CHANNELING + && spell->GetSpellInfo()->HasChannelInterruptFlag(flag) + && (!source || spell->GetSpellInfo()->Id != source->Id) + && !IsInterruptFlagIgnoredForSpell(flag, this, spell->GetSpellInfo(), source)) + InterruptNonMeleeSpells(false, 0, true); + + UpdateInterruptMask(); +} + +template TC_GAME_API void Unit::RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags flag, SpellInfo const* source); +template TC_GAME_API void Unit::RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags2 flag, SpellInfo const* source); + +void Unit::RemoveAurasWithFamily(SpellFamilyNames family, 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, AuraRemoveFlags::ByDefault, 0, true); + + RemoveAurasWithMechanic(1 << MECHANIC_ROOT, AuraRemoveFlags::ByDefault, 0, false); +} + +void Unit::RemoveAurasWithMechanic(uint32 mechanicMaskToRemove, AuraRemoveFlags removeMode, uint32 exceptSpellId, bool withEffectMechanics) +{ + std::vector aurasToUpdateTargets; + RemoveAppliedAuras([=, &aurasToUpdateTargets](AuraApplication const* aurApp) + { + Aura* aura = aurApp->GetBase(); + if (exceptSpellId && aura->GetId() == exceptSpellId) + return false; + + uint32 appliedMechanicMask = aura->GetSpellInfo()->GetSpellMechanicMaskByEffectMask(aurApp->GetEffectMask()); + if (!(appliedMechanicMask & mechanicMaskToRemove)) + return false; + + // spell mechanic matches required mask for removal + if ((1 << aura->GetSpellInfo()->Mechanic) & mechanicMaskToRemove || withEffectMechanics) + return true; + + // effect mechanic matches required mask for removal - don't remove, only update targets + aurasToUpdateTargets.push_back(aura); + return false; + }, removeMode); + + for (Aura* aura : aurasToUpdateTargets) + { + aura->UpdateTargetMap(aura->GetCaster()); + + // Fully remove the aura if all effects were removed + if (!aura->IsPassive() && aura->GetOwner() == this && !aura->GetApplicationOfTarget(GetGUID())) + aura->Remove(removeMode); + } +} + +void Unit::RemoveAurasByShapeShift() +{ + 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)) + { + RemoveAura(iter); + continue; + } + ++iter; + } +} + +void Unit::RemoveAreaAurasDueToLeaveWorld() +{ + // make sure that all area auras not applied on self are removed - prevent access to deleted pointer later + for (AuraMap::iterator iter = m_ownedAuras.begin(); iter != m_ownedAuras.end();) + { + Aura* aura = iter->second; + ++iter; + Aura::ApplicationMap const& appMap = aura->GetApplicationMap(); + for (Aura::ApplicationMap::const_iterator itr = appMap.begin(); itr!= appMap.end();) + { + AuraApplication * aurApp = itr->second; + ++itr; + Unit* target = aurApp->GetTarget(); + if (target == this) + continue; + target->RemoveAura(aurApp); + // things linked on aura remove may apply new area aura - so start from the beginning + iter = m_ownedAuras.begin(); + } + } + + // remove area auras owned by others + for (AuraApplicationMap::iterator iter = m_appliedAuras.begin(); iter != m_appliedAuras.end();) + { + if (iter->second->GetBase()->GetOwner() != this) + { + RemoveAura(iter); + } + else + ++iter; + } +} + +void Unit::RemoveAllAuras() +{ + // this may be a dead loop if some events on aura remove will continiously apply aura on remove + // we want to have all auras removed, so use your brain when linking events + for (uint32 counter = 0; !m_appliedAuras.empty() || !m_ownedAuras.empty(); counter++) + { + AuraApplicationMap::iterator aurAppIter; + for (aurAppIter = m_appliedAuras.begin(); aurAppIter != m_appliedAuras.end();) + _UnapplyAura(aurAppIter, AuraRemoveFlags::ByDefault); + + AuraMap::iterator aurIter; + for (aurIter = m_ownedAuras.begin(); aurIter != m_ownedAuras.end();) + RemoveOwnedAura(aurIter); + + const int maxIteration = 50; + // give this loop a few tries, if there are still auras then log as much information as possible + if (counter >= maxIteration) + { + std::stringstream sstr; + sstr << "Unit::RemoveAllAuras() iterated " << maxIteration << " times already but there are still " + << m_appliedAuras.size() << " m_appliedAuras and " << m_ownedAuras.size() << " m_ownedAuras.\n"; + + TC_LOG_ERROR("entities.unit", "%s", sstr.str().c_str()); + ASSERT(false); + + break; + } + } +} + +void Unit::RemoveArenaAuras() +{ + // in join, remove positive buffs, on end, remove negative + // used to remove positive visible auras in arenas + RemoveAppliedAuras([](AuraApplication const* aurApp) + { + Aura const* aura = aurApp->GetBase(); + return (!aura->GetSpellInfo()->HasAttribute(SPELL_ATTR4_ALLOW_ENTERING_ARENA) // don't remove stances, shadowform, pally/hunter auras + && !aura->IsPassive() // don't remove passive auras + && (aurApp->IsPositive() || !aura->GetSpellInfo()->HasAttribute(SPELL_ATTR3_ALLOW_AURA_WHILE_DEAD))) || // not negative death persistent auras + aura->GetSpellInfo()->HasAttribute(SPELL_ATTR5_REMOVE_ENTERING_ARENA); // special marker, always remove + }); +} + +void Unit::RemoveAurasOnEvade() +{ + if (IsCharmedOwnedByPlayerOrPlayer()) // if it is a player owned creature it should not remove the aura + return; + + // don't remove vehicle auras, passengers aren't supposed to drop off the vehicle + // don't remove clone caster on evade (to be verified) + auto evadeAuraCheck = [](Aura const* aura) + { + if (aura->HasEffectType(SPELL_AURA_CONTROL_VEHICLE)) + return false; + + if (aura->HasEffectType(SPELL_AURA_CLONE_CASTER)) + return false; + + if (aura->GetSpellInfo()->HasAttribute(SPELL_ATTR1_AURA_STAYS_AFTER_COMBAT)) + return false; + + return true; + }; + + auto evadeAuraApplicationCheck = [&evadeAuraCheck](AuraApplication const* aurApp) + { + return evadeAuraCheck(aurApp->GetBase()); + }; + + RemoveAppliedAuras(evadeAuraApplicationCheck); + RemoveOwnedAuras(evadeAuraCheck); +} + +void Unit::RemoveAllAurasOnDeath() +{ + // used just after dieing to remove all visible auras + // and disable the mods for the passive ones + for (AuraApplicationMap::iterator iter = m_appliedAuras.begin(); iter != m_appliedAuras.end();) + { + Aura const* aura = iter->second->GetBase(); + if (!aura->IsPassive() && !aura->IsDeathPersistent()) + _UnapplyAura(iter, AuraRemoveFlags::ByDeath); + else + ++iter; + } + + for (AuraMap::iterator iter = m_ownedAuras.begin(); iter != m_ownedAuras.end();) + { + Aura* aura = iter->second; + if (!aura->IsPassive() && !aura->IsDeathPersistent()) + RemoveOwnedAura(iter, AuraRemoveFlags::ByDeath); + 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, AuraRemoveFlags::ByDefault); + 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, AuraRemoveFlags::ByDefault); + 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, AuraRemoveFlags::ByDefault); + } + + for (AuraMap::iterator iter = m_ownedAuras.begin(); iter != m_ownedAuras.end();) + { + Aura* aura = iter->second; + if (aura->GetSpellInfo()->HasAura(type)) + ++iter; + else + RemoveOwnedAura(iter, AuraRemoveFlags::ByDefault); + } +} + +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, AuraRemoveFlags::ByDefault); + } + + 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, AuraRemoveFlags::ByDefault); + } +} + +void Unit::RemoveAllGroupBuffsFromCaster(ObjectGuid casterGUID) +{ + for (AuraMap::iterator iter = m_ownedAuras.begin(); iter != m_ownedAuras.end();) + { + Aura* aura = iter->second; + if (aura->GetCasterGUID() == casterGUID && aura->GetSpellInfo()->IsGroupBuff()) + { + RemoveOwnedAura(iter); + continue; + } + ++iter; + } +} + +void Unit::DelayOwnedAuras(uint32 spellId, ObjectGuid caster, int32 delaytime) +{ + AuraMapBoundsNonConst range = m_ownedAuras.equal_range(spellId); + for (; range.first != range.second; ++range.first) + { + Aura* aura = range.first->second; + if (!caster || aura->GetCasterGUID() == caster) + { + if (aura->GetDuration() < delaytime) + aura->SetDuration(0); + else + aura->SetDuration(aura->GetDuration() - delaytime); + + // update for out of range group members (on 1 slot use) + aura->SetNeedClientUpdateForTargets(); + } + } +} + +void Unit::_RemoveAllAuraStatMods() +{ + for (AuraApplicationMap::iterator i = m_appliedAuras.begin(); i != m_appliedAuras.end(); ++i) + (*i).second->GetBase()->HandleAllEffects(i->second, AURA_EFFECT_HANDLE_STAT, false); +} + +void Unit::_ApplyAllAuraStatMods() +{ + for (AuraApplicationMap::iterator i = m_appliedAuras.begin(); i != m_appliedAuras.end(); ++i) + (*i).second->GetBase()->HandleAllEffects(i->second, AURA_EFFECT_HANDLE_STAT, true); +} + +bool Unit::HasSingleCastAuraOfSpell(uint32 spellId) const +{ + AuraList const& scAuras = GetSingleCastAuras(); + for (Aura const* aura : scAuras) + if (aura->GetSpellInfo()->Id == spellId) + return true; + + return false; +} + +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 == uint32(name) && !spell->SpellFamilyFlags) + 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::GetDummyAuraEffect(SpellFamilyNames name, uint32 iconId, uint8 effIndex) const +{ + return GetAuraEffect(SPELL_AURA_DUMMY, name, iconId, effIndex); +} + +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(WorldObject const* caster, uint32 dispelMask, DispelChargesList& dispelList, bool isReflect /*= false*/) const +{ + // 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); + + AuraMap const& auras = GetOwnedAuras(); + for (auto itr = auras.begin(); itr != auras.end(); ++itr) + { + Aura* aura = itr->second; + AuraApplication const* aurApp = aura->GetApplicationOfTarget(GetGUID()); + if (!aurApp) + continue; + + // don't try to remove passive auras + if (aura->IsPassive()) + continue; + + if (aura->GetSpellInfo()->GetDispelMask() & dispelMask) + { + // do not remove positive auras if friendly target + // negative auras if non-friendly + // unless we're reflecting (dispeller eliminates one of it's benefitial buffs) + if (isReflect != (aurApp->IsPositive() == IsFriendlyTo(caster))) + continue; + + // 2.4.3 Patch Notes: "Dispel effects will no longer attempt to remove effects that have 100% dispel resistance." + int32 chance = aura->CalcDispelChance(this, !IsFriendlyTo(caster)); + if (!chance) + continue; + + // The charges / stack amounts don't count towards the total number of auras that can be dispelled. + // Ie: A dispel on a target with 5 stacks of Winters Chill and a Polymorph has 1 / (1 + 1) -> 50% chance to dispell + // Polymorph instead of 1 / (5 + 1) -> 16%. + bool const dispelCharges = aura->GetSpellInfo()->HasAttribute(SPELL_ATTR7_DISPEL_REMOVES_CHARGES); + uint8 charges = dispelCharges ? aura->GetCharges() : aura->GetStackAmount(); + if (charges > 0) + dispelList.emplace_back(aura, chance, charges); + } + } +} + +bool Unit::HasAuraEffect(uint32 spellId, uint8 effIndex, ObjectGuid caster) const +{ + AuraApplicationMapBounds range = m_appliedAuras.equal_range(spellId); + for (AuraApplicationMap::const_iterator itr = range.first; itr != range.second; ++itr) + { + if (itr->second->HasEffect(effIndex) + && (!caster || itr->second->GetBase()->GetCasterGUID() == caster)) + { + return true; + } + } + return false; +} + +uint32 Unit::GetAuraCount(uint32 spellId) const +{ + uint32 count = 0; + AuraApplicationMapBounds range = m_appliedAuras.equal_range(spellId); + + for (AuraApplicationMap::const_iterator itr = range.first; itr != range.second; ++itr) + { + if (itr->second->GetBase()->GetStackAmount() == 0) + ++count; + else + count += (uint32)itr->second->GetBase()->GetStackAmount(); + } + + return count; +} + +bool Unit::HasAura(uint32 spellId, ObjectGuid casterGUID, ObjectGuid itemCasterGUID, 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 +{ + for (AuraEffect const* eff : GetAuraEffectsByType(auraType)) + if (caster == eff->GetCasterGUID()) + return true; + return false; +} + +bool Unit::HasAuraTypeWithMiscvalue(AuraType auraType, int32 miscvalue) const +{ + AuraEffectList const& mTotalAuraList = GetAuraEffectsByType(auraType); + for (AuraEffectList::const_iterator i = mTotalAuraList.begin(); i != mTotalAuraList.end(); ++i) + if (miscvalue == (*i)->GetMiscValue()) + return true; + return false; +} + +bool Unit::HasAuraTypeWithAffectMask(AuraType auraType, SpellInfo const* affectedSpell) const +{ + AuraEffectList const& mTotalAuraList = GetAuraEffectsByType(auraType); + for (AuraEffectList::const_iterator i = mTotalAuraList.begin(); i != mTotalAuraList.end(); ++i) + if ((*i)->IsAffectingSpell(affectedSpell)) + return true; + return false; +} + +bool Unit::HasAuraTypeWithValue(AuraType auraType, int32 value) const +{ + AuraEffectList const& mTotalAuraList = GetAuraEffectsByType(auraType); + for (AuraEffectList::const_iterator i = mTotalAuraList.begin(); i != mTotalAuraList.end(); ++i) + if (value == (*i)->GetAmount()) + return true; + return false; +} + +template +bool Unit::HasNegativeAuraWithInterruptFlag(InterruptFlags flag, ObjectGuid guid) const +{ + if (!HasInterruptFlag(flag)) + return false; + + for (AuraApplicationList::const_iterator iter = m_interruptableAuras.begin(); iter != m_interruptableAuras.end(); ++iter) + { + if (!(*iter)->IsPositive() && (*iter)->GetBase()->GetSpellInfo()->HasAuraInterruptFlag(flag) && (!guid || (*iter)->GetBase()->GetCasterGUID() == guid)) + return true; + } + return false; +} + +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; +} + +bool Unit::HasStrongerAuraWithDR(SpellInfo const* auraSpellInfo, Unit* caster, bool triggered) const +{ + DiminishingGroup diminishGroup = auraSpellInfo->GetDiminishingReturnsGroupForSpell(triggered); + DiminishingLevels level = GetDiminishing(diminishGroup); + for (auto itr = m_appliedAuras.begin(); itr != m_appliedAuras.end(); ++itr) + { + SpellInfo const* spellInfo = itr->second->GetBase()->GetSpellInfo(); + if (spellInfo->GetDiminishingReturnsGroupForSpell(triggered) != diminishGroup) + continue; + + int32 existingDuration = itr->second->GetBase()->GetDuration(); + int32 newDuration = auraSpellInfo->GetMaxDuration(); + ApplyDiminishingToDuration(auraSpellInfo, triggered, newDuration, caster, level); + if (newDuration > 0 && newDuration < existingDuration) + return true; + } + + return false; +} + +AuraEffect* Unit::IsScriptOverriden(SpellInfo const* spell, int32 script) const +{ + AuraEffectList const& auras = GetAuraEffectsByType(SPELL_AURA_OVERRIDE_CLASS_SCRIPTS); + for (AuraEffectList::const_iterator i = auras.begin(); i != auras.end(); ++i) + { + if ((*i)->GetMiscValue() == script) + if ((*i)->IsAffectingSpell(spell)) + return (*i); + } + return nullptr; +} + +uint32 Unit::GetDiseasesByCaster(ObjectGuid casterGUID, bool remove) +{ + static const AuraType diseaseAuraTypes[] = + { + SPELL_AURA_PERIODIC_DAMAGE, // Frost Fever and Blood Plague + SPELL_AURA_LINKED // Crypt Fever and Ebon Plague + }; + + // Burning Blood - Blood DK T12 Bonus + // "Causes owning Death Knight's abilities to treat the target as if it had 2 diseases active." + if (HasAura(98957, casterGUID)) + return 2; + + uint32 diseases = 0; + for (AuraType aType : diseaseAuraTypes) + { + for (auto itr = m_modAuras[aType].begin(); itr != m_modAuras[aType].end();) + { + // Get auras with disease dispel type by caster + if ((*itr)->GetSpellInfo()->Dispel == DISPEL_DISEASE + && (*itr)->GetCasterGUID() == casterGUID) + { + ++diseases; + + if (remove) + { + RemoveAura((*itr)->GetId(), (*itr)->GetCasterGUID()); + itr = m_modAuras[aType].begin(); + continue; + } + } + ++itr; + } + } + return diseases; +} + +uint32 Unit::GetDoTsByCaster(ObjectGuid casterGUID) const +{ + static const AuraType diseaseAuraTypes[] = + { + SPELL_AURA_PERIODIC_DAMAGE, + SPELL_AURA_PERIODIC_DAMAGE_PERCENT, + SPELL_AURA_NONE + }; + + uint32 dots = 0; + for (AuraType const* itr = &diseaseAuraTypes[0]; itr && itr[0] != SPELL_AURA_NONE; ++itr) + { + Unit::AuraEffectList const& auras = GetAuraEffectsByType(*itr); + for (AuraEffectList::const_iterator i = auras.begin(); i != auras.end(); ++i) + { + // Get auras by caster + if ((*i)->GetCasterGUID() == casterGUID) + ++dots; + } + } + return dots; +} + +int32 Unit::GetTotalAuraModifier(AuraType auraType, std::function const& predicate) const +{ + AuraEffectList const& mTotalAuraList = GetAuraEffectsByType(auraType); + if (mTotalAuraList.empty()) + return 0; + + std::map sameEffectSpellGroup; + int32 modifier = 0; + + for (AuraEffect const* aurEff : mTotalAuraList) + { + if (predicate(aurEff)) + { + // Check if the Aura Effect has a the Same Effect Stack Rule and if so, use the highest amount of that SpellGroup + // If the Aura Effect does not have this Stack Rule, it returns false so we can add to the multiplier as usual + if (!sSpellMgr->AddSameEffectStackRuleSpellGroups(aurEff->GetSpellInfo(), static_cast(auraType), aurEff->GetAmount(), sameEffectSpellGroup)) + modifier += aurEff->GetAmount(); + } + } + + // Add the highest of the Same Effect Stack Rule SpellGroups to the accumulator + for (auto itr = sameEffectSpellGroup.begin(); itr != sameEffectSpellGroup.end(); ++itr) + modifier += itr->second; + + return modifier; +} + +float Unit::GetTotalAuraMultiplier(AuraType auraType, std::function const& predicate) const +{ + AuraEffectList const& mTotalAuraList = GetAuraEffectsByType(auraType); + if (mTotalAuraList.empty()) + return 1.0f; + + std::map sameEffectSpellGroup; + float multiplier = 1.0f; + + for (AuraEffect const* aurEff : mTotalAuraList) + { + if (predicate(aurEff)) + { + // Check if the Aura Effect has a the Same Effect Stack Rule and if so, use the highest amount of that SpellGroup + // If the Aura Effect does not have this Stack Rule, it returns false so we can add to the multiplier as usual + if (!sSpellMgr->AddSameEffectStackRuleSpellGroups(aurEff->GetSpellInfo(), static_cast(auraType), aurEff->GetAmount(), sameEffectSpellGroup)) + AddPct(multiplier, aurEff->GetAmount()); + } + } + + // Add the highest of the Same Effect Stack Rule SpellGroups to the multiplier + for (auto itr = sameEffectSpellGroup.begin(); itr != sameEffectSpellGroup.end(); ++itr) + AddPct(multiplier, itr->second); + + return multiplier; +} + +int32 Unit::GetMaxPositiveAuraModifier(AuraType auraType, std::function const& predicate) const +{ + AuraEffectList const& mTotalAuraList = GetAuraEffectsByType(auraType); + if (mTotalAuraList.empty()) + return 0; + + int32 modifier = 0; + for (AuraEffect const* aurEff : mTotalAuraList) + { + if (predicate(aurEff)) + modifier = std::max(modifier, aurEff->GetAmount()); + } + + return modifier; +} + +int32 Unit::GetMaxNegativeAuraModifier(AuraType auraType, std::function const& predicate) const +{ + AuraEffectList const& mTotalAuraList = GetAuraEffectsByType(auraType); + if (mTotalAuraList.empty()) + return 0; + + int32 modifier = 0; + for (AuraEffect const* aurEff : mTotalAuraList) + { + if (predicate(aurEff)) + modifier = std::min(modifier, aurEff->GetAmount()); + } + + return modifier; +} + +int32 Unit::GetTotalAuraModifier(AuraType auraType) const +{ + return GetTotalAuraModifier(auraType, [](AuraEffect const* /*aurEff*/) { return true; }); +} + +float Unit::GetTotalAuraMultiplier(AuraType auraType) const +{ + return GetTotalAuraMultiplier(auraType, [](AuraEffect const* /*aurEff*/) { return true; }); +} + +int32 Unit::GetMaxPositiveAuraModifier(AuraType auraType) const +{ + return GetMaxPositiveAuraModifier(auraType, [](AuraEffect const* /*aurEff*/) { return true; }); +} + +int32 Unit::GetMaxNegativeAuraModifier(AuraType auraType) const +{ + return GetMaxNegativeAuraModifier(auraType, [](AuraEffect const* /*aurEff*/) { return true; }); +} + +int32 Unit::GetTotalAuraModifierByMiscMask(AuraType auraType, uint32 miscMask) const +{ + return GetTotalAuraModifier(auraType, [miscMask](AuraEffect const* aurEff) -> bool + { + if ((aurEff->GetMiscValue() & miscMask) != 0) + return true; + return false; + }); +} + +float Unit::GetTotalAuraMultiplierByMiscMask(AuraType auraType, uint32 miscMask) const +{ + return GetTotalAuraMultiplier(auraType, [miscMask](AuraEffect const* aurEff) -> bool + { + if ((aurEff->GetMiscValue() & miscMask) != 0) + return true; + return false; + }); +} + +int32 Unit::GetMaxPositiveAuraModifierByMiscMask(AuraType auraType, uint32 miscMask, AuraEffect const* except /*= nullptr*/) const +{ + return GetMaxPositiveAuraModifier(auraType, [miscMask, except](AuraEffect const* aurEff) -> bool + { + if (except != aurEff && (aurEff->GetMiscValue() & miscMask) != 0) + return true; + return false; + }); +} + +int32 Unit::GetMaxNegativeAuraModifierByMiscMask(AuraType auraType, uint32 miscMask) const +{ + return GetMaxNegativeAuraModifier(auraType, [miscMask](AuraEffect const* aurEff) -> bool + { + if ((aurEff->GetMiscValue() & miscMask) != 0) + return true; + return false; + }); +} + +int32 Unit::GetTotalAuraModifierByMiscValue(AuraType auraType, int32 miscValue) const +{ + return GetTotalAuraModifier(auraType, [miscValue](AuraEffect const* aurEff) -> bool + { + if (aurEff->GetMiscValue() == miscValue) + return true; + return false; + }); +} + +float Unit::GetTotalAuraMultiplierByMiscValue(AuraType auraType, int32 miscValue) const +{ + return GetTotalAuraMultiplier(auraType, [miscValue](AuraEffect const* aurEff) -> bool + { + if (aurEff->GetMiscValue() == miscValue) + return true; + return false; + }); +} + +int32 Unit::GetMaxPositiveAuraModifierByMiscValue(AuraType auraType, int32 miscValue) const +{ + return GetMaxPositiveAuraModifier(auraType, [miscValue](AuraEffect const* aurEff) -> bool + { + if (aurEff->GetMiscValue() == miscValue) + return true; + return false; + }); +} + +int32 Unit::GetMaxNegativeAuraModifierByMiscValue(AuraType auraType, int32 miscValue) const +{ + return GetMaxNegativeAuraModifier(auraType, [miscValue](AuraEffect const* aurEff) -> bool + { + if (aurEff->GetMiscValue() == miscValue) + return true; + return false; + }); +} + +int32 Unit::GetTotalAuraModifierByAffectMask(AuraType auraType, SpellInfo const* affectedSpell) const +{ + return GetTotalAuraModifier(auraType, [affectedSpell](AuraEffect const* aurEff) -> bool + { + if (aurEff->IsAffectingSpell(affectedSpell)) + return true; + return false; + }); +} + +float Unit::GetTotalAuraMultiplierByAffectMask(AuraType auraType, SpellInfo const* affectedSpell) const +{ + return GetTotalAuraMultiplier(auraType, [affectedSpell](AuraEffect const* aurEff) -> bool + { + if (aurEff->IsAffectingSpell(affectedSpell)) + return true; + return false; + }); +} + +int32 Unit::GetMaxPositiveAuraModifierByAffectMask(AuraType auraType, SpellInfo const* affectedSpell) const +{ + return GetMaxPositiveAuraModifier(auraType, [affectedSpell](AuraEffect const* aurEff) -> bool + { + if (aurEff->IsAffectingSpell(affectedSpell)) + return true; + return false; + }); +} + +int32 Unit::GetMaxNegativeAuraModifierByAffectMask(AuraType auraType, SpellInfo const* affectedSpell) const +{ + return GetMaxNegativeAuraModifier(auraType, [affectedSpell](AuraEffect const* aurEff) -> bool + { + if (aurEff->IsAffectingSpell(affectedSpell)) + return true; + return false; + }); +} + +void Unit::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::_RegisterDynObject(DynamicObject* dynObj) +{ + m_dynObj.push_back(dynObj); + if (GetTypeId() == TYPEID_UNIT && IsAIEnabled()) + ToCreature()->AI()->JustRegisteredDynObject(dynObj); +} + +void Unit::_UnregisterDynObject(DynamicObject* dynObj) +{ + m_dynObj.remove(dynObj); + if (GetTypeId() == TYPEID_UNIT && IsAIEnabled()) + ToCreature()->AI()->JustUnregisteredDynObject(dynObj); +} + +DynamicObject* Unit::GetDynObject(uint32 spellId) const +{ + std::vector dynamicobjects = GetDynObjects(spellId); + return dynamicobjects.empty() ? nullptr : dynamicobjects.front(); +} + +std::vector Unit::GetDynObjects(uint32 spellId) const +{ + std::vector dynamicobjects; + for (DynObjectList::const_iterator i = m_dynObj.begin(); i != m_dynObj.end(); ++i) + if ((*i)->GetSpellId() == spellId) + dynamicobjects.push_back(*i); + + return dynamicobjects; +} + +void Unit::RemoveDynObject(uint32 spellId) +{ + if (m_dynObj.empty()) + return; + for (DynObjectList::iterator i = m_dynObj.begin(); i != m_dynObj.end();) + { + DynamicObject* dynObj = *i; + if (dynObj->GetSpellId() == spellId) + { + dynObj->Remove(); + i = m_dynObj.begin(); + } + else + ++i; + } +} + +void Unit::RemoveAllDynObjects() +{ + while (!m_dynObj.empty()) + m_dynObj.front()->Remove(); +} + +GameObject* Unit::GetGameObject(uint32 spellId) const +{ + std::vector gameobjects = GetGameObjects(spellId); + return gameobjects.empty() ? nullptr : gameobjects.front(); +} + +std::vector Unit::GetGameObjects(uint32 spellId) const +{ + std::vector gameobjects; + for (GameObjectList::const_iterator i = m_gameObj.begin(); i != m_gameObj.end(); ++i) + if ((*i)->GetSpellId() == spellId) + gameobjects.push_back(*i); + + return gameobjects; +} + +void Unit::AddGameObject(GameObject* gameObj) +{ + if (!gameObj || gameObj->GetOwnerGUID()) + return; + + m_gameObj.push_back(gameObj); + gameObj->SetOwnerGUID(GetGUID()); + + if (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) + GetSpellHistory()->StartCooldown(createBySpell, 0, nullptr, true); + } + + if (GetTypeId() == TYPEID_UNIT && ToCreature()->IsAIEnabled()) + ToCreature()->AI()->JustSummonedGameobject(gameObj); +} + +void Unit::RemoveGameObject(GameObject* gameObj, bool del) +{ + if (!gameObj || gameObj->GetOwnerGUID() != GetGUID()) + return; + + gameObj->SetOwnerGUID(ObjectGuid::Empty); + + for (uint8 i = 0; i < MAX_GAMEOBJECT_SLOT; ++i) + { + if (m_ObjectSlot[i] == gameObj->GetGUID()) + { + m_ObjectSlot[i].Clear(); + break; + } + } + + // GO created by some spell + if (uint32 spellid = gameObj->GetSpellId()) + { + RemoveAurasDueToSpell(spellid); + + SpellInfo const* createBySpell = sSpellMgr->GetSpellInfo(spellid); + // Need activate spell use for owner + if (createBySpell && createBySpell->IsCooldownStartedOnEvent()) + // note: item based cooldowns and cooldown spell mods with charges ignored (unknown existing cases) + GetSpellHistory()->SendCooldownEvent(createBySpell); + } + + m_gameObj.remove(gameObj); + + if (GetTypeId() == TYPEID_UNIT && ToCreature()->IsAIEnabled()) + ToCreature()->AI()->SummonedGameobjectDespawn(gameObj); + + if (del) + { + gameObj->SetRespawnTime(0); + gameObj->Delete(); + } +} + +void Unit::RemoveGameObject(uint32 spellid, bool del) +{ + if (m_gameObj.empty()) + return; + GameObjectList::iterator i, next; + for (i = m_gameObj.begin(); i != m_gameObj.end(); i = next) + { + next = i; + if (spellid == 0 || (*i)->GetSpellId() == spellid) + { + (*i)->SetOwnerGUID(ObjectGuid::Empty); + if (del) + { + (*i)->SetRespawnTime(0); + (*i)->Delete(); + } + + next = m_gameObj.erase(i); + } + else + ++next; + } +} + +void Unit::RemoveAllGameObjects() +{ + // remove references to unit + while (!m_gameObj.empty()) + { + GameObjectList::iterator i = m_gameObj.begin(); + (*i)->SetOwnerGUID(ObjectGuid::Empty); + (*i)->SetRespawnTime(0); + (*i)->Delete(); + m_gameObj.erase(i); + } +} + +void Unit::SendSpellNonMeleeDamageLog(SpellNonMeleeDamage* log) +{ + WorldPacket data(SMSG_SPELLNONMELEEDAMAGELOG, (16+4+4+4+1+4+4+1+1+4+4+1)); // we guess size + data << log->target->GetPackGUID(); + data << log->attacker->GetPackGUID(); + data << uint32(log->SpellID); + data << uint32(log->damage); // damage amount + int32 overkill = log->damage - log->target->GetHealth(); + data << uint32(overkill > 0 ? overkill : 0); // overkill + data << uint8 (log->schoolMask); // damage school + data << uint32(log->absorb); // AbsorbedDamage + data << uint32(log->resist); // resist + data << uint8 (log->periodicLog); // 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(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, uint32 spellID, uint32 damage, SpellSchoolMask damageSchoolMask, uint32 absorbedDamage, uint32 resist, bool isPeriodic, uint32 blocked, bool criticalHit, bool split) +{ + SpellNonMeleeDamage log(this, target, spellID, damageSchoolMask); + log.damage = damage - absorbedDamage - resist - blocked; + log.absorb = absorbedDamage; + log.resist = resist; + log.periodicLog = isPeriodic; + log.blocked = blocked; + log.HitInfo = 0; + if (criticalHit) + log.HitInfo |= SPELL_HIT_TYPE_CRIT; + if (split) + log.HitInfo |= SPELL_HIT_TYPE_SPLIT; + SendSpellNonMeleeDamageLog(&log); +} + +/*static*/ void Unit::ProcSkillsAndAuras(Unit* actor, Unit* actionTarget, ProcFlags typeMaskActor, ProcFlags typeMaskActionTarget, + ProcFlagsSpellType spellTypeMask, ProcFlagsSpellPhase spellPhaseMask, ProcFlagsHit hitMask, Spell* spell, DamageInfo* damageInfo, HealInfo* healInfo) +{ + static constexpr int32 ProcChainHardLimit = 10; + if (spell && spell->GetProcChainLength() >= ProcChainHardLimit) + { + TC_LOG_ERROR("spells.aura.effect", "Unit::ProcSkillsAndAuras: Possible infinite proc loop detected, current triggering spell %u", spell->GetSpellInfo()->Id); + return; + } + + WeaponAttackType attType = damageInfo ? damageInfo->GetAttackType() : BASE_ATTACK; + SpellInfo const* spellInfo = [&]() -> SpellInfo const* + { + if (spell) + return spell->GetSpellInfo(); + if (damageInfo) + return damageInfo->GetSpellInfo(); + if (healInfo) + return healInfo->GetSpellInfo(); + return nullptr; + }(); + + if (typeMaskActor && actor && !(spellInfo && spellInfo->HasAttribute(SPELL_ATTR3_SUPPRESS_CASTER_PROCS))) + actor->ProcSkillsAndReactives(false, actionTarget, typeMaskActor, hitMask, attType); + + if (typeMaskActionTarget && actionTarget && !(spellInfo && spellInfo->HasAttribute(SPELL_ATTR3_SUPPRESS_TARGET_PROCS))) + actionTarget->ProcSkillsAndReactives(true, actor, typeMaskActionTarget, hitMask, attType); + + if (actor) + actor->TriggerAurasProcOnEvent(actionTarget, typeMaskActor, typeMaskActionTarget, spellTypeMask, spellPhaseMask, hitMask, spell, damageInfo, healInfo); +} + +void Unit::SendPeriodicAuraLog(SpellPeriodicAuraLogInfo* pInfo) +{ + AuraEffect const* aura = pInfo->auraEff; + + WorldPackets::CombatLog::SpellPeriodicAuraLog periodicAuraLog; + periodicAuraLog.TargetGUID = GetGUID(); + periodicAuraLog.CasterGUID = aura->GetCasterGUID(); + periodicAuraLog.SpellID = aura->GetId(); + + WorldPackets::CombatLog::PeriodicAuraLogEffect& logEffect = periodicAuraLog.Entries.emplace_back(); + logEffect.Effect = aura->GetAuraType(); + logEffect.Amount = pInfo->damage; + logEffect.OverHealOrKill = pInfo->overDamage; + logEffect.Resisted = pInfo->resist; + logEffect.Crit = pInfo->critical; + + switch (logEffect.Effect) + { + case SPELL_AURA_PERIODIC_DAMAGE: + case SPELL_AURA_PERIODIC_DAMAGE_PERCENT: + logEffect.AbsorbedOrAmplitude = pInfo->absorb; + logEffect.SchoolMaskOrPower = aura->GetSpellInfo()->GetSchoolMask(); + break; + case SPELL_AURA_PERIODIC_HEAL: + case SPELL_AURA_OBS_MOD_HEALTH: + logEffect.AbsorbedOrAmplitude = pInfo->absorb; + break; + case SPELL_AURA_OBS_MOD_POWER: + case SPELL_AURA_PERIODIC_ENERGIZE: + logEffect.SchoolMaskOrPower = aura->GetMiscValue(); + break; + case SPELL_AURA_PERIODIC_MANA_LEECH: + logEffect.SchoolMaskOrPower = aura->GetMiscValue(); + logEffect.AbsorbedOrAmplitude = pInfo->multiplier; + break; + default: + TC_LOG_ERROR("entities.unit", "Unit::SendPeriodicAuraLog: unknown aura %u", uint32(aura->GetAuraType())); + return; + } + + SendMessageToSet(periodicAuraLog.Write(), true); +} + +void Unit::SendSpellDamageResist(Unit* target, uint32 spellId) +{ + WorldPacket data(SMSG_PROCRESIST, 8+8+4+1); + data << uint64(GetGUID()); + data << uint64(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 << uint64(GetGUID()); + data << uint64(target->GetGUID()); + data << uint32(spellId); + data << uint8(0); // bool - log format: 0-default, 1-debug + SendMessageToSet(&data, true); +} + +void Unit::SendAttackStateUpdate(CalcDamageInfo* damageInfo) +{ + WorldPackets::CombatLog::AttackerStateUpdate packet; + packet.HitInfo = damageInfo->HitInfo; + packet.AttackerGUID = damageInfo->Attacker->GetGUID(); + packet.VictimGUID = damageInfo->Target->GetGUID(); + packet.Damage = damageInfo->Damage; + int32 overkill = damageInfo->Damage - damageInfo->Target->GetHealth(); + packet.OverDamage = (overkill < 0 ? -1 : overkill); + + if (damageInfo->Damage) + { + packet.SubDmg.emplace(); + packet.SubDmg->SchoolMask = damageInfo->DamageSchoolMask; // School of sub damage + packet.SubDmg->FDamage = damageInfo->Damage; // sub damage + packet.SubDmg->Damage = damageInfo->Damage; // Sub Damage + packet.SubDmg->Absorbed = damageInfo->Absorb; + packet.SubDmg->Resisted = damageInfo->Resist; + } + + packet.VictimState = damageInfo->TargetState; + packet.BlockAmount = damageInfo->Blocked; + packet.RageGained = damageInfo->RageGained; + + SendMessageToSet(packet.Write(), 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.Damage = Damage - AbsorbDamage - Resist - BlockedAmount; + dmgInfo.DamageSchoolMask = damageSchoolMask; + dmgInfo.Absorb = AbsorbDamage; + dmgInfo.Resist = Resist; + dmgInfo.TargetState = TargetState; + dmgInfo.Blocked = BlockedAmount; + dmgInfo.RageGained = 0; + SendAttackStateUpdate(&dmgInfo); +} + +void Unit::SetPowerType(PowerType power) +{ + if (GetPowerType() == power) + return; + + SetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_POWER_TYPE, power); + + // Creatures can swap out their power type at index 0 so we do keep track of the new power type here + if (IsCreature()) + { + _usedPowerTypes[GetPowerIndex(power)] = power; + UpdatePowerRegeneration(power); + } + + if (GetTypeId() == TYPEID_PLAYER) + { + 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->GetTypeId() == TYPEID_PLAYER) && owner->ToPlayer()->GetGroup()) + owner->ToPlayer()->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_PET_POWER_TYPE); + } + } + + // Update max power + UpdateMaxPower(power); + + // Update current power + switch (power) + { + case POWER_MANA: // Keep the same (druid form switching...) + case POWER_ENERGY: + break; + case POWER_RAGE: // Reset to zero + SetPower(POWER_RAGE, 0); + break; + case POWER_FOCUS: // Make it full + SetFullPower(power); + break; + default: + break; + } +} + +PowerType Unit::CalculateDisplayPowerType() const +{ + PowerType displayPower = POWER_MANA; + switch (GetShapeshiftForm()) + { + case FORM_GHOUL: + case FORM_CAT: + displayPower = POWER_ENERGY; + break; + case FORM_BEAR: + displayPower = POWER_RAGE; + break; + case FORM_TRAVEL: + case FORM_GHOSTWOLF: + displayPower = POWER_MANA; + break; + default: + { + ChrClassesEntry const* cEntry = sChrClassesStore.LookupEntry(getClass()); + if (cEntry && cEntry->DisplayPower < MAX_POWERS) + displayPower = PowerType(cEntry->DisplayPower); + + if (Vehicle* vehicle = GetVehicleKit()) + { + if (PowerDisplayEntry const* powerDisplay = sPowerDisplayStore.LookupEntry(vehicle->GetVehicleInfo()->PowerDisplayID[0])) // To-do: 4.x has 3 power display id fields. + displayPower = PowerType(powerDisplay->ActualType); + else if (getClass() == CLASS_ROGUE) + displayPower = POWER_ENERGY; + } + else if (Pet const* pet = ToPet()) + { + if (pet->getPetType() == HUNTER_PET) // Hunter pets have focus + displayPower = POWER_FOCUS; + else if (pet->IsPetGhoul() || pet->IsRisenAlly()) // DK pets have energy + displayPower = POWER_ENERGY; + } + break; + } + } + + return displayPower; +} + +void Unit::UpdateDisplayPower() +{ + SetPowerType(CalculateDisplayPowerType()); +} + +void Unit::SetSheath(SheathState sheathed) +{ + SetByteValue(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_SHEATH_STATE, sheathed); + if (sheathed == SHEATH_STATE_UNARMED) + RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Sheathing); +} + +void Unit::_addAttacker(Unit* pAttacker) +{ + m_attackers.insert(pAttacker); +} + +void Unit::_removeAttacker(Unit* pAttacker) +{ + m_attackers.erase(pAttacker); +} + +Unit* Unit::getAttackerForHelper() const // If someone wants to help, who to give them +{ + if (!IsEngaged()) + return nullptr; + + if (Unit* victim = GetVictim()) + if ((!IsPet() && !IsCharmerOrSelfPlayer()) || IsInCombatWith(victim)) + return victim; + + CombatManager const& mgr = GetCombatManager(); + // pick arbitrary targets; our pvp combat > owner's pvp combat > our pve combat > owner's pve combat + Unit* owner = GetCharmerOrOwner(); + if (mgr.HasPvPCombat()) + return mgr.GetPvPCombatRefs().begin()->second->GetOther(this); + if (owner && (owner->GetCombatManager().HasPvPCombat())) + return owner->GetCombatManager().GetPvPCombatRefs().begin()->second->GetOther(owner); + if (mgr.HasPvECombat()) + return mgr.GetPvECombatRefs().begin()->second->GetOther(this); + if (owner && (owner->GetCombatManager().HasPvECombat())) + return owner->GetCombatManager().GetPvECombatRefs().begin()->second->GetOther(owner); + return nullptr; +} + +bool Unit::Attack(Unit* victim, bool meleeAttack) +{ + if (!victim || victim == this) + return false; + + // dead units can neither attack nor be attacked + if (!IsAlive() || !victim->IsInWorld() || !victim->IsAlive()) + return false; + + // player cannot attack in mount state + if (GetTypeId() == TYPEID_PLAYER && IsMounted()) + return false; + + Creature* creature = ToCreature(); + if (creature) + { + // creatures cannot attack while evading + if (creature->IsInEvadeMode()) + return false; + + if (!creature->CanMelee()) + meleeAttack = false; + } + + if (HasAuraType(SPELL_AURA_DISABLE_ATTACKING_EXCEPT_ABILITIES)) + return false; + + // nobody can attack GM in GM-mode + if (victim->GetTypeId() == TYPEID_PLAYER) + { + if (victim->ToPlayer()->IsGameMaster()) + return false; + } + else + { + if (victim->ToCreature()->IsEvadingAttacks()) + return false; + } + + if (m_attacking) + { + if (m_attacking == victim) + { + // switch to melee attack from ranged/magic + if (meleeAttack) + { + if (!HasUnitState(UNIT_STATE_MELEE_ATTACKING)) + { + AddUnitState(UNIT_STATE_MELEE_ATTACKING); + SendMeleeAttackStart(victim); + return true; + } + } + else if (HasUnitState(UNIT_STATE_MELEE_ATTACKING)) + { + ClearUnitState(UNIT_STATE_MELEE_ATTACKING); + SendMeleeAttackStop(victim); + return true; + } + return false; + } + + // switch target + InterruptSpell(CURRENT_MELEE_SPELL); + if (!meleeAttack) + ClearUnitState(UNIT_STATE_MELEE_ATTACKING); + } + + if (m_attacking) + m_attacking->_removeAttacker(this); + + m_attacking = victim; + m_attacking->_addAttacker(this); + + // Set our target + SetTarget(victim->GetGUID()); + + if (meleeAttack) + AddUnitState(UNIT_STATE_MELEE_ATTACKING); + + // set position before any AI calls/assistance + //if (GetTypeId() == TYPEID_UNIT) + // ToCreature()->SetCombatStartPosition(GetPositionX(), GetPositionY(), GetPositionZ()); + + if (creature && !IsControlledByPlayer()) + { + EngageWithTarget(victim); // ensure that anything we're attacking has threat + creature->CallAssistance(); + + // Remove emote state - will be restored on creature reset + SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_ONESHOT_NONE); + } + + // delay offhand weapon attack by 50% of the base attack time + if (haveOffhandWeapon() && GetTypeId() != TYPEID_PLAYER) + setAttackTimer(OFF_ATTACK, std::max(getAttackTimer(OFF_ATTACK), getAttackTimer(BASE_ATTACK) + uint32(CalculatePct(GetFloatValue(UNIT_FIELD_BASEATTACKTIME), 50)))); + + + if (meleeAttack) + SendMeleeAttackStart(victim); + + // Let the pet know we've started attacking someting. Handles melee attacks only + // Spells such as auto-shot and others handled in WorldSession::HandleCastSpellOpcode + if (GetTypeId() == TYPEID_PLAYER) + { + for (Unit* controlled : m_Controlled) + if (Creature* cControlled = controlled->ToCreature()) + if (CreatureAI* controlledAI = cControlled->AI()) + controlledAI->OwnerAttacked(victim); + } + + return true; +} + +bool Unit::AttackStop() +{ + if (!m_attacking) + return false; + + Unit* victim = m_attacking; + + m_attacking->_removeAttacker(this); + m_attacking = nullptr; + + // Clear our target + SetTarget(ObjectGuid::Empty); + + ClearUnitState(UNIT_STATE_MELEE_ATTACKING); + + InterruptSpell(CURRENT_MELEE_SPELL); + + // reset only at real combat stop + if (Creature* creature = ToCreature()) + creature->SetNoCallAssistance(false); + + SendMeleeAttackStop(victim); + + return true; +} + +void Unit::ValidateAttackersAndOwnTarget() +{ + // iterate attackers + UnitVector toRemove; + AttackerSet const& attackers = getAttackers(); + for (Unit* attacker : attackers) + if (!attacker->IsValidAttackTarget(this)) + toRemove.push_back(attacker); + + for (Unit* attacker : toRemove) + attacker->AttackStop(); + + // remove our own victim + if (Unit* victim = GetVictim()) + if (!IsValidAttackTarget(victim)) + AttackStop(); +} + +void Unit::CombatStop(bool includingCast, bool mutualPvP, bool (*unitFilter)(Unit const* otherUnit)) +{ + if (includingCast && IsNonMeleeSpellCast(false)) + InterruptNonMeleeSpells(false); + + AttackStop(); + + if (!unitFilter) + RemoveAllAttackers(); + else + { + std::vector attackersToRemove; + attackersToRemove.reserve(m_attackers.size()); + std::copy_if(m_attackers.begin(), m_attackers.end(), std::back_inserter(attackersToRemove), unitFilter); + + for (Unit* attacker : attackersToRemove) + attacker->AttackStop(); + } + if (GetTypeId() == TYPEID_PLAYER) + ToPlayer()->SendAttackSwingCancelAttack(); // melee and ranged forced attack cancel + + m_combatManager.EndAllPvECombat(unitFilter); + if (mutualPvP) + m_combatManager.EndAllPvPCombat(unitFilter); + else // vanish and brethren are weird + m_combatManager.SuppressPvPCombat(unitFilter); +} + +void Unit::CombatStopWithPets(bool includingCast) +{ + CombatStop(includingCast); + + for (Unit* minion : m_Controlled) + minion->CombatStop(includingCast); +} + +bool Unit::isAttackingPlayer() const +{ + if (HasUnitState(UNIT_STATE_ATTACK_PLAYER)) + return true; + + for (ControlList::const_iterator itr = m_Controlled.begin(); itr != m_Controlled.end(); ++itr) + if ((*itr)->isAttackingPlayer()) + return true; + + for (uint8 i = 0; i < MAX_SUMMON_SLOT; ++i) + if (m_SummonSlot[i]) + if (Creature* summon = GetMap()->GetCreature(m_SummonSlot[i])) + if (summon->isAttackingPlayer()) + return true; + + return false; +} + +void Unit::RemoveAllAttackers() +{ + while (!m_attackers.empty()) + { + AttackerSet::iterator iter = m_attackers.begin(); + if (!(*iter)->AttackStop()) + { + TC_LOG_ERROR("entities.unit", "WORLD: Unit has an attacker that isn't attacking it!"); + m_attackers.erase(iter); + } + } +} + +void Unit::ModifyAuraState(AuraStateType flag, bool apply) +{ + if (apply) + { + if (!HasFlag(UNIT_FIELD_AURASTATE, 1<<(flag-1))) + { + SetFlag(UNIT_FIELD_AURASTATE, 1<<(flag-1)); + if (GetTypeId() == TYPEID_PLAYER) + { + PlayerSpellMap const& sp_list = ToPlayer()->GetSpellMap(); + for (PlayerSpellMap::const_iterator itr = sp_list.begin(); itr != sp_list.end(); ++itr) + { + if (itr->second.state == PLAYERSPELL_REMOVED || itr->second.disabled) + continue; + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(itr->first); + if (!spellInfo || !spellInfo->IsPassive()) + continue; + if (spellInfo->CasterAuraState == uint32(flag)) + CastSpell(this, itr->first, true); + } + } + else if (Pet* pet = ToCreature()->ToPet()) + { + for (PetSpellMap::const_iterator itr = pet->m_spells.begin(); itr != pet->m_spells.end(); ++itr) + { + if (itr->second.state == PETSPELL_REMOVED) + continue; + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(itr->first); + if (!spellInfo || !spellInfo->IsPassive()) + continue; + if (spellInfo->CasterAuraState == uint32(flag)) + CastSpell(this, itr->first, true); + } + } + } + } + else + { + if (HasFlag(UNIT_FIELD_AURASTATE, 1<<(flag-1))) + { + RemoveFlag(UNIT_FIELD_AURASTATE, 1<<(flag-1)); + + Unit::AuraApplicationMap& tAuras = GetAppliedAuras(); + for (Unit::AuraApplicationMap::iterator itr = tAuras.begin(); itr != tAuras.end();) + { + SpellInfo const* spellProto = itr->second->GetBase()->GetSpellInfo(); + if (itr->second->GetBase()->GetCasterGUID() == GetGUID() && spellProto->CasterAuraState == uint32(flag) && (spellProto->IsPassive() || flag != AURA_STATE_ENRAGE)) + RemoveAura(itr); + else + ++itr; + } + } + } +} + +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 (AuraEffect const* aurEff : stateAuras) + if (aurEff->IsAffectingSpell(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; + + // 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(GetMapId()); + WorldPacket packet; + BuildValuesUpdateBlockForPlayer(&udata, player); + udata.BuildPacket(&packet); + player->SendDirectMessage(&packet); + + RemoveFieldNotifyFlag(UF_FLAG_OWNER); +} + +Player* Unit::GetControllingPlayer() const +{ + if (ObjectGuid guid = GetCharmerOrOwnerGUID()) + { + if (Unit* master = ObjectAccessor::GetUnit(*this, guid)) + return master->GetControllingPlayer(); + return nullptr; + } + else + return const_cast(ToPlayer()); +} + +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; + + TC_LOG_ERROR("entities.unit", "Unit::GetFirstMinion: Minion %s not exist.", pet_guid.ToString().c_str()); + 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; + + TC_LOG_FATAL("entities.unit", "Unit::GetGuardianPet: Guardian %s not exist.", pet_guid.ToString().c_str()); + const_cast(this)->SetPetGUID(ObjectGuid::Empty); + } + + return nullptr; +} + +void Unit::SetMinion(Minion* minion, bool apply) +{ + if (!minion) + { + TC_LOG_ERROR("entities.unit", "Unit::SetMinion: Unit %s tried to reference a non existing minion", GetGUID().ToString().c_str()); + return; + } + + TC_LOG_DEBUG("entities.unit", "SetMinion %u for %u, apply %u", minion->GetEntry(), GetEntry(), apply); + + if (apply) + { + if (minion->GetOwnerGUID()) + { + TC_LOG_FATAL("entities.unit", "SetMinion: Minion %u is not the minion of owner %u", minion->GetEntry(), GetEntry()); + return; + } + + minion->SetOwnerGUID(GetGUID()); + + m_Controlled.insert(minion); + + if (GetTypeId() == TYPEID_PLAYER) + { + minion->m_ControlledByPlayer = true; + minion->SetFlag(UNIT_FIELD_FLAGS, 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 (oldPet->IsPet()) + ((Pet*)oldPet)->Remove(PET_SAVE_DISMISS); + else + oldPet->UnSummon(); + SetPetGUID(minion->GetGUID()); + SetMinionGUID(ObjectGuid::Empty); + } + } + else + { + SetPetGUID(minion->GetGUID()); + SetMinionGUID(ObjectGuid::Empty); + } + } + + if (minion->HasUnitTypeMask(UNIT_MASK_CONTROLABLE_GUARDIAN)) + AddGuidValue(UNIT_FIELD_SUMMON, minion->GetGUID()); + + if (minion->m_Properties && SummonTitle(minion->m_Properties->Title) == SummonTitle::Companion) + SetCritterGUID(minion->GetGUID()); + + // PvP, FFAPvP + minion->SetByteValue(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PVP_FLAG, GetByteValue(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PVP_FLAG)); + + // 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()) + GetSpellHistory()->StartCooldown(spellInfo, 0, nullptr, true); + } + else + { + if (minion->GetOwnerGUID() != GetGUID()) + { + TC_LOG_FATAL("entities.unit", "SetMinion: Minion %u is not the minion of owner %u", minion->GetEntry(), GetEntry()); + return; + } + + if (m_Controlled.find(minion) != m_Controlled.end()) + m_Controlled.erase(minion); + + if (minion->m_Properties && SummonTitle(minion->m_Properties->Title) == SummonTitle::Companion) + if (GetCritterGUID() == minion->GetGUID()) + SetCritterGUID(ObjectGuid::Empty); + + if (minion->IsGuardianPet()) + { + if (GetPetGUID() == minion->GetGUID()) + SetPetGUID(ObjectGuid::Empty); + } + + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(minion->GetUInt32Value(UNIT_CREATED_BY_SPELL)); + // Remove infinity cooldown + if (spellInfo && (spellInfo->IsCooldownStartedOnEvent())) + GetSpellHistory()->SendCooldownEvent(spellInfo); + + //if (minion->HasUnitTypeMask(UNIT_MASK_GUARDIAN)) + { + if (RemoveGuidValue(UNIT_FIELD_SUMMON, minion->GetGUID())) + { + // Check if there is another minion + for (Unit const* controlled : m_Controlled) + { + // do not use this check, creature do not have charm guid + //if (GetCharmedGUID() == (*itr)->GetGUID()) + if (GetGUID() == controlled->GetCharmerGUID()) + continue; + + //ASSERT((*itr)->GetOwnerGUID() == GetGUID()); + if (controlled->GetOwnerOrCreatorGUID() != GetGUID()) + { + OutDebugInfo(); + controlled->OutDebugInfo(); + ABORT(); + } + ASSERT(controlled->IsCreature()); + + if (!controlled->HasUnitTypeMask(UNIT_MASK_CONTROLABLE_GUARDIAN)) + continue; + + if (AddGuidValue(UNIT_FIELD_SUMMON, controlled->GetGUID())) + { + // show another pet bar if there is no charm bar + if (IsPlayer() && !GetCharmedGUID()) + { + if (controlled->IsPet()) + ToPlayer()->PetSpellInitialize(); + else + ToPlayer()->CharmSpellInitialize(); + } + } + break; + } + } + } + } + UpdatePetCombatState(); +} + +void Unit::GetAllMinionsByEntry(std::list& Minions, uint32 entry) +{ + for (Unit::ControlList::iterator itr = m_Controlled.begin(); itr != m_Controlled.end();) + { + Unit* unit = *itr; + ++itr; + if (unit->GetEntry() == entry && unit->GetTypeId() == TYPEID_UNIT + && unit->IsSummon()) // minion, actually + Minions.push_back(unit->ToCreature()); + } +} + +void Unit::RemoveAllMinionsByEntry(uint32 entry) +{ + for (Unit::ControlList::iterator itr = m_Controlled.begin(); itr != m_Controlled.end();) + { + Unit* unit = *itr; + ++itr; + if (unit->GetEntry() == entry && unit->GetTypeId() == TYPEID_UNIT + && unit->IsSummon()) // minion, actually + unit->ToTempSummon()->UnSummon(); + // i think this is safe because i have never heard that a despawned minion will trigger a same minion + } +} + +void Unit::SetCharm(Unit* charm, bool apply) +{ + if (apply) + { + if (GetTypeId() == TYPEID_PLAYER) + { + ASSERT(AddGuidValue(UNIT_FIELD_CHARM, charm->GetGUID()), + "Player %s is trying to charm unit %u, but it already has a charmed unit %s", GetName().c_str(), charm->GetEntry(), GetCharmedGUID().ToString().c_str()); + m_charmed = charm; + + charm->m_ControlledByPlayer = true; + /// @todo maybe we can use this flag to check if controlled by player + charm->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED); + } + else + charm->m_ControlledByPlayer = false; + + // PvP, FFAPvP + charm->SetByteValue(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PVP_FLAG, GetByteValue(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PVP_FLAG)); + + ASSERT(charm->AddGuidValue(UNIT_FIELD_CHARMEDBY, GetGUID()), + "Unit %u is being charmed, but it already has a charmer %s", charm->GetEntry(), charm->GetCharmerGUID().ToString().c_str()); + charm->m_charmer = this; + + _isWalkingBeforeCharm = charm->IsWalking(); + if (_isWalkingBeforeCharm) + charm->SetWalk(false); + + m_Controlled.insert(charm); + } + else + { + charm->ClearUnitState(UNIT_STATE_CHARMED); + + if (GetTypeId() == TYPEID_PLAYER) + { + ASSERT(RemoveGuidValue(UNIT_FIELD_CHARM, charm->GetGUID()), + "Player %s is trying to uncharm unit %u, but it has another charmed unit %s", GetName().c_str(), charm->GetEntry(), GetCharmedGUID().ToString().c_str()); + m_charmed = nullptr; + } + + ASSERT(charm->RemoveGuidValue(UNIT_FIELD_CHARMEDBY, GetGUID()), + "Unit %u is being uncharmed, but it has another charmer %s", charm->GetEntry(), charm->GetCharmerGUID().ToString().c_str()); + charm->m_charmer = nullptr; + + if (charm->GetTypeId() == TYPEID_PLAYER) + { + charm->m_ControlledByPlayer = true; + charm->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED); + charm->ToPlayer()->UpdatePvPState(); + } + else if (Player* player = charm->GetCharmerOrOwnerPlayerOrPlayerItself()) + { + charm->m_ControlledByPlayer = true; + charm->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED); + charm->SetByteValue(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PVP_FLAG, player->GetByteValue(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PVP_FLAG)); + } + else + { + charm->m_ControlledByPlayer = false; + charm->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED); + charm->SetByteValue(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PVP_FLAG, 0); + } + + if (charm->IsWalking() != _isWalkingBeforeCharm) + charm->SetWalk(_isWalkingBeforeCharm); + + if (charm->GetTypeId() == TYPEID_PLAYER + || !charm->ToCreature()->HasUnitTypeMask(UNIT_MASK_MINION) + || charm->GetOwnerOrCreatorGUID() != GetGUID()) + { + m_Controlled.erase(charm); + } + } + UpdatePetCombatState(); +} + +/*static*/ void Unit::DealHeal(HealInfo& healInfo) +{ + int32 gain = 0; + Unit* healer = healInfo.GetHealer(); + Unit* victim = healInfo.GetTarget(); + uint32 addhealth = healInfo.GetHeal(); + + if (healer) + { + if (UnitAI* victimAI = victim->GetAI()) + victimAI->HealReceived(healer, addhealth); + + if (UnitAI* healerAI = healer->GetAI()) + healerAI->HealDone(victim, addhealth); + } + + if (addhealth) + gain = victim->ModifyHealth(int32(addhealth)); + + // Hook for OnHeal Event + sScriptMgr->OnHeal(healer, victim, (uint32&)gain); + + Unit* unit = healer; + if (healer && healer->GetTypeId() == TYPEID_UNIT && healer->IsTotem()) + unit = healer->GetOwner(); + + if (unit) + { + if (Player* player = unit->ToPlayer()) + { + if (!healInfo.GetSpellInfo() || !healInfo.GetSpellInfo()->HasAttribute(SPELL_ATTR7_DO_NOT_COUNT_FOR_PVP_SCOREBOARD)) + if (Battleground* bg = player->GetBattleground()) + bg->UpdatePlayerScore(player, SCORE_HEALING_DONE, gain); + + // use the actual gain, as the overheal shall not be counted, skip gain 0 (it ignored anyway in to criteria) + if (gain) + player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HEALING_DONE, gain, 0, 0, victim); + + player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HIGHEST_HEAL_CAST, addhealth); + } + } + + if (Player* player = victim->ToPlayer()) + { + player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_TOTAL_HEALING_RECEIVED, gain); + player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HIGHEST_HEALING_RECEIVED, addhealth); + } + + if (gain) + healInfo.SetEffectiveHeal(gain > 0 ? static_cast(gain) : 0UL); +} + +bool Unit::IsMagnet() const +{ + // Grounding Totem + if (GetUInt32Value(UNIT_CREATED_BY_SPELL) == 8177) /// @todo: find a more generic solution + return true; + + return false; +} + +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(AuraRemoveFlags::Expired); + return magnet; + } + } + return victim; +} + +Unit* Unit::GetFirstControlled() const +{ + // Sequence: charmed, pet, other guardians + Unit* unit = GetCharmed(); + if (!unit) + if (ObjectGuid guid = GetMinionGUID()) + unit = ObjectAccessor::GetUnit(*this, guid); + + return unit; +} + +void Unit::RemoveAllControlled() +{ + // possessed pet and vehicle + if (GetTypeId() == TYPEID_PLAYER) + ToPlayer()->StopCastingCharm(); + + while (!m_Controlled.empty()) + { + Unit* target = *m_Controlled.begin(); + m_Controlled.erase(m_Controlled.begin()); + if (target->GetCharmerGUID() == GetGUID()) + target->RemoveCharmAuras(); + else if (target->GetOwnerOrCreatorGUID() == GetGUID() && target->IsSummon()) + target->ToTempSummon()->UnSummon(); + else + TC_LOG_ERROR("entities.unit", "Unit %u is trying to release unit %u which is neither charmed nor owned by it", GetEntry(), target->GetEntry()); + } + if (GetPetGUID()) + TC_LOG_FATAL("entities.unit", "Unit %u is not able to release its pet %s", GetEntry(), GetPetGUID().ToString().c_str()); + if (GetMinionGUID()) + TC_LOG_FATAL("entities.unit", "Unit %u is not able to release its minion %s", GetEntry(), GetMinionGUID().ToString().c_str()); + if (GetCharmedGUID()) + TC_LOG_FATAL("entities.unit", "Unit %u is not able to release its charm %s", GetEntry(), GetCharmedGUID().ToString().c_str()); + if (!IsPet()) // pets don't use the flag for this + RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PET_IN_COMBAT); // m_controlled is now empty, so we know none of our minions are in combat +} + +bool Unit::isPossessedByPlayer() const +{ + return HasUnitState(UNIT_STATE_POSSESSED) && GetCharmerGUID().IsPlayer(); +} + +bool Unit::isPossessing(Unit* u) const +{ + return u->isPossessed() && GetCharmedGUID() == u->GetGUID(); +} + +bool Unit::isPossessing() const +{ + if (Unit* u = GetCharmed()) + return u->isPossessed(); + else + return false; +} + +Unit* Unit::GetCharmerOrSelf() const +{ + if (IsCharmed()) + return GetCharmer(); + else + return const_cast(this); +} + +Unit* Unit::GetNextRandomRaidMemberOrPet(float radius) +{ + Player* player = nullptr; + if (GetTypeId() == TYPEID_PLAYER) + player = ToPlayer(); + // Should we enable this also for charmed units? + else if (GetTypeId() == TYPEID_UNIT && IsPet()) + player = GetOwner()->ToPlayer(); + + if (!player) + return nullptr; + Group* group = player->GetGroup(); + // When there is no group check pet presence + if (!group) + { + // We are pet now, return owner + if (player != this) + return IsWithinDistInMap(player, radius) ? player : nullptr; + Unit* pet = GetGuardianPet(); + // No pet, no group, nothing to return + if (!pet) + return nullptr; + // We are owner now, return pet + return IsWithinDistInMap(pet, radius) ? pet : nullptr; + } + + std::vector nearMembers; + // reserve place for players and pets because resizing vector every unit push is unefficient (vector is reallocated then) + nearMembers.reserve(group->GetMembersCount() * 2); + + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + if (Player* Target = itr->GetSource()) + { + // IsHostileTo check duel and controlled by enemy + if (Target != this && IsWithinDistInMap(Target, radius) && Target->IsAlive() && !IsHostileTo(Target)) + nearMembers.push_back(Target); + + // Push player's pet to vector + if (Unit* pet = Target->GetGuardianPet()) + if (pet != this && IsWithinDistInMap(pet, radius) && pet->IsAlive() && !IsHostileTo(pet)) + nearMembers.push_back(pet); + } + + if (nearMembers.empty()) + return nullptr; + + uint32 randTarget = urand(0, nearMembers.size()-1); + return nearMembers[randTarget]; +} + +// only called in Player::SetSeer +// so move it to Player? +void Unit::AddPlayerToVision(Player* player) +{ + if (m_sharedVision.empty()) + { + setActive(true); + SetWorldObject(true); + } + m_sharedVision.push_back(player); +} + +// only called in Player::SetSeer +void Unit::RemovePlayerFromVision(Player* player) +{ + m_sharedVision.remove(player); + if (m_sharedVision.empty()) + { + setActive(false); + SetWorldObject(false); + } +} + +void Unit::RemoveBindSightAuras() +{ + RemoveAurasByType(SPELL_AURA_BIND_SIGHT); +} + +void Unit::RemoveCharmAuras() +{ + RemoveAurasByType(SPELL_AURA_MOD_CHARM); + RemoveAurasByType(SPELL_AURA_MOD_POSSESS); + RemoveAurasByType(SPELL_AURA_AOE_CHARM); +} + +void Unit::UnsummonAllTotems() +{ + for (uint8 i = 0; i < MAX_SUMMON_SLOT; ++i) + { + if (!m_SummonSlot[i]) + continue; + + if (Creature* OldTotem = GetMap()->GetCreature(m_SummonSlot[i])) + if (OldTotem->IsSummon()) + OldTotem->ToTempSummon()->UnSummon(); + } +} + +void Unit::SendHealSpellLog(HealInfo& healInfo, bool critical /*= false*/) +{ + // we guess size + WorldPacket data(SMSG_SPELLHEALLOG, 8 + 8 + 4 + 4 + 4 + 4 + 1 + 1); + data << healInfo.GetTarget()->GetPackGUID(); + data << healInfo.GetHealer()->GetPackGUID(); + data << uint32(healInfo.GetSpellInfo()->Id); + data << uint32(healInfo.GetHeal()); + data << uint32(healInfo.GetHeal() - healInfo.GetEffectiveHeal()); + 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 /*= false*/) +{ + // calculate heal absorb and reduce healing + Unit::CalcHealAbsorb(healInfo); + Unit::DealHeal(healInfo); + SendHealSpellLog(healInfo, critical); + return healInfo.GetEffectiveHeal(); +} + +void Unit::SendEnergizeSpellLog(Unit* victim, uint32 spellId, int32 damage, PowerType powerType) +{ + WorldPackets::CombatLog::SpellEnergizeLog packet; + packet.TargetGUID = victim->GetGUID(); + packet.CasterGUID = GetGUID(); + packet.SpellID = spellId; + packet.Amount = damage; + packet.Type = powerType; + + SendMessageToSet(packet.Write(), true); +} + +void Unit::EnergizeBySpell(Unit* victim, uint32 spellId, int32 damage, PowerType powerType) +{ + if (SpellInfo const* info = sSpellMgr->GetSpellInfo(spellId)) + EnergizeBySpell(victim, info, damage, powerType); +} + +void Unit::EnergizeBySpell(Unit* victim, SpellInfo const* spellInfo, int32 damage, PowerType powerType) +{ + victim->ModifyPower(powerType, damage, false); + victim->GetThreatManager().ForwardThreatForAssistingMe(this, float(damage) / 2, spellInfo, true); + SendEnergizeSpellLog(victim, spellInfo->Id, damage, powerType); +} + +int32 Unit::SpellDamageBonusDone(Unit* victim, SpellInfo const* spellProto, int32 pdamage, DamageEffectType damagetype, uint8 effIndex, uint32 stack /*= 1*/, Spell* spell /*= nullptr*/, AuraEffect const* aurEff /*= nullptr*/) const +{ + if (!spellProto || !victim) + return pdamage; + + int32 DoneTotal = 0; + float DoneTotalMod = 1.0f; + + auto callDamageScript = [&](int32& dmg, int32& flatMod, float& pctMod) + { + if (spell) + spell->CallScriptCalcDamageHandlers(victim, dmg, flatMod, pctMod); + else if (aurEff) + aurEff->GetBase()->CallScriptCalcDamageAndHealingHandlers(aurEff, aurEff->GetBase()->GetApplicationOfTarget(victim->GetGUID()), victim, dmg, flatMod, pctMod); + }; + + // Some spells don't benefit from done mods + if (damagetype == DIRECT_DAMAGE || spellProto->HasAttribute(SPELL_ATTR3_IGNORE_CASTER_MODIFIERS)) + { + callDamageScript(pdamage, DoneTotal, DoneTotalMod); + return int32(std::max(float(pdamage + DoneTotal) * DoneTotalMod, 0.0f)); + } + + // Totems get their bonus damage from their owner + if (IsCreature() && IsTotem()) + if (Unit* creator = ObjectAccessor::GetUnit(*this, GetCreatorGUID())) + return creator->SpellDamageBonusDone(victim, spellProto, pdamage, damagetype, effIndex, stack, spell, aurEff); + + DoneTotalMod = SpellDamagePctDone(victim, spellProto, damagetype); + + // done scripted mod (take it from owner) + Unit const* owner = GetOwner() ? GetOwner() : this; + DoneTotal += owner->GetTotalAuraModifier(SPELL_AURA_OVERRIDE_CLASS_SCRIPTS, [spellProto](AuraEffect const* aurEff) -> bool + { + if (!aurEff->IsAffectingSpell(spellProto)) + return false; + + switch (aurEff->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 + return true; + default: + break; + } + + return false; + }); + + // Some spells don't benefit from pct done mods + if (!spellProto->HasAttribute(SPELL_ATTR6_IGNORE_CASTER_DAMAGE_MODIFIERS)) + DoneTotal += GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_FLAT_SPELL_DAMAGE_VERSUS, victim->GetCreatureTypeMask()); + + // Done fixed damage bonus auras + int32 DoneAdvertisedBenefit = SpellBaseDamageBonusDone(spellProto->GetSchoolMask(), true); + // modify spell power by victim's SPELL_AURA_MOD_DAMAGE_TAKEN auras (eg Amplify/Dampen Magic) + DoneAdvertisedBenefit += GetTotalAuraModifier(SPELL_AURA_MOD_DAMAGE_TAKEN, [spellProto](AuraEffect const* aurEff) + { + if (spellProto->HasAttribute(SPELL_ATTR10_IGNORE_POSITIVE_DAMAGE_TAKEN_MODS) && aurEff->GetAmount() > 0) + return false; + + if ((aurEff->GetMiscValue() & spellProto->GetSchoolMask()) != 0) + return true; + + return false; + }); + + // Custom scripted damage + switch (spellProto->SpellFamilyName) + { + case SPELLFAMILY_HUNTER: + // Hunter Pet basic attacks (RAP * 0.4 * 0.2) + if (spellProto->SpellFamilyFlags[0] & 0x00080000) + DoneTotal += (owner->GetTotalAttackPowerValue(RANGED_ATTACK) * 0.4f) * 0.2f; + + // Kill Command + if (spellProto->SpellFamilyFlags[1] & 0x00000800) + DoneTotal += owner->GetTotalAttackPowerValue(RANGED_ATTACK) * 0.516f; + break; + case SPELLFAMILY_WARLOCK: + // Warlock Minions inherit 50% of their owner's spell power + if (IsMinion() && GetOwner()) + DoneAdvertisedBenefit = owner->SpellBaseDamageBonusDone(spellProto->GetSchoolMask(), true) * 0.5f; + break; + case SPELLFAMILY_MAGE: + // Mage Water Elementals inherit 40% of their owner's spell power + if (IsMinion() && GetOwner()) + DoneAdvertisedBenefit = owner->SpellBaseDamageBonusDone(spellProto->GetSchoolMask(), true) * 0.4f; + break; + default: + break; + } + + if (spellProto->BonusCoefficientFromAP > 0.0f) + { + float ApCoeffMod = spellProto->BonusCoefficientFromAP; + if (Player* modOwner = GetSpellModOwner()) + { + ApCoeffMod *= 100.0f; + modOwner->ApplySpellMod(spellProto, SpellModOp::BonusCoefficient, ApCoeffMod); + ApCoeffMod /= 100.0f; + } + + WeaponAttackType const attType = [&]() + { + if ((spellProto->IsRangedWeaponSpell() && spellProto->DmgClass != SPELL_DAMAGE_CLASS_MELEE)) + return RANGED_ATTACK; + + if (spellProto->HasAttribute(SPELL_ATTR3_REQUIRES_OFF_HAND_WEAPON) && !spellProto->HasAttribute(SPELL_ATTR3_REQUIRES_MAIN_HAND_WEAPON)) + return OFF_ATTACK; + + return BASE_ATTACK; + }(); + + float APbonus = float(victim->GetTotalAuraModifier(attType != RANGED_ATTACK ? SPELL_AURA_MELEE_ATTACK_POWER_ATTACKER_BONUS : SPELL_AURA_RANGED_ATTACK_POWER_ATTACKER_BONUS)); + APbonus += GetTotalAttackPowerValue(attType); + DoneTotal += int32(stack * ApCoeffMod * APbonus); + } + + // Default calculation + if (DoneAdvertisedBenefit) + { + float coeff = spellProto->Effects[effIndex].BonusMultiplier * spellProto->GetSpellScalingMultiplier(getLevel(), false); + if (Player* modOwner = GetSpellModOwner()) + { + coeff *= 100.0f; + modOwner->ApplySpellMod(spellProto, SpellModOp::BonusCoefficient, coeff); + coeff /= 100.0f; + } + + DoneTotal += int32(DoneAdvertisedBenefit * coeff * stack); + } + + callDamageScript(pdamage, DoneTotal, DoneTotalMod); + + float tmpDamage = float(pdamage + DoneTotal) * DoneTotalMod; + + // apply spellmod to Done damage (flat and pct) + if (Player* modOwner = GetSpellModOwner()) + modOwner->ApplySpellMod(spellProto, damagetype == DOT ? SpellModOp::PeriodicHealingAndDamage : SpellModOp::HealingAndDamage, tmpDamage); + + return int32(std::max(tmpDamage, 0.0f)); +} + +float Unit::SpellDamagePctDone(Unit* victim, SpellInfo const* spellProto, DamageEffectType damagetype) const +{ + if (!spellProto || !victim || damagetype == DIRECT_DAMAGE) + return 1.0f; + + // Some spells don't benefit from done mods + if (spellProto->HasAttribute(SPELL_ATTR3_IGNORE_CASTER_MODIFIERS)) + return 1.0f; + + // Some spells don't benefit from pct done mods + if (spellProto->HasAttribute(SPELL_ATTR6_IGNORE_CASTER_DAMAGE_MODIFIERS)) + return 1.0f; + + // For totems pct done mods are calculated when its calculation is run on the player in SpellDamageBonusDone. + if (GetTypeId() == TYPEID_UNIT && IsTotem()) + return 1.0f; + + // Done total percent damage auras + float DoneTotalMod = 1.0f; + + // Pet damage? + if (GetTypeId() == TYPEID_UNIT && !IsPet()) + DoneTotalMod *= ToCreature()->GetSpellDamageMod(ToCreature()->GetCreatureTemplate()->rank); + + float maxModDamagePercentSchool = 0.0f; + if (GetTypeId() == TYPEID_PLAYER) + { + for (uint32 i = 0; i < MAX_SPELL_SCHOOL; ++i) + if (spellProto->GetSchoolMask() & (1 << i)) + maxModDamagePercentSchool = std::max(maxModDamagePercentSchool, GetFloatValue(PLAYER_FIELD_MOD_DAMAGE_DONE_PCT + i)); + } + else + maxModDamagePercentSchool = GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_DAMAGE_PERCENT_DONE, spellProto->GetSchoolMask()); + + DoneTotalMod *= maxModDamagePercentSchool; + + uint32 creatureTypeMask = victim->GetCreatureTypeMask(); + + DoneTotalMod *= GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_DAMAGE_DONE_VERSUS, creatureTypeMask); + + // bonus against aurastate + DoneTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_DAMAGE_DONE_VERSUS_AURASTATE, [victim](AuraEffect const* aurEff) -> bool + { + if (victim->HasAuraState(static_cast(aurEff->GetMiscValue()))) + return true; + return false; + }); + + // Add SPELL_AURA_MOD_DAMAGE_DONE_FOR_MECHANIC percent bonus + AddPct(DoneTotalMod, GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_DAMAGE_DONE_FOR_MECHANIC, spellProto->Mechanic)); + + // done scripted mod (take it from owner) + Unit const* 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)->IsAffectingSpell(spellProto)) + continue; + + switch ((*i)->GetMiscValue()) + { + case 4920: // Molten Fury + case 4919: + { + if (victim->HasAuraState(AURA_STATE_HEALTHLESS_35_PERCENT, spellProto, this)) + AddPct(DoneTotalMod, (*i)->GetAmount()); + break; + } + case 6917: // Death's Embrace damage effect + case 6926: + case 6928: + { + // Health at 25% or less (25% stored at effect 2 of the spell) + if (victim->HealthBelowPct(CalculateSpellDamage(this, (*i)->GetSpellInfo(), EFFECT_2))) + AddPct(DoneTotalMod, (*i)->GetAmount()); + break; + } + case 6916: // Death's Embrace heal effect + case 6925: + case 6927: + if (HealthBelowPct(CalculateSpellDamage(this, (*i)->GetSpellInfo(), EFFECT_2))) + AddPct(DoneTotalMod, (*i)->GetAmount()); + break; + case 4992: // Soul Siphon + case 4993: + { + // EFFECT_0 contains the bonus amount per application, EFFECT_1 contains the maximum bonus amount + int32 bonusPerApplication = CalculateSpellDamage(this, (*i)->GetSpellInfo(), EFFECT_0); + int32 pctBonus = 0; + for (auto const& itr : victim->GetAppliedAuras()) + { + Aura const* aura = itr.second->GetBase(); + if (aura->GetCasterGUID() != GetGUID() || !aura->GetSpellInfo()->IsAbilityOfSkillType(SKILL_AFFLICTION)) + continue; + + pctBonus += bonusPerApplication; + } + + pctBonus = std::min(pctBonus, (*i)->GetAmount()); + AddPct(DoneTotalMod, pctBonus); + break; + } + case 5481: // Starfire Bonus + { + if (victim->GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_DRUID, 0x200002, 0, 0)) + AddPct(DoneTotalMod, (*i)->GetAmount()); + break; + } + } + } + + // Add SPELL_AURA_MOD_DAMAGE_FROM_MANA percent bonus + if (HasAuraType(SPELL_AURA_MOD_DAMAGE_FROM_MANA) && GetMaxPower(POWER_MANA)) + { + float totalBonus = owner->GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_DAMAGE_FROM_MANA, spellProto->GetSchoolMask()) - 1.f; + totalBonus *= ((float)GetPower(POWER_MANA) / GetMaxPower(POWER_MANA)); + if (totalBonus != 0.f) + DoneTotalMod += DoneTotalMod * totalBonus; + } + + // Custom scripted damage + switch (spellProto->SpellFamilyName) + { + case SPELLFAMILY_MAGE: + // Mastery: Frostburn + if (AuraEffect* aurEff = GetAuraEffect(76613, 0)) + if (victim->HasAuraState(AURA_STATE_FROZEN)) + if (aurEff->IsAffectingSpell(spellProto)) + AddPct(DoneTotalMod, aurEff->GetAmount()); + + // Ice Lance + if (spellProto->SpellIconID == 186) + { + if (victim->HasAuraState(AURA_STATE_FROZEN, spellProto, this) + || GetAuraEffect(SPELL_AURA_ABILITY_IGNORE_AURASTATE, SPELLFAMILY_MAGE, 0, 0, 0x0000000A, GetGUID())) + DoneTotalMod *= 2.0f; + } + + // Fingers of Frost + if (spellProto->SpellFamilyFlags[0] & 0x00020000) + if (GetAuraEffect(SPELL_AURA_ABILITY_IGNORE_AURASTATE, SPELLFAMILY_MAGE, 0, 0, 0x0000000A, GetGUID())) + AddPct(DoneTotalMod, 25.0f); + + // Torment the weak + if (spellProto->GetSchoolMask() & SPELL_SCHOOL_MASK_ARCANE) + { + if (victim->HasAuraWithMechanic((1<GetSpellInfo()->SpellIconID == 2215) + { + AddPct(DoneTotalMod, (*i)->GetAmount()); + break; + } + } + } + } + break; + case SPELLFAMILY_PRIEST: + // Smite + 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()); + } + break; + case SPELLFAMILY_DEATHKNIGHT: + // Sigil of the Vengeful Heart + if (spellProto->SpellFamilyFlags[0] & 0x2000) + if (AuraEffect* aurEff = GetAuraEffect(64962, EFFECT_1)) + DoneTotalMod += aurEff->GetAmount(); + break; + } + + return DoneTotalMod; +} + +int32 Unit::SpellDamageBonusTaken(Unit* caster, SpellInfo const* spellProto, int32 pdamage, DamageEffectType damagetype) const +{ + if (!spellProto || damagetype == DIRECT_DAMAGE) + return pdamage; + + float TakenTotalMod = 1.0f; + + // Mod damage from spell mechanic + if (uint32 mechanicMask = spellProto->GetAllEffectsMechanicMask()) + { + TakenTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_MECHANIC_DAMAGE_TAKEN_PERCENT, [mechanicMask, spellProto](AuraEffect const* aurEff) + { + if (spellProto->HasAttribute(SPELL_ATTR10_IGNORE_POSITIVE_DAMAGE_TAKEN_MODS) && aurEff->GetAmount() > 0) + return false; + + if (mechanicMask & uint32(1 << (aurEff->GetMiscValue()))) + return true; + return false; + }); + } + + //.. taken pct: dummy auras + AuraEffectList const& mDummyAuras = GetAuraEffectsByType(SPELL_AURA_DUMMY); + for (AuraEffectList::const_iterator i = mDummyAuras.begin(); i != mDummyAuras.end(); ++i) + { + switch ((*i)->GetSpellInfo()->SpellIconID) + { + // Cheat Death + case 2109: + if ((*i)->GetMiscValue() & SPELL_SCHOOL_MASK_NORMAL) + AddPct(TakenTotalMod, (*i)->GetAmount()); + break; + } + } + + // Spells with SPELL_ATTR4_IGNORE_DAMAGE_TAKEN_MODIFIERS should only benefit from mechanic damage mod auras. + if (!spellProto->HasAttribute(SPELL_ATTR4_IGNORE_DAMAGE_TAKEN_MODIFIERS)) + { + // from positive and negative SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN + // multiplicative bonus, for example Dispersion + Shadowform (0.10*0.85=0.085) + TakenTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN, [spellProto](AuraEffect const* aurEff) + { + if (spellProto->HasAttribute(SPELL_ATTR10_IGNORE_POSITIVE_DAMAGE_TAKEN_MODS) && aurEff->GetAmount() > 0) + return false; + + if ((aurEff->GetMiscValue() & spellProto->GetSchoolMask()) != 0) + return true; + + return false; + }); + + // From caster spells + if (caster) + { + TakenTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_DAMAGE_FROM_CASTER, [caster, spellProto](AuraEffect const* aurEff) + { + if (spellProto->HasAttribute(SPELL_ATTR10_IGNORE_POSITIVE_DAMAGE_TAKEN_MODS) && aurEff->GetAmount() > 0) + return false; + + if (aurEff->GetCasterGUID() == caster->GetGUID() && aurEff->IsAffectingSpell(spellProto)) + return true; + return false; + }); + } + } + + // Sanctified Wrath (bypass damage reduction) + if (caster && TakenTotalMod < 1.0f) + { + float damageReduction = 1.0f - TakenTotalMod; + Unit::AuraEffectList const& casterIgnoreResist = caster->GetAuraEffectsByType(SPELL_AURA_MOD_IGNORE_TARGET_RESIST); + for (AuraEffect const* aurEff : casterIgnoreResist) + { + if (!(aurEff->GetMiscValue() & spellProto->GetSchoolMask())) + continue; + + AddPct(damageReduction, -aurEff->GetAmount()); + } + + TakenTotalMod = 1.0f - damageReduction; + } + + float tmpDamage = pdamage * TakenTotalMod; + return int32(std::max(tmpDamage, 0.0f)); +} + +int32 Unit::SpellBaseDamageBonusDone(SpellSchoolMask schoolMask, bool withSpellPowerPctMod /*= false*/) const +{ + if (IsPlayer()) + { + float overrideSP = GetFloatValue(PLAYER_FIELD_OVERRIDE_SPELL_POWER_BY_AP_PCT); + if (overrideSP > 0.0f) + return int32(CalculatePct(GetTotalAttackPowerValue(BASE_ATTACK), overrideSP) + 0.5f); + } + + int32 DoneAdvertisedBenefit = GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_DAMAGE_DONE, schoolMask); + + if (Player const* player = ToPlayer()) + { + // Base value + DoneAdvertisedBenefit += player->GetBaseSpellPowerBonus(); + + // Check if we are ever using mana - PaperDollFrame.lua + if (GetPowerIndex(POWER_MANA) != MAX_POWERS) + DoneAdvertisedBenefit += std::max(0, int32(GetStat(StatType::Intellect)) - 10); // spellpower from intellect + + // Spell Power bonus from SPELL_AURA_MOD_SPELL_POWER_PCT + if (withSpellPowerPctMod) + DoneAdvertisedBenefit *= player->GetFloatValue(PLAYER_FIELD_MOD_SPELL_POWER_PCT); + + // Damage bonus from stats + AuraEffectList const& mDamageDoneOfStatPercent = GetAuraEffectsByType(SPELL_AURA_MOD_SPELL_DAMAGE_OF_STAT_PERCENT); + for (AuraEffect const* aurEff : mDamageDoneOfStatPercent) + { + if ((aurEff->GetMiscValue() & schoolMask) != 0) + { + // stat used stored in miscValueB for this aura + StatType const usedStat = static_cast(aurEff->GetMiscValueB()); + DoneAdvertisedBenefit += static_cast(CalculatePct(GetStat(usedStat), aurEff->GetAmount())); + } + } + + // ... and attack power + DoneAdvertisedBenefit += static_cast(CalculatePct(GetTotalAttackPowerValue(BASE_ATTACK), GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_SPELL_DAMAGE_OF_ATTACK_POWER, schoolMask))); + } + + return DoneAdvertisedBenefit; +} + +float Unit::SpellCritChanceDone(SpellInfo const* spellInfo, SpellSchoolMask schoolMask, WeaponAttackType attackType /*= BASE_ATTACK*/, bool isPeriodic /*= false*/) const +{ + //! Mobs can't crit with spells. (Except player controlled) + if (GetTypeId() == TYPEID_UNIT && !GetSpellModOwner()) + return 0.0f; + + // not critting spell + if (!isPeriodic && !spellInfo->HasAttribute(SPELL_ATTR0_CU_CAN_CRIT)) + return 0.0f; + + float crit_chance = 0.0f; + switch (spellInfo->DmgClass) + { + case SPELL_DAMAGE_CLASS_MAGIC: + { + if (schoolMask & SPELL_SCHOOL_MASK_NORMAL) + crit_chance = 0.0f; + // For other schools + else if (GetTypeId() == TYPEID_PLAYER) + crit_chance = GetFloatValue(PLAYER_SPELL_CRIT_PERCENTAGE1 + uint32(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: + { + crit_chance += GetUnitCriticalChanceDone(attackType); + crit_chance += GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_SPELL_CRIT_CHANCE_SCHOOL, schoolMask); + break; + } + 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(spellInfo, SpellModOp::CritChance, crit_chance); + + return std::max(crit_chance, 0.0f); +} + +float Unit::SpellCritChanceTaken(Unit const* caster, SpellInfo const* spellInfo, SpellSchoolMask schoolMask, float doneChance, WeaponAttackType attackType /*= BASE_ATTACK*/, bool isPeriodic /*= false*/) const +{ + // not critting spell + if (!isPeriodic && !spellInfo->HasAttribute(SPELL_ATTR0_CU_CAN_CRIT)) + return 0.0f; + + float crit_chance = doneChance; + switch (spellInfo->DmgClass) + { + case SPELL_DAMAGE_CLASS_MAGIC: + { + // taken + if (!spellInfo->IsPositive()) + { + // Modify critical chance by victim SPELL_AURA_MOD_ATTACKER_SPELL_CRIT_CHANCE + crit_chance += GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_ATTACKER_SPELL_CRIT_CHANCE, schoolMask); + + // Modify critical chance by victim SPELL_AURA_MOD_ATTACKER_SPELL_AND_WEAPON_CRIT_CHANCE + // applied after resilience + crit_chance += GetTotalAuraModifier(SPELL_AURA_MOD_ATTACKER_SPELL_AND_WEAPON_CRIT_CHANCE); + } + + // scripted (increase crit chance ... against ... target by x% + if (caster) + { + AuraEffectList const& mOverrideClassScript = caster->GetAuraEffectsByType(SPELL_AURA_OVERRIDE_CLASS_SCRIPTS); + for (AuraEffect const* aurEff : mOverrideClassScript) + { + if (!aurEff->IsAffectingSpell(spellInfo)) + continue; + + switch (aurEff->GetMiscValue()) + { + case 911: // Shatter + if (HasAuraState(AURA_STATE_FROZEN, spellInfo, caster)) + crit_chance *= (1 * aurEff->GetSpellInfo()->GetRank()); + else if (spellInfo->SpellFamilyFlags[0] & 0x00020000 || spellInfo->SpellFamilyFlags[1] & 0x00100000) + if (GetAuraEffect(SPELL_AURA_ABILITY_IGNORE_AURASTATE, SPELLFAMILY_MAGE, 0, 0, 0x0000000A, caster->GetGUID())) + crit_chance *= (1 * aurEff->GetSpellInfo()->GetRank()); + break; + case 57470: // Renewed Hope + case 57472: + if (HasAura(6788) || HasAura(47930)) + crit_chance += aurEff->GetAmount(); + break; + default: + break; + } + } + // Custom crit by class + switch (spellInfo->SpellFamilyName) + { + case SPELLFAMILY_MAGE: + // Glyph of Fire Blast + if (spellInfo->SpellFamilyFlags[0] == 0x2 && spellInfo->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_ROGUE: + // Shiv-applied poisons can't crit + if (caster->FindCurrentSpellBySpellId(5938)) + crit_chance = 0.0f; + break; + case SPELLFAMILY_PALADIN: + // Flash of light + if (spellInfo->SpellFamilyFlags[0] & 0x40000000) + { + // Sacred Shield + if (AuraEffect const* aura = GetAuraEffect(58597, 1, GetGUID())) + crit_chance += aura->GetAmount(); + break; + } + // Exorcism + else if (spellInfo->GetCategory() == 19) + { + if (GetCreatureTypeMask() & CREATURE_TYPEMASK_DEMON_OR_UNDEAD) + return 100.0f; + break; + } + + // Last Word + if (AuraEffect const* effect = caster->GetDummyAuraEffect(SPELLFAMILY_PALADIN, 2139, EFFECT_0)) + if (effect->IsAffectingSpell(spellInfo)) + if (HasAuraState(AURA_STATE_HEALTHLESS_35_PERCENT)) + crit_chance += effect->GetAmount(); + break; + case SPELLFAMILY_SHAMAN: + // Lava Burst + if ((spellInfo->SpellFamilyFlags[1] & 0x1000) != 0 || (spellInfo->SpellFamilyFlags[2] & 0x400000) != 0) + { + 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; + case SPELLFAMILY_WARLOCK: + // Searing Pain - Soulburn bonus + if (spellInfo->SpellFamilyFlags[0] & 0x00000100) + if (HasAura(74434, GetGUID())) + return 100.0f; + + // Improved Searing Pain + if (AuraEffect const* effect = caster->GetDummyAuraEffect(SPELLFAMILY_WARLOCK, 816, EFFECT_0)) + if (effect->IsAffectingSpell(spellInfo)) + if (GetHealthPct() <= 25.f) + crit_chance += effect->GetAmount(); + break; + } + + // Spell crit suppression + if (GetTypeId() == TYPEID_UNIT) + { + int32 const levelDiff = static_cast(getLevelForTarget(caster)) - caster->getLevel(); + if (levelDiff > 0) + crit_chance -= levelDiff * 0.7f; + } + } + break; + } + case SPELL_DAMAGE_CLASS_MELEE: + { + // Custom crit by class + if (caster) + { + switch (spellInfo->SpellFamilyName) + { + case SPELLFAMILY_DRUID: + // Rend and Tear - bonus crit chance for Ferocious Bite on bleeding targets + if (spellInfo->SpellFamilyFlags[0] & 0x00800000 + && spellInfo->SpellIconID == 1680 + && HasAuraState(AURA_STATE_BLEEDING)) + { + if (AuraEffect const* rendAndTear = caster->GetDummyAuraEffect(SPELLFAMILY_DRUID, 2859, 1)) + crit_chance += rendAndTear->GetAmount(); + break; + } + + // Predatory Strikes + if (spellInfo->SpellFamilyFlags[0] & 0x00010000) + if (AuraEffect const* critAuraEff = caster->GetDummyAuraEffect(SPELLFAMILY_DRUID, 1563, EFFECT_0)) + if (AuraEffect const* healthAuraEff = caster->GetDummyAuraEffect(SPELLFAMILY_DRUID, 1563, EFFECT_1)) + if (GetHealthPct() >= healthAuraEff->GetAmount()) + crit_chance += critAuraEff->GetAmount(); + break; + case SPELLFAMILY_HUNTER: + // Kill Command - Improved Kill Command + if (spellInfo->SpellFamilyFlags[1] & 0x00000800) + if (Player* player = caster->GetSpellModOwner()) + if (AuraEffect const* improvedKillCommand = player->GetDummyAuraEffect(SPELLFAMILY_HUNTER, 2221, EFFECT_0)) + crit_chance += improvedKillCommand->GetAmount(); + break; + } + } + [[fallthrough]]; // Calculate critical strike chance for both Ranged and Melee spells + } + case SPELL_DAMAGE_CLASS_RANGED: + if (caster) + { + switch (spellInfo->SpellFamilyName) + { + case SPELLFAMILY_HUNTER: + { + // Careful Aim + if (GetHealthPct() >= 90) + { + switch (spellInfo->Id) + { + case 56641: // Steady Shot + case 77767: // Cobra Shot + case 19434: // Aimed Shot + case 82928: // Aimed Shot! + if (AuraEffect* aurEff = caster->GetDummyAuraEffect(SPELLFAMILY_HUNTER, 2222, 0)) + crit_chance += aurEff->GetAmount(); + break; + default: + break; + } + } + break; + } + } + crit_chance = GetUnitCriticalChanceTaken(attackType, crit_chance); + } + break; + case SPELL_DAMAGE_CLASS_NONE: + default: + return 0.f; + } + + // for this types the bonus was already added in GetUnitCriticalChance, do not add twice + if (caster) + { + crit_chance += GetTotalAuraModifier(SPELL_AURA_MOD_CRIT_CHANCE_FOR_CASTER, [caster, spellInfo](AuraEffect const* aurEff) -> bool + { + if (aurEff->GetCasterGUID() == caster->GetGUID() && aurEff->IsAffectingSpell(spellInfo)) + return true; + return false; + }); + } + + return std::max(crit_chance, 0.0f); +} + +/*static*/ uint32 Unit::SpellCriticalDamageBonus(Unit const* caster, SpellInfo const* spellProto, uint32 damage, bool periodicDamage /*= false*/) +{ + // Calculate critical bonus + int32 crit_bonus = damage; + float crit_mod = 0.0f; + + switch (spellProto->DmgClass) + { + // Spells which are either marked as ranged or melee damage, will deal 100% additional damage + case SPELL_DAMAGE_CLASS_MELEE: + crit_bonus += damage; + break; + case SPELL_DAMAGE_CLASS_RANGED: + // Ranged spells which have magic periodic damage effect on the other hand, only deal 50% damage, just like spells + // This is a very specific case that is currently only known for Hunter spells, such as Serpent Sting and Black Arrow + if (periodicDamage && (spellProto->GetSchoolMask() & SPELL_SCHOOL_MASK_MAGIC) != 0) + crit_bonus += damage / 2; + else + crit_bonus += damage; + break; + default: + // Magic spells will simply deal 50% additional crit damage + crit_bonus += damage / 2; + break; + } + + if (caster) + { + crit_mod += (caster->GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_CRIT_DAMAGE_BONUS, spellProto->GetSchoolMask()) - 1.0f) * 100; + + if (crit_bonus != 0) + AddPct(crit_bonus, crit_mod); + + crit_bonus -= damage; + + if (damage > uint32(crit_bonus)) + { + // adds additional damage to critBonus (from talents) + if (Player* modOwner = caster->GetSpellModOwner()) + modOwner->ApplySpellMod(spellProto, SpellModOp::CritDamageAndHealing, crit_bonus); + } + + crit_bonus += damage; + } + + return crit_bonus; +} + +/*static*/ uint32 Unit::SpellCriticalHealingBonus(Unit const* caster, uint32 damage) +{ + // As of Patch 4.2.0 (2011-06-28): all healing spells now crit for 200% instead of 150% + damage *= 2; + + if (caster) + damage = int32(float(damage) * caster->GetTotalAuraMultiplier(SPELL_AURA_MOD_CRITICAL_HEALING_AMOUNT)); + + return damage; +} + +int32 Unit::SpellHealingBonusDone(Unit* victim, SpellInfo const* spellProto, int32 healamount, DamageEffectType damagetype, uint8 effIndex, uint32 stack /*= 1*/, Spell* spell /*= nullptr*/, AuraEffect const* aurEff /*= nullptr*/) const +{ + // For totems get healing bonus from owner (statue isn't totem in fact) + if (GetTypeId() == TYPEID_UNIT && IsTotem()) + if (Unit* creator = ObjectAccessor::GetUnit(*this, GetCreatorGUID())) + return creator->SpellHealingBonusDone(victim, spellProto, healamount, damagetype, effIndex, stack, spell, aurEff); + + // Some spells don't benefit from done mods + if (spellProto->HasAttribute(SPELL_ATTR3_IGNORE_CASTER_MODIFIERS)) + return healamount; + + // No bonus healing for potion spells + if (spellProto->SpellFamilyName == SPELLFAMILY_POTION) + return healamount; + + int32 DoneTotal = 0; + float DoneTotalMod = SpellHealingPctDone(victim, spellProto); + + // done scripted mod (take it from owner) + Unit const* owner = GetOwner() ? GetOwner() : this; + for (AuraEffect const* overrideClassScripts : owner->GetAuraEffectsByType(SPELL_AURA_OVERRIDE_CLASS_SCRIPTS)) + { + if (!overrideClassScripts->IsAffectingSpell(spellProto)) + continue; + + switch (overrideClassScripts->GetMiscValue()) + { + case 4415: // Increased Rejuvenation Healing + case 4953: + DoneTotal += overrideClassScripts->GetAmount(); + break; + default: + break; + } + } + + // Done fixed damage bonus auras + int32 DoneAdvertisedBenefit = SpellBaseHealingBonusDone(spellProto->GetSchoolMask(), true); + // modify spell power by victim's SPELL_AURA_MOD_HEALING auras (eg Amplify/Dampen Magic) + DoneAdvertisedBenefit += victim->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_HEALING, spellProto->GetSchoolMask()); + + // Pets just add their bonus damage to their spell damage + // note that their spell damage is just gain of their own auras + if (HasUnitTypeMask(UNIT_MASK_GUARDIAN)) + DoneAdvertisedBenefit += static_cast(this)->GetBonusDamage(); + + + // Check for table values + if (spellProto->BonusCoefficientFromAP > 0.0f) + { + WeaponAttackType const attType = (spellProto->IsRangedWeaponSpell() && spellProto->DmgClass != SPELL_DAMAGE_CLASS_MELEE) ? RANGED_ATTACK : BASE_ATTACK; + float APbonus = float(victim->GetTotalAuraModifier(attType == BASE_ATTACK ? SPELL_AURA_MELEE_ATTACK_POWER_ATTACKER_BONUS : SPELL_AURA_RANGED_ATTACK_POWER_ATTACKER_BONUS)); + APbonus += GetTotalAttackPowerValue(attType); + + DoneTotal += int32(spellProto->BonusCoefficientFromAP * stack * APbonus); + } + + // Default calculation + if (DoneAdvertisedBenefit) + { + float coeff = spellProto->Effects[effIndex].BonusMultiplier * spellProto->GetSpellScalingMultiplier(getLevel(), false); + if (Player* modOwner = GetSpellModOwner()) + { + coeff *= 100.0f; + modOwner->ApplySpellMod(spellProto, SpellModOp::BonusCoefficient, coeff); + coeff /= 100.0f; + } + + DoneTotal += int32(DoneAdvertisedBenefit * coeff * stack); + } + + 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; + } + if (spellProto->Effects[i].Effect == SPELL_EFFECT_HEALTH_LEECH) + DoneTotal = 0; + } + + if (spell) + spell->CallScriptCalcHealingHandlers(victim, healamount, DoneTotal, DoneTotalMod); + else if (aurEff) + aurEff->GetBase()->CallScriptCalcDamageAndHealingHandlers(aurEff, aurEff->GetBase()->GetApplicationOfTarget(victim->GetGUID()), victim, healamount, DoneTotal, DoneTotalMod); + + float heal = float(healamount + DoneTotal) * DoneTotalMod; + + + // apply spellmod to Done amount + if (Player* modOwner = GetSpellModOwner()) + modOwner->ApplySpellMod(spellProto, damagetype == DOT ? SpellModOp::PeriodicHealingAndDamage : SpellModOp::HealingAndDamage, heal); + + return int32(std::max(heal, 0.0f)); +} + +float Unit::SpellHealingPctDone(Unit* victim, SpellInfo const* spellProto) const +{ + // For totems pct done mods are calculated when its calculation is run on the player in SpellHealingBonusDone. + if (GetTypeId() == TYPEID_UNIT && IsTotem()) + return 1.0f; + + // No bonus healing for potion spells + if (spellProto->SpellFamilyName == SPELLFAMILY_POTION) + return 1.0f; + + float DoneTotalMod = 1.0f; + + // Healing done percent + if (IsPlayer()) + DoneTotalMod *= GetFloatValue(PLAYER_FIELD_MOD_HEALING_DONE_PCT); + else + DoneTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_HEALING_DONE_PERCENT); + + // done scripted mod (take it from owner) + Unit const* owner = GetOwner() ? GetOwner() : this; + for (AuraEffect const* overrideClassScripts : owner->GetAuraEffectsByType(SPELL_AURA_OVERRIDE_CLASS_SCRIPTS)) + { + if (!overrideClassScripts->IsAffectingSpell(spellProto)) + continue; + + switch (overrideClassScripts->GetMiscValue()) + { + case 21: // Test of Faith + case 6935: + case 6918: + if (victim->HealthBelowPct(50)) + AddPct(DoneTotalMod, overrideClassScripts->GetAmount()); + break; + case 8477: // Nourish Heal Boost + { + int32 modPercent = 0; + for (AuraEffect const* periodicHeal : victim->GetAuraEffectsByType(SPELL_AURA_PERIODIC_HEAL)) + { + if (periodicHeal->GetCasterGUID() != GetGUID() || periodicHeal->GetSpellInfo()->SpellFamilyName != SPELLFAMILY_DRUID) + continue; + + if (periodicHeal->GetSpellInfo()->SpellFamilyFlags.HasFlag(0x10 | 0x40, 0x10 | 0x4000000, 0)) + modPercent += overrideClassScripts->GetAmount() * periodicHeal->GetBase()->GetStackAmount(); + } + AddPct(DoneTotalMod, modPercent); + break; + } + default: + break; + } + } + + // Custom scripted damage + switch (spellProto->SpellFamilyName) + { + case SPELLFAMILY_SHAMAN: + { + // Nature's Blessing + if (AuraEffect const* blessing = GetDummyAuraEffect(SPELLFAMILY_SHAMAN, 2012, EFFECT_0)) + if (blessing->IsAffectingSpell(spellProto) && victim->HasAura(974, GetGUID())) + AddPct(DoneTotalMod, blessing->GetAmount()); + break; + } + default: + break; + } + + float healthPctDiff = 100.f - victim->GetHealthPct(); + for (AuraEffect const* healingDonePctVsTargetHealth : owner->GetAuraEffectsByType(SPELL_AURA_MOD_HEALING_DONE_PCT_VERSUS_TARGET_HEALTH)) + { + if (!healingDonePctVsTargetHealth->IsAffectingSpell(spellProto)) + continue; + + AddPct(DoneTotalMod, CalculatePct(float(healingDonePctVsTargetHealth->GetAmount()), healthPctDiff)); + } + + return DoneTotalMod; +} + +int32 Unit::SpellHealingBonusTaken(Unit* caster, SpellInfo const* spellProto, int32 healamount, DamageEffectType damagetype) const +{ + float TakenTotalMod = 1.0f; + + // Healing taken percent + if (IsPlayer()) + TakenTotalMod *= GetFloatValue(PLAYER_FIELD_MOD_HEALING_PCT); + else + { + float 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()); + + // Unused in Cataclysm (15595) + if (damagetype == DOT) + { + // Healing over time taken percent + float minval_hot = float(GetMaxNegativeAuraModifier(SPELL_AURA_259)); + if (minval_hot) + AddPct(TakenTotalMod, minval_hot); + + float maxval_hot = float(GetMaxPositiveAuraModifier(SPELL_AURA_259)); + if (maxval_hot) + AddPct(TakenTotalMod, maxval_hot); + } + + if (caster) + { + TakenTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_HEALING_RECEIVED, [caster, spellProto](AuraEffect const* aurEff) -> bool + { + if (caster->GetGUID() == aurEff->GetCasterGUID() && aurEff->IsAffectingSpell(spellProto)) + return true; + return false; + }); + } + + float heal = healamount * TakenTotalMod; + + return int32(std::max(heal, 0.0f)); +} + +int32 Unit::SpellBaseHealingBonusDone(SpellSchoolMask schoolMask, bool withSpellPowerPctMod /*= false*/) const +{ + if (IsPlayer()) + { + float overrideSP = GetFloatValue(PLAYER_FIELD_OVERRIDE_SPELL_POWER_BY_AP_PCT); + if (overrideSP > 0.0f) + return int32(CalculatePct(GetTotalAttackPowerValue(BASE_ATTACK), overrideSP) + 0.5f); + } + + int32 advertisedBenefit = GetTotalAuraModifier(SPELL_AURA_MOD_HEALING_DONE, [schoolMask](AuraEffect const* aurEff) -> bool + { + if (!aurEff->GetMiscValue() || (aurEff->GetMiscValue() & schoolMask) != 0) + return true; + return false; + }); + + // Healing bonus of spirit, intellect and strength + if (Player const* player = ToPlayer()) + { + // Base value + advertisedBenefit += player->GetBaseSpellPowerBonus(); + + // Check if we are ever using mana - PaperDollFrame.lua + if (GetPowerIndex(POWER_MANA) != MAX_POWERS) + advertisedBenefit += std::max(0, int32(GetStat(StatType::Intellect)) - 10); // spellpower from intellect + + // Spell Power bonus from SPELL_AURA_MOD_SPELL_POWER_PCT + if (withSpellPowerPctMod) + advertisedBenefit *= player->GetFloatValue(PLAYER_FIELD_MOD_SPELL_POWER_PCT); + + // 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) + StatType usedStat = StatType((*i)->GetSpellInfo()->Effects[(*i)->GetEffIndex()].MiscValue); + advertisedBenefit += int32(CalculatePct(GetStat(usedStat), (*i)->GetAmount())); + } + + // ... and attack power + advertisedBenefit += CalculatePct(GetTotalAttackPowerValue(BASE_ATTACK), GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_SPELL_HEALING_OF_ATTACK_POWER, schoolMask)); + } + return advertisedBenefit; +} + +bool Unit::IsImmunedToDamage(SpellSchoolMask schoolMask) const +{ + if (schoolMask == SPELL_SCHOOL_MASK_NONE) + return false; + + // If m_immuneToSchool type contain this school type, IMMUNE damage. + uint32 schoolImmunityMask = GetSchoolImmunityMask(); + if ((schoolImmunityMask & schoolMask) == schoolMask) // We need to be immune to all types + return true; + + // If m_immuneToDamage type contain magic, IMMUNE damage. + uint32 damageImmunityMask = GetDamageImmunityMask(); + if ((damageImmunityMask & schoolMask) == schoolMask) // We need to be immune to all types + return true; + + return false; +} + +bool Unit::IsImmunedToDamage(SpellInfo const* spellInfo) const +{ + if (!spellInfo) + return false; + + // for example 40175 + if (spellInfo->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES) && spellInfo->HasAttribute(SPELL_ATTR3_ALWAYS_HIT)) + return false; + + if (spellInfo->HasAttribute(SPELL_ATTR1_IMMUNITY_TO_HOSTILE_AND_FRIENDLY_EFFECTS) || spellInfo->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES)) + return false; + + if (uint32 schoolMask = spellInfo->GetSchoolMask()) + { + // If m_immuneToSchool type contain this school type, IMMUNE damage. + uint32 schoolImmunityMask = 0; + SpellImmuneContainer const& schoolList = m_spellImmune[IMMUNITY_SCHOOL]; + for (auto itr = schoolList.begin(); itr != schoolList.end(); ++itr) + if ((itr->first & schoolMask) && !spellInfo->CanPierceImmuneAura(sSpellMgr->GetSpellInfo(itr->second))) + schoolImmunityMask |= itr->first; + + // // We need to be immune to all types + if ((schoolImmunityMask & schoolMask) == schoolMask) + return true; + + // If m_immuneToDamage type contain magic, IMMUNE damage. + uint32 damageImmunityMask = GetDamageImmunityMask(); + if ((damageImmunityMask & schoolMask) == schoolMask) // We need to be immune to all types + return true; + } + + return false; +} + +bool Unit::IsImmunedToSpell(SpellInfo const* spellInfo, WorldObject const* caster, bool requireImmunityPurgesEffectAttribute /*= false*/) const +{ + if (!spellInfo) + return false; + + auto hasImmunity = [requireImmunityPurgesEffectAttribute](SpellImmuneContainer const& container, uint32 key) + { + Trinity::IteratorPair range = Trinity::Containers::MapEqualRange(container, key); + if (!requireImmunityPurgesEffectAttribute) + return range.begin() != range.end(); + + return std::any_of(range.begin(), range.end(), [](SpellImmuneContainer::value_type const& entry) + { + if (SpellInfo const* immunitySourceSpell = sSpellMgr->GetSpellInfo(entry.second)) + if (immunitySourceSpell->HasAttribute(SPELL_ATTR1_IMMUNITY_PURGES_EFFECT)) + return true; + + return false; + }); + }; + + // Single spell immunity. + SpellImmuneContainer const& idList = m_spellImmune[IMMUNITY_ID]; + if (hasImmunity(idList, spellInfo->Id)) + return true; + + if (spellInfo->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES)) + return false; + + if (uint32 dispel = spellInfo->Dispel) + { + SpellImmuneContainer const& dispelList = m_spellImmune[IMMUNITY_DISPEL]; + if (hasImmunity(dispelList, dispel)) + return true; + } + + // Spells that don't have effectMechanics. + if (uint32 mechanic = spellInfo->Mechanic) + { + SpellImmuneContainer const& mechanicList = m_spellImmune[IMMUNITY_MECHANIC]; + if (hasImmunity(mechanicList, mechanic)) + return true; + } + + bool immuneToAllEffects = true; + for (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; + + if (!IsImmunedToSpellEffect(spellInfo, i, caster, requireImmunityPurgesEffectAttribute)) + { + immuneToAllEffects = false; + break; + } + if (spellInfo->HasAttribute(SPELL_ATTR4_NO_PARTIAL_IMMUNITY)) + return true; + } + + if (immuneToAllEffects) //Return immune only if the target is immune to all spell effects. + return true; + + if (uint32 schoolMask = spellInfo->GetSchoolMask()) + { + uint32 schoolImmunityMask = 0; + SpellImmuneContainer const& schoolList = m_spellImmune[IMMUNITY_SCHOOL]; + for (auto itr = schoolList.begin(); itr != schoolList.end(); ++itr) + { + if ((itr->first & schoolMask) == 0) + continue; + + SpellInfo const* immuneSpellInfo = sSpellMgr->GetSpellInfo(itr->second); + if (requireImmunityPurgesEffectAttribute) + if (!immuneSpellInfo || !immuneSpellInfo->HasAttribute(SPELL_ATTR1_IMMUNITY_PURGES_EFFECT)) + continue; + + // Consider the school immune if any of these conditions are not satisfied. + // In case of no immuneSpellInfo, ignore that condition and check only the other conditions + if ((immuneSpellInfo && !immuneSpellInfo->IsPositive()) || !spellInfo->IsPositive() || !caster || !IsFriendlyTo(caster)) + if (!spellInfo->CanPierceImmuneAura(immuneSpellInfo)) + schoolImmunityMask |= itr->first; + } + + if ((schoolImmunityMask & schoolMask) == schoolMask) + return true; + } + + return false; +} + +uint32 Unit::GetSchoolImmunityMask() const +{ + uint32 mask = 0; + SpellImmuneContainer const& schoolList = m_spellImmune[IMMUNITY_SCHOOL]; + for (auto itr = schoolList.begin(); itr != schoolList.end(); ++itr) + mask |= itr->first; + + return mask; +} + +uint32 Unit::GetDamageImmunityMask() const +{ + uint32 mask = 0; + SpellImmuneContainer const& damageList = m_spellImmune[IMMUNITY_DAMAGE]; + for (auto itr = damageList.begin(); itr != damageList.end(); ++itr) + mask |= itr->first; + + return mask; +} + +uint32 Unit::GetMechanicImmunityMask() const +{ + uint32 mask = 0; + SpellImmuneContainer const& mechanicList = m_spellImmune[IMMUNITY_MECHANIC]; + for (auto itr = mechanicList.begin(); itr != mechanicList.end(); ++itr) + mask |= (1 << itr->first); + + return mask; +} + +bool Unit::IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index, WorldObject const* caster, + bool requireImmunityPurgesEffectAttribute /*= false*/) const +{ + if (!spellInfo || !spellInfo->Effects[index].IsEffect()) + return false; + + if (spellInfo->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES)) + return false; + + auto hasImmunity = [requireImmunityPurgesEffectAttribute](SpellImmuneContainer const& container, uint32 key) + { + Trinity::IteratorPair range = Trinity::Containers::MapEqualRange(container, key); + if (!requireImmunityPurgesEffectAttribute) + return range.begin() != range.end(); + + return std::any_of(range.begin(), range.end(), [](SpellImmuneContainer::value_type const& entry) + { + if (SpellInfo const* immunitySourceSpell = sSpellMgr->GetSpellInfo(entry.second)) + if (immunitySourceSpell->HasAttribute(SPELL_ATTR1_IMMUNITY_PURGES_EFFECT)) + return true; + + return false; + }); + }; + + + // If m_immuneToEffect type contain this effect type, IMMUNE effect. + uint32 effect = spellInfo->Effects[index].Effect; + SpellImmuneContainer const& effectList = m_spellImmune[IMMUNITY_EFFECT]; + if (hasImmunity(effectList, effect)) + return true; + + if (uint32 mechanic = spellInfo->Effects[index].Mechanic) + { + SpellImmuneContainer const& mechanicList = m_spellImmune[IMMUNITY_MECHANIC]; + if (hasImmunity(mechanicList, mechanic)) + return true; + } + + if (!spellInfo->HasAttribute(SPELL_ATTR3_ALWAYS_HIT)) + { + if (uint32 aura = spellInfo->Effects[index].ApplyAuraName) + { + SpellImmuneContainer const& list = m_spellImmune[IMMUNITY_STATE]; + if (hasImmunity(list, aura)) + return true; + + if (!spellInfo->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES)) + { + // Check for immune to application of harmful magical effects + AuraEffectList const& immuneAuraApply = GetAuraEffectsByType(SPELL_AURA_MOD_IMMUNE_AURA_APPLY_SCHOOL); + for (AuraEffectList::const_iterator iter = immuneAuraApply.begin(); iter != immuneAuraApply.end(); ++iter) + if (((*iter)->GetMiscValue() & spellInfo->GetSchoolMask()) && // Check school + ((caster && !IsFriendlyTo(caster)) || !spellInfo->IsPositiveEffect(index))) // Harmful + return true; + } + } + } + + return false; +} + +int32 Unit::MeleeDamageBonusDone(Unit* victim, int32 damage, WeaponAttackType attType, DamageEffectType damagetype, SpellInfo const* spellProto /*= nullptr*/, Mechanics mechanic /*= MECHANIC_NONE*/, SpellSchoolMask damageSchoolMask /*= SPELL_SCHOOL_MASK_NORMAL*/, Spell* spell /*= nullptr*/, AuraEffect const* aurEff /*= nullptr*/) +{ + if (!victim || damage == 0) + return 0; + + 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 const normalized = spellProto && spellProto->HasEffect(SPELL_EFFECT_NORMALIZED_WEAPON_DMG); + DoneFlatBenefit += int32(APbonus / 14.0f * GetAPMultiplier(attType, normalized)); + } + + // Done total percent damage auras + float DoneTotalMod = 1.0f; + + SpellSchoolMask schoolMask = spellProto ? spellProto->GetSchoolMask() : damageSchoolMask; + + if (!(schoolMask & SPELL_SCHOOL_MASK_NORMAL)) + { + // Some spells don't benefit from pct done mods + // mods for SPELL_SCHOOL_MASK_NORMAL are already factored in base melee damage calculation + if (!spellProto || !spellProto->HasAttribute(SPELL_ATTR6_IGNORE_CASTER_DAMAGE_MODIFIERS)) + { + float maxModDamagePercentSchool = 0.0f; + if (GetTypeId() == TYPEID_PLAYER) + { + for (uint32 i = SPELL_SCHOOL_HOLY; i < MAX_SPELL_SCHOOL; ++i) + if (schoolMask & (1 << i)) + maxModDamagePercentSchool = std::max(maxModDamagePercentSchool, GetFloatValue(PLAYER_FIELD_MOD_DAMAGE_DONE_PCT + i)); + } + else if (spellProto) + maxModDamagePercentSchool = GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_DAMAGE_PERCENT_DONE, spellProto->GetSchoolMask()); + + DoneTotalMod *= maxModDamagePercentSchool; + } + } + + DoneTotalMod *= GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_DAMAGE_DONE_VERSUS, creatureTypeMask); + + // bonus against aurastate + DoneTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_DAMAGE_DONE_VERSUS_AURASTATE, [victim](AuraEffect const* aurEff) -> bool + { + if (victim->HasAuraState(AuraStateType(aurEff->GetMiscValue()))) + return true; + return false; + }); + + // Add SPELL_AURA_MOD_DAMAGE_DONE_FOR_MECHANIC percent bonus + // Add SPELL_AURA_MOD_DAMAGE_DONE_FOR_MECHANIC percent bonus + if (mechanic != MECHANIC_NONE) + AddPct(DoneTotalMod, GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_DAMAGE_DONE_FOR_MECHANIC, mechanic)); + else if (spellProto && spellProto->Mechanic) + AddPct(DoneTotalMod, GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_DAMAGE_DONE_FOR_MECHANIC, spellProto->Mechanic)); + + if (!spellProto) + { + // Add SPELL_AURA_MOD_AUTOATTACK_DAMAGE percent bonus + AddPct(DoneTotalMod, GetTotalAuraModifier(SPELL_AURA_MOD_AUTOATTACK_DAMAGE)); + + // Add SPELL_AURA_MOD_MELEE_DAMAGE_FROM_CASTER percent bonus + // This aura name is a bit misleading but it's actually only suposed to increase melee auto attack damage + AddPct(DoneTotalMod, victim->GetTotalAuraMultiplier(SPELL_AURA_MOD_MELEE_DAMAGE_FROM_CASTER, [this](AuraEffect const* effect)->bool + { + return effect->GetCasterGUID() == GetGUID(); + })); + } + + if (spell) + spell->CallScriptCalcDamageHandlers(victim, damage, DoneFlatBenefit, DoneTotalMod); + else if (aurEff) + aurEff->GetBase()->CallScriptCalcDamageAndHealingHandlers(aurEff, aurEff->GetBase()->GetApplicationOfTarget(victim->GetGUID()), victim, damage, DoneFlatBenefit, DoneTotalMod); + + float damageF = float(damage + DoneFlatBenefit) * DoneTotalMod; + + // apply spellmod to Done damage + if (spellProto) + if (Player* modOwner = GetSpellModOwner()) + modOwner->ApplySpellMod(spellProto, damagetype == DOT ? SpellModOp::PeriodicHealingAndDamage : SpellModOp::HealingAndDamage, damageF); + + // bonus result can be negative + return int32(std::max(damageF, 0.0f)); +} + +int32 Unit::MeleeDamageBonusTaken(Unit* attacker, int32 pdamage, WeaponAttackType attType, SpellInfo const* spellProto /*= nullptr*/, SpellSchoolMask damageSchoolMask /*= SPELL_SCHOOL_MASK_NORMAL*/) +{ + if (pdamage == 0) + return 0; + + int32 TakenFlatBenefit = 0; + + uint32 meleeDamageSchoolMask = spellProto ? spellProto->GetSchoolMask() : attacker->GetMeleeDamageSchoolMask(); + + // ..taken + TakenFlatBenefit += GetTotalAuraModifier(SPELL_AURA_MOD_DAMAGE_TAKEN, [meleeDamageSchoolMask, spellProto](AuraEffect const* aurEff) + { + if (spellProto && spellProto->HasAttribute(SPELL_ATTR10_IGNORE_POSITIVE_DAMAGE_TAKEN_MODS) && aurEff->GetAmount() > 0) + return false; + + if ((aurEff->GetMiscValue() & meleeDamageSchoolMask) != 0) + return true; + + return false; + }); + + AuraType modDamageTaken = SPELL_AURA_MOD_RANGED_DAMAGE_TAKEN; + if (attType != RANGED_ATTACK) + modDamageTaken = SPELL_AURA_MOD_MELEE_DAMAGE_TAKEN; + + TakenFlatBenefit += GetTotalAuraModifier(modDamageTaken, [spellProto](AuraEffect const* aurEff) + { + if (spellProto && spellProto->HasAttribute(SPELL_ATTR10_IGNORE_POSITIVE_DAMAGE_TAKEN_MODS) && aurEff->GetAmount() > 0) + return false; + + return true; + }); + + if ((TakenFlatBenefit < 0) && (pdamage < -TakenFlatBenefit)) + return 0; + + // Taken total percent damage auras + float TakenTotalMod = 1.0f; + + // ..taken + TakenTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN, [meleeDamageSchoolMask, spellProto](AuraEffect const* aurEff) + { + if (spellProto && spellProto->HasAttribute(SPELL_ATTR10_IGNORE_POSITIVE_DAMAGE_TAKEN_MODS) && aurEff->GetAmount() > 0) + return false; + + if ((aurEff->GetMiscValue() & meleeDamageSchoolMask) != 0) + return true; + + return false; + }); + + // .. taken pct (special attacks) + if (spellProto) + { + // From caster spells + TakenTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_DAMAGE_FROM_CASTER, [attacker, spellProto](AuraEffect const* aurEff) -> bool + { + if (spellProto && spellProto->HasAttribute(SPELL_ATTR10_IGNORE_POSITIVE_DAMAGE_TAKEN_MODS) && aurEff->GetAmount() > 0) + return false; + + if (aurEff->GetCasterGUID() == attacker->GetGUID() && aurEff->IsAffectingSpell(spellProto)) + return true; + return false; + }); + + // 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, spellProto](AuraEffect const* aurEff) -> bool + { + if (spellProto && spellProto->HasAttribute(SPELL_ATTR10_IGNORE_POSITIVE_DAMAGE_TAKEN_MODS) && aurEff->GetAmount() > 0) + return false; + + if (mechanicMask & uint32(1 << (aurEff->GetMiscValue()))) + return true; + return false; + }); + } + } + + // .. taken pct: dummy auras + AuraEffectList const& mDummyAuras = GetAuraEffectsByType(SPELL_AURA_DUMMY); + for (AuraEffectList::const_iterator i = mDummyAuras.begin(); i != mDummyAuras.end(); ++i) + { + switch ((*i)->GetSpellInfo()->SpellIconID) + { + // Cheat Death + case 2109: + if ((*i)->GetMiscValue() & SPELL_SCHOOL_MASK_NORMAL) + { + if (Player* player = ToPlayer()) + { + float mod = player->GetRatingBonusValue(CR_RESILIENCE_PLAYER_DAMAGE_TAKEN) * (-8.0f); + AddPct(TakenTotalMod, std::max(mod, float((*i)->GetAmount()))); + } + } + break; + } + } + + + AuraType modDamageTakenPct = SPELL_AURA_MOD_RANGED_DAMAGE_TAKEN_PCT; + if (attType != RANGED_ATTACK) + modDamageTakenPct = SPELL_AURA_MOD_MELEE_DAMAGE_TAKEN_PCT; + + TakenTotalMod *= GetTotalAuraMultiplier(modDamageTakenPct, [spellProto](AuraEffect const* aurEff) + { + if (spellProto && spellProto->HasAttribute(SPELL_ATTR10_IGNORE_POSITIVE_DAMAGE_TAKEN_MODS) && aurEff->GetAmount() > 0) + return false; + + return true; + }); + + // Sanctified Wrath (bypass damage reduction) + if (TakenTotalMod < 1.0f) + { + SpellSchoolMask const attackSchoolMask = spellProto ? spellProto->GetSchoolMask() : damageSchoolMask; + + float damageReduction = 1.0f - TakenTotalMod; + Unit::AuraEffectList const& casterIgnoreResist = attacker->GetAuraEffectsByType(SPELL_AURA_MOD_IGNORE_TARGET_RESIST); + for (AuraEffect const* aurEff : casterIgnoreResist) + { + if (!(aurEff->GetMiscValue() & attackSchoolMask)) + continue; + + AddPct(damageReduction, -aurEff->GetAmount()); + } + + TakenTotalMod = 1.0f - damageReduction; + } + + return int32(std::max(std::ceil(static_cast(pdamage + TakenFlatBenefit) * TakenTotalMod), 0.0f)); +} + +void Unit::ApplySpellImmune(uint32 spellId, uint32 op, uint32 type, bool apply) +{ + if (apply) + m_spellImmune[op].emplace(type, spellId); + else + { + auto bounds = m_spellImmune[op].equal_range(type); + for (auto itr = bounds.first; itr != bounds.second;) + { + if (itr->second == spellId) + itr = m_spellImmune[op].erase(itr); + else + ++itr; + } + } +} + +float Unit::GetWeaponProcChance() const +{ + // normalized proc chance for weapon attack speed + // (odd formula...) + if (isAttackReady(BASE_ATTACK)) + return (GetBaseAttackTime(BASE_ATTACK) * 1.8f / 1000.0f); + else if (haveOffhandWeapon() && isAttackReady(OFF_ATTACK)) + return (GetBaseAttackTime(OFF_ATTACK) * 1.6f / 1000.0f); + return 0; +} + +float Unit::GetPPMProcChance(uint32 WeaponSpeed, float PPM, SpellInfo const* spellProto) const +{ + // proc per minute chance calculation + if (PPM <= 0) + return 0.0f; + + // Apply chance modifer aura + if (spellProto) + if (Player* modOwner = GetSpellModOwner()) + modOwner->ApplySpellMod(spellProto, SpellModOp::ProcFrequency, PPM); + + return std::floor((WeaponSpeed * PPM) / 600.0f); // result is chance in percents (probability = Speed_in_sec * (PPM / 60)) +} + +void Unit::Mount(uint32 mount, uint32 VehicleId, uint32 creatureEntry) +{ + if (mount) + SetUInt32Value(UNIT_FIELD_MOUNTDISPLAYID, mount); + + SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_MOUNT); + + if (Player* player = ToPlayer()) + { + // mount as a vehicle + if (VehicleId) + { + if (CreateVehicleKit(VehicleId, creatureEntry)) + { + player->SendOnCancelExpectedVehicleRideAura(); + + // 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->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_STUNNED); + else + player->UnsummonPetTemporaryIfAny(); + } + + // if we have charmed npc, stun him also (everywhere) + if (Unit* charm = player->GetCharmed()) + if (charm->GetTypeId() == TYPEID_UNIT) + charm->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_STUNNED); + + if (player->IsInWorld()) + player->SendMovementSetCollisionHeight(player->GetCollisionHeight(), UPDATE_COLLISION_HEIGHT_MOUNT); + } + + RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Mount); +} + +void Unit::Dismount() +{ + if (!IsMounted()) + return; + + SetUInt32Value(UNIT_FIELD_MOUNTDISPLAYID, 0); + RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_MOUNT); + + if (Player* thisPlayer = ToPlayer()) + thisPlayer->SendMovementSetCollisionHeight(thisPlayer->GetCollisionHeight(), UPDATE_COLLISION_HEIGHT_MOUNT); + + WorldPackets::Misc::Dismount packet; + packet.Guid = GetGUID(); + SendMessageToSet(packet.Write(), IsPlayer()); + + // dismount as a vehicle + if (GetTypeId() == TYPEID_PLAYER && GetVehicleKit()) + RemoveVehicleKit(); + + RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Dismount); + + // only resummon old pet if the player is already added to a map + // this prevents adding a pet to a not created map which would otherwise cause a crash + // (it could probably happen when logging in after a previous crash) + if (Player* player = ToPlayer()) + { + if (Pet* pPet = player->GetPet()) + { + if (pPet->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_STUNNED) && !pPet->HasUnitState(UNIT_STATE_STUNNED)) + pPet->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_STUNNED); + } + else + player->ResummonPetTemporaryUnSummonedIfAny(); + + // if we have charmed npc, remove stun also + if (Unit* charm = player->GetCharmed()) + if (charm->GetTypeId() == TYPEID_UNIT && charm->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_STUNNED) && !charm->HasUnitState(UNIT_STATE_STUNNED)) + charm->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_STUNNED); + } +} + +MountCapabilityEntry const* Unit::GetMountCapability(uint32 mountType) const +{ + if (!mountType) + return nullptr; + + MountTypeEntry const* mountTypeEntry = sMountTypeStore.LookupEntry(mountType); + if (!mountTypeEntry) + return nullptr; + + + uint32 areaId = GetAreaId(); + uint32 ridingSkill = 5000; + EnumFlag mountFlags = AreaMountFlags::None; + bool isUnderwater = false; + bool isInWater = false; + + if (GetTypeId() == TYPEID_PLAYER) + ridingSkill = ToPlayer()->GetSkillValue(SKILL_RIDING); + + if (HasAuraType(SPELL_AURA_MOUNT_RESTRICTIONS)) + { + for (AuraEffect const* auraEffect : GetAuraEffectsByType(SPELL_AURA_MOUNT_RESTRICTIONS)) + mountFlags |= AreaMountFlags(auraEffect->GetMiscValue()); + } + else if (AreaTableEntry const* areaTable = sAreaTableStore.LookupEntry(areaId)) + mountFlags = areaTable->GetMountFlags(); + + ZLiquidStatus liquidStatus = GetLiquidStatus(); + isUnderwater = (liquidStatus & LIQUID_MAP_UNDER_WATER) != 0; + isInWater = (liquidStatus & LIQUID_MAP_IN_WATER) != 0; + + for (uint32 i = MAX_MOUNT_CAPABILITIES; i > 0; --i) + { + MountCapabilityEntry const* mountCapability = sMountCapabilityStore.LookupEntry(mountTypeEntry->Capability[i - 1]); + if (!mountCapability) + continue; + + if (ridingSkill < mountCapability->ReqRidingSkill) + continue; + + if (!(mountCapability->Flags & MOUNT_CAPABIILTY_FLAG_IGNORE_RESTRICTIONS)) + { + if (mountCapability->Flags & MOUNT_CAPABILITY_FLAG_GROUND && !(mountFlags.HasFlag(AreaMountFlags::AllowGroundMounts))) + continue; + if (mountCapability->Flags & MOUNT_CAPABILITY_FLAG_FLYING && !(mountFlags.HasFlag(AreaMountFlags::AllowFlyingMounts))) + continue; + if (mountCapability->Flags & MOUNT_CAPABILITY_FLAG_FLOAT && !(mountFlags.HasFlag(AreaMountFlags::AllowSurfaceSwimmingMounts))) + continue; + if (mountCapability->Flags & MOUNT_CAPABILITY_FLAG_UNDERWATER && !(mountFlags.HasFlag(AreaMountFlags::AllowUnderwaterSwimmingMounts))) + continue; + } + + // Do not allow underwater restricted capabilities when not underwater + if (!(mountCapability->Flags & MOUNT_CAPABILITY_FLAG_UNDERWATER) && isUnderwater) + continue; + + // Do not allow water surface restricted capabilities when not on water surface + if (!(mountCapability->Flags & MOUNT_CAPABILITY_FLAG_FLOAT) && isInWater) + continue; + + // Do not allow liquid restricted mounts on ground or in air + if (!(mountCapability->Flags & (MOUNT_CAPABILITY_FLAG_GROUND | MOUNT_CAPABILITY_FLAG_FLYING)) && !isUnderwater && !isInWater) + continue; + + if (mountCapability->ReqMapID != -1 && + int32(GetMapId()) != mountCapability->ReqMapID && + GetMap()->GetEntry()->ParentMapID != mountCapability->ReqMapID) + continue; + + if (mountCapability->ReqAreaID && !DBCManager::IsInArea(areaId, mountCapability->ReqAreaID)) + continue; + + if (mountCapability->ReqSpellAuraID && !HasAura(mountCapability->ReqSpellAuraID)) + continue; + + if (mountCapability->ReqSpellKnownID && !HasSpell(mountCapability->ReqSpellKnownID)) + continue; + + return mountCapability; + } + + return nullptr; +} + +void Unit::UpdateMountCapability() +{ + AuraEffectList mounts = GetAuraEffectsByType(SPELL_AURA_MOUNTED); + for (AuraEffect* aurEff : mounts) + { + aurEff->RecalculateAmount(); + if (!aurEff->GetAmount()) + aurEff->GetBase()->Remove(); + else if (MountCapabilityEntry const* capability = sMountCapabilityStore.LookupEntry(aurEff->GetAmount())) // aura may get removed by interrupt flag, reapply + if (!HasAura(capability->ModSpellAuraID)) + CastSpell(this, capability->ModSpellAuraID, aurEff); + } +} + +bool Unit::IsServiceProvider() const +{ + return HasFlag(UNIT_NPC_FLAGS, + UNIT_NPC_FLAG_VENDOR | UNIT_NPC_FLAG_TRAINER | UNIT_NPC_FLAG_FLIGHTMASTER | + UNIT_NPC_FLAG_PETITIONER | UNIT_NPC_FLAG_BATTLEMASTER | UNIT_NPC_FLAG_BANKER | + UNIT_NPC_FLAG_INNKEEPER | UNIT_NPC_FLAG_SPIRITHEALER | + UNIT_NPC_FLAG_SPIRITGUIDE | UNIT_NPC_FLAG_TABARDDESIGNER | UNIT_NPC_FLAG_AUCTIONEER); +} + +void Unit::EngageWithTarget(Unit* enemy) +{ + if (!enemy) + return; + + if (CanHaveThreatList()) + m_threatManager.AddThreat(enemy, 0.0f, nullptr, true, true); + else + SetInCombatWith(enemy); +} + +void Unit::SetImmuneToAll(bool apply, bool keepCombat) +{ + if (apply) + { + SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC | UNIT_FLAG_IMMUNE_TO_NPC); + ValidateAttackersAndOwnTarget(); + if (!keepCombat) + m_combatManager.EndAllCombat(); + } + else + RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC | UNIT_FLAG_IMMUNE_TO_NPC); +} + +void Unit::SetImmuneToPC(bool apply, bool keepCombat) +{ + if (apply) + { + SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC); + ValidateAttackersAndOwnTarget(); + if (!keepCombat) + { + std::list toEnd; + for (auto const& pair : m_combatManager.GetPvECombatRefs()) + if (pair.second->GetOther(this)->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED)) + toEnd.push_back(pair.second); + for (auto const& pair : m_combatManager.GetPvPCombatRefs()) + if (pair.second->GetOther(this)->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED)) + toEnd.push_back(pair.second); + for (CombatReference* ref : toEnd) + ref->EndCombat(); + } + } + else + RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC); +} + +void Unit::SetImmuneToNPC(bool apply, bool keepCombat) +{ + if (apply) + { + SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_NPC); + ValidateAttackersAndOwnTarget(); + if (!keepCombat) + { + std::list toEnd; + for (auto const& pair : m_combatManager.GetPvECombatRefs()) + if (!pair.second->GetOther(this)->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED)) + toEnd.push_back(pair.second); + for (auto const& pair : m_combatManager.GetPvPCombatRefs()) + if (!pair.second->GetOther(this)->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED)) + toEnd.push_back(pair.second); + for (CombatReference* ref : toEnd) + ref->EndCombat(); + } + } + else + RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_NPC); +} + +bool Unit::IsThreatened() const +{ + return !m_threatManager.IsThreatListEmpty(); +} + +bool Unit::isTargetableForAttack(bool checkFakeDeath) const +{ + if (!IsAlive()) + return false; + + if (HasFlag(UNIT_FIELD_FLAGS, + UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NOT_SELECTABLE)) + return false; + + if (GetTypeId() == TYPEID_PLAYER && ToPlayer()->IsGameMaster()) + return false; + + return !HasUnitState(UNIT_STATE_UNATTACKABLE) && (!checkFakeDeath || !HasUnitState(UNIT_STATE_DIED)); +} + +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(PowerType power, int32 dVal, bool withPowerUpdate /*= true*/) +{ + int32 gain = 0; + + if (dVal == 0) + return 0; + + int32 curPower = GetPower(power); + + int32 val = dVal + curPower; + if (val <= GetMinPower(power)) + { + SetPower(power, GetMinPower(power), withPowerUpdate); + return -curPower; + } + + int32 maxPower = GetMaxPower(power); + + if (val < maxPower) + { + SetPower(power, val, withPowerUpdate); + gain = val - curPower; + } + else if (curPower != maxPower) + { + SetPower(power, maxPower, withPowerUpdate); + gain = maxPower - curPower; + } + + return gain; +} + +// Based on client function +float Unit::GetPowerRegen(PowerType powerType, bool isInCombat) const +{ + uint32 powerSlot = MAX_POWERS; + float totalRegeneration = 0.f; + double result = DBCManager::GetBasePowerRegen(powerType, isInCombat, _powerBarId); + float combatRegeneration = result; + + powerSlot = GetPowerIndex(powerType); + if (powerSlot != MAX_POWERS) + { + uint32 powerRegenIndex = isInCombat ? UNIT_FIELD_POWER_REGEN_INTERRUPTED_FLAT_MODIFIER : UNIT_FIELD_POWER_REGEN_FLAT_MODIFIER; + totalRegeneration = GetFloatValue(powerRegenIndex + powerSlot) + combatRegeneration; + result = totalRegeneration; + } + else + totalRegeneration = result; + + if (IsPlayer() && DBCManager::IsPowerTypeAffectedByHaste(powerType)) + { + float hasteRegen = GetFloatValue(PLAYER_FIELD_MOD_HASTE_REGEN); + if (G3D::fuzzyNe(hasteRegen, 0.f)) + result = totalRegeneration / hasteRegen; + } + + return result; +} + +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; + + return false; +} + +bool Unit::IsVisible() const +{ + return (m_serverSideVisibility.GetValue(SERVERSIDE_VISIBILITY_GM) > SEC_PLAYER) ? false : true; +} + +void Unit::SetVisible(bool x) +{ + if (!x) + m_serverSideVisibility.SetValue(SERVERSIDE_VISIBILITY_GM, SEC_GAMEMASTER); + else + m_serverSideVisibility.SetValue(SERVERSIDE_VISIBILITY_GM, SEC_PLAYER); + + UpdateObjectVisibility(); +} + +void Unit::UpdateSpeed(UnitMoveType mtype) +{ + int32 main_speed_mod = 0; + float stack_bonus = 1.0f; + float non_stack_bonus = 1.0f; + + switch (mtype) + { + // Only apply debuffs + case MOVE_FLIGHT_BACK: + case MOVE_RUN_BACK: + case MOVE_SWIM_BACK: + break; + case MOVE_WALK: + return; + case MOVE_RUN: + { + if (IsMounted()) // Use on mount auras + { + main_speed_mod = GetMaxPositiveAuraModifier(SPELL_AURA_MOD_INCREASE_MOUNTED_SPEED); + stack_bonus = GetTotalAuraMultiplier(SPELL_AURA_MOD_MOUNTED_SPEED_ALWAYS); + non_stack_bonus += GetMaxPositiveAuraModifier(SPELL_AURA_MOD_MOUNTED_SPEED_NOT_STACK) / 100.0f; + } + else + { + main_speed_mod = GetMaxPositiveAuraModifier(SPELL_AURA_MOD_INCREASE_SPEED); + stack_bonus = GetTotalAuraMultiplier(SPELL_AURA_MOD_SPEED_ALWAYS); + non_stack_bonus += GetMaxPositiveAuraModifier(SPELL_AURA_MOD_SPEED_NOT_STACK) / 100.0f; + } + break; + } + case MOVE_SWIM: + { + main_speed_mod = GetMaxPositiveAuraModifier(SPELL_AURA_MOD_INCREASE_SWIM_SPEED); + break; + } + case MOVE_FLIGHT: + { + if (GetTypeId() == TYPEID_UNIT && IsControlledByPlayer()) // not sure if good for pet + { + main_speed_mod = GetMaxPositiveAuraModifier(SPELL_AURA_MOD_INCREASE_VEHICLE_FLIGHT_SPEED); + stack_bonus = GetTotalAuraMultiplier(SPELL_AURA_MOD_VEHICLE_SPEED_ALWAYS); + + // for some spells this mod is applied on vehicle owner + int32 owner_speed_mod = 0; + + if (Unit* owner = GetCharmer()) + owner_speed_mod = owner->GetMaxPositiveAuraModifier(SPELL_AURA_MOD_INCREASE_VEHICLE_FLIGHT_SPEED); + + main_speed_mod = std::max(main_speed_mod, owner_speed_mod); + } + else if (IsMounted()) + { + main_speed_mod = GetMaxPositiveAuraModifier(SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED); + stack_bonus = GetTotalAuraMultiplier(SPELL_AURA_MOD_MOUNTED_FLIGHT_SPEED_ALWAYS); + } + else // Use not mount (shapeshift for example) auras (should stack) + main_speed_mod = GetTotalAuraModifier(SPELL_AURA_MOD_INCREASE_FLIGHT_SPEED) + GetTotalAuraModifier(SPELL_AURA_MOD_INCREASE_VEHICLE_FLIGHT_SPEED); + + non_stack_bonus += GetMaxPositiveAuraModifier(SPELL_AURA_MOD_FLIGHT_SPEED_NOT_STACK) / 100.0f; + + // Update speed for vehicle if available + if (GetTypeId() == TYPEID_PLAYER && GetVehicle()) + GetVehicleBase()->UpdateSpeed(MOVE_FLIGHT); + break; + } + default: + TC_LOG_ERROR("entities.unit", "Unit::UpdateSpeed: Unsupported move type (%d)", 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()) + { + CreatureMovementInfo const& movementInfo = ToCreature()->GetCreatureMovementInfo(); + speed *= movementInfo.HasRunSpeedOverriden ? (movementInfo.RunSpeed / baseMoveSpeed[MOVE_RUN]) : 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; + } + + // Apply strongest slow aura mod to speed + int32 slow = GetMaxNegativeAuraModifier(SPELL_AURA_MOD_DECREASE_SPEED); + if (slow) + AddPct(speed, slow); + + if (float minSpeedMod = (float)GetMaxPositiveAuraModifier(SPELL_AURA_MOD_MINIMUM_SPEED)) + { + float baseMinSpeed = 1.0f; + if (!GetOwnerOrCreatorGUID().IsPlayer() && !IsHunterPet() && IsCreature()) + { + CreatureMovementInfo const& movementInfo = ToCreature()->GetCreatureMovementInfo(); + baseMinSpeed = movementInfo.HasRunSpeedOverriden ? (movementInfo.RunSpeed / baseMoveSpeed[MOVE_RUN]) : ToCreature()->GetCreatureTemplate()->speed_run; + } + + float min_speed = CalculatePct(baseMinSpeed, minSpeedMod); + if (speed < min_speed) + speed = min_speed; + } + + SetSpeedRate(mtype, speed); +} + +float Unit::GetSpeed(UnitMoveType mtype) const +{ + return m_speed_rate[mtype]*(IsControlledByPlayer() ? playerBaseMoveSpeed[mtype] : baseMoveSpeed[mtype]); +} + +void Unit::SetSpeed(UnitMoveType mtype, float newValue) +{ + SetSpeedRate(mtype, newValue / (IsControlledByPlayer() ? playerBaseMoveSpeed[mtype] : baseMoveSpeed[mtype])); +} + +void Unit::SetSpeedRate(UnitMoveType mtype, float rate) +{ + if (rate < 0) + rate = 0.0f; + + // Update speed only on change + if (m_speed_rate[mtype] == rate) + return; + + + float newSpeedFlat = rate * (IsControlledByPlayer() ? playerBaseMoveSpeed[mtype] : baseMoveSpeed[mtype]); + if (IsMovedByClient() && IsInWorld()) + { + MovementPacketSender::SendSpeedChangeToMover(this, mtype, newSpeedFlat); + SetSpeedRateReal(mtype, rate); + } + else if (IsMovedByClient() && !IsInWorld()) // (1) + SetSpeedRateReal(mtype, rate); + else // <=> if(!IsMovedByPlayer()) + { + SetSpeedRateReal(mtype, rate); + MovementPacketSender::SendSpeedChangeToAll(this, mtype, newSpeedFlat); + } + + // explaination of (1): + // If the player is not in the world yet, it won't reply to the packets requiring an ack. And once the player is in the world, next time a movement + // packet which requires an ack is sent to the client (change of speed for example), the client is kicked from the + // server on the ground that it should have replied to the first packet first. That line is a hacky fix + // in the sense that it doesn't work like that in retail since buffs are applied only after the player has been + // initialized in the world. cf description of PR #18771 +} + +void Unit::SetSpeedRateReal(UnitMoveType mtype, float rate) +{ + if (!IsInCombat() && ToPlayer()) + if (Pet* pet = ToPlayer()->GetPet()) + pet->SetSpeedRate(mtype, rate); + + m_speed_rate[mtype] = rate; + PropagateSpeedChange(); +} + +void Unit::FollowTarget(Unit* target) +{ + if (!target) + { + TC_LOG_ERROR("entities.unit", "Unit::FollowTarget: Unit (%s) tried to follow a non-existant target.", GetGUID().ToString().c_str()); + return; + } + + // Determine follow configuration + bool joinFormation = false; // unit will follow its target in a generated formation shape and catches up to its target + bool catchUpToTarget = false; // unit will allign to the target speed and catches up to the target automatically + bool faceTarget = false; // unit will face its target with every spline + float distance = DEFAULT_FOLLOW_DISTANCE_PET; + + if (TempSummon* summon = ToTempSummon()) + { + if (SummonPropertiesEntry const* properties = summon->m_Properties) + { + // Allied summons, pet summons join a formation unless the following exceptions are being met. + if (properties->Control == SUMMON_CATEGORY_ALLY || properties->Control == SUMMON_CATEGORY_PET) + joinFormation = true; + + // Companion minipets will always be able to catch up to their target + if (properties->Slot == SUMMON_SLOT_MINIPET) + { + joinFormation = false; + catchUpToTarget = true; + faceTarget = true; + distance = DEFAULT_FOLLOW_DISTANCE; + } + + // Quest npcs follow their target outside of formations + if (properties->Slot == SUMMON_SLOT_QUEST) + { + joinFormation = false; + distance = DEFAULT_FOLLOW_DISTANCE; + } + } + + // Pets and minions alwys move in a formation of their target + if (summon->IsPet()) + joinFormation = true; + } + else if (IsCharmed()) + joinFormation = true; + + // Unit is already following its target + if (joinFormation && target->HasFormationFollower(this)) + return; + + GetMotionMaster()->MoveFollow(target, distance, DEFAULT_FOLLOW_ANGLE, joinFormation, catchUpToTarget, faceTarget); +} + +void Unit::RemoveFormationFollower(Unit* follower) +{ + for (FormationFollowerGUIDContainer::const_iterator itr = _formationFollowers.begin(); itr != _formationFollowers.end();) + { + ObjectGuid guid = *itr; + if (Unit* followingUnit = ObjectAccessor::GetUnit(*this, guid)) + { + if (followingUnit == follower) + itr = _formationFollowers.erase(itr); + else + ++itr; + } + else + itr = _formationFollowers.erase(itr); + } +} + +bool Unit::HasFormationFollower(Unit* follower) const +{ + for (ObjectGuid guid : _formationFollowers) + if (Unit* followingUnit = ObjectAccessor::GetUnit(*this, guid)) + if (followingUnit == follower) + return true; + + return false; +} + +void Unit::RemoveAllPursuers() +{ + for (uint8 i = 0; i < AsUnderlyingType(PursuingType::Max); ++i) + { + while (!_unitsPursuingMe[i].empty()) + (*_unitsPursuingMe[i].begin())->SetTarget(PursuingType(i), nullptr); + } +} + +void Unit::setDeathState(DeathState s) +{ + // Death state needs to be updated before RemoveAllAurasOnDeath() is called, to prevent entering combat + m_deathState = s; + + if (s != ALIVE && s != JUST_RESPAWNED) + { + CombatStop(); + ClearComboPointHolders(); // any combo points pointed to unit lost at it death + + if (IsNonMeleeSpellCast(false)) + InterruptNonMeleeSpells(false); + + ExitVehicle(); // Exit vehicle before calling RemoveAllControlled + // vehicles use special type of charm that is not removed by the next function + // triggering an assert + UnsummonAllTotems(); + RemoveAllControlled(); + RemoveAllAurasOnDeath(); + } + + if (s == JUST_DIED) + { + // remove aurastates allowing special moves + ClearAllReactives(); + ClearDiminishings(); + CancelPendingCastRequest(); + if (IsInWorld()) + { + // Only clear MotionMaster for entities that exists in world + // Avoids crashes in the following conditions : + // * Using 'call pet' on dead pets + // * Using 'call stabled pet' + // * Logging in with dead pets + GetMotionMaster()->Clear(false); + GetMotionMaster()->MoveIdle(); + } + StopMoving(); + DisableSpline(); + // without this when removing IncreaseMaxHealth aura player may stuck with 1 hp + // do not why since in IncreaseMaxHealth currenthealth is checked + SetHealth(0); + SetPower(GetPowerType(), 0); + SetUInt32Value(UNIT_NPC_EMOTESTATE, 0); + + // players in instance don't have ZoneScript, but they have InstanceScript + if (ZoneScript* zoneScript = GetZoneScript() ? GetZoneScript() : GetInstanceScript()) + zoneScript->OnUnitDeath(this); + } + else if (s == JUST_RESPAWNED) + RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SKINNABLE); // clear skinnable for creature and player (at battleground) +} + +//====================================================================== + +void Unit::AtEnterCombat() +{ + if (Spell* spell = m_currentSpells[CURRENT_GENERIC_SPELL]) + if (spell->getState() == SPELL_STATE_PREPARING + && spell->m_spellInfo->HasAttribute(SPELL_ATTR0_NOT_IN_COMBAT_ONLY_PEACEFUL) + && spell->m_spellInfo->InterruptFlags.HasFlag(SpellInterruptFlags::Combat) + && !spell->m_spellInfo->HasAura(SPELL_AURA_MOUNTED)) // for some reason mounts are not interrupted by entering combat even with both interrupt flag and attribute + InterruptSpell(CURRENT_GENERIC_SPELL, false, false); + + RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::EnteringCombat); + ProcSkillsAndAuras(this, nullptr, PROC_FLAG_ENTER_COMBAT, PROC_FLAG_NONE, PROC_SPELL_TYPE_MASK_ALL, PROC_SPELL_PHASE_NONE, PROC_HIT_NONE, nullptr, nullptr, nullptr); +} + +void Unit::AtExitCombat() +{ + RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::LeavingCombat); +} + +void Unit::AtTargetAttacked(Unit* target, bool canInitialAggro) +{ + if (!target->IsEngaged() && !canInitialAggro) + return; + target->EngageWithTarget(this); + if (Unit* targetOwner = target->GetCharmerOrOwner()) + targetOwner->EngageWithTarget(this); + + Player* myPlayerOwner = GetCharmerOrOwnerPlayerOrPlayerItself(); + Player* targetPlayerOwner = target->GetCharmerOrOwnerPlayerOrPlayerItself(); + if (myPlayerOwner && targetPlayerOwner && !(myPlayerOwner->duel && myPlayerOwner->duel->opponent == targetPlayerOwner)) + { + myPlayerOwner->UpdatePvP(true); + myPlayerOwner->SetContestedPvP(targetPlayerOwner); + myPlayerOwner->RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::PvPActive); + } +} + +void Unit::UpdatePetCombatState() +{ + ASSERT(!IsPet()); // player pets do not use UNIT_FLAG_PET_IN_COMBAT for this purpose - but player pets should also never have minions of their own to call this + + // UNIT_FLAG_PET_IN_COMBAT is set by default when the summoner is in combat by himself + if (IsInCombat()) + return; + + bool state = false; + for (Unit* minion : m_Controlled) + if (minion->IsInCombat()) + { + state = true; + break; + } + + if (state) + SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PET_IN_COMBAT); + else + RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PET_IN_COMBAT); +} + +//====================================================================== + +DiminishingLevels Unit::GetDiminishing(DiminishingGroup group) const +{ + DiminishingReturn const& diminish = m_Diminishing[group]; + if (!diminish.hitCount) + return DIMINISHING_LEVEL_1; + + // If last spell was cast more than 15 seconds ago - reset level + if (!diminish.stack && GetMSTimeDiffToNow(diminish.hitTime) > 15000) + return DIMINISHING_LEVEL_1; + + return DiminishingLevels(diminish.hitCount); +} + +void Unit::IncrDiminishing(SpellInfo const* auraSpellInfo, bool triggered) +{ + DiminishingGroup group = auraSpellInfo->GetDiminishingReturnsGroupForSpell(triggered); + uint32 currentLevel = GetDiminishing(group); + uint32 const maxLevel = auraSpellInfo->GetDiminishingReturnsMaxLevel(triggered); + + DiminishingReturn& diminish = m_Diminishing[group]; + if (currentLevel < maxLevel) + diminish.hitCount = currentLevel + 1; +} + +bool Unit::ApplyDiminishingToDuration(SpellInfo const* auraSpellInfo, bool triggered, int32& duration, WorldObject* caster, DiminishingLevels previousLevel) const +{ + DiminishingGroup const group = auraSpellInfo->GetDiminishingReturnsGroupForSpell(triggered); + if (duration == -1 || group == DIMINISHING_NONE) + return true; + + int32 const limitDuration = auraSpellInfo->GetDiminishingReturnsLimitDuration(triggered); + + // test pet/charm masters instead pets/charmeds + Unit const* targetOwner = GetCharmerOrOwner(); + Unit const* casterOwner = caster->GetCharmerOrOwner(); + + // 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; + WorldObject const* source = casterOwner ? casterOwner : caster; + + if (target->IsAffectedByDiminishingReturns() && source->GetTypeId() == TYPEID_PLAYER) + duration = limitDuration; + } + + float mod = 1.0f; + if (group == DIMINISHING_TAUNT) + { + if (GetTypeId() == TYPEID_UNIT && (ToCreature()->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_TAUNT_DIMINISH)) + { + DiminishingLevels diminish = previousLevel; + switch (diminish) + { + case DIMINISHING_LEVEL_1: break; + case DIMINISHING_LEVEL_2: mod = 0.65f; break; + case DIMINISHING_LEVEL_3: mod = 0.4225f; break; + case DIMINISHING_LEVEL_4: mod = 0.274625f; break; + case DIMINISHING_LEVEL_TAUNT_IMMUNE: mod = 0.0f; break; + default: break; + } + } + } + // Some diminishings applies to mobs too (for example, Stun) + else if (auraSpellInfo->GetDiminishingReturnsGroupType(triggered) == DRTYPE_ALL || + (auraSpellInfo->GetDiminishingReturnsGroupType(triggered) == DRTYPE_PLAYER && + (targetOwner ? targetOwner->IsAffectedByDiminishingReturns() : IsAffectedByDiminishingReturns()))) + { + DiminishingLevels diminish = previousLevel; + switch (diminish) + { + case DIMINISHING_LEVEL_1: break; + case DIMINISHING_LEVEL_2: mod = 0.5f; break; + case DIMINISHING_LEVEL_3: mod = 0.25f; break; + case DIMINISHING_LEVEL_IMMUNE: mod = 0.0f; break; + default: break; + } + } + + duration = int32(duration * mod); + return (duration != 0); +} + +void Unit::ApplyDiminishingAura(DiminishingGroup group, bool apply) +{ + // Checking for existing in the table + DiminishingReturn& diminish = m_Diminishing[group]; + + if (apply) + ++diminish.stack; + else if (diminish.stack) + { + --diminish.stack; + + // Remember time after last aura from group removed + if (!diminish.stack) + diminish.hitTime = getMSTime(); + } +} + +void Unit::ClearDiminishings() +{ + for (DiminishingReturn& dim : m_Diminishing) + dim.Clear(); +} + +uint32 Unit::GetCreatureType() const +{ + if (GetTypeId() == TYPEID_PLAYER) + { + ShapeshiftForm form = GetShapeshiftForm(); + SpellShapeshiftFormEntry const* ssEntry = sSpellShapeshiftFormStore.LookupEntry(form); + if (ssEntry && ssEntry->CreatureType > 0) + return ssEntry->CreatureType; + else + return CREATURE_TYPE_HUMANOID; + } + else + return ToCreature()->GetCreatureTemplate()->type; +} + +uint32 Unit::GetCreatureTypeMask() const +{ + uint32 creatureType = GetCreatureType(); + return (creatureType >= 1) ? (1 << (creatureType - 1)) : 0; +} + +void Unit::SetShapeshiftForm(ShapeshiftForm form) +{ + SetByteValue(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_SHAPESHIFT_FORM, form); +} + +bool Unit::IsInFeralForm() const +{ + ShapeshiftForm form = GetShapeshiftForm(); + return form == FORM_CAT || form == FORM_BEAR; +} + +bool Unit::IsInDisallowedMountForm() const +{ + if (ShapeshiftForm form = GetShapeshiftForm()) + { + SpellShapeshiftFormEntry const* shapeshift = sSpellShapeshiftFormStore.LookupEntry(form); + if (!shapeshift) + return true; + + if (!shapeshift->GetFlags().HasFlag(SpellShapeshiftFormFlags::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->Flags & 0x80)) + if (race && !(race->Flags & 0x4)) + return true; + + return false; +} + +/*####################################### +######## ######## +######## STAT SYSTEM ######## +######## ######## +#######################################*/ + +void Unit::HandleStatFlatModifier(UnitMods unitMod, UnitModifierFlatType modifierType, float amount, bool apply) +{ + if (unitMod >= UNIT_MOD_END || modifierType >= MODIFIER_TYPE_FLAT_END) + { + TC_LOG_ERROR("entities.unit", "ERROR in HandleStatFlatModifier(): non-existing UnitMods or wrong UnitModifierType!"); + return; + } + + if (!amount) + return; + + switch (modifierType) + { + case BASE_VALUE: + case TOTAL_VALUE: + m_auraFlatModifiersGroup[unitMod][modifierType] += apply ? amount : -amount; + break; + default: + break; + } + + UpdateUnitMod(unitMod); +} + +void Unit::ApplyStatPctModifier(UnitMods unitMod, UnitModifierPctType modifierType, float pct) +{ + if (unitMod >= UNIT_MOD_END || modifierType >= MODIFIER_TYPE_PCT_END) + { + TC_LOG_ERROR("entities.unit", "ERROR in ApplyStatPctModifier(): non-existing UnitMods or wrong UnitModifierType!"); + return; + } + + if (!pct) + return; + + switch (modifierType) + { + case BASE_PCT: + case TOTAL_PCT: + AddPct(m_auraPctModifiersGroup[unitMod][modifierType], pct); + break; + default: + break; + } + + UpdateUnitMod(unitMod); +} + +void Unit::SetStatFlatModifier(UnitMods unitMod, UnitModifierFlatType modifierType, float val) +{ + if (m_auraFlatModifiersGroup[unitMod][modifierType] == val) + return; + + m_auraFlatModifiersGroup[unitMod][modifierType] = val; + UpdateUnitMod(unitMod); +} + +void Unit::SetStatPctModifier(UnitMods unitMod, UnitModifierPctType modifierType, float val) +{ + if (m_auraPctModifiersGroup[unitMod][modifierType] == val) + return; + + m_auraPctModifiersGroup[unitMod][modifierType] = val; + UpdateUnitMod(unitMod); +} + +float Unit::GetFlatModifierValue(UnitMods unitMod, UnitModifierFlatType modifierType) const +{ + if (unitMod >= UNIT_MOD_END || modifierType >= MODIFIER_TYPE_FLAT_END) + { + TC_LOG_ERROR("entities.unit", "attempt to access non-existing modifier value from UnitMods!"); + return 0.0f; + } + + return m_auraFlatModifiersGroup[unitMod][modifierType]; +} + +float Unit::GetPctModifierValue(UnitMods unitMod, UnitModifierPctType modifierType) const +{ + if (unitMod >= UNIT_MOD_END || modifierType >= MODIFIER_TYPE_PCT_END) + { + TC_LOG_ERROR("entities.unit", "attempt to access non-existing modifier value from UnitMods!"); + return 0.0f; + } + + return m_auraPctModifiersGroup[unitMod][modifierType]; +} + +void Unit::UpdateUnitMod(UnitMods unitMod) +{ + if (!CanModifyStats()) + return; + + switch (unitMod) + { + case UNIT_MOD_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_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::GetTotalAuraModValue(UnitMods unitMod) const +{ + if (unitMod >= UNIT_MOD_END) + { + TC_LOG_ERROR("entities.unit", "attempt to access non-existing UnitMods in GetTotalAuraModValue()!"); + return 0.0f; + } + + float value = 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; +} + +PowerType 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_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) const +{ + if (attType == RANGED_ATTACK) + { + int32 ap = GetInt32Value(UNIT_FIELD_RANGED_ATTACK_POWER) + + GetInt32Value(UNIT_FIELD_RANGED_ATTACK_POWER_MOD_POS) + - GetInt32Value(UNIT_FIELD_RANGED_ATTACK_POWER_MOD_NEG); + + 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_MOD_POS) + - GetInt32Value(UNIT_FIELD_ATTACK_POWER_MOD_NEG); + + if (ap < 0) + return 0.0f; + return ap * (1.0f + GetFloatValue(UNIT_FIELD_ATTACK_POWER_MULTIPLIER)); + } +} + +float Unit::GetWeaponDamageRange(WeaponAttackType attType, WeaponDamageRange type) const +{ + if (attType == OFF_ATTACK && !haveOffhandWeapon()) + return 0.0f; + + return m_weaponDamage[attType][type]; +} + +bool Unit::CanFreeMove() const +{ + return !HasUnitState(UNIT_STATE_CONFUSED | UNIT_STATE_FLEEING | UNIT_STATE_IN_FLIGHT | + UNIT_STATE_ROOT | UNIT_STATE_STUNNED | UNIT_STATE_DISTRACTED) && GetOwnerOrCreatorGUID().IsEmpty(); +} + +void Unit::SetLevel(uint8 lvl) +{ + SetUInt32Value(UNIT_FIELD_LEVEL, lvl); + + if (Player* player = ToPlayer()) + { + // group update + if (player->GetGroup()) + player->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_LEVEL); + + sCharacterCache->UpdateCharacterLevel(GetGUID(), lvl); + } +} + +void Unit::SetHealth(uint32 val) +{ + if (getDeathState() == JUST_DIED || getDeathState() == CORPSE) + val = 0; + else if (GetTypeId() == TYPEID_PLAYER && getDeathState() == DEAD) + val = 1; + else + { + uint32 maxHealth = GetMaxHealth(); + if (maxHealth < val) + val = maxHealth; + } + + SetUInt32Value(UNIT_FIELD_HEALTH, val); + + // group update + if (Player* player = ToPlayer()) + { + if (player->GetGroup()) + player->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_CUR_HP); + } + else if (Pet* pet = ToCreature()->ToPet()) + { + if (pet->isControlled()) + { + Unit* owner = GetOwner(); + if (owner && (owner->GetTypeId() == TYPEID_PLAYER) && owner->ToPlayer()->GetGroup()) + owner->ToPlayer()->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 (GetTypeId() == TYPEID_PLAYER) + { + if (ToPlayer()->GetGroup()) + ToPlayer()->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_MAX_HP); + } + else if (Pet* pet = ToCreature()->ToPet()) + { + if (pet->isControlled()) + { + Unit* owner = GetOwner(); + if (owner && (owner->GetTypeId() == TYPEID_PLAYER) && owner->ToPlayer()->GetGroup()) + owner->ToPlayer()->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_PET_MAX_HP); + } + } + + if (val < health) + SetHealth(val); +} + +int32 Unit::GetPower(PowerType power) const +{ + uint32 powerIndex = GetPowerIndex(power); + if (powerIndex == MAX_POWERS) + return 0; + + return GetUInt32Value(UNIT_FIELD_POWER1 + powerIndex); +} + +int32 Unit::GetMaxPower(PowerType power) const +{ + uint32 powerIndex = GetPowerIndex(power); + if (powerIndex == MAX_POWERS) + return 0; + + return GetInt32Value(UNIT_FIELD_MAXPOWER1 + powerIndex); +} + +void Unit::SetPower(PowerType power, int32 val, bool withPowerUpdate /*= true*/) +{ + uint32 powerIndex = GetPowerIndex(power); + if (powerIndex == MAX_POWERS) + return; + + int32 maxPower = int32(GetMaxPower(power)); + if (maxPower < val) + val = maxPower; + + SetInt32Value(UNIT_FIELD_POWER1 + powerIndex, val); + + if (IsInWorld() && withPowerUpdate) + { + WorldPackets::Combat::PowerUpdate packet; + packet.Guid = GetGUID(); + packet.Powers.emplace_back(val, power); + SendMessageToSet(packet.Write(), IsPlayer()); + } + + // group update + if (Player* player = ToPlayer()) + { + 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->GetTypeId() == TYPEID_PLAYER) && owner->ToPlayer()->GetGroup()) + owner->ToPlayer()->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_PET_CUR_POWER); + } + } +} + +void Unit::SetMaxPower(PowerType power, int32 val) +{ + uint32 powerIndex = GetPowerIndex(power); + if (powerIndex == MAX_POWERS) + return; + + int32 cur_power = GetPower(power); + SetInt32Value(UNIT_FIELD_MAXPOWER1 + powerIndex, val); + + // group update + if (GetTypeId() == TYPEID_PLAYER) + { + if (ToPlayer()->GetGroup()) + ToPlayer()->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_MAX_POWER); + } + else if (Pet* pet = ToCreature()->ToPet()) + { + if (pet->isControlled()) + { + Unit* owner = GetOwner(); + if (owner && (owner->GetTypeId() == TYPEID_PLAYER) && owner->ToPlayer()->GetGroup()) + owner->ToPlayer()->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_PET_MAX_POWER); + } + } + + if (val < cur_power) + SetPower(power, val); +} + +void Unit::Regenerate(PowerType powerType, uint32 diff) +{ + uint32 maxValue = GetMaxPower(powerType); + if (!maxValue) + return; + + if (!HasFlag(UNIT_FIELD_FLAGS_2, UNIT_FLAG2_REGENERATE_POWER)) + return; + + uint32 curValue = GetPower(powerType); + + // Block power regeneration + if (HasAuraTypeWithValue(SPELL_AURA_PREVENT_REGENERATE_POWER, powerType) || HasAuraType(SPELL_AURA_INTERRUPT_REGEN)) + return; + + // Skip regeneration for power type we cannot have + uint32 powerIndex = GetPowerIndex(powerType); + if (powerIndex == MAX_POWERS) + return; + + float addValue = GetPowerRegen(powerType, IsInCombat()) * (diff / float(IN_MILLISECONDS)); + + // Apply config values + switch (powerType) + { + case POWER_MANA: + addValue *= sWorld->getRate(RATE_POWER_MANA); + break; + case POWER_RAGE: + addValue *= sWorld->getRate(RATE_POWER_RAGE_LOSS); + break; + case POWER_RUNIC_POWER: + addValue *= sWorld->getRate(RATE_POWER_RUNICPOWER_LOSS); + break; + case POWER_FOCUS: + addValue *= sWorld->getRate(RATE_POWER_FOCUS); + break; + case POWER_ENERGY: + addValue *= sWorld->getRate(RATE_POWER_ENERGY); + break; + default: + break; + } + + if (addValue < 0.0f) + { + if (curValue == 0) + return; + } + else if (addValue > 0.0f) + { + if (curValue == maxValue) + return; + } + else + return; + + addValue += _powerFraction[powerIndex]; + uint32 integerValue = uint32(std::fabs(addValue)); + + if (addValue < 0.0f) + { + if (curValue > integerValue) + { + curValue -= integerValue; + _powerFraction[powerIndex] = addValue + integerValue; + } + else + { + curValue = 0; + _powerFraction[powerIndex] = 0; + } + } + else + { + curValue += integerValue; + + if (curValue > maxValue) + { + curValue = maxValue; + _powerFraction[powerIndex] = 0; + } + else + _powerFraction[powerIndex] = addValue - integerValue; + } + + // Players update their powers in 2 seconds intervals, creatures in 1 second ones + if (_powerUpdateTimer <= 0) + SetPower(powerType, curValue); + else + UpdateUInt32Value(UNIT_FIELD_POWER1 + powerIndex, curValue); +} + +void Unit::RegisterPowerTypes() +{ + for (uint8 i = POWER_MANA; i < MAX_POWERS; ++i) + { + uint32 powerIndex = GetPowerIndex(PowerType(i)); + if (powerIndex == MAX_POWERS || powerIndex == MAX_POWERS_PER_CLASS) + continue; + + _usedPowerTypes[powerIndex] = static_cast(i); + } +} + +void Unit::AIUpdateTick(uint32 diff) +{ + if (UnitAI* ai = GetAI()) + { + m_aiLocked = true; + ai->UpdateAI(diff); + m_aiLocked = false; + } +} + +void Unit::PushAI(UnitAI* newAI) +{ + i_AIs.emplace(newAI); +} + +void Unit::SetAI(UnitAI* newAI) +{ + PushAI(newAI); + RefreshAI(); +} + +bool Unit::PopAI() +{ + if (!i_AIs.empty()) + { + i_AIs.pop(); + return true; + } + else + return false; +} + +void Unit::RefreshAI() +{ + ASSERT(!m_aiLocked, "Tried to change current AI during UpdateAI()"); + if (i_AIs.empty()) + i_AI = nullptr; + else + i_AI = i_AIs.top(); +} + +void Unit::ScheduleAIChange() +{ + bool const charmed = IsCharmed(); + + if (charmed) + PushAI(GetScheduledChangeAI()); + else + { + RestoreDisabledAI(); + PushAI(GetScheduledChangeAI()); //This could actually be PopAI() to get the previous AI but it's required atm to trigger UpdateCharmAI() + } +} + +void Unit::RestoreDisabledAI() +{ + // Keep popping the stack until we either reach the bottom or find a valid AI + while (PopAI()) + if (GetTopAI() && dynamic_cast(GetTopAI()) == nullptr) + return; +} + +UnitAI* Unit::GetScheduledChangeAI() +{ + if (Creature* creature = ToCreature()) + return new ScheduledChangeAI(creature); + else + return nullptr; +} + +bool Unit::HasScheduledAIChange() const +{ + if (UnitAI* ai = GetAI()) + return dynamic_cast(ai) != nullptr; + else + return true; +} + +void Unit::AddToWorld() +{ + WorldObject::AddToWorld(); + + RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::EnterWorld); +} + +void Unit::RemoveFromWorld() +{ + // cleanup + ASSERT(GetGUID()); + + if (IsInWorld()) + { + m_duringRemoveFromWorld = true; + if (UnitAI* ai = GetAI()) + ai->LeavingWorld(); + + if (IsVehicle()) + RemoveVehicleKit(true); + + RemoveCharmAuras(); + RemoveBindSightAuras(); + RemoveNotOwnSingleTargetAuras(); + RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::LeaveWorld); + + RemoveAllGameObjects(); + RemoveAllDynObjects(); + + ExitVehicle(); // Remove applied auras with SPELL_AURA_CONTROL_VEHICLE + UnsummonAllTotems(); + RemoveAllControlled(); + + RemoveAreaAurasDueToLeaveWorld(); + + // clear idle movement slot to finalize follow movement to unregister formation targets + GetMotionMaster()->Clear(MOTION_SLOT_IDLE); + RemoveAllPursuers(); + + if (IsCharmed()) + RemoveCharmedBy(nullptr); + + ASSERT(!GetCharmedGUID(), "Unit %u has charmed guid when removed from world", GetEntry()); + ASSERT(!GetCharmerGUID(), "Unit %u has charmer guid when removed from world", GetEntry()); + + if (Unit* owner = GetOwner()) + { + if (owner->m_Controlled.find(this) != owner->m_Controlled.end()) + { + TC_LOG_FATAL("entities.unit", "Unit %u is in controlled list of %u when removed from world", GetEntry(), owner->GetEntry()); + ABORT(); + } + } + + WorldObject::RemoveFromWorld(); + m_duringRemoveFromWorld = false; + } +} + +void Unit::CleanupBeforeRemoveFromMap(bool finalCleanup) +{ + // This needs to be before RemoveFromWorld to make GetCaster() return a valid pointer on aura removal + InterruptNonMeleeSpells(true); + + if (IsInWorld()) + RemoveFromWorld(); + else + { + // cleanup that must happen even if not in world + if (IsVehicle()) + RemoveVehicleKit(true); + } + + ASSERT(GetGUID()); + + // A unit may be in removelist and not in world, but it is still in grid + // and may have some references during delete + RemoveAllAuras(); + RemoveAllGameObjects(); + + if (finalCleanup) + m_cleanupDone = true; + + CombatStop(); + ClearComboPointHolders(); +} + +void Unit::CleanupsBeforeDelete(bool finalCleanup) +{ + CleanupBeforeRemoveFromMap(finalCleanup); + + WorldObject::CleanupsBeforeDelete(finalCleanup); +} + +void Unit::UpdateCharmAI() +{ + if (IsCharmed()) + { + UnitAI* newAI = nullptr; + if (IsPlayer()) + { + if (Unit* charmer = GetCharmer()) + { + // first, we check if the creature's own AI specifies an override playerai for its owned players + if (Creature* creatureCharmer = charmer->ToCreature()) + { + if (CreatureAI* charmerAI = creatureCharmer->AI()) + newAI = charmerAI->GetAIForCharmedPlayer(ToPlayer()); + } + else + TC_LOG_ERROR("misc", "Attempt to assign charm AI to player %s who is charmed by non-creature %s.", GetGUID().ToString().c_str(), GetCharmerGUID().ToString().c_str()); + } + if (!newAI) // otherwise, we default to the generic one + newAI = new SimpleCharmedPlayerAI(ToPlayer()); + } + else + { + ASSERT(IsCreature()); + if (isPossessed() || IsVehicle()) + newAI = new PossessedAI(ToCreature()); + else + newAI = new PetAI(ToCreature()); + } + + ASSERT(newAI); + SetAI(newAI); + newAI->OnCharmed(true); + } + else + { + RestoreDisabledAI(); + // Hack: this is required because we want to call OnCharmed(true) on the restored AI + RefreshAI(); + if (UnitAI* ai = GetAI()) + ai->OnCharmed(true); + } +} + +CharmInfo* Unit::InitCharmInfo() +{ + if (!m_charmInfo) + m_charmInfo = std::make_unique(this); + + return m_charmInfo.get(); +} + +void Unit::DeleteCharmInfo() +{ + if (!m_charmInfo) + return; + + m_charmInfo->RestoreState(); + m_charmInfo.reset(); +} + +bool Unit::isFrozen() const +{ + return HasAuraState(AURA_STATE_FROZEN); +} + +ProcFlagsHit createProcHitMask(SpellNonMeleeDamage* damageInfo, SpellMissInfo missCondition) +{ + ProcFlagsHit hitMask = PROC_HIT_NONE; + // Check victim state + if (missCondition != SPELL_MISS_NONE) + { + switch (missCondition) + { + case SPELL_MISS_MISS: + hitMask |= PROC_HIT_MISS; + break; + case SPELL_MISS_DODGE: + hitMask |= PROC_HIT_DODGE; + break; + case SPELL_MISS_PARRY: + hitMask |= PROC_HIT_PARRY; + break; + case SPELL_MISS_BLOCK: + // spells can't be partially blocked (it's damage can though) + hitMask |= PROC_HIT_BLOCK | PROC_HIT_FULL_BLOCK; + break; + case SPELL_MISS_EVADE: + hitMask |= PROC_HIT_EVADE; + break; + case SPELL_MISS_IMMUNE: + case SPELL_MISS_IMMUNE2: + hitMask |= PROC_HIT_IMMUNE; + break; + case SPELL_MISS_DEFLECT: + hitMask |= PROC_HIT_DEFLECT; + break; + case SPELL_MISS_ABSORB: + hitMask |= PROC_HIT_ABSORB; + break; + case SPELL_MISS_REFLECT: + hitMask |= PROC_HIT_REFLECT; + break; + case SPELL_MISS_RESIST: + hitMask |= PROC_HIT_FULL_RESIST; + break; + default: + break; + } + } + else + { + // On block + if (damageInfo->blocked) + { + hitMask |= PROC_HIT_BLOCK; + if (damageInfo->fullBlock) + hitMask |= PROC_HIT_FULL_BLOCK; + } + // On absorb + if (damageInfo->absorb) + hitMask |= PROC_HIT_ABSORB; + + // Don't set hit/crit hitMask if damage is nullified + bool const damageNullified = (damageInfo->HitInfo & (HITINFO_FULL_ABSORB | HITINFO_FULL_RESIST)) != 0 || (hitMask & PROC_HIT_FULL_BLOCK) != 0; + if (!damageNullified) + { + // On crit + if (damageInfo->HitInfo & SPELL_HIT_TYPE_CRIT) + hitMask |= PROC_HIT_CRITICAL; + else + hitMask |= PROC_HIT_NORMAL; + } + else if ((damageInfo->HitInfo & HITINFO_FULL_RESIST) != 0) + hitMask |= PROC_HIT_FULL_RESIST; + } + + return hitMask; +} + +void Unit::ProcSkillsAndReactives(bool isVictim, Unit* procTarget, ProcFlags typeMask, ProcFlagsHit hitMask, WeaponAttackType /*attType*/) +{ + // Player is loaded now - do not allow passive spell casts to proc + if (GetTypeId() == TYPEID_PLAYER && ToPlayer()->GetSession()->PlayerLoading()) + return; + + // For melee/ranged based attack need update skills and set some Aura states if victim present + if (typeMask & MELEE_BASED_TRIGGER_MASK && procTarget) + { + // If exist crit/parry/dodge/block need update aura state (for victim and attacker) + if (hitMask & (PROC_HIT_CRITICAL | PROC_HIT_PARRY | PROC_HIT_DODGE | PROC_HIT_BLOCK)) + { + // for victim + if (isVictim) + { + // Skip checks for Death Knights when in Blood Presence (Rune Strike) + if (getClass() == CLASS_DEATH_KNIGHT && hitMask & (PROC_HIT_DODGE | PROC_HIT_PARRY | PROC_HIT_BLOCK)) + if (GetAuraEffect(SPELL_AURA_MOD_BASE_RESISTANCE_PCT, SPELLFAMILY_DEATHKNIGHT, 0x800000, 0, 0, GetGUID())) + return; + + // if victim and dodge attack + if (hitMask & PROC_HIT_DODGE) + { + // Update AURA_STATE on dodge + if (getClass() != CLASS_ROGUE) // skip Rogue Riposte + { + ModifyAuraState(AURA_STATE_DEFENSE, true); + StartReactiveTimer(REACTIVE_DEFENSE); + } + } + // if victim and parry attack + if (hitMask & PROC_HIT_PARRY) + { + // For Hunters only Counterattack (skip Mongoose bite) + if (getClass() == CLASS_HUNTER) + { + 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 (hitMask & PROC_HIT_BLOCK) + { + ModifyAuraState(AURA_STATE_DEFENSE, true); + StartReactiveTimer(REACTIVE_DEFENSE); + } + } + else // For attacker + { + // Overpower on victim dodge + if ((hitMask & PROC_HIT_DODGE) && GetTypeId() == TYPEID_PLAYER && getClass() == CLASS_WARRIOR) + { + ToPlayer()->AddComboPoints(procTarget, 1); + StartReactiveTimer(REACTIVE_OVERPOWER); + } + } + } + } +} + +void Unit::GetProcAurasTriggeredOnEvent(AuraApplicationProcContainer& aurasTriggeringProc, AuraApplicationList* procAuras, ProcEventInfo& eventInfo) +{ + std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now(); + + auto processAuraApplication = [&](AuraApplication* aurApp) + { + if (uint32 procEffectMask = aurApp->GetBase()->GetProcEffectMask(aurApp, eventInfo, now)) + { + aurApp->GetBase()->PrepareProcToTrigger(aurApp, eventInfo, now); + aurasTriggeringProc.emplace_back(procEffectMask, aurApp); + } + else + { + if (aurApp->GetBase()->GetSpellInfo()->HasAttribute(SPELL_ATTR0_PROC_FAILURE_BURNS_CHARGE)) + { + if (SpellProcEntry const* procEntry = sSpellMgr->GetSpellProcEntry(aurApp->GetBase()->GetSpellInfo()->Id)) + { + aurApp->GetBase()->PrepareProcChargeDrop(procEntry, eventInfo); + aurasTriggeringProc.emplace_back(0, aurApp); + } + } + + if (aurApp->GetBase()->GetSpellInfo()->HasAttribute(SPELL_ATTR2_PROC_COOLDOWN_ON_FAILURE)) + if (SpellProcEntry const* procEntry = sSpellMgr->GetSpellProcEntry(aurApp->GetBase()->GetSpellInfo()->Id)) + aurApp->GetBase()->AddProcCooldown(procEntry, now); + } + }; + + // use provided list of auras which can proc + if (procAuras) + { + for (AuraApplication* aurApp : *procAuras) + { + ASSERT(aurApp->GetTarget() == this); + processAuraApplication(aurApp); + } + } + // or generate one on our own + else + { + for (AuraApplicationMap::iterator itr = GetAppliedAuras().begin(); itr != GetAppliedAuras().end(); ++itr) + processAuraApplication(itr->second); + } +} + +void Unit::TriggerAurasProcOnEvent(Unit* actionTarget, ProcFlags typeMaskActor, ProcFlags typeMaskActionTarget, ProcFlagsSpellType spellTypeMask, + ProcFlagsSpellPhase spellPhaseMask, ProcFlagsHit hitMask, Spell* spell, DamageInfo* damageInfo, HealInfo* healInfo) +{ + // prepare data for self trigger + ProcEventInfo myProcEventInfo(this, actionTarget, actionTarget, typeMaskActor, spellTypeMask, spellPhaseMask, hitMask, spell, damageInfo, healInfo); + if (typeMaskActor) + { + AuraApplicationProcContainer myAurasTriggeringProc; + GetProcAurasTriggeredOnEvent(myAurasTriggeringProc, nullptr, myProcEventInfo); + + // needed for example for Cobra Strikes, pet does the attack, but aura is on owner + if (Player* modOwner = GetSpellModOwner()) + { + if (modOwner != this && spell) + { + AuraApplicationList modAuras; + for (auto itr = modOwner->GetAppliedAuras().begin(); itr != modOwner->GetAppliedAuras().end(); ++itr) + { + if (spell->m_appliedMods.count(itr->second->GetBase()) != 0) + modAuras.push_front(itr->second); + } + modOwner->GetProcAurasTriggeredOnEvent(myAurasTriggeringProc, &modAuras, myProcEventInfo); + } + } + TriggerAurasProcOnEvent(myProcEventInfo, myAurasTriggeringProc); + } + + // prepare data for target trigger + ProcEventInfo targetProcEventInfo(this, actionTarget, this, typeMaskActionTarget, spellTypeMask, spellPhaseMask, hitMask, spell, damageInfo, healInfo); + if (typeMaskActionTarget && actionTarget) + { + AuraApplicationProcContainer targetAurasTriggeringProc; + actionTarget->GetProcAurasTriggeredOnEvent(targetAurasTriggeringProc, nullptr, targetProcEventInfo); + actionTarget->TriggerAurasProcOnEvent(targetProcEventInfo, targetAurasTriggeringProc); + } +} + +void Unit::TriggerAurasProcOnEvent(ProcEventInfo& eventInfo, AuraApplicationProcContainer& aurasTriggeringProc) +{ + Spell const* triggeringSpell = eventInfo.GetProcSpell(); + bool const disableProcs = triggeringSpell && triggeringSpell->IsProcDisabled(); + + int32 oldProcChainLength = std::exchange(m_procChainLength, std::max(m_procChainLength + 1, triggeringSpell ? triggeringSpell->GetProcChainLength() : 0)); + + if (disableProcs) + SetCantProc(true); + + for (auto const& [procEffectMask, aurApp] : aurasTriggeringProc) + { + if (aurApp->GetRemoveMode().HasAnyFlag()) + continue; + + aurApp->GetBase()->TriggerProcOnEvent(procEffectMask, aurApp, eventInfo); + } + + if (disableProcs) + SetCantProc(false); + + m_procChainLength = oldProcChainLength; +} + +///----------Pet responses methods----------------- +void Unit::SendPetActionFeedback(uint8 response, uint32 spellId /*=0*/) +{ + Unit* owner = GetOwner(); + if (!owner || owner->GetTypeId() != TYPEID_PLAYER) + return; + + WorldPackets::Pet::PetActionFeedback actionFeedback; + actionFeedback.Response = response; + actionFeedback.SpellID = spellId; + owner->ToPlayer()->SendDirectMessage(actionFeedback.Write()); +} + +void Unit::SendPetActionSound(int32 action) +{ + Unit* owner = GetOwner(); + if (!owner || owner->GetTypeId() != TYPEID_PLAYER) + return; + + WorldPackets::Pet::PetActionSound actionSound; + actionSound.UnitGUID = GetGUID(); + actionSound.Action = action; + owner->ToPlayer()->SendDirectMessage(actionSound.Write()); +} + +void Unit::SendPetAIReaction(ObjectGuid guid) +{ + Unit* owner = GetOwner(); + if (!owner || owner->GetTypeId() != TYPEID_PLAYER) + return; + + WorldPackets::Combat::AIReaction packet; + packet.UnitGUID = guid; + packet.Reaction = AI_REACTION_HOSTILE; + + owner->ToPlayer()->SendDirectMessage(packet.Write()); +} + +///----------End of Pet responses methods---------- + +void Unit::PropagateSpeedChange() +{ + GetMotionMaster()->PropagateSpeedChange(); +} + +void Unit::StopMoving() +{ + ClearUnitState(UNIT_STATE_MOVING); + + // not need send any packets if not in world or not moving + if (!IsInWorld() || movespline->Finalized()) + return; + + // Update position now since Stop does not start a new movement that can be updated later + if (movespline->HasStarted()) + UpdateSplinePosition(); + Movement::MoveSplineInit init(this); + init.Stop(); +} + +void Unit::PauseMovement(uint32 timer/* = 0*/, uint8 slot/* = 0*/, bool forced/* = true*/) +{ + if (IsInvalidMovementSlot(slot)) + return; + + if (MovementGenerator* movementGenerator = GetMotionMaster()->GetMotionSlot(MovementSlot(slot))) + movementGenerator->Pause(timer); + + if (forced && GetMotionMaster()->GetCurrentSlot() == MovementSlot(slot)) + StopMoving(); +} + +void Unit::ResumeMovement(uint32 timer/* = 0*/, uint8 slot/* = 0*/) +{ + if (IsInvalidMovementSlot(slot)) + return; + + if (MovementGenerator* movementGenerator = GetMotionMaster()->GetMotionSlot(MovementSlot(slot))) + movementGenerator->Resume(timer); +} + +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; +} + +void Unit::SetStandState(uint8 state) +{ + SetByteValue(UNIT_FIELD_BYTES_1, UNIT_BYTES_1_OFFSET_STAND_STATE, state); + + if (IsStandState()) + RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Standing); + + if (IsPlayer()) + { + WorldPackets::Misc::StandStateUpdate packet; + packet.State = state; + ToPlayer()->SendDirectMessage(packet.Write()); + } +} + +void Unit::SetAnimTier(AnimTier animTier, bool notifyClient /*= true*/) +{ + SetByteValue(UNIT_FIELD_BYTES_1, UNIT_BYTES_1_OFFSET_ANIM_TIER, AsUnderlyingType(animTier)); + + if (notifyClient) + { + WorldPackets::Misc::SetAnimTier setAnimTier; + setAnimTier.Unit = GetGUID(); + setAnimTier.Tier = AsUnderlyingType(animTier); + SendMessageToSet(setAnimTier.Write(), true); + } +} + +bool Unit::IsPolymorphed() const +{ + uint32 transformId = 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 = GetTypeId() == TYPEID_PLAYER ? 0.1 : 0.01; + SetObjectScale(std::max(scale, scaleMin)); +} + +void Unit::SetDisplayId(uint32 modelId, bool setNative /*= false*/) +{ + SetUInt32Value(UNIT_FIELD_DISPLAYID, modelId); + // Set Gender by modelId + if (CreatureModelInfo const* minfo = sObjectMgr->GetCreatureModelInfo(modelId)) + SetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_GENDER, minfo->gender); + + if (setNative) + SetUInt32Value(UNIT_FIELD_NATIVEDISPLAYID, modelId); +} + +void Unit::RestoreDisplayId() +{ + AuraEffect* handledAura = nullptr; + // try to receive model from transform auras + AuraEffectList const& transforms = GetAuraEffectsByType(SPELL_AURA_TRANSFORM); + if (!transforms.empty()) + { + // iterate over already applied transform auras - from newest to oldest + for (auto i = transforms.begin(); i != transforms.end(); ++i) + { + if (AuraApplication const* aurApp = (*i)->GetBase()->GetApplicationOfTarget(GetGUID())) + { + if (!handledAura) + handledAura = (*i); + // prefer negative auras + if (!aurApp->IsPositive()) + { + handledAura = (*i); + break; + } + } + } + } + + AuraEffectList const& shapeshiftAura = GetAuraEffectsByType(SPELL_AURA_MOD_SHAPESHIFT); + + // transform aura was found + if (handledAura) + { + handledAura->HandleEffect(this, AURA_EFFECT_HANDLE_SEND_FOR_CLIENT, true); + return; + } + 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; + } + } + + // no auras found - set modelid to default + SetDisplayId(GetNativeDisplayId()); +} + +void Unit::SendComboPoints() +{ + if (!IsPlayer()) + return; + + if (Unit* combotarget = ObjectAccessor::GetUnit(*this, m_comboTarget)) + { + WorldPacket data; + Player* movingMe = GetCharmerOrSelfPlayer(); + if (movingMe != this) + { + data.Initialize(SMSG_PET_UPDATE_COMBO_POINTS, movingMe->GetPackGUID().size() + combotarget->GetPackGUID().size() + 1); + data << movingMe->GetPackGUID(); + } + else + data.Initialize(SMSG_UPDATE_COMBO_POINTS, combotarget->GetPackGUID().size() + 1); + data << combotarget->GetPackGUID(); + data << uint8(m_comboPoints); + ToPlayer()->SendDirectMessage(&data); + } +} + +void Unit::AddComboPoints(Unit* target, int8 count, Spell* spell) +{ + if (!count) + return; + + int8* comboPoints = spell ? &spell->m_comboPointGain : &m_comboPoints; + + // without combo points lost (duration checked in aura) + RemoveAurasByType(SPELL_AURA_RETAIN_COMBO_POINTS); + + if (target->GetGUID() == m_comboTarget) + *comboPoints += count; + else + { + if (m_comboTarget) + if (Unit* target2 = ObjectAccessor::GetUnit(*this, m_comboTarget)) + target2->RemoveComboPointHolder(GetGUID()); + + // Spells will always add value to m_comboPoints eventualy, so it must be cleared first + if (spell) + m_comboPoints = 0; + + m_comboTarget = target->GetGUID(); + *comboPoints = count; + + target->AddComboPointHolder(GetGUID()); + } + + if (*comboPoints > 5) + *comboPoints = 5; + else if (*comboPoints < 0) + *comboPoints = 0; + + if (!spell) + SendComboPoints(); +} + +void Unit::GainSpellComboPoints(int8 count) +{ + if (!count) + return; + + m_comboPoints += count; + if (m_comboPoints > 5) m_comboPoints = 5; + else if (m_comboPoints < 0) m_comboPoints = 0; + + SendComboPoints(); +} + +void Unit::ClearComboPoints() +{ + if (!m_comboTarget) + return; + + // without combopoints lost (duration checked in aura) + RemoveAurasByType(SPELL_AURA_RETAIN_COMBO_POINTS); + + m_comboPoints = 0; + + SendComboPoints(); + + if (Unit* target = ObjectAccessor::GetUnit(*this, m_comboTarget)) + target->RemoveComboPointHolder(GetGUID()); + + m_comboTarget.Clear(); +} + +void Unit::ClearComboPointHolders() +{ + while (!m_ComboPointHolders.empty()) + { + ObjectGuid guid = *m_ComboPointHolders.begin(); + + Player* player = ObjectAccessor::GetPlayer(*this, guid); + if (player && player->GetComboTarget() == GetGUID()) // recheck for safe + player->ClearComboPoints(); // remove also guid from m_ComboPointHolders; + else + m_ComboPointHolders.erase(guid); // or remove manually + } +} + +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 (getClass() == CLASS_HUNTER && HasAuraState(AURA_STATE_HUNTER_PARRY)) + ModifyAuraState(AURA_STATE_HUNTER_PARRY, false); + if (getClass() == CLASS_WARRIOR && GetTypeId() == TYPEID_PLAYER) + ToPlayer()->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 (getClass() == CLASS_HUNTER && HasAuraState(AURA_STATE_HUNTER_PARRY)) + ModifyAuraState(AURA_STATE_HUNTER_PARRY, false); + break; + case REACTIVE_OVERPOWER: + if (getClass() == CLASS_WARRIOR && GetTypeId() == TYPEID_PLAYER) + ToPlayer()->ClearComboPoints(); + break; + } + } + else + { + m_reactiveTimer[reactive] -= p_time; + } + } +} + +Unit* Unit::SelectNearbyTarget(Unit* exclude, float dist) const +{ + std::list targets; + Trinity::AnyUnfriendlyUnitInObjectRangeCheck u_check(this, this, dist); + Trinity::UnitListSearcher searcher(this, targets, u_check); + Cell::VisitAllObjects(this, searcher, dist); + + // remove current target + if (GetVictim()) + targets.remove(GetVictim()); + + if (exclude) + targets.remove(exclude); + + // remove not LoS targets + for (std::list::iterator tIter = targets.begin(); tIter != targets.end();) + { + if (!IsWithinLOSInMap(*tIter) || (*tIter)->IsTotem() || (*tIter)->IsSpiritService() || (*tIter)->IsCritter()) + targets.erase(tIter++); + else + ++tIter; + } + + // no appropriate targets + if (targets.empty()) + return nullptr; + + // select random + return Trinity::Containers::SelectRandomContainerElement(targets); +} + +void ApplyPercentModFloatVar(float& var, float val, bool apply) +{ + var *= (apply ? (100.0f + val) / 100.0f : 100.0f / (100.0f + val)); +} + +uint32 Unit::GetBaseAttackTime(WeaponAttackType att) const +{ + return m_baseAttackSpeed[att]; +} + +void Unit::SetBaseAttackTime(WeaponAttackType att, uint32 val) +{ + m_baseAttackSpeed[att] = val; + UpdateAttackTimeField(att); +} + +void Unit::UpdateAttackTimeField(WeaponAttackType att) +{ + switch (att) + { + case BASE_ATTACK: + case OFF_ATTACK: + SetFloatValue(UNIT_FIELD_BASEATTACKTIME + AsUnderlyingType(att), uint32(m_baseAttackSpeed[att] * m_modAttackSpeedPct[att])); + break; + case RANGED_ATTACK: + SetFloatValue(UNIT_FIELD_RANGEDATTACKTIME, uint32(m_baseAttackSpeed[RANGED_ATTACK] * m_modAttackSpeedPct[RANGED_ATTACK])); + break; + default: + break; + } +} + +void Unit::ApplyAttackTimePercentMod(WeaponAttackType att, float val, bool apply) +{ + float amount = GetFloatValue(UNIT_FIELD_BASEATTACKTIME + AsUnderlyingType(att)); + float hasteAmount = GetTypeId() == TYPEID_PLAYER ? GetFloatValue(PLAYER_FIELD_MOD_HASTE) : 0.f; + float rangedHasteAmount = GetTypeId() == TYPEID_PLAYER ? GetFloatValue(PLAYER_FIELD_MOD_RANGED_HASTE) : 0.f; + + float remainingTimePct = float(m_attackTimer[att]) / (m_baseAttackSpeed[att] * m_modAttackSpeedPct[att]); + if (val > 0.f) + { + ApplyPercentModFloatVar(m_modAttackSpeedPct[att], val, !apply); + ApplyPercentModFloatVar(amount, val, !apply); + + if (GetTypeId() == TYPEID_PLAYER) + { + if (att == BASE_ATTACK) + ApplyPercentModFloatVar(hasteAmount, val, !apply); + else if (att == RANGED_ATTACK) + ApplyPercentModFloatVar(rangedHasteAmount, val, !apply); + } + } + else + { + ApplyPercentModFloatVar(m_modAttackSpeedPct[att], -val, apply); + ApplyPercentModFloatVar(amount, -val, apply); + + if (GetTypeId() == TYPEID_PLAYER) + { + if (att == BASE_ATTACK) + ApplyPercentModFloatVar(hasteAmount, -val, apply); + else if (att == RANGED_ATTACK) + ApplyPercentModFloatVar(rangedHasteAmount, -val, apply); + } + } + + SetFloatValue(UNIT_FIELD_BASEATTACKTIME + AsUnderlyingType(att), amount); + + if (GetTypeId() == TYPEID_PLAYER) + { + SetFloatValue(PLAYER_FIELD_MOD_HASTE, hasteAmount); + SetFloatValue(PLAYER_FIELD_MOD_RANGED_HASTE, rangedHasteAmount); + } + + UpdateAttackTimeField(att); + m_attackTimer[att] = uint32(m_baseAttackSpeed[att] * m_modAttackSpeedPct[att] * remainingTimePct); +} + +void Unit::ApplyHasteRegenMod(float val, bool apply) +{ + if (!IsPlayer()) + return; + + float amount = GetFloatValue(PLAYER_FIELD_MOD_HASTE_REGEN); + + if (val > 0.f) + ApplyPercentModFloatVar(amount, val, !apply); + else + ApplyPercentModFloatVar(amount, -val, apply); + + SetFloatValue(PLAYER_FIELD_MOD_HASTE_REGEN, amount); + + for (PowerType powerType : GetUsedPowerTypes()) + { + if (powerType == MAX_POWERS || GetPowerIndex(powerType) == MAX_POWERS) + continue; + + if (DBCManager::IsPowerTypeAffectedByHaste(powerType)) + UpdatePowerRegeneration(powerType); + } + + if (IsPlayer() && getClass() == CLASS_DEATH_KNIGHT) + UpdatePowerRegeneration(POWER_RUNE); +} + +void Unit::ApplyCastTimePercentMod(float val, bool apply, bool withCastHaste /*= true*/, bool withCastSpeed /*= true*/) +{ + float speedAmount = GetFloatValue(UNIT_MOD_CAST_SPEED); + float hasteAmount = GetFloatValue(UNIT_MOD_CAST_HASTE); + + if (val > 0.f) + { + if (withCastSpeed) + ApplyPercentModFloatVar(speedAmount, val, !apply); + if (withCastHaste) + ApplyPercentModFloatVar(hasteAmount, val, !apply); + } + else + { + if (withCastSpeed) + ApplyPercentModFloatVar(speedAmount, -val, apply); + if (withCastHaste) + ApplyPercentModFloatVar(hasteAmount, -val, apply); + } + + SetFloatValue(UNIT_MOD_CAST_SPEED, speedAmount); + SetFloatValue(UNIT_MOD_CAST_HASTE, hasteAmount); +} + +uint32 Unit::GetCastingTimeForBonus(SpellInfo const* spellProto, DamageEffectType damagetype, uint32 CastingTime) const +{ + // Not apply this to creature cast spells with casttime == 0 + if (CastingTime == 0 && GetTypeId() == TYPEID_UNIT && !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; + } + break; + default: + break; + } + + if (spellProto->Effects[i].IsTargetingArea()) + AreaEffect = true; + } + + // Combined Spells with Both Over Time and Direct Damage + if (overTime > 0 && CastingTime > 0 && DirectDamage) + { + // mainly for DoTs which are 3500 here otherwise + uint32 OriginalCastTime = spellProto->CalcCastTime(getLevel()); + 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 (GetTypeId() == TYPEID_UNIT && IsPet()) + { + Pet* pet = ((Pet*)this); + if (pet->isControlled()) + { + Unit* owner = GetOwner(); + if (owner && (owner->GetTypeId() == TYPEID_PLAYER) && owner->ToPlayer()->GetGroup()) + { + owner->ToPlayer()->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_PET_AURAS); + pet->SetAuraUpdateMaskForRaid(slot); + } + } + } +} + +void Unit::SetCantProc(bool apply) +{ + if (apply) + ++m_procDeep; + else + { + ASSERT(m_procDeep); + --m_procDeep; + } +} + +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(getLevel()); + // 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) const +{ + if (GetTypeId() != TYPEID_PLAYER || (IsInFeralForm() && !normalized)) + return GetBaseAttackTime(attType) / 1000.0f; + + Item* weapon = ToPlayer()->GetWeaponForAttack(attType, true); + if (!weapon) + return 2.0f; + + if (!normalized) + return weapon->GetTemplate()->GetDelay() / 1000.0f; + + switch (weapon->GetTemplate()->GetSubClass()) + { + case ITEM_SUBCLASS_WEAPON_AXE2: + case ITEM_SUBCLASS_WEAPON_MACE2: + case ITEM_SUBCLASS_WEAPON_POLEARM: + case ITEM_SUBCLASS_WEAPON_SWORD2: + case ITEM_SUBCLASS_WEAPON_STAFF: + case ITEM_SUBCLASS_WEAPON_FISHING_POLE: + return 3.3f; + case ITEM_SUBCLASS_WEAPON_BOW: + case ITEM_SUBCLASS_WEAPON_GUN: + case ITEM_SUBCLASS_WEAPON_CROSSBOW: + case ITEM_SUBCLASS_WEAPON_THROWN: + return 2.8f; + case ITEM_SUBCLASS_WEAPON_AXE: + case ITEM_SUBCLASS_WEAPON_MACE: + case ITEM_SUBCLASS_WEAPON_SWORD: + case ITEM_SUBCLASS_WEAPON_EXOTIC: + case ITEM_SUBCLASS_WEAPON_EXOTIC2: + case ITEM_SUBCLASS_WEAPON_FIST_WEAPON: + return 2.4f; + case ITEM_SUBCLASS_WEAPON_DAGGER: + return 1.7f; + default: + return weapon->GetTemplate()->GetDelay() / 1000.0f; + } +} + +Pet* Unit::CreateTamedPetFrom(Creature* creatureTarget, uint32 spell_id) +{ + if (GetTypeId() != TYPEID_PLAYER) + return nullptr; + + Pet* pet = new Pet(ToPlayer(), HUNTER_PET); + + if (!pet->CreateBaseAtCreature(creatureTarget)) + { + delete pet; + return nullptr; + } + + uint8 level = getLevel(); + + InitTamedPet(pet, level, spell_id); + + return pet; +} + +Pet* Unit::CreateTamedPetFrom(uint32 creatureEntry, uint32 spell_id) +{ + if (GetTypeId() != TYPEID_PLAYER) + return nullptr; + + CreatureTemplate const* creatureInfo = sObjectMgr->GetCreatureTemplate(creatureEntry); + if (!creatureInfo) + return nullptr; + + Pet* pet = new Pet(ToPlayer(), HUNTER_PET); + + if (!pet->CreateBaseAtCreatureInfo(creatureInfo, this) || !InitTamedPet(pet, getLevel(), spell_id)) + { + delete pet; + return nullptr; + } + + return pet; +} + +bool Unit::InitTamedPet(Pet* pet, uint8 level, uint32 spell_id) +{ + pet->SetCreatorGUID(GetGUID()); + pet->SetFaction(GetFaction()); + pet->SetUInt32Value(UNIT_CREATED_BY_SPELL, spell_id); + + if (GetTypeId() == TYPEID_PLAYER) + pet->SetUInt32Value(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED); + + if (!pet->InitStatsForLevel(level)) + { + TC_LOG_ERROR("entities.unit", "Pet::InitStatsForLevel() failed for creature (Entry: %u)!", pet->GetEntry()); + return false; + } + + PhasingHandler::InheritPhaseShift(pet, this); + + pet->GetCharmInfo()->SetPetNumber(sObjectMgr->GeneratePetNumber(), true); + // this enables pet details window (Shift+P) + pet->InitPetCreateSpells(); + //pet->InitLevelupSpellsForLevel(); + pet->SetFullHealth(); + pet->CastSpell(pet, 99289, true); // Energize + pet->SetReactState(REACT_ASSIST); + return true; +} + +void Unit::SendDurabilityLoss(Player* receiver, uint32 percent) +{ + WorldPackets::Misc::DurabilityDamageDeath packet; + packet.Percent = percent; + receiver->SendDirectMessage(packet.Write()); +} + +void Unit::PlayOneShotAnimKitId(uint16 animKitId) +{ + WorldPackets::Misc::PlayOneShotAnimKit packet; + packet.Unit = GetGUID(); + packet.AnimKitID = animKitId; + SendMessageToSet(packet.Write(), true); +} + +/*static*/ void Unit::Kill(Unit* attacker, Unit* victim, bool durabilityLoss /*= true*/) +{ + // Prevent killing unit twice (and giving reward from kill twice) + if (!victim->GetHealth()) + return; + + if (attacker && !attacker->IsInMap(victim)) + attacker = nullptr; + + // find player: owner of controlled `this` or `this` itself maybe + Player* player = nullptr; + if (attacker) + player = attacker->GetCharmerOrOwnerPlayerOrPlayerItself(); + + Creature* creature = victim->ToCreature(); + + bool isRewardAllowed = true; + if (creature) + { + isRewardAllowed = creature->IsDamageEnoughForLootingAndReward(); + if (!isRewardAllowed) + creature->SetLootRecipient(nullptr); + } + + if (isRewardAllowed && creature) + { + if (Player* lootRecipient = creature->GetLootRecipient()) + { + // Loot recipient can be in a different map + if (!creature->IsInMap(lootRecipient)) + { + if (Group* group = creature->GetLootRecipientGroup()) + { + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (!member || !creature->IsInMap(member)) + continue; + + player = member; + break; + } + } + } + else + player = creature->GetLootRecipient(); + } + } + + // Exploit fix + if (creature && creature->IsPet() && creature->GetOwnerOrCreatorGUID().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 << uint64(player->GetGUID()); // player with killing blow + data << uint64(victim->GetGUID()); // victim + + Player* looter = player; + Group* group = player->GetGroup(); + bool hasLooterGuid = false; + + if (group) + { + group->BroadcastPacket(&data, group->GetMemberGroup(player->GetGUID()) != 0); + + if (creature && !creature->IsLootDisabled()) + { + group->UpdateLooterGuid(creature, true); + if (group->GetLooterGuid()) + { + 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 && !creature->IsLootDisabled()) + { + WorldPackets::Loot::LootList lootList; + lootList.Owner = creature->GetGUID(); + lootList.RoundRobinWinner = player->GetGUID(); + + player->SendMessageToSet(lootList.Write(), false); + } + } + + // Generate loot before updating looter + if (creature && !creature->IsLootDisabled()) + { + Loot* loot = &creature->loot; + loot->clear(); + + if (uint32 lootid = creature->GetCreatureTemplate()->lootid) + loot->FillLoot(lootid, LootTemplates_Creature, looter, false, false, creature->GetLootMode()); + + if (creature->GetLootMode() > 0) + 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 (!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 (attacker && (attacker->IsPet() || attacker->IsTotem())) + { + // proc only once for victim + if (Unit* owner = attacker->GetOwner()) + Unit::ProcSkillsAndAuras(owner, victim, PROC_FLAG_KILL, PROC_FLAG_NONE, PROC_SPELL_TYPE_MASK_ALL, PROC_SPELL_PHASE_NONE, PROC_HIT_NONE, nullptr, nullptr, nullptr); + } + + if (!victim->IsCritter()) + Unit::ProcSkillsAndAuras(attacker, victim, PROC_FLAG_KILL, PROC_FLAG_NONE, PROC_SPELL_TYPE_MASK_ALL, PROC_SPELL_PHASE_NONE, PROC_HIT_NONE, nullptr, nullptr, nullptr); + + // Proc auras on death - must be before aura/combat remove + Unit::ProcSkillsAndAuras(victim, victim, PROC_FLAG_NONE, PROC_FLAG_DEATH, PROC_SPELL_TYPE_MASK_ALL, PROC_SPELL_PHASE_NONE, PROC_HIT_NONE, nullptr, nullptr, nullptr); + + // update get killing blow achievements, must be done before setDeathState to be able to require auras on target + // and before Spirit of Redemption as it also removes auras + if (attacker) + if (Player* killerPlayer = attacker->GetCharmerOrOwnerPlayerOrPlayerItself()) + killerPlayer->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_GET_KILLING_BLOWS, 1, 0, 0, victim); + + TC_LOG_DEBUG("entities.unit", "SET JUST_DIED"); + victim->setDeathState(JUST_DIED); + + // Inform pets (if any) when player kills target) + // MUST come after victim->setDeathState(JUST_DIED); or pet next target + // selection will get stuck on same target and break pet react state + if (player) + { + Pet* pet = player->GetPet(); + if (pet && pet->IsAlive() && pet->isControlled()) + { + if (pet->IsAIEnabled()) + pet->AI()->KilledUnit(victim); + else + TC_LOG_ERROR("entities.unit", "Pet doesn't have any AI in Unit::Kill(). %s", pet->GetGUID().ToString().c_str()); + } + } + + // 10% durability loss on death + if (Player* plrVictim = victim->ToPlayer()) + { + // remember victim PvP death for corpse type and corpse reclaim delay + // at original death (not at SpiritOfRedemtionTalent timeout) + plrVictim->SetPvPDeath(player != nullptr); + + // only if not player and not controlled by player pet. And not at BG + if ((durabilityLoss && !player && !victim->ToPlayer()->InBattleground()) || (player && sWorld->getBoolConfig(CONFIG_DURABILITY_LOSS_IN_PVP))) + { + double baseLoss = sWorld->getRate(RATE_DURABILITY_LOSS_ON_DEATH); + uint32 loss = uint32(baseLoss - (baseLoss * plrVictim->GetTotalAuraMultiplier(SPELL_AURA_MOD_DURABILITY_LOSS))); + TC_LOG_DEBUG("entities.unit", "We are dead, losing %u percent durability", loss); + // Durability loss is calculated more accurately again for each item in Player::DurabilityLoss + plrVictim->DurabilityLossAll(baseLoss, false); + // durability lost message + plrVictim->SendDurabilityLoss(plrVictim, loss); + } + // Call KilledUnit for creatures + if (attacker && attacker->IsCreature() && attacker->IsAIEnabled()) + attacker->ToCreature()->AI()->KilledUnit(victim); + + // last damage from non duel opponent or opponent controlled creature + if (plrVictim->duel) + { + plrVictim->duel->opponent->CombatStopWithPets(true); + plrVictim->CombatStopWithPets(true); + plrVictim->DuelComplete(DUEL_INTERRUPTED); + } + } + else // creature died + { + TC_LOG_DEBUG("entities.unit", "DealDamageNotPlayer"); + + if (creature && !creature->IsPet()) + { + // must be after setDeathState which resets dynamic flags + if (!creature->loot.isLooted()) + creature->SetFlag(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_LOOTABLE); + else + creature->AllLootRemovedFromCorpse(); + } + + // Call KilledUnit for creatures, this needs to be called after the lootable flag is set + if (attacker && attacker->IsCreature() && attacker->IsAIEnabled()) + attacker->ToCreature()->AI()->KilledUnit(victim); + + // Call creature just died function + if (CreatureAI* ai = creature->AI()) + ai->JustDied(attacker); + + if (TempSummon* summon = creature->ToTempSummon()) + if (Unit* summoner = summon->GetSummoner()) + if (summoner->ToCreature() && summoner->IsAIEnabled()) + summoner->ToCreature()->AI()->SummonedCreatureDies(creature, attacker); + + // Dungeon specific stuff, only applies to players killing creatures + if (creature->GetInstanceId()) + { + Map* instanceMap = creature->GetMap(); + /// @todo do instance binding anyway if the charmer/owner is offline + + if (instanceMap->IsDungeon() && ((attacker && attacker->GetCharmerOrOwnerPlayerOrPlayerItself()) || attacker == victim)) + { + if (instanceMap->IsRaidOrHeroicDungeon()) + { + if (creature->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_INSTANCE_BIND) + instanceMap->ToInstanceMap()->PermBindAllPlayers(); + } + else + { + // the reset time is set but not added to the scheduler + // until the players leave the instance + time_t resettime = GameTime::GetGameTime() + 2 * HOUR; + if (InstanceSave* save = sInstanceSaveMgr->GetInstanceSave(creature->GetInstanceId())) + if (save->GetResetTime() < resettime) + save->SetResetTime(resettime); + } + } + } + } + + // outdoor pvp things, do these after setting the death state, else the player activity notify won't work... doh... + // handle player kill only if not suicide (spirit of redemption for example) + if (player && attacker != victim) + { + if (OutdoorPvP* pvp = player->GetOutdoorPvP()) + pvp->HandleKill(player, victim); + + if (Battlefield* bf = sBattlefieldMgr->GetBattlefieldToZoneId(player->GetMap(), player->GetZoneId())) + bf->HandleKill(player, victim); + } + + //if (victim->GetTypeId() == TYPEID_PLAYER) + // if (OutdoorPvP* pvp = victim->ToPlayer()->GetOutdoorPvP()) + // pvp->HandlePlayerActivityChangedpVictim->ToPlayer(); + + // battleground things (do this at the end, so the death state flag will be properly set to handle in the bg->handlekill) + if (player && player->InBattleground()) + { + if (Battleground* bg = player->GetBattleground()) + { + if (Player* playerVictim = victim->ToPlayer()) + bg->HandleKillPlayer(playerVictim, player); + else + bg->HandleKillUnit(victim->ToCreature(), player); + } + } + + // achievement stuff + if (attacker && victim->GetTypeId() == TYPEID_PLAYER) + { + if (attacker->GetTypeId() == TYPEID_UNIT) + victim->ToPlayer()->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_KILLED_BY_CREATURE, attacker->GetEntry()); + else if (attacker->GetTypeId() == TYPEID_PLAYER && victim != attacker) + victim->ToPlayer()->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_KILLED_BY_PLAYER, 1, attacker->ToPlayer()->GetTeam()); + } + + // Hook for OnPVPKill Event + if (attacker) + { + if (Player* killerPlr = attacker->ToPlayer()) + { + if (Player* killedPlr = victim->ToPlayer()) + sScriptMgr->OnPVPKill(killerPlr, killedPlr); + else if (Creature* killedCre = victim->ToCreature()) + sScriptMgr->OnCreatureKill(killerPlr, killedCre); + } + else if (Creature* killerCre = attacker->ToCreature()) + { + if (Player* killed = victim->ToPlayer()) + sScriptMgr->OnPlayerKilledByCreature(killerCre, killed); + } + } +} + +void Unit::SetControlled(bool apply, UnitState state) +{ + if (apply) + { + if (HasUnitState(state)) + return; + + if (state & UNIT_STATE_CONTROLLED) + CastStop(); + + AddUnitState(state); + switch (state) + { + case UNIT_STATE_STUNNED: + SetStunned(true); + break; + case UNIT_STATE_ROOT: + if (!HasUnitState(UNIT_STATE_STUNNED)) + SetRooted(true); + break; + case UNIT_STATE_CONFUSED: + if (!HasUnitState(UNIT_STATE_STUNNED)) + { + ClearUnitState(UNIT_STATE_MELEE_ATTACKING); + SendMeleeAttackStop(); + // SendAutoRepeatCancel ? + SetConfused(true); + } + break; + case UNIT_STATE_FLEEING: + if (!HasUnitState(UNIT_STATE_STUNNED | UNIT_STATE_CONFUSED)) + { + ClearUnitState(UNIT_STATE_MELEE_ATTACKING); + SendMeleeAttackStop(); + // SendAutoRepeatCancel ? + SetFeared(true); + } + break; + default: + break; + } + } + else + { + switch (state) + { + case UNIT_STATE_STUNNED: + if (HasAuraType(SPELL_AURA_MOD_STUN)) + return; + + ClearUnitState(state); + SetStunned(false); + break; + case UNIT_STATE_ROOT: + if (HasAuraType(SPELL_AURA_MOD_ROOT) || GetVehicle() || (IsCreature() && ToCreature()->IsSessile())) + return; + + ClearUnitState(state); + SetRooted(false); + break; + case UNIT_STATE_CONFUSED: + if (HasAuraType(SPELL_AURA_MOD_CONFUSE)) + return; + + ClearUnitState(state); + SetConfused(false); + break; + case UNIT_STATE_FLEEING: + if (HasAuraType(SPELL_AURA_MOD_FEAR)) + return; + + ClearUnitState(state); + SetFeared(false); + break; + default: + return; + } + + ApplyControlStatesIfNeeded(); + } +} + +void Unit::ApplyControlStatesIfNeeded() +{ + // Unit States might have been already cleared but auras still present. I need to check with HasAuraType + if (HasUnitState(UNIT_STATE_STUNNED) || HasAuraType(SPELL_AURA_MOD_STUN)) + SetStunned(true); + + if (HasUnitState(UNIT_STATE_ROOT) || HasAuraType(SPELL_AURA_MOD_ROOT)) + SetRooted(true); + + if (HasUnitState(UNIT_STATE_CONFUSED) || HasAuraType(SPELL_AURA_MOD_CONFUSE)) + SetConfused(true); + + if (HasUnitState(UNIT_STATE_FLEEING) || HasAuraType(SPELL_AURA_MOD_FEAR)) + SetFeared(true); +} + +void Unit::SetStunned(bool apply) +{ + if (apply) + { + SetTarget(ObjectGuid::Empty); + SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_STUNNED); + + // Do not stop movement when the unit is affected by parabolic spline movement (knockbacks, pulls, scripted leaps etc.) + if (!movespline->isParabolic()) + StopMoving(); + + if (GetTypeId() == TYPEID_PLAYER) + SetStandState(UNIT_STAND_STATE_STAND); + SetRooted(true); + + CastStop(); + } + else + { + if (IsAlive() && GetVictim()) + SetTarget(EnsureVictim()->GetGUID()); + + // don't remove UNIT_FLAG_STUNNED for pet when owner is mounted (disabled pet's interface) + Unit* owner = GetCharmerOrOwner(); + if (!owner || owner->GetTypeId() != TYPEID_PLAYER || !owner->ToPlayer()->IsMounted()) + RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_STUNNED); + + if (!HasUnitState(UNIT_STATE_ROOT)) // prevent moving if it also has root effect + SetRooted(false); + } +} + +void Unit::SetRooted(bool apply, bool packetOnly /*= false*/) +{ + if (!packetOnly) + { + if (apply) + { + // MOVEMENTFLAG_ROOT cannot be used in conjunction with MOVEMENTFLAG_MASK_MOVING (tested 3.3.5a) + // this will freeze clients. That's why we remove MOVEMENTFLAG_MASK_MOVING before + // setting MOVEMENTFLAG_ROOT + RemoveUnitMovementFlag(MOVEMENTFLAG_MASK_MOVING); + AddUnitMovementFlag(MOVEMENTFLAG_ROOT); + + // Do not stop movement when the unit is affected by parabolic spline movement (knockbacks, pulls, scripted leaps etc.) + if (!movespline->isParabolic()) + StopMoving(); + } + else + RemoveUnitMovementFlag(MOVEMENTFLAG_ROOT); + } + + if (apply) + Movement::PacketSender(this, SMSG_SPLINE_MOVE_ROOT, SMSG_MOVE_ROOT, SMSG_MOVE_ROOT).Send(); + else + Movement::PacketSender(this, SMSG_SPLINE_MOVE_UNROOT, SMSG_MOVE_UNROOT, SMSG_MOVE_UNROOT).Send(); +} + +void Unit::SetFeared(bool apply) +{ + if (apply) + { + // block control to real player in control (eg charmer) + if (GetCharmerOrSelfPlayer()) + GetCharmerOrSelfPlayer()->SetClientControl(this, false); + + SetTarget(ObjectGuid::Empty); + + Unit* caster = nullptr; + Unit::AuraEffectList const& fearAuras = GetAuraEffectsByType(SPELL_AURA_MOD_FEAR); + if (!fearAuras.empty()) + caster = ObjectAccessor::GetUnit(*this, fearAuras.front()->GetCasterGUID()); + if (!caster) + caster = getAttackerForHelper(); + GetMotionMaster()->MoveFleeing(caster, fearAuras.empty() ? sWorld->getIntConfig(CONFIG_CREATURE_FAMILY_FLEE_DELAY) : 0); // caster == nullptr processed in MoveFleeing + } + else + { + if (IsAlive()) + { + if (GetMotionMaster()->GetCurrentMovementGeneratorType() == FLEEING_MOTION_TYPE) + GetMotionMaster()->MovementExpired(); + if (GetVictim()) + SetTarget(EnsureVictim()->GetGUID()); + } + + // allow control to real player in control (eg charmer) + if (GetCharmerOrSelfPlayer()) + GetCharmerOrSelfPlayer()->SetClientControl(this, true); + } +} + +void Unit::SetConfused(bool apply) +{ + if (apply) + { + // block control to real player in control (eg charmer) + if (GetCharmerOrSelfPlayer()) + GetCharmerOrSelfPlayer()->SetClientControl(this, false); + + SetTarget(ObjectGuid::Empty); + GetMotionMaster()->MoveConfused(); + } + else + { + if (IsAlive()) + { + if (GetMotionMaster()->GetCurrentMovementGeneratorType() == CONFUSED_MOTION_TYPE) + GetMotionMaster()->MovementExpired(); + if (GetVictim()) + SetTarget(EnsureVictim()->GetGUID()); + } + + // allow control to real player in control (eg charmer) + if (GetCharmerOrSelfPlayer()) + GetCharmerOrSelfPlayer()->SetClientControl(this, true); + } +} + +bool Unit::SetCharmedBy(Unit* charmer, CharmType type, AuraApplication const* aurApp) +{ + if (!charmer) + return false; + + // dismount players when charmed + if (GetTypeId() == TYPEID_PLAYER) + RemoveAurasByType(SPELL_AURA_MOUNTED); + + if (charmer->GetTypeId() == TYPEID_PLAYER) + charmer->RemoveAurasByType(SPELL_AURA_MOUNTED); + + ASSERT(type != CHARM_TYPE_POSSESS || charmer->GetTypeId() == TYPEID_PLAYER); + ASSERT((type == CHARM_TYPE_VEHICLE) == (GetVehicleKit() && GetVehicleKit()->IsControllableVehicle())); + + TC_LOG_DEBUG("entities.unit", "SetCharmedBy: charmer %u (GUID %u), charmed %u (GUID %u), type %u.", charmer->GetEntry(), charmer->GetGUID().GetCounter(), GetEntry(), GetGUID().GetCounter(), uint32(type)); + + if (this == charmer) + { + TC_LOG_FATAL("entities.unit", "Unit::SetCharmedBy: Unit %u (GUID %u) is trying to charm itself!", GetEntry(), GetGUID().GetCounter()); + return false; + } + + //if (HasUnitState(UNIT_STATE_UNATTACKABLE)) + // return false; + + if (GetTypeId() == TYPEID_PLAYER && ToPlayer()->GetTransport()) + { + TC_LOG_FATAL("entities.unit", "Unit::SetCharmedBy: Player on transport is trying to charm %u (GUID %u)", GetEntry(), GetGUID().GetCounter()); + return false; + } + + // Already charmed + if (GetCharmerGUID()) + { + TC_LOG_FATAL("entities.unit", "Unit::SetCharmedBy: %u (GUID %u) has already been charmed but %u (GUID %u) is trying to charm it!", GetEntry(), GetGUID().GetCounter(), charmer->GetEntry(), charmer->GetGUID().GetCounter()); + return false; + } + + CastStop(); + CombatStop(); /// @todo CombatStop(true) may cause crash (interrupt spells) + + Player* playerCharmer = charmer->ToPlayer(); + + // Charmer stop charming + if (playerCharmer) + { + playerCharmer->StopCastingCharm(); + playerCharmer->StopCastingBindSight(); + } + + // Charmed stop charming + if (GetTypeId() == TYPEID_PLAYER) + { + ToPlayer()->StopCastingCharm(); + ToPlayer()->StopCastingBindSight(); + } + + // StopCastingCharm may remove a possessed pet? + if (!IsInWorld()) + { + TC_LOG_FATAL("entities.unit", "Unit::SetCharmedBy: %u (GUID %u) is not in world but %u (GUID %u) is trying to charm it!", GetEntry(), GetGUID().GetCounter(), charmer->GetEntry(), charmer->GetGUID().GetCounter()); + 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().HasAnyFlag()) + { + // properly clean up charm changes up to this point to avoid leaving the unit in partially charmed state + SetFaction(_oldFactionId); + GetMotionMaster()->InitDefault(); + charmer->SetCharm(this, false); + return false; + } + + _oldFactionId = GetFaction(); + SetFaction(charmer->GetFaction()); + + // Set charmed + charmer->SetCharm(this, true); + + if (GetTypeId() == TYPEID_UNIT) + { + GetMotionMaster()->Clear(MOTION_SLOT_ACTIVE); + PauseMovement(0, MOTION_SLOT_IDLE); + StopMoving(); + } + else if (Player* player = ToPlayer()) + { + if (player->isAFK()) + player->ToggleAFK(); + + player->SetClientControl(this, false); + } + + // charm is set by aura, and aura effect remove handler was called during apply handler execution + // prevent undefined behaviour + if (aurApp && aurApp->GetRemoveMode().HasAnyFlag()) + return false; + + // Pets already have a properly initialized CharmInfo, don't overwrite it. + if (type != CHARM_TYPE_VEHICLE && !GetCharmInfo()) + { + if (CharmInfo* charmInfo = InitCharmInfo()) + { + if (type == CHARM_TYPE_POSSESS) + charmInfo->InitPossessCreateSpells(); + else + charmInfo->InitCharmCreateSpells(); + } + } + + if (playerCharmer) + { + switch (type) + { + case CHARM_TYPE_VEHICLE: + SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_POSSESSED); + playerCharmer->SetClientControl(this, true); + playerCharmer->VehicleSpellInitialize(); + break; + case CHARM_TYPE_POSSESS: + SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_POSSESSED); + charmer->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_REMOVE_CLIENT_CONTROL); + playerCharmer->SetClientControl(this, true); + playerCharmer->PossessSpellInitialize(); + AddUnitState(UNIT_STATE_POSSESSED); + break; + case CHARM_TYPE_CHARM: + if (GetTypeId() == TYPEID_UNIT && charmer->getClass() == CLASS_WARLOCK) + { + CreatureTemplate const* cinfo = ToCreature()->GetCreatureTemplate(); + if (cinfo && cinfo->type == CREATURE_TYPE_DEMON) + { + // to prevent client crash + SetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_CLASS, (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())); // cast can't be helped + } + } + playerCharmer->CharmSpellInitialize(); + break; + default: + case CHARM_TYPE_CONVERT: + break; + } + } + + AddUnitState(UNIT_STATE_CHARMED); + + if ((GetTypeId() != TYPEID_PLAYER) || (charmer->GetTypeId() != TYPEID_PLAYER)) + { + // AI will schedule its own change if appropriate + if (UnitAI* ai = GetAI()) + ai->OnCharmed(false); + else + ScheduleAIChange(); + } + return true; +} + +void Unit::RemoveCharmedBy(Unit* charmer) +{ + if (!IsCharmed()) + return; + + if (charmer) + ASSERT(charmer == GetCharmer()); + else + charmer = GetCharmer(); + + ASSERT(charmer); + + CharmType type; + if (HasUnitState(UNIT_STATE_POSSESSED)) + type = CHARM_TYPE_POSSESS; + else if (charmer && charmer->IsOnVehicle(this)) + type = CHARM_TYPE_VEHICLE; + else + type = CHARM_TYPE_CHARM; + + CastStop(); + AttackStop(); + + if (_oldFactionId) + { + SetFaction(_oldFactionId); + _oldFactionId = 0; + } + else + RestoreFaction(); + + GetMotionMaster()->InitDefault(); + + // Vehicle should not attack its passenger after he exists the seat + if (type != CHARM_TYPE_VEHICLE) + LastCharmerGUID = charmer->GetGUID(); + + ASSERT(type != CHARM_TYPE_POSSESS || charmer->GetTypeId() == TYPEID_PLAYER); + ASSERT(type != CHARM_TYPE_VEHICLE || (GetTypeId() == TYPEID_UNIT && IsVehicle())); + + charmer->SetCharm(this, false); + m_combatManager.RevalidateCombat(); + + Player* playerCharmer = charmer->ToPlayer(); + + if (playerCharmer) + { + switch (type) + { + case CHARM_TYPE_VEHICLE: + playerCharmer->SetClientControl(this, false); + playerCharmer->SetClientControl(charmer, true); + RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_POSSESSED); + break; + case CHARM_TYPE_POSSESS: + ClearUnitState(UNIT_STATE_POSSESSED); + playerCharmer->SetClientControl(this, false); + playerCharmer->SetClientControl(charmer, true); + charmer->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_REMOVE_CLIENT_CONTROL); + RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_POSSESSED); + break; + case CHARM_TYPE_CHARM: + if (GetTypeId() == TYPEID_UNIT && charmer->getClass() == CLASS_WARLOCK) + { + CreatureTemplate const* cinfo = ToCreature()->GetCreatureTemplate(); + if (cinfo && cinfo->type == CREATURE_TYPE_DEMON) + { + SetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_CLASS, uint8(cinfo->unit_class)); + if (GetCharmInfo()) + GetCharmInfo()->SetPetNumber(0, true); + else + TC_LOG_ERROR("entities.unit", "Aura::HandleModCharm: %s has a charm aura but no charm info!", GetGUID().ToString().c_str()); + } + } + break; + case CHARM_TYPE_CONVERT: + break; + } + } + + if (Player* player = ToPlayer()) + player->SetClientControl(this, true); + + EngageWithTarget(charmer); + + if (playerCharmer && this != charmer->GetFirstControlled()) + playerCharmer->SendRemoveControlBar(); + + // a guardian should always have charminfo + if (!IsGuardian()) + DeleteCharmInfo(); + + // reset confused movement for example + ApplyControlStatesIfNeeded(); + + if (GetTypeId() != TYPEID_PLAYER || charmer->GetTypeId() == TYPEID_UNIT) + { + if (UnitAI* charmedAI = GetAI()) + charmedAI->OnCharmed(false); // AI will potentially schedule a charm ai update + else + ScheduleAIChange(); + } +} + +void Unit::RestoreFaction() +{ + if (HasAuraType(SPELL_AURA_MOD_FACTION)) + { + SetFaction(GetAuraEffectsByType(SPELL_AURA_MOD_FACTION).front()->GetMiscValue()); + return; + } + + if (GetTypeId() == TYPEID_PLAYER) + ToPlayer()->SetFactionForRace(getRace()); + else + { + if (HasUnitTypeMask(UNIT_MASK_MINION)) + { + if (Unit* owner = GetOwner()) + { + SetFaction(owner->GetFaction()); + return; + } + } + + if (CreatureTemplate const* cinfo = ToCreature()->GetCreatureTemplate()) // normal creature + SetFaction(cinfo->faction); + } +} + +bool Unit::CreateVehicleKit(uint32 id, uint32 creatureEntry, bool loading /*= false*/) +{ + VehicleEntry const* vehInfo = sVehicleStore.LookupEntry(id); + if (!vehInfo) + return false; + + m_vehicleKit = std::make_unique(this, vehInfo, creatureEntry); + m_updateFlag.Vehicle = true; + m_unitTypeMask |= UNIT_MASK_VEHICLE; + + if (!loading) + SendSetVehicleRecId(id); + + return true; +} + +void Unit::RemoveVehicleKit(bool onRemoveFromWorld /*= false*/) +{ + if (!m_vehicleKit) + return; + + if (!onRemoveFromWorld) + SendSetVehicleRecId(0); + + m_vehicleKit->Uninstall(); + m_vehicleKit.reset(); + + m_updateFlag.Vehicle = false; + m_unitTypeMask &= ~UNIT_MASK_VEHICLE; + RemoveFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_SPELLCLICK | UNIT_NPC_FLAG_PLAYER_VEHICLE); +} + +bool Unit::IsOnVehicle(Unit const* vehicle) const +{ + return m_vehicle && m_vehicle == vehicle->GetVehicleKit(); +} + +Unit* Unit::GetVehicleBase() const +{ + return m_vehicle ? m_vehicle->GetBase() : nullptr; +} + +Creature* Unit::GetVehicleCreatureBase() const +{ + if (Unit* veh = GetVehicleBase()) + if (Creature* c = veh->ToCreature()) + return c; + + return nullptr; +} + +ObjectGuid Unit::GetTransGUID() const +{ + if (GetVehicle()) + return GetVehicleBase()->GetGUID(); + if (GetTransport()) + return GetTransport()->GetTransportGUID(); + + return ObjectGuid::Empty; +} + +TransportBase* Unit::GetDirectTransport() const +{ + if (Vehicle* veh = GetVehicle()) + return veh; + return GetTransport(); +} + +bool Unit::IsInPartyWith(Unit const* unit) const +{ + if (this == unit) + return true; + + Unit const* u1 = GetCharmerOrOwnerOrCreatorOrSelf(); + Unit const* u2 = unit->GetCharmerOrOwnerOrCreatorOrSelf(); + if (u1 == u2) + return true; + + if (u1->GetTypeId() == TYPEID_PLAYER && u2->GetTypeId() == TYPEID_PLAYER) + return u1->ToPlayer()->IsInSameGroupWith(u2->ToPlayer()); + else if ((u2->GetTypeId() == TYPEID_PLAYER && u1->GetTypeId() == TYPEID_UNIT && u1->ToCreature()->HasStaticFlag(CREATURE_STATIC_FLAG_4_TREAT_AS_RAID_UNIT_FOR_HELPFUL_SPELLS)) || + (u1->GetTypeId() == TYPEID_PLAYER && u2->GetTypeId() == TYPEID_UNIT && u2->ToCreature()->HasStaticFlag(CREATURE_STATIC_FLAG_4_TREAT_AS_RAID_UNIT_FOR_HELPFUL_SPELLS))) + return true; + + return u1->GetTypeId() == TYPEID_UNIT && u2->GetTypeId() == TYPEID_UNIT && u1->GetFaction() == u2->GetFaction(); +} + +bool Unit::IsInRaidWith(Unit const* unit) const +{ + if (this == unit) + return true; + + Unit const* u1 = GetCharmerOrOwnerOrCreatorOrSelf(); + Unit const* u2 = unit->GetCharmerOrOwnerOrCreatorOrSelf(); + if (u1 == u2) + return true; + + if (u1->GetTypeId() == TYPEID_PLAYER && u2->GetTypeId() == TYPEID_PLAYER) + return u1->ToPlayer()->IsInSameRaidWith(u2->ToPlayer()); + else if ((u2->GetTypeId() == TYPEID_PLAYER && u1->GetTypeId() == TYPEID_UNIT && u1->ToCreature()->HasStaticFlag(CREATURE_STATIC_FLAG_4_TREAT_AS_RAID_UNIT_FOR_HELPFUL_SPELLS)) || + (u1->GetTypeId() == TYPEID_PLAYER && u2->GetTypeId() == TYPEID_UNIT && u2->ToCreature()->HasStaticFlag(CREATURE_STATIC_FLAG_4_TREAT_AS_RAID_UNIT_FOR_HELPFUL_SPELLS))) + return true; + + return u1->GetTypeId() == TYPEID_UNIT && u2->GetTypeId() == TYPEID_UNIT && u1->GetFaction() == u2->GetFaction(); +} + +void Unit::GetPartyMembers(std::list &TagUnitMap) +{ + Unit* owner = GetCharmerOrOwnerOrSelf(); + Group* group = nullptr; + if (owner->GetTypeId() == TYPEID_PLAYER) + group = owner->ToPlayer()->GetGroup(); + + if (group) + { + uint8 subgroup = owner->ToPlayer()->GetSubGroup(); + + for (GroupReference* 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); + + if (Guardian* pet = Target->GetGuardianPet()) + if (pet->IsAlive()) + TagUnitMap.push_back(pet); + } + } + } + else + { + if ((owner == this || IsInMap(owner)) && owner->IsAlive()) + TagUnitMap.push_back(owner); + if (Guardian* pet = owner->GetGuardianPet()) + if ((pet == this || IsInMap(pet)) && pet->IsAlive()) + TagUnitMap.push_back(pet); + } +} + +bool Unit::IsContestedGuard() const +{ + if (FactionTemplateEntry const* entry = GetFactionTemplateEntry()) + return entry->IsHostileToPvpActivePlayers(); + + return false; +} + +void Unit::SetPvP(bool state) +{ + if (state) + SetByteFlag(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PVP_FLAG, UNIT_BYTE2_FLAG_PVP); + else + RemoveByteFlag(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PVP_FLAG, UNIT_BYTE2_FLAG_PVP); +} + +Aura* Unit::AddAura(uint32 spellId, Unit* target) +{ + if (!target) + return nullptr; + + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); + if (!spellInfo) + 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->IsAlive() && !spellInfo->IsPassive() && !spellInfo->HasAttribute(SPELL_ATTR2_ALLOW_DEAD_TARGET)) + return nullptr; + + if (target->IsImmunedToSpell(spellInfo, this)) + return nullptr; + + for (uint32 i = 0; i < MAX_SPELL_EFFECTS; ++i) + { + if (!(effMask & (1 << i))) + continue; + + if (target->IsImmunedToSpellEffect(spellInfo, i, this)) + effMask &= ~(1 << i); + } + + if (!effMask) + return nullptr; + + AuraCreateInfo createInfo(spellInfo, effMask, target); + createInfo.SetCaster(this); + + if (Aura* aura = Aura::TryRefreshStackOrCreate(createInfo)) + { + aura->ApplyForTargets(); + return aura; + } + return nullptr; +} + +void Unit::SetAuraStack(uint32 spellId, Unit* target, uint32 stack) +{ + Aura* aura = target->GetAura(spellId, GetGUID()); + if (!aura) + aura = AddAura(spellId, target); + if (aura && stack) + aura->SetStackAmount(stack); +} + +void Unit::SendPlaySpellVisualKit(uint32 id, uint32 type, uint32 duration) const +{ + if (!sSpellVisualKitStore.LookupEntry(id)) + return; + + WorldPackets::Spells::PlaySpellVisualKit packet; + packet.Unit = GetGUID(); + packet.KitRecID = id; // SpellVisualKit.dbc index + packet.KitType = type; + packet.Duration = duration; + SendMessageToSet(packet.Write(), IsPlayer()); +} + +void Unit::SendPlaySpellVisual(uint32 spellVisualId, Unit const* target /*= nullptr*/, Optional targetPosition /*= {}*/, float travelSpeed /*= 0.f*/, uint16 missReason /*= 0*/, uint16 reflectStatus /*= 0*/, bool speedAsTime /*= false*/) const +{ + if (!sSpellVisualStore.LookupEntry(spellVisualId)) + return; + + WorldPackets::Spells::PlaySpellVisual packet; + packet.SpellVisualID = spellVisualId; + packet.Source = GetGUID(); + if (target) + packet.Target = target->GetGUID(); + + if (targetPosition) + packet.TargetPosition = *targetPosition; + + packet.TravelSpeed = travelSpeed; + packet.MissReason = missReason; + packet.ReflectStatus = reflectStatus; + packet.SpeedAsTime = speedAsTime; + + SendMessageToSet(packet.Write(), true); +} + +void Unit::CancelSpellMissiles(uint32 spellId, bool reverseMissile /*= false*/) +{ + bool hasMissile = false; + for (auto& itr : m_Events.GetEvents()) + { + if (Spell const* spell = Spell::ExtractSpellFromEvent(itr.second)) + { + if (spell->GetSpellInfo()->Id == spellId) + { + if (!itr.second->IsAbortScheduled()) + { + itr.second->ScheduleAbort(); + hasMissile = true; + } + } + } + } + + if (hasMissile) + { + WorldPackets::Spells::MissileCancel packet; + packet.OwnerGUID = GetGUID(); + packet.SpellID = spellId; + packet.Reverse = reverseMissile; + SendMessageToSet(packet.Write(), false); + } +} + +bool Unit::CanApplyResilience() const +{ + return !IsVehicle() && GetOwnerGUID().IsPlayer(); +} + +/*static*/ void Unit::ApplyResilience(Unit const* victim, int32* damage) +{ + // player mounted on multi-passenger mount is also classified as vehicle + if (victim->IsVehicle() && victim->GetTypeId() != TYPEID_PLAYER) + return; + + Unit const* target = nullptr; + if (victim->GetTypeId() == TYPEID_PLAYER) + target = victim; + else // victim->GetTypeId() == TYPEID_UNIT + { + if (Unit* owner = victim->GetOwner()) + if (owner->GetTypeId() == TYPEID_PLAYER) + target = owner; + } + + if (!target) + return; + + *damage -= target->GetDamageReduction(*damage); +} + +int32 Unit::CalculateAOEAvoidance(int32 damage, uint32 schoolMask, bool npcCaster) const +{ + damage = int32(float(damage) * GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_AOE_DAMAGE_AVOIDANCE, schoolMask)); + if (npcCaster) + damage = int32(float(damage) * GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_CREATURE_AOE_DAMAGE_AVOIDANCE, schoolMask)); + + return damage; +} + +// Melee based spells can be miss, parry or dodge on this step +// Crit or block - determined on damage calculation phase! (and can be both in some time) +float Unit::MeleeSpellMissChance(Unit const* victim, WeaponAttackType attType, SpellInfo const* spellInfo /*= nullptr*/) const +{ + if (spellInfo && spellInfo->HasAttribute(SPELL_ATTR7_NO_ATTACK_MISS)) + return 0.f; + + // Calculate miss chance + float missChance = GetMeleeMissChance(victim); + + if (!spellInfo && haveOffhandWeapon()) + missChance += 19.f; + + // Spellmod from SpellModOp::HitChance + float resistMissChance = 100.0f; + if (spellInfo) + if (Player* modOwner = GetSpellModOwner()) + modOwner->ApplySpellMod(spellInfo, SpellModOp::HitChance, resistMissChance); + + missChance -= resistMissChance - 100.0f; + + if (attType == RANGED_ATTACK) + missChance -= m_modRangedHitChance; + else + missChance -= m_modMeleeHitChance; + + // miss chance from auras after calculating skill based miss + missChance -= GetTotalAuraModifier(SPELL_AURA_MOD_HIT_CHANCE); + if (attType == RANGED_ATTACK) + missChance -= victim->GetTotalAuraModifier(SPELL_AURA_MOD_ATTACKER_RANGED_HIT_CHANCE); + else + missChance -= victim->GetTotalAuraModifier(SPELL_AURA_MOD_ATTACKER_MELEE_HIT_CHANCE); + + return std::max(missChance, 0.f); +} + +void Unit::OnPhaseChange() +{ + if (!IsInWorld()) + return; + + // Inform player groups that a member's phase has changed so the next PartyMemberState packet will update the phasing accordingly + if (IsPlayer() && ToPlayer()->GetGroup()) + ToPlayer()->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_PHASE); +} + +void Unit::UpdateObjectVisibility(bool forced) +{ + if (!forced) + AddToNotify(NOTIFY_VISIBILITY_CHANGED); + else + { + WorldObject::UpdateObjectVisibility(true); + // call MoveInLineOfSight for nearby creatures + Trinity::AIRelocationNotifier notifier(*this); + Cell::VisitAllObjects(this, notifier, GetVisibilityRange()); + } +} + +void Unit::SendMoveKnockBack(Player* player, float speedXY, float speedZ, float vcos, float vsin) +{ + WorldPackets::Movement::MoveKnockBack moveKnockBack; + moveKnockBack.MoverGUID = GetGUID(); + moveKnockBack.SequenceIndex = m_movementCounter++; + moveKnockBack.Speeds.HorzSpeed = speedXY; + moveKnockBack.Speeds.VertSpeed = speedZ; + moveKnockBack.Direction = Position(vcos, vsin); + player->SendDirectMessage(moveKnockBack.Write()); +} + +void Unit::KnockbackFrom(float x, float y, float speedXY, float speedZ) +{ + if (IsMovedByServer()) + GetMotionMaster()->MoveKnockbackFrom(x, y, speedXY, speedZ); + else + { + float vcos, vsin; + GetSinCos(x, y, vsin, vcos); + SendMoveKnockBack(GetGameClientMovingMe()->GetBasePlayer(), speedXY, -speedZ, vcos, vsin); + + if (HasAuraType(SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED) || HasAuraType(SPELL_AURA_FLY)) + SetCanFly(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) const +{ + // Hardcoded cases + switch (spellId) + { + case 7090: // Bear Form + return 29414; + case 35200: // Roc Form + return 4877; + default: + break; + } + + if (GetTypeId() == TYPEID_PLAYER) + { + switch (form) + { + case FORM_CAT: + // Based on Hair color + if (getRace() == RACE_NIGHTELF) + { + uint8 hairColor = GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_HAIR_COLOR_ID); + switch (hairColor) + { + case 7: // Violet + case 8: + return 29405; + case 3: // Light Blue + return 29406; + case 0: // Green + case 1: // Light Green + case 2: // Dark Green + return 29407; + case 4: // White + return 29408; + default: // original - Dark Blue + return 892; + } + } + else if (getRace() == RACE_TROLL) + { + uint8 hairColor = GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_HAIR_COLOR_ID); + switch (hairColor) + { + case 0: // Red + case 1: + return 33668; + case 2: // Yellow + case 3: + return 33667; + case 4: // Blue + case 5: + case 6: + return 33666; + case 7: // Purple + case 10: + return 33665; + default: // original - white + return 33669; + } + } + else if (getRace() == RACE_WORGEN) + { + // Based on Skin color + uint8 skinColor = GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_SKIN_ID); + // Male + if (getGender() == GENDER_MALE) + { + switch (skinColor) + { + case 1: // Brown + return 33662; + case 2: // Black + case 7: + return 33661; + case 4: // yellow + return 33664; + case 3: // White + case 5: + return 33663; + default: // original - Gray + return 33660; + } + } + // Female + else + { + switch (skinColor) + { + case 5: // Brown + case 6: + return 33662; + case 7: // Black + case 8: + return 33661; + case 3: // yellow + case 4: + return 33664; + case 2: // White + return 33663; + default: // original - Gray + return 33660; + } + } + } + // Based on Skin color + else if (getRace() == RACE_TAUREN) + { + uint8 skinColor = GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_SKIN_ID); + // Male + if (getGender() == GENDER_MALE) + { + switch (skinColor) + { + case 12: // White + case 13: + case 14: + case 18: // Completly White + return 29409; + case 9: // Light Brown + case 10: + case 11: + return 29410; + case 6: // Brown + case 7: + case 8: + return 29411; + case 0: // Dark + case 1: + case 2: + case 3: // Dark Grey + case 4: + case 5: + return 29412; + default: // original - Grey + return 8571; + } + } + // Female + else + { + switch (skinColor) + { + case 10: // White + return 29409; + case 6: // Light Brown + case 7: + return 29410; + case 4: // Brown + case 5: + return 29411; + case 0: // Dark + case 1: + case 2: + case 3: + return 29412; + default: // original - Grey + return 8571; + } + } + } + else if (Player::TeamForRace(getRace()) == ALLIANCE) + return 892; + else + return 8571; + case FORM_BEAR: + // Based on Hair color + if (getRace() == RACE_NIGHTELF) + { + uint8 hairColor = GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_HAIR_COLOR_ID); + switch (hairColor) + { + case 0: // Green + case 1: // Light Green + case 2: // Dark Green + return 29413; // 29415? + case 6: // Dark Blue + return 29414; + case 4: // White + return 29416; + case 3: // Light Blue + return 29417; + default: // original - Violet + return 2281; + } + } + else if (getRace() == RACE_TROLL) + { + uint8 hairColor = GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_HAIR_COLOR_ID); + switch (hairColor) + { + case 0: // Red + case 1: + return 33657; + case 2: // Yellow + case 3: + return 33659; + case 7: // Purple + case 10: + return 33656; + case 8: // White + case 9: + case 11: + case 12: + return 33658; + default: // original - Blue + return 33655; + } + } + else if (getRace() == RACE_WORGEN) + { + // Based on Skin color + uint8 skinColor = GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_SKIN_ID); + // Male + if (getGender() == GENDER_MALE) + { + switch (skinColor) + { + case 1: // Brown + return 33653; + case 2: // Black + case 7: + return 33651; + case 4: // Yellow + return 33652; + case 3: // White + case 5: + return 33654; + default: // original - Gray + return 33650; + } + } + // Female + else + { + switch (skinColor) + { + case 5: // Brown + case 6: + return 33652; + case 7: // Black + case 8: + return 33651; + case 3: // yellow + case 4: + return 33654; + case 2: // White + return 33653; + default: // original - Gray + return 33650; + } + } + } + // Based on Skin color + else if (getRace() == RACE_TAUREN) + { + uint8 skinColor = GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_SKIN_ID); + // Male + if (getGender() == GENDER_MALE) + { + switch (skinColor) + { + case 0: // Dark (Black) + case 1: + case 2: + return 29418; + case 3: // White + case 4: + case 5: + case 12: + case 13: + case 14: + return 29419; + case 9: // Light Brown/Grey + case 10: + case 11: + case 15: + case 16: + case 17: + return 29420; + case 18: // Completly White + return 29421; + default: // original - Brown + return 2289; + } + } + // Female + else + { + switch (skinColor) + { + case 0: // Dark (Black) + case 1: + return 29418; + case 2: // White + case 3: + return 29419; + case 6: // Light Brown/Grey + case 7: + case 8: + case 9: + return 29420; + case 10: // Completly White + return 29421; + default: // original - Brown + return 2289; + } + } + } + else if (Player::TeamForRace(getRace()) == ALLIANCE) + return 2281; + else + return 2289; + case FORM_FLIGHT: + if (Player::TeamForRace(getRace()) == ALLIANCE) + return 20857; + return 20872; + case FORM_FLIGHT_EPIC: + if (Player::TeamForRace(getRace()) == ALLIANCE) + return (getRace() == RACE_WORGEN ? 37729 : 21243); + if (getRace() == RACE_TROLL) + return 37730; + return 21244; + case FORM_MOONKIN: + if (getRace() == RACE_TROLL) + return 37174; + if (getRace() == RACE_WORGEN) + return 37173; + return 15374; + case FORM_GHOSTWOLF: + if (HasAura(58135)) //! Glyph of Arctic Wolf + return 27312; + return 4613; + default: + break; + } + } + + uint32 modelid = 0; + SpellShapeshiftFormEntry const* formEntry = sSpellShapeshiftFormStore.LookupEntry(form); + if (formEntry && formEntry->CreatureDisplayID[0]) + { + // Take the alliance modelid as default + if (GetTypeId() != TYPEID_PLAYER) + return formEntry->CreatureDisplayID[0]; + else + { + if (Player::TeamForRace(getRace()) == ALLIANCE) + modelid = formEntry->CreatureDisplayID[0]; + else + modelid = formEntry->CreatureDisplayID[1]; + + // If the player is horde but there are no values for the horde modelid - take the alliance modelid + if (!modelid && Player::TeamForRace(getRace()) == HORDE) + modelid = formEntry->CreatureDisplayID[0]; + } + } + + return modelid; +} + +uint32 Unit::GetModelForTotem(PlayerTotemType totemType) +{ + switch (getRace()) + { + case RACE_ORC: + { + switch (totemType) + { + case SUMMON_TYPE_TOTEM_FIRE: // fire + return 30758; + case SUMMON_TYPE_TOTEM_EARTH: // earth + return 30757; + case SUMMON_TYPE_TOTEM_WATER: // water + return 30759; + case SUMMON_TYPE_TOTEM_AIR: // air + return 30756; + } + break; + } + case RACE_DWARF: + { + switch (totemType) + { + case SUMMON_TYPE_TOTEM_FIRE: // fire + return 30754; + case SUMMON_TYPE_TOTEM_EARTH: // earth + return 30753; + case SUMMON_TYPE_TOTEM_WATER: // water + return 30755; + case SUMMON_TYPE_TOTEM_AIR: // air + return 30736; + } + break; + } + case RACE_TROLL: + { + switch (totemType) + { + case SUMMON_TYPE_TOTEM_FIRE: // fire + return 30762; + case SUMMON_TYPE_TOTEM_EARTH: // earth + return 30761; + case SUMMON_TYPE_TOTEM_WATER: // water + return 30763; + case SUMMON_TYPE_TOTEM_AIR: // air + return 30760; + } + break; + } + case RACE_TAUREN: + { + switch (totemType) + { + case SUMMON_TYPE_TOTEM_FIRE: // fire + return 4589; + case SUMMON_TYPE_TOTEM_EARTH: // earth + return 4588; + case SUMMON_TYPE_TOTEM_WATER: // water + return 4587; + case SUMMON_TYPE_TOTEM_AIR: // air + return 4590; + } + break; + } + case RACE_DRAENEI: + { + switch (totemType) + { + case SUMMON_TYPE_TOTEM_FIRE: // fire + return 19074; + case SUMMON_TYPE_TOTEM_EARTH: // earth + return 19073; + case SUMMON_TYPE_TOTEM_WATER: // water + return 19075; + case SUMMON_TYPE_TOTEM_AIR: // air + return 19071; + } + break; + } + case RACE_GOBLIN: + { + switch (totemType) + { + case SUMMON_TYPE_TOTEM_FIRE: // fire + return 30783; + case SUMMON_TYPE_TOTEM_EARTH: // earth + return 30782; + case SUMMON_TYPE_TOTEM_WATER: // water + return 30784; + case SUMMON_TYPE_TOTEM_AIR: // air + return 30781; + } + break; + } + } + return 0; +} + +void Unit::JumpTo(float speedXY, float speedZ, bool forward, Optional dest) +{ + float angle = forward ? 0 : float(M_PI); + if (dest) + angle += GetRelativeAngle(*dest); + + if (GetTypeId() == TYPEID_UNIT) + GetMotionMaster()->MoveJumpTo(angle, speedXY, speedZ); + else + { + float vcos = std::cos(angle + GetOrientation()); + float vsin = std::sin(angle + GetOrientation()); + SendMoveKnockBack(ToPlayer(), speedXY, -speedZ, vcos, vsin); + } +} + +void Unit::JumpTo(WorldObject* obj, float speedZ, bool withOrientation) +{ + float x, y, z; + obj->GetContactPoint(this, x, y, z); + float speedXY = GetExactDist2d(x, y) * 10.0f / speedZ; + GetMotionMaster()->MoveJump(x, y, z, GetAngle(obj), speedXY, speedZ, EVENT_JUMP, withOrientation); +} + +bool Unit::HandleSpellClick(Unit* clicker, int8 seatId) +{ + bool result = false; + uint32 spellClickEntry = GetVehicleKit() ? GetVehicleKit()->GetCreatureEntry() : GetEntry(); + TriggerCastFlags const flags = GetVehicleKit() ? TRIGGERED_IGNORE_CASTER_MOUNTED_OR_ON_VEHICLE : TRIGGERED_NONE; + + 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 + if (!sConditionMgr->IsObjectMeetingSpellClickConditions(spellClickEntry, itr->second.spellId, clicker, this)) + 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) ? GetOwnerOrCreatorGUID() : clicker->GetGUID(); + + SpellInfo const* spellEntry = sSpellMgr->GetSpellInfo(itr->second.spellId); + // if (!spellEntry) should be checked at npc_spellclick load + + if (seatId > -1) + { + uint8 i = 0; + bool valid = false; + while (i < MAX_SPELL_EFFECTS && !valid) + { + if (spellEntry->Effects[i].ApplyAuraName == SPELL_AURA_CONTROL_VEHICLE) + { + valid = true; + break; + } + ++i; + } + + if (!valid) + { + TC_LOG_ERROR("sql.sql", "Spell %u specified in npc_spellclick_spells is not a valid vehicle enter aura!", itr->second.spellId); + continue; + } + + if (IsInMap(caster)) + { + CastSpellExtraArgs args(flags); + args.OriginalCaster = origCasterGUID; + args.AddSpellMod(SpellValueMod(SPELLVALUE_BASE_POINT0 + i), seatId + 1); + caster->CastSpell(target, itr->second.spellId, args); + } + else // This can happen during Player::_LoadAuras + { + int32 bp[MAX_SPELL_EFFECTS] = { }; + for (uint32 j = 0; j < MAX_SPELL_EFFECTS; ++j) + bp[j] = spellEntry->Effects[j].BasePoints; + + bp[i] = seatId; + + AuraCreateInfo createInfo(spellEntry, MAX_EFFECT_MASK, this); + createInfo + .SetCaster(clicker) + .SetBaseAmount(bp) + .SetCasterGUID(origCasterGUID); + + Aura::TryRefreshStackOrCreate(createInfo); + } + } + else + { + if (IsInMap(caster)) + caster->CastSpell(target, spellEntry->Id, CastSpellExtraArgs().SetOriginalCaster(origCasterGUID)); + else + { + AuraCreateInfo createInfo(spellEntry, MAX_EFFECT_MASK, this); + createInfo + .SetCaster(clicker) + .SetCasterGUID(origCasterGUID); + + Aura::TryRefreshStackOrCreate(createInfo); + } + } + + result = true; + } + + Creature* creature = ToCreature(); + if (creature && creature->IsAIEnabled()) + creature->AI()->OnSpellClick(clicker, result); + + return result; +} + +void Unit::EnterVehicle(Unit* base, int8 seatId) +{ + CastSpellExtraArgs args(TRIGGERED_IGNORE_CASTER_MOUNTED_OR_ON_VEHICLE); + args.AddSpellBP0(seatId + 1); + CastSpell(base, VEHICLE_SPELL_RIDE_HARDCODED, args); +} + +void Unit::_EnterVehicle(Vehicle* vehicle, int8 seatId, AuraApplication const* aurApp) +{ + // Must be called only from aura handler + ASSERT(aurApp); + + if (!IsAlive() || GetVehicleKit() == vehicle || vehicle->GetBase()->IsOnVehicle(this)) + return; + + if (m_vehicle) + { + if (m_vehicle != vehicle) + { + TC_LOG_DEBUG("entities.vehicle", "EnterVehicle: %u exit %u and enter %u.", GetEntry(), m_vehicle->GetBase()->GetEntry(), vehicle->GetBase()->GetEntry()); + ExitVehicle(); + } + else if (seatId >= 0 && seatId == GetTransSeat()) + return; + else + { + //Exit the current vehicle because unit will reenter in a new seat. + m_vehicle->GetBase()->RemoveAurasByType(SPELL_AURA_CONTROL_VEHICLE, GetGUID(), aurApp->GetBase()); + } + } + + if (aurApp->GetRemoveMode().HasAnyFlag()) + return; + + if (Player* player = ToPlayer()) + { + if (vehicle->GetBase()->GetTypeId() == TYPEID_PLAYER && player->IsInCombat()) + { + vehicle->GetBase()->RemoveAura(const_cast(aurApp)); + return; + } + + if (vehicle->GetBase()->GetTypeId() == TYPEID_UNIT) + { + // If a player entered a vehicle that is part of a formation, remove it from said formation + if (CreatureGroup* creatureGroup = vehicle->GetBase()->ToCreature()->GetFormation()) + creatureGroup->RemoveMember(vehicle->GetBase()->ToCreature()); + } + } + + // If vehicle flag for fixed position set (cannons), or if the following hardcoded units, then set state rooted + // 30236 | Argent Cannon + // 39759 | Tankbuster Cannon + if ((vehicle->GetVehicleInfo()->Flags & VEHICLE_FLAG_FIXED_POSITION) || vehicle->GetBase()->GetEntry() == 30236 || vehicle->GetBase()->GetEntry() == 39759) + SetControlled(true, UNIT_STATE_ROOT); + + ASSERT(!m_vehicle); + (void)vehicle->AddVehiclePassenger(this, seatId); +} + +void Unit::ChangeSeat(int8 seatId, bool next) +{ + if (!m_vehicle) + return; + + // Don't change if current and new seat are identical + if (seatId == GetTransSeat()) + return; + + SeatMap::const_iterator seat = (seatId < 0 ? m_vehicle->GetNextEmptySeat(GetTransSeat(), next) : m_vehicle->Seats.find(seatId)); + // The second part of the check will only return true if seatId >= 0. @Vehicle::GetNextEmptySeat makes sure of that. + if (seat == m_vehicle->Seats.end() || !seat->second.IsEmpty()) + return; + + AuraEffect* rideVehicleEffect = nullptr; + AuraEffectList const& vehicleAuras = m_vehicle->GetBase()->GetAuraEffectsByType(SPELL_AURA_CONTROL_VEHICLE); + for (AuraEffectList::const_iterator itr = vehicleAuras.begin(); itr != vehicleAuras.end(); ++itr) + { + if ((*itr)->GetCasterGUID() != GetGUID()) + continue; + + // Make sure there is only one ride vehicle aura on target cast by the unit changing seat + ASSERT(!rideVehicleEffect); + rideVehicleEffect = *itr; + } + + // Unit riding a vehicle must always have control vehicle aura on target + ASSERT(rideVehicleEffect); + + rideVehicleEffect->ChangeAmount(seat->first + 1); +} + +void Unit::ExitVehicle(Position const* /*exitPosition*/) +{ + //! This function can be called at upper level code to initialize an exit from the passenger's side. + if (!m_vehicle) + return; + + GetVehicleBase()->RemoveAurasByType(SPELL_AURA_CONTROL_VEHICLE, GetGUID()); + //! The following call would not even be executed successfully as the + //! SPELL_AURA_CONTROL_VEHICLE unapply handler already calls _ExitVehicle without + //! specifying an exitposition. The subsequent call below would return on if (!m_vehicle). + /*_ExitVehicle(exitPosition);*/ + //! To do: + //! We need to allow SPELL_AURA_CONTROL_VEHICLE unapply handlers in spellscripts + //! to specify exit coordinates and either store those per passenger, or we need to + //! init spline movement based on those coordinates in unapply handlers, and + //! relocate exiting passengers based on Unit::moveSpline data. Either way, + //! Coming Soon(TM) +} + +void Unit::_ExitVehicle(Position const* exitPosition) +{ + /// It's possible m_vehicle is nullptr, when this function is called indirectly from @VehicleJoinEvent::Abort. + /// In that case it was not possible to add the passenger to the vehicle. The vehicle aura has already been removed + /// from the target in the aforementioned function and we don't need to do anything else at this point. + if (!m_vehicle) + return; + + // This should be done before dismiss, because there may be some aura removal + VehicleSeatAddon const* seatAddon = m_vehicle->GetSeatAddonForSeatOfPassenger(this); + Vehicle* vehicle = m_vehicle->RemovePassenger(this); + + if (!vehicle) + { + TC_LOG_ERROR("entities.vehicle", "RemovePassenger() couldn't remove current unit from vehicle."); + return; + } + + Player* player = ToPlayer(); + + // If the player is on mounted duel and exits the mount, he should immediatly lose the duel + if (player && player->duel && player->duel->isMounted) + player->DuelComplete(DUEL_FLED); + + SetControlled(false, UNIT_STATE_ROOT); // SMSG_MOVE_FORCE_UNROOT, ~MOVEMENTFLAG_ROOT + + AddUnitState(UNIT_STATE_MOVE); + + if (player) + player->SetFallInformation(0, GetPositionZ()); + + Position pos; + // If we ask for a specific exit position, use that one. Otherwise allow scripts to modify it + if (exitPosition) + pos = *exitPosition; + else + { + // Set exit position to vehicle position and use the current orientation + pos = vehicle->GetBase()->GetPosition(); + pos.SetOrientation(GetOrientation()); + + // Change exit position based on seat entry + 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 }); + } + } + + float height = pos.GetPositionZ() + vehicle->GetBase()->GetCollisionHeight(); + + Movement::MoveSplineInit init(this); + + // Creatures without inhabit type air should begin falling after exiting the vehicle + if (GetTypeId() == TYPEID_UNIT && !CanFly() && height > GetMap()->GetWaterOrGroundLevel(GetPhaseShift(), pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ() + vehicle->GetBase()->GetCollisionHeight(), &height)) + init.SetFall(); + + init.MoveTo(pos.GetPositionX(), pos.GetPositionY(), height, false); + init.SetFacing(pos.GetOrientation()); + init.SetTransportExit(); + GetMotionMaster()->LaunchMoveSpline(std::move(init), EVENT_VEHICLE_EXIT, MOTION_SLOT_CONTROLLED); + + if (player) + player->ResummonPetTemporaryUnSummonedIfAny(); + + if (vehicle->GetBase()->HasUnitTypeMask(UNIT_MASK_MINION) && vehicle->GetBase()->GetTypeId() == TYPEID_UNIT) + if (((Minion*)vehicle->GetBase())->GetOwner() == this) + vehicle->GetBase()->ToCreature()->DespawnOrUnsummon(1); + + if (HasUnitTypeMask(UNIT_MASK_ACCESSORY)) + { + // Vehicle just died, we die too + if (vehicle->GetBase()->getDeathState() == JUST_DIED) + setDeathState(JUST_DIED); + // If for other reason we as minion are exiting the vehicle (ejected, master dismounted) - unsummon + else + ToTempSummon()->UnSummon(2000); // Approximation + } +} + +bool Unit::IsFalling() const +{ + return m_movementInfo.HasMovementFlag(MOVEMENTFLAG_FALLING | MOVEMENTFLAG_FALLING_FAR) || movespline->isFalling(); +} + +bool Unit::CanSwim() const +{ + // Mirror client behavior, if this method returns false then client will not use swimming animation and for players will apply gravity as if there was no water + if (HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_CANT_SWIM)) + return false; + if (HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED)) // is player + return true; + if (HasFlag(UNIT_FIELD_FLAGS_2, UNIT_FLAG2_AI_WILL_ONLY_SWIM_IF_TARGET_SWIMS)) + return false; + if (HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PET_IN_COMBAT)) + return true; + return HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_RENAME | UNIT_FLAG_CAN_SWIM); +} + +void Unit::NearTeleportTo(Position const& pos, bool casting /*= false*/) +{ + DisableSpline(); + if (GetTypeId() == TYPEID_PLAYER) + { + WorldLocation target(GetMapId(), pos); + ToPlayer()->TeleportTo(target, TELE_TO_NOT_LEAVE_TRANSPORT | TELE_TO_NOT_LEAVE_COMBAT | TELE_TO_NOT_UNSUMMON_PET | (casting ? TELE_TO_SPELL : TELE_TO_NONE)); + } + else + { + SendTeleportPacket(pos); + UpdatePosition(pos, true); + UpdateObjectVisibility(); + } +} + +void Unit::WriteMovementInfo(WorldPacket& data, Movement::ExtraMovementStatusElement* extras /*= nullptr*/, uint32* movementCounter /*= nullptr*/) +{ + MovementInfo const& mi = m_movementInfo; + + bool hasMovementFlags = GetUnitMovementFlags() != 0; + bool hasMovementFlags2 = GetExtraUnitMovementFlags() != 0; + bool hasTimestamp = true; + bool hasOrientation = !G3D::fuzzyEq(GetOrientation(), 0.0f); + bool hasTransportData = GetTransGUID() != 0; + bool hasSpline = IsSplineEnabled(); + + bool hasTransportTime2 = hasTransportData && m_movementInfo.transport.time2 != 0; + bool hasTransportVehicleId = hasTransportData && m_movementInfo.transport.vehicleId != 0; + bool hasPitch = HasUnitMovementFlag(MovementFlags(MOVEMENTFLAG_SWIMMING | MOVEMENTFLAG_FLYING)) || HasExtraUnitMovementFlag(MOVEMENTFLAG2_ALWAYS_ALLOW_PITCHING); + bool hasFallDirection = HasUnitMovementFlag(MOVEMENTFLAG_FALLING); + bool hasFallData = hasFallDirection || m_movementInfo.jump.fallTime != 0; + bool hasSplineElevation = HasUnitMovementFlag(MOVEMENTFLAG_SPLINE_ELEVATION); + + MovementStatusElements const* sequence = GetMovementStatusElementsSequence(static_cast(data.GetOpcode())); + if (!sequence) + { + TC_LOG_ERROR("network", "Unit::WriteMovementInfo: No movement sequence found for opcode %s", GetOpcodeNameForLogging(static_cast(data.GetOpcode())).c_str()); + return; + } + + ObjectGuid guid = GetGUID(); + ObjectGuid tguid = hasTransportData ? GetTransGUID() : ObjectGuid::Empty; + + for (; *sequence != MSEEnd; ++sequence) + { + MovementStatusElements const& element = *sequence; + + switch (element) + { + case MSEHasGuidByte0: + case MSEHasGuidByte1: + case MSEHasGuidByte2: + case MSEHasGuidByte3: + case MSEHasGuidByte4: + case MSEHasGuidByte5: + case MSEHasGuidByte6: + case MSEHasGuidByte7: + data.WriteBit(guid[element - MSEHasGuidByte0]); + break; + case MSEHasTransportGuidByte0: + case MSEHasTransportGuidByte1: + case MSEHasTransportGuidByte2: + case MSEHasTransportGuidByte3: + case MSEHasTransportGuidByte4: + case MSEHasTransportGuidByte5: + case MSEHasTransportGuidByte6: + case MSEHasTransportGuidByte7: + if (hasTransportData) + data.WriteBit(tguid[element - MSEHasTransportGuidByte0]); + break; + case MSEGuidByte0: + case MSEGuidByte1: + case MSEGuidByte2: + case MSEGuidByte3: + case MSEGuidByte4: + case MSEGuidByte5: + case MSEGuidByte6: + case MSEGuidByte7: + data.WriteByteSeq(guid[element - MSEGuidByte0]); + break; + case MSETransportGuidByte0: + case MSETransportGuidByte1: + case MSETransportGuidByte2: + case MSETransportGuidByte3: + case MSETransportGuidByte4: + case MSETransportGuidByte5: + case MSETransportGuidByte6: + case MSETransportGuidByte7: + if (hasTransportData) + data.WriteByteSeq(tguid[element - MSETransportGuidByte0]); + break; + case MSEHasMovementFlags: + data.WriteBit(!hasMovementFlags); + break; + case MSEHasMovementFlags2: + data.WriteBit(!hasMovementFlags2); + break; + case MSEHasTimestamp: + data.WriteBit(!hasTimestamp); + break; + case MSEHasOrientation: + data.WriteBit(!hasOrientation); + break; + case MSEHasTransportData: + data.WriteBit(hasTransportData); + break; + case MSEHasTransportTime2: + if (hasTransportData) + data.WriteBit(hasTransportTime2); + break; + case MSEHasVehicleId: + if (hasTransportData) + data.WriteBit(hasTransportVehicleId); + break; + case MSEHasPitch: + data.WriteBit(!hasPitch); + break; + case MSEHasFallData: + data.WriteBit(hasFallData); + break; + case MSEHasFallDirection: + if (hasFallData) + data.WriteBit(hasFallDirection); + break; + case MSEHasSplineElevation: + data.WriteBit(!hasSplineElevation); + break; + case MSEHasSpline: + data.WriteBit(hasSpline); + break; + case MSEHasHeightChangeFailed: + data.WriteBit(mi.HasHeightChangeFailed()); + break; + case MSEMovementFlags: + if (hasMovementFlags) + data.WriteBits(GetUnitMovementFlags(), 30); + break; + case MSEMovementFlags2: + if (hasMovementFlags2) + data.WriteBits(GetExtraUnitMovementFlags(), 12); + break; + case MSETimestamp: + if (hasTimestamp) + data << mi.time; + break; + case MSEPositionX: + data << GetPositionX(); + break; + case MSEPositionY: + data << GetPositionY(); + break; + case MSEPositionZ: + data << GetPositionZ(); + break; + case MSEOrientation: + if (hasOrientation) + data << GetOrientation(); + break; + case MSETransportPositionX: + if (hasTransportData) + data << GetTransOffsetX(); + break; + case MSETransportPositionY: + if (hasTransportData) + data << GetTransOffsetY(); + break; + case MSETransportPositionZ: + if (hasTransportData) + data << GetTransOffsetZ(); + break; + case MSETransportOrientation: + if (hasTransportData) + data << GetTransOffsetO(); + break; + case MSETransportSeat: + if (hasTransportData) + data << GetTransSeat(); + break; + case MSETransportTime: + if (hasTransportData) + data << GetTransTime(); + break; + case MSETransportTime2: + if (hasTransportData && hasTransportTime2) + data << mi.transport.time2; + break; + case MSETransportVehicleId: + if (hasTransportData && hasTransportVehicleId) + data << mi.transport.vehicleId; + break; + case MSEPitch: + if (hasPitch) + data << mi.pitch; + break; + case MSEFallTime: + if (hasFallData) + data << mi.jump.fallTime; + break; + case MSEFallVerticalSpeed: + if (hasFallData) + data << mi.jump.zspeed; + break; + case MSEFallCosAngle: + if (hasFallData && hasFallDirection) + data << mi.jump.cosAngle; + break; + case MSEFallSinAngle: + if (hasFallData && hasFallDirection) + data << mi.jump.sinAngle; + break; + case MSEFallHorizontalSpeed: + if (hasFallData && hasFallDirection) + data << mi.jump.xyspeed; + break; + case MSESplineElevation: + if (hasSplineElevation) + data << mi.splineElevation; + break; + case MSECounter: + data << uint32(movementCounter ? *movementCounter : 0); + break; + case MSEZeroBit: + data.WriteBit(0); + break; + case MSEOneBit: + data.WriteBit(1); + break; + case MSEFlushBits: + data.FlushBits(); + break; + case MSEExtraElement: + extras->WriteNextElement(data); + break; + default: + ASSERT(Movement::PrintInvalidSequenceElement(element, __FUNCTION__)); + break; + } + } +} + +void Unit::SendTeleportPacket(Position const& pos) +{ + // SMSG_MOVE_UPDATE_TELEPORT is sent to nearby players to signal the teleport + // MSG_MOVE_TELEPORT is sent to self in order to trigger MSG_MOVE_TELEPORT_ACK and update the position server side + if (IsMovedByClient()) + { + Player* playerMover = GetGameClientMovingMe()->GetBasePlayer(); + float x, y, z, o; + pos.GetPosition(x, y, z, o); + if (TransportBase* transportBase = GetDirectTransport()) + transportBase->CalculatePassengerOffset(x, y, z, &o); + + WorldPackets::Movement::MoveTeleport moveTeleport; + moveTeleport.MoverGUID = GetGUID(); + moveTeleport.Pos = Position(x, y, z); + if (GetTransGUID()) + moveTeleport.TransportGUID = GetTransGUID(); + moveTeleport.Facing = o; + moveTeleport.SequenceIndex = m_movementCounter++; + playerMover->SendDirectMessage(moveTeleport.Write()); + } + else + { + // This is the only packet sent for creatures which contains MovementInfo structure + // we do not update m_movementInfo for creatures so it needs to be done manually here + WorldPackets::Movement::MoveUpdateTeleport moveUpdateTeleport; + moveUpdateTeleport.Status = &m_movementInfo; + moveUpdateTeleport.Status->guid = GetGUID(); + moveUpdateTeleport.Status->pos.Relocate(pos); + moveUpdateTeleport.Status->time = GameTime::GetGameTimeMS(); + SendMessageToSet(moveUpdateTeleport.Write(), false); + } +} + +bool Unit::UpdatePosition(float x, float y, float z, float orientation, bool teleport) +{ + // prevent crash when a bad coord is sent by the client + if (!Trinity::IsValidMapCoord(x, y, z, orientation)) + { + TC_LOG_DEBUG("entities.unit", "Unit::UpdatePosition(%f, %f, %f) .. bad coordinates!", x, y, z); + return false; + } + + // Check if angular distance changed + bool const turn = G3D::fuzzyGt(M_PI - fabs(fabs(GetOrientation() - orientation) - M_PI), 0.0f); + + // G3D::fuzzyEq won't help here, in some cases magnitudes differ by a little more than G3D::eps, but should be considered equal + bool const relocated = (teleport || + std::fabs(GetPositionX() - x) > 0.001f || + std::fabs(GetPositionY() - y) > 0.001f || + std::fabs(GetPositionZ() - z) > 0.001f); + + if (relocated) + { + // move and update visible state if need + if (GetTypeId() == TYPEID_PLAYER) + GetMap()->PlayerRelocation(ToPlayer(), x, y, z, orientation); + else + GetMap()->CreatureRelocation(ToCreature(), x, y, z, orientation); + } + else if (turn) + UpdateOrientation(orientation); + + _positionUpdateInfo.Relocated = relocated; + _positionUpdateInfo.Turned = turn; + + return (relocated || turn); +} + +bool Unit::UpdatePosition(Position const& pos, bool teleport) +{ + return UpdatePosition(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), pos.GetOrientation(), teleport); +} + +//! Only server-side orientation update, does not broadcast to client +void Unit::UpdateOrientation(float orientation) +{ + SetOrientation(orientation); + if (IsVehicle()) + GetVehicleKit()->RelocatePassengers(); +} + +//! Only server-side height update, does not broadcast to client +void Unit::UpdateHeight(float newZ) +{ + Relocate(GetPositionX(), GetPositionY(), newZ); + if (IsVehicle()) + GetVehicleKit()->RelocatePassengers(); +} + +void Unit::ClearPendingMovementChangeForType(MovementChangeType changeType) +{ + m_pendingMovementChanges.erase(changeType); +} + +void Unit::AssignPendingMovementChange(MovementChangeType changeType, PlayerMovementPendingChange&& newChange) +{ + m_pendingMovementChanges[changeType] = newChange; +} + +PlayerMovementPendingChange const* Unit::GetPendingMovementChange(MovementChangeType changeType) const +{ + return Trinity::Containers::MapGetValuePtr(m_pendingMovementChanges, changeType); +} + +void Unit::CheckPendingMovementAcks() +{ + if (sWorld->getIntConfig(CONFIG_PENDING_MOVE_CHANGES_TIMEOUT) == 0) + return; + + if (!HasPendingMovementChange()) + return; + + for (std::pair const& pendingChange : m_pendingMovementChanges) + { + if (GameTime::GetGameTimeMS() > pendingChange.second.time + sWorld->getIntConfig(CONFIG_PENDING_MOVE_CHANGES_TIMEOUT)) + { + /* + when players are teleported from one corner of a map to an other (example: from Dragonblight to the entrance of Naxxramas, both in the same map: Northend), + is it done through what is called a 'near' teleport. A near teleport always involve teleporting a player from one point to an other in the same map, even if + the distance is huge. When that distance is big enough, a loading screen appears on the client side. During that time, the client loads the surrounding zone + of the new location (and everything it contains). The problem is that, as long as the client hasn't finished loading the new zone, it will NOT ack the near + teleport. So if the server sends a near teleport order at a certain time and the client takes 20s to load the new zone (let's imagine a very slow computer), + even with zero latency, the server will receive an ack from the client only after 20s. + + For this reason and because the current implementation is simple (you dear reader, feel free to improve it if you can), we will just ignore checking for + near teleport acks (for now. @todo). + */ + if (pendingChange.second.movementChangeType == MovementChangeType::TELEPORT) + return; + + GameClient* controller = GetGameClientMovingMe(); + controller->GetWorldSession()->KickPlayer(); + TC_LOG_INFO("cheat", "Unit::CheckPendingMovementAcks: Player GUID: %s took too long to acknowledge a movement change. He was therefore kicked.", controller->GetBasePlayer()->GetGUID().ToString().c_str()); + break; + } + } +} + +void Unit::PurgeAndApplyPendingMovementChanges(bool informObservers /* = true */) +{ + for (std::pair const& pendingChange : m_pendingMovementChanges) + { + float speedFlat = pendingChange.second.newValue; + MovementChangeType changeType = pendingChange.second.movementChangeType; + Optional moveType; + switch (changeType) + { + case MovementChangeType::SPEED_CHANGE_WALK: moveType = MOVE_WALK; break; + case MovementChangeType::SPEED_CHANGE_RUN: moveType = MOVE_RUN; break; + case MovementChangeType::SPEED_CHANGE_RUN_BACK: moveType = MOVE_RUN_BACK; break; + case MovementChangeType::SPEED_CHANGE_SWIM: moveType = MOVE_SWIM; break; + case MovementChangeType::SPEED_CHANGE_SWIM_BACK: moveType = MOVE_SWIM_BACK; break; + case MovementChangeType::RATE_CHANGE_TURN: moveType = MOVE_TURN_RATE; break; + case MovementChangeType::SPEED_CHANGE_FLIGHT_SPEED: moveType = MOVE_FLIGHT; break; + case MovementChangeType::SPEED_CHANGE_FLIGHT_BACK_SPEED: moveType = MOVE_FLIGHT_BACK; break; + case MovementChangeType::RATE_CHANGE_PITCH: moveType = MOVE_PITCH_RATE; break; + case MovementChangeType::GRAVITY_DISABLE: + Unit::SetDisableGravity(pendingChange.second.apply); + break; + case MovementChangeType::SET_CAN_FLY: + Unit::SetCanFly(pendingChange.second.apply); + break; + case MovementChangeType::SET_CAN_TRANSITION_BETWEEN_SWIM_AND_FLY: + Unit::SetCanTransitionBetweenSwimAndFly(pendingChange.second.apply); + break; + case MovementChangeType::SET_COLLISION_HGT: + break; + default: + ASSERT(false); + return; + } + + if (moveType.has_value()) + { + float newSpeedRate = speedFlat / (IsControlledByPlayer() ? playerBaseMoveSpeed[*moveType] : baseMoveSpeed[*moveType]); + SetSpeedRateReal(*moveType, newSpeedRate); + + if (informObservers) + MovementPacketSender::SendSpeedChangeToObservers(this, *moveType, speedFlat); + } + else if (informObservers) + { + switch (changeType) + { + case MovementChangeType::GRAVITY_DISABLE: + case MovementChangeType::SET_CAN_FLY: + case MovementChangeType::SET_CAN_TRANSITION_BETWEEN_SWIM_AND_FLY: + MovementPacketSender::SendMovementFlagChangeToObservers(this); + break; + default: + break; + } + } + } + + m_pendingMovementChanges.clear(); +} + +PlayerMovementPendingChange::PlayerMovementPendingChange() +{ + time = GameTime::GetGameTimeMS(); +} + +// baseRage means damage taken when attacker = false +int32 Unit::RewardRage(uint32 baseRage, bool asAttacker) +{ + float addRage = baseRage; + + addRage *= sWorld->getRate(RATE_POWER_RAGE_INCOME); + return ModifyPower(POWER_RAGE, static_cast(addRage), !asAttacker); +} + +void Unit::StopAttackFaction(uint32 faction_id) +{ + if (Unit* victim = GetVictim()) + { + if (victim->GetFactionTemplateEntry()->Faction == faction_id) + { + AttackStop(); + if (IsNonMeleeSpellCast(false)) + InterruptNonMeleeSpells(false); + + // melee and ranged forced attack cancel + if (GetTypeId() == TYPEID_PLAYER) + ToPlayer()->SendAttackSwingCancelAttack(); + } + } + + AttackerSet const& attackers = getAttackers(); + for (AttackerSet::const_iterator itr = attackers.begin(); itr != attackers.end();) + { + if ((*itr)->GetFactionTemplateEntry()->Faction == faction_id) + { + (*itr)->AttackStop(); + itr = attackers.begin(); + } + else + ++itr; + } + + std::vector refsToEnd; + for (auto const& pair : m_combatManager.GetPvECombatRefs()) + if (pair.second->GetOther(this)->GetFactionTemplateEntry()->Faction == faction_id) + refsToEnd.push_back(pair.second); + for (CombatReference* ref : refsToEnd) + ref->EndCombat(); + + for (Unit* minion : m_Controlled) + minion->StopAttackFaction(faction_id); +} + +void Unit::OutDebugInfo() const +{ + TC_LOG_ERROR("entities.unit", "Unit::OutDebugInfo"); + TC_LOG_DEBUG("entities.unit", "%s name %s", GetGUID().ToString().c_str(), GetName().c_str()); + TC_LOG_DEBUG("entities.unit", "Owner %s, Minion %s, Charmer %s, Charmed %s", GetOwnerOrCreatorGUID().ToString().c_str(), GetMinionGUID().ToString().c_str(), GetCharmerGUID().ToString().c_str(), GetCharmedGUID().ToString().c_str()); + TC_LOG_DEBUG("entities.unit", "In world %u, unit type mask %u", (uint32)(IsInWorld() ? 1 : 0), m_unitTypeMask); + if (IsInWorld()) + TC_LOG_DEBUG("entities.unit", "Mapid %u", GetMapId()); + + std::ostringstream o; + o << "Summon Slot: "; + for (uint32 i = 0; i < MAX_SUMMON_SLOT; ++i) + o << m_SummonSlot[i].ToString() << ", "; + + TC_LOG_DEBUG("entities.unit", "%s", o.str().c_str()); + o.str(""); + + o << "Controlled List: "; + for (ControlList::const_iterator itr = m_Controlled.begin(); itr != m_Controlled.end(); ++itr) + o << (*itr)->GetGUID().ToString() << ", "; + TC_LOG_DEBUG("entities.unit", "%s", o.str().c_str()); + o.str(""); + + o << "Aura List: "; + for (AuraApplicationMap::const_iterator itr = m_appliedAuras.begin(); itr != m_appliedAuras.end(); ++itr) + o << itr->first << ", "; + TC_LOG_DEBUG("entities.unit", "%s", o.str().c_str()); + o.str(""); + + if (IsVehicle()) + { + o << "Passenger List: "; + for (SeatMap::iterator itr = GetVehicleKit()->Seats.begin(); itr != GetVehicleKit()->Seats.end(); ++itr) + if (Unit* passenger = ObjectAccessor::GetUnit(*GetVehicleBase(), itr->second.Passenger.Guid)) + o << passenger->GetGUID().ToString() << ", "; + TC_LOG_DEBUG("entities.unit", "%s", o.str().c_str()); + } + + if (GetVehicle()) + TC_LOG_DEBUG("entities.unit", "On vehicle %u.", GetVehicleBase()->GetEntry()); +} + +void Unit::SendClearTarget() +{ + WorldPackets::Combat::BreakTarget packet; + packet.UnitGUID = GetGUID(); + SendMessageToSet(packet.Write(), false); +} + +uint32 Unit::GetResistance(SpellSchoolMask mask) const +{ + int32 resist = -1; + for (int32 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 CharmInfo::SetIsCommandAttack(bool val) +{ + _isCommandAttack = val; +} + +bool CharmInfo::IsCommandAttack() +{ + return _isCommandAttack; +} + +void CharmInfo::SetIsCommandFollow(bool val) +{ + _isCommandFollow = val; +} + +bool CharmInfo::IsCommandFollow() +{ + return _isCommandFollow; +} + +void CharmInfo::SaveStayPosition() +{ + //! At this point a new spline destination is enabled because of Unit::StopMoving() + G3D::Vector3 stayPos = _unit->movespline->FinalDestination(); + + if (_unit->movespline->onTransport) + if (TransportBase* transport = _unit->GetDirectTransport()) + transport->CalculatePassengerPosition(stayPos.x, stayPos.y, stayPos.z); + + _stayX = stayPos.x; + _stayY = stayPos.y; + _stayZ = stayPos.z; +} + +void CharmInfo::GetStayPosition(float &x, float &y, float &z) +{ + x = _stayX; + y = _stayY; + z = _stayZ; +} + +void CharmInfo::SetIsAtStay(bool val) +{ + _isAtStay = val; +} + +bool CharmInfo::IsAtStay() +{ + return _isAtStay; +} + +void CharmInfo::SetIsFollowing(bool val) +{ + _isFollowing = val; +} + +bool CharmInfo::IsFollowing() +{ + return _isFollowing; +} + +void CharmInfo::SetIsReturning(bool val) +{ + _isReturning = val; +} + +bool CharmInfo::IsReturning() +{ + return _isReturning; +} + +void Unit::SetOrientationTowards(WorldObject const* target) +{ + SetOrientation(GetAngle(target)); +} + +void Unit::SetFacingTo(float ori, bool force) +{ + // do not face when already moving + if (!force && (!IsStopped() || !movespline->Finalized())) + return; + + Movement::MoveSplineInit init(this); + init.MoveTo(GetPositionX(), GetPositionY(), GetPositionZ(), false); + if (GetTransport()) + init.DisableTransportPathTransformations(); // It makes no sense to target global orientation + init.SetFacing(ori); + init.Launch(); + UpdateSplineMovement(1); +} + +void Unit::SetFacingToObject(WorldObject const* object, bool force) +{ + // do not face when already moving + if (!force && (!IsStopped() || !movespline->Finalized())) + return; + + /// @todo figure out under what conditions creature will move towards object instead of facing it where it currently is. + Movement::MoveSplineInit init(this); + init.MoveTo(GetPositionX(), GetPositionY(), GetPositionZ(), false); + init.SetFacing(GetAngle(object)); // when on transport, GetAngle will still return global coordinates (and angle) that needs transforming + init.Launch(); + UpdateSplineMovement(1); +} + +void Unit::SetFacingToPoint(Position const& point, bool force) +{ + // do not face when already moving + if (!force && (!IsStopped() || !movespline->Finalized())) + return; + + /// @todo figure out under what conditions creature will move towards object instead of facing it where it currently is. + Movement::MoveSplineInit init(this); + init.MoveTo(GetPositionX(), GetPositionY(), GetPositionZ(), false); + if (GetTransport()) + init.DisableTransportPathTransformations(); // It makes no sense to target global orientation + init.SetFacing(point.GetPositionX(), point.GetPositionY(), point.GetPositionZ()); + init.Launch(); + UpdateSplineMovement(1); +} + +bool Unit::SetWalk(bool enable) +{ + if (enable == IsWalking()) + return false; + + if (enable) + AddUnitMovementFlag(MOVEMENTFLAG_WALKING); + else + RemoveUnitMovementFlag(MOVEMENTFLAG_WALKING); + + ///@ TODO: Find proper opcode for walk mode setting in player mind controlling a player case + if (enable) + Movement::PacketSender(this, SMSG_SPLINE_MOVE_SET_WALK_MODE, SMSG_SPLINE_MOVE_SET_WALK_MODE).Send(); + else + Movement::PacketSender(this, SMSG_SPLINE_MOVE_SET_RUN_MODE, SMSG_SPLINE_MOVE_SET_RUN_MODE).Send(); + + return true; +} + +bool Unit::SetDisableGravity(bool disable, bool /*updateAnimTier = true*/) +{ + if (disable == IsGravityDisabled()) + return false; + + if (disable) + { + AddUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY); + RemoveUnitMovementFlag(MOVEMENTFLAG_SWIMMING | MOVEMENTFLAG_SPLINE_ELEVATION); + SetFall(false); + } + else + { + RemoveUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY); + if (!HasUnitMovementFlag(MOVEMENTFLAG_CAN_FLY)) + SetFall(true); + } + + if (IsAlive()) + { + if (IsGravityDisabled() || IsHovering()) + SetPlayHoverAnim(true); + else + SetPlayHoverAnim(false); + } + else if (IsPlayer()) // To update player who dies while flying/hovering + SetPlayHoverAnim(false, false); + + return true; +} + +bool Unit::SetFall(bool enable) +{ + if (enable == HasUnitMovementFlag(MOVEMENTFLAG_FALLING)) + return false; + + if (enable) + { + AddUnitMovementFlag(MOVEMENTFLAG_FALLING); + m_movementInfo.SetFallTime(0); + } + else + RemoveUnitMovementFlag(MOVEMENTFLAG_FALLING | MOVEMENTFLAG_FALLING_FAR); + + return true; +} + +bool Unit::SetSwim(bool enable) +{ + if (enable == HasUnitMovementFlag(MOVEMENTFLAG_SWIMMING)) + return false; + + if (enable) + AddUnitMovementFlag(MOVEMENTFLAG_SWIMMING); + else + RemoveUnitMovementFlag(MOVEMENTFLAG_SWIMMING); + + if (enable) + Movement::PacketSender(this, SMSG_SPLINE_MOVE_START_SWIM, NULL_OPCODE).Send(); + else + Movement::PacketSender(this, SMSG_SPLINE_MOVE_STOP_SWIM, NULL_OPCODE).Send(); + + return true; +} + +bool Unit::SetCanFly(bool enable) +{ + if (enable == HasUnitMovementFlag(MOVEMENTFLAG_CAN_FLY)) + return false; + + if (enable) + { + AddUnitMovementFlag(MOVEMENTFLAG_CAN_FLY); + RemoveUnitMovementFlag(MOVEMENTFLAG_SWIMMING | MOVEMENTFLAG_SPLINE_ELEVATION); + SetFall(false); + } + else + { + if (IsFlying() && !IsGravityDisabled()) + SetFall(true); + RemoveUnitMovementFlag(MOVEMENTFLAG_CAN_FLY | MOVEMENTFLAG_MASK_MOVING_FLY); + } + + return true; +} + +bool Unit::SetCanTransitionBetweenSwimAndFly(bool enable) +{ + if (enable == HasExtraUnitMovementFlag(MOVEMENTFLAG2_CAN_SWIM_TO_FLY_TRANS)) + return false; + + if (enable) + AddExtraUnitMovementFlag(MOVEMENTFLAG2_CAN_SWIM_TO_FLY_TRANS); + else + RemoveExtraUnitMovementFlag(MOVEMENTFLAG2_CAN_SWIM_TO_FLY_TRANS); + + return true; +} + +bool Unit::SetWaterWalking(bool enable) +{ + if (enable == HasUnitMovementFlag(MOVEMENTFLAG_WATERWALKING)) + return false; + + if (enable) + AddUnitMovementFlag(MOVEMENTFLAG_WATERWALKING); + else + RemoveUnitMovementFlag(MOVEMENTFLAG_WATERWALKING); + + if (enable) + Movement::PacketSender(this, SMSG_SPLINE_MOVE_SET_WATER_WALK, SMSG_MOVE_WATER_WALK).Send(); + else + Movement::PacketSender(this, SMSG_SPLINE_MOVE_SET_LAND_WALK, SMSG_MOVE_LAND_WALK).Send(); + + return true; +} + +bool Unit::SetFeatherFall(bool enable) +{ + if (enable == HasUnitMovementFlag(MOVEMENTFLAG_FALLING_SLOW)) + return false; + + if (enable) + AddUnitMovementFlag(MOVEMENTFLAG_FALLING_SLOW); + else + RemoveUnitMovementFlag(MOVEMENTFLAG_FALLING_SLOW); + + if (enable) + Movement::PacketSender(this, SMSG_SPLINE_MOVE_SET_FEATHER_FALL, SMSG_MOVE_FEATHER_FALL).Send(); + else + Movement::PacketSender(this, SMSG_SPLINE_MOVE_SET_NORMAL_FALL, SMSG_MOVE_NORMAL_FALL).Send(); + + return true; +} + +bool Unit::SetHover(bool enable, bool /*updateAnimTier = true*/) +{ + if (enable == HasUnitMovementFlag(MOVEMENTFLAG_HOVER)) + return false; + + float hoverHeight = GetFloatValue(UNIT_FIELD_HOVERHEIGHT); + + if (enable) + { + //! No need to check height on ascent + AddUnitMovementFlag(MOVEMENTFLAG_HOVER); + if (hoverHeight && GetPositionZ() - GetFloorZ() < hoverHeight) + UpdateHeight(GetPositionZ() + hoverHeight); + } + else + { + RemoveUnitMovementFlag(MOVEMENTFLAG_HOVER); + //! Dying creatures will MoveFall from setDeathState + if (hoverHeight && (!isDying() || GetTypeId() != TYPEID_UNIT)) + { + float newZ = std::max(GetFloorZ(), GetPositionZ() - hoverHeight); + UpdateAllowedPositionZ(GetPositionX(), GetPositionY(), newZ); + UpdateHeight(newZ); + } + } + + if (enable) + Movement::PacketSender(this, SMSG_SPLINE_MOVE_SET_HOVER, SMSG_MOVE_SET_HOVER).Send(); + else + Movement::PacketSender(this, SMSG_SPLINE_MOVE_UNSET_HOVER, SMSG_MOVE_UNSET_HOVER).Send(); + + if (IsAlive()) + { + if (IsGravityDisabled() || IsHovering()) + SetPlayHoverAnim(true); + else + SetPlayHoverAnim(false); + } + else if (IsPlayer()) // To update player who dies while flying/hovering + SetPlayHoverAnim(false, false); + + return true; +} + +void Unit::SendSetVehicleRecId(uint32 vehicleId) +{ + WorldPackets::Vehicle::SetVehicleRecID setVehicleRec; + setVehicleRec.VehicleGUID = GetGUID(); + setVehicleRec.VehicleRecID = vehicleId; + SendMessageToSet(setVehicleRec.Write(), true); +} + +void Unit::SetPlayHoverAnim(bool enable, bool sendUpdate /*= true*/) +{ + if (IsPlayingHoverAnim() == enable) + return; + + m_updateFlag.PlayerHoverAnim = enable; + + if (!sendUpdate) + return; + + WorldPackets::Misc::SetPlayHoverAnim packet; + packet.UnitGUID = GetGUID(); + packet.PlayHoverAnim = enable; + SendMessageToSet(packet.Write(), true); +} + +bool Unit::IsSplineEnabled() const +{ + return movespline->Initialized() && !movespline->Finalized(); +} + +void Unit::BuildValuesUpdate(uint8 updateType, ByteBuffer* data, Player* target) const +{ + if (!target) + return; + + ByteBuffer fieldBuffer; + + uint32 valCount = m_valuesCount; + + uint32* flags = UnitUpdateFieldFlags; + uint32 visibleFlag = UF_FLAG_PUBLIC; + + if (target == this) + visibleFlag |= UF_FLAG_PRIVATE; + else if (GetTypeId() == TYPEID_PLAYER) + valCount = PLAYER_END_NOT_SELF; + + UpdateMaskPacketBuilder updateMask(valCount); + + Player* plr = GetCharmerOrOwnerPlayerOrPlayerItself(); + if (GetOwnerGUID() == target->GetGUID()) + visibleFlag |= UF_FLAG_OWNER; + + if (HasFlag(UNIT_DYNAMIC_FLAGS, 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; + + if (IsCreature()) + visibleFlag |= UF_FLAG_UNIT_ALL; + + Creature const* creature = ToCreature(); + for (uint16 index = 0; index < valCount; ++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) + { + uint32 appendValue = m_uint32Values[UNIT_NPC_FLAGS]; + + if (creature) + { + if (!target->CanSeeSpellClickOn(creature)) + appendValue &= ~UNIT_NPC_FLAG_SPELLCLICK; + + if (!creature->IsClassTrainerOf(target)) + appendValue &= ~UNIT_NPC_FLAG_TRAINER_CLASS; + } + + fieldBuffer << uint32(appendValue); + } + else if (index == UNIT_FIELD_AURASTATE) + { + // Check per caster aura states to not enable using a spell in client if specified aura is not by target + fieldBuffer << BuildAuraStateUpdateForTarget(target); + } + // 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_RESISTANCEBUFFMODSPOSITIVE && index <= (UNIT_FIELD_RESISTANCEBUFFMODSPOSITIVE + 6)) || + (index >= UNIT_FIELD_RESISTANCEBUFFMODSNEGATIVE && index <= (UNIT_FIELD_RESISTANCEBUFFMODSNEGATIVE + 6))) + { + fieldBuffer << uint32(m_floatValues[index]); + } + // Gamemasters should be always able to select units - remove not selectable flag + else if (index == UNIT_FIELD_FLAGS) + { + uint32 appendValue = m_uint32Values[UNIT_FIELD_FLAGS]; + if (target->IsGameMaster()) + appendValue &= ~UNIT_FLAG_NOT_SELECTABLE; + + fieldBuffer << uint32(appendValue); + } + // use modelid_a if not gm, _h if gm for CREATURE_FLAG_EXTRA_TRIGGER creatures + else if (index == UNIT_FIELD_DISPLAYID) + { + 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->flags_extra & CREATURE_FLAG_EXTRA_TRIGGER) + if (target->IsGameMaster()) + displayId = cinfo->GetFirstVisibleModel()->CreatureDisplayID; + } + + fieldBuffer << uint32(displayId); + } + // hide lootable animation for unallowed players + else if (index == UNIT_DYNAMIC_FLAGS) + { + 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; + + fieldBuffer << dynamicFlags; + } + // FG: pretend that OTHER players in own group are friendly ("blue") + else if (index == UNIT_FIELD_BYTES_2 || index == UNIT_FIELD_FACTIONTEMPLATE) + { + 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)) + { + if (index == UNIT_FIELD_BYTES_2) + // Allow targetting opposite faction in party when enabled in config + fieldBuffer << (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 !! + else + // pretend that all other HOSTILE players have own faction, to allow follow, heal, rezz (trade wont work) + fieldBuffer << uint32(target->GetFaction()); + } + else + fieldBuffer << m_uint32Values[index]; + } + else + fieldBuffer << m_uint32Values[index]; + } + else + { + // send in current format (float as float, uint32 as uint32) + fieldBuffer << m_uint32Values[index]; + } + } + } + + updateMask.AppendToPacket(data); + data->append(fieldBuffer); +} + +void Unit::DestroyForPlayer(Player* target, bool /*onDeath = false*/) const +{ + if (Battleground* bg = target->GetBattleground()) + { + if (bg->isArena()) + { + WorldPacket data(SMSG_ARENA_UNIT_DESTROYED, 8); + data << GetGUID(); + target->SendDirectMessage(&data); + } + } + + WorldObject::DestroyForPlayer(target); +} + +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 + 1) + itr = auras.begin(); + } + } + } + else if (diff < 0) + return false; + } + } + + return true; +} + +void Unit::Talk(std::string const& text, ChatMsg msgType, Language language, float textRange, WorldObject const* target) +{ + Trinity::CustomChatTextBuilder builder(this, msgType, text, language, target); + Trinity::LocalizedPacketDo localizer(builder); + Trinity::PlayerDistWorker > worker(this, textRange, localizer); + Cell::VisitWorldObjects(this, worker, textRange); +} + +void Unit::Say(std::string const& 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 const& 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 const& 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 const& 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); +} + +void Unit::Talk(uint32 textId, ChatMsg msgType, float textRange, WorldObject const* target) +{ + if (!sObjectMgr->GetBroadcastText(textId)) + { + TC_LOG_ERROR("entities.unit", "WorldObject::MonsterText: `broadcast_text` (ID: %u) was not found", textId); + return; + } + + Trinity::BroadcastTextBuilder builder(this, msgType, textId, getGender(), target); + Trinity::LocalizedPacketDo localizer(builder); + Trinity::PlayerDistWorker > worker(this, textRange, localizer); + Cell::VisitWorldObjects(this, worker, textRange); +} + +void Unit::Say(uint32 textId, WorldObject const* target /*= nullptr*/) +{ + Talk(textId, CHAT_MSG_MONSTER_SAY, sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_SAY), target); +} + +void Unit::Yell(uint32 textId, WorldObject const* target /*= nullptr*/) +{ + Talk(textId, CHAT_MSG_MONSTER_YELL, sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_YELL), target); +} + +void Unit::TextEmote(uint32 textId, WorldObject const* target /*= nullptr*/, bool isBossEmote /*= false*/) +{ + Talk(textId, isBossEmote ? CHAT_MSG_RAID_BOSS_EMOTE : CHAT_MSG_MONSTER_EMOTE, sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_TEXTEMOTE), target); +} + +void Unit::Whisper(uint32 textId, Player* target, bool isBossWhisper /*= false*/) +{ + if (!target) + return; + + BroadcastText const* bct = sObjectMgr->GetBroadcastText(textId); + if (!bct) + { + TC_LOG_ERROR("entities.unit", "WorldObject::MonsterWhisper: `broadcast_text` was not %u 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); +} + +// Returns collisionheight of the unit. If it is 0, it returns DEFAULT_COLLISION_HEIGHT. +float Unit::GetCollisionHeight() const +{ + float scaleMod = GetObjectScale(); // 99% sure about this + + if (IsMounted()) + { + if (CreatureDisplayInfoEntry const* mountDisplayInfo = sCreatureDisplayInfoStore.LookupEntry(GetUInt32Value(UNIT_FIELD_MOUNTDISPLAYID))) + { + if (CreatureModelDataEntry const* mountModelData = sCreatureModelDataStore.LookupEntry(mountDisplayInfo->ModelID)) + { + CreatureDisplayInfoEntry const* displayInfo = sCreatureDisplayInfoStore.AssertEntry(GetNativeDisplayId()); + CreatureModelDataEntry const* modelData = sCreatureModelDataStore.AssertEntry(displayInfo->ModelID); + float const collisionHeight = scaleMod * ((mountModelData->MountHeight * mountDisplayInfo->CreatureModelScale) + (modelData->CollisionHeight * modelData->ModelScale * displayInfo->CreatureModelScale * 0.5f)); + return collisionHeight == 0.0f ? DEFAULT_COLLISION_HEIGHT : collisionHeight; + } + } + } + + //! Dismounting case - use basic default model data + CreatureDisplayInfoEntry const* displayInfo = sCreatureDisplayInfoStore.AssertEntry(GetNativeDisplayId()); + CreatureModelDataEntry const* modelData = sCreatureModelDataStore.AssertEntry(displayInfo->ModelID); + + float const collisionHeight = scaleMod * modelData->CollisionHeight * modelData->ModelScale * displayInfo->CreatureModelScale; + return collisionHeight == 0.0f ? DEFAULT_COLLISION_HEIGHT : collisionHeight; +} + +SpellInfo const* Unit::GetCastSpellInfo(SpellInfo const* spellInfo, TriggerCastFlags& triggerFlag) const +{ + Unit::AuraEffectList const& swaps = GetAuraEffectsByType(SPELL_AURA_OVERRIDE_ACTIONBAR_SPELLS); + Unit::AuraEffectList const& swaps2 = GetAuraEffectsByType(SPELL_AURA_OVERRIDE_ACTIONBAR_SPELLS_TRIGGERED); + + for (AuraEffect const* auraEffect : swaps) + { + if (uint32(auraEffect->GetMiscValue()) == spellInfo->Id || auraEffect->IsAffectingSpell(spellInfo)) + { + if (SpellInfo const* newInfo = sSpellMgr->GetSpellInfo(auraEffect->GetAmount())) + { + if (auraEffect->GetSpellInfo()->HasAttribute(SPELL_ATTR8_IGNORE_SPELLCAST_OVERRIDE_COST)) + triggerFlag |= TRIGGERED_IGNORE_POWER_COST; + else + triggerFlag &= ~TRIGGERED_IGNORE_POWER_COST; + + return newInfo; + } + } + } + + for (AuraEffect const* auraEffect : swaps2) + { + if (uint32(auraEffect->GetMiscValue()) == spellInfo->Id || auraEffect->IsAffectingSpell(spellInfo)) + { + if (SpellInfo const* newInfo = sSpellMgr->GetSpellInfo(auraEffect->GetAmount())) + { + if (auraEffect->GetSpellInfo()->HasAttribute(SPELL_ATTR8_IGNORE_SPELLCAST_OVERRIDE_COST)) + triggerFlag |= TRIGGERED_IGNORE_POWER_COST; + else + triggerFlag &= ~TRIGGERED_IGNORE_POWER_COST; + + return newInfo; + } + } + } + + return spellInfo; +} + +bool Unit::CanExecutePendingSpellCastRequest(SpellInfo const* spellInfo) const +{ + // Generic and melee spells have to wait, channeled spells can be processed immediately. + if (!GetCurrentSpell(CURRENT_CHANNELED_SPELL) && HasUnitState(UNIT_STATE_CASTING)) + return false; + + // Waiting for the global cooldown to expire + if (GetSpellHistory()->GetRemainingGlobalCooldown(spellInfo) > 0) + return false; + + return true; +} + +void Unit::RequestSpellCast(std::unique_ptr castRequest, SpellInfo const* spellInfo) +{ + // We are overriding an already existing spell cast request so inform the client that the old cast is being replaced + if (_pendingSpellCastRequest) + CancelPendingCastRequest(); + + _pendingSpellCastRequest = std::move(castRequest); + + // If we can process the cast request right now, do it. + if (CanExecutePendingSpellCastRequest(spellInfo)) + ProcessPendingSpellCastRequest(); +} + +void Unit::CancelPendingCastRequest() +{ + if (!_pendingSpellCastRequest) + return; + + // We have to inform the client that the cast has been canceled to avoid the cast button to stay highlightened + if (IsPlayer()) + { + // @todo: convert to packet class + WorldPacket data(SMSG_CAST_FAILED, 1 + 4 + 1); + data << uint8(_pendingSpellCastRequest->CastRequest.CastID); + data << uint32(_pendingSpellCastRequest->CastRequest.SpellID); + data << uint8(SPELL_FAILED_DONT_REPORT); + ToPlayer()->SendDirectMessage(&data); + } + + _pendingSpellCastRequest = nullptr; +} + +// A spell can be queued up within 400 milliseconds before global cooldown expires or the cast finishes +static constexpr uint16 const SPELL_QUEUE_TIME_WINDOW = 400; + +bool Unit::CanRequestSpellCast(SpellInfo const* spellInfo) const +{ + if (GetSpellHistory()->GetRemainingGlobalCooldown(spellInfo) > SPELL_QUEUE_TIME_WINDOW) + return false; + + for (CurrentSpellTypes spellSlot : { CURRENT_MELEE_SPELL, CURRENT_GENERIC_SPELL }) + if (Spell const* spell = GetCurrentSpell(spellSlot)) + if (spell->GetRemainingCastTime() > SPELL_QUEUE_TIME_WINDOW) + return false; + + return true; +} + +void Unit::ProcessPendingSpellCastRequest() +{ + if (!_pendingSpellCastRequest) + return; + + // Sanity check. If the player requested an invalid spell cast, just skip the request. + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(_pendingSpellCastRequest->CastRequest.SpellID); + if (!spellInfo) + { + _pendingSpellCastRequest = nullptr; + return; + } + + // Waiting for ongoing casts to finish and the global cooldown to expire. + if (!CanExecutePendingSpellCastRequest(spellInfo)) + return; + + // Spell can be executed now. Check data and prepare cast. + Unit* caster = this; + + // Process packet provided targets + SpellCastTargets targets(caster, _pendingSpellCastRequest->CastRequest); + + // The spell cast has been requested by using an item. Handle the cast accordingly. + if (_pendingSpellCastRequest->CastItemData.has_value()) + { + ProcessItemCast(*_pendingSpellCastRequest, targets); + _pendingSpellCastRequest = nullptr; + return; + } + + if (caster->IsCreature() && !caster->ToCreature()->HasSpell(spellInfo->Id)) + { + // If the vehicle creature does not have the spell but it allows the passenger to cast own spells + // change caster to player and let him cast + + Unit* charmer = caster->GetCharmer(); + if (charmer && (!charmer->IsOnVehicle(this) || spellInfo->CheckVehicle(charmer) != SPELL_CAST_OK)) + return; + else if (charmer) + caster = charmer; + else + { + TC_LOG_ERROR("spells", "Creature %s tried to allow its charmer to cast spell %u but there is no charmer.", caster->GetGUID().ToString().c_str(), spellInfo->Id); + return; + } + } + + TriggerCastFlags triggerFlag = TRIGGERED_NONE; + + if (caster->GetTypeId() == TYPEID_PLAYER && !caster->ToPlayer()->HasActiveSpell(spellInfo->Id) && !spellInfo->HasAttribute(SPELL_ATTR8_SKIP_IS_KNOWN_CHECK) && + !caster->ToPlayer()->HasArchProject(static_cast(spellInfo->ResearchProjectId))) + { + bool allow = false; + + // allow casting of unknown spells for special lock cases + if (GameObject *go = targets.GetGOTarget()) + if (go->GetSpellForLock(caster->ToPlayer()) == spellInfo) + allow = true; + + // TODO: Preparation for #23204 + // allow casting of spells triggered by clientside periodic trigger auras + /* + if (caster->HasAuraTypeWithTriggerSpell(SPELL_AURA_PERIODIC_TRIGGER_SPELL_FROM_CLIENT, spellId)) + { + allow = true; + triggerFlag = TRIGGERED_FULL_MASK; + } + */ + + if (!allow) + { + _pendingSpellCastRequest = nullptr; + return; + } + } + + // Check possible spell cast overrides + spellInfo = caster->GetCastSpellInfo(spellInfo, triggerFlag); + + // can't use our own spells when we're in possession of another unit, + if (caster->isPossessing()) + { + _pendingSpellCastRequest = nullptr; + return; + } + + // Client is resending autoshot cast opcode when other spell is cast during shoot rotation + // Skip it to prevent "interrupt" message + // Also check targets! target may have changed and we need to interrupt current spell + if (spellInfo->IsAutoRepeatRangedSpell()) + { + if (Spell* spell = caster->GetCurrentSpell(CURRENT_AUTOREPEAT_SPELL)) + { + if (spell->m_spellInfo == spellInfo && spell->m_targets.GetUnitTargetGUID() == targets.GetUnitTargetGUID()) + { + _pendingSpellCastRequest.reset(); + return; + } + } + } + + // auto-selection buff level base at target level (in spellInfo) + // TODO: is this even necessary? client already seems to send correct rank for "standard" buffs + if (spellInfo->IsPositive()) + { + if (Unit* target = targets.GetUnitTarget()) + { + SpellInfo const* actualSpellInfo = spellInfo->GetAuraRankForLevel(target->getLevel()); + + // if rank not found then function return NULL but in explicit cast case original spell can be cast and later failed with appropriate error message + if (actualSpellInfo) + spellInfo = actualSpellInfo; + } + } + + if (!_pendingSpellCastRequest->CastRequest.Weight.empty()) + { + ArchData archaeologyCastData; + for (WorldPackets::Spells::SpellWeight const& weight : _pendingSpellCastRequest->CastRequest.Weight) + { + switch (weight.Type) + { + case 1: // Currency + archaeologyCastData.FragId = weight.ID; + archaeologyCastData.FragCount = weight.Quantity; + break; + case 2: // Item + archaeologyCastData.KeyId = weight.ID; + archaeologyCastData.KeyCount = weight.Quantity; + break; + default: + break; + } + } + + if (Player* player = caster->ToPlayer()) + player->SetArchData(archaeologyCastData); + } + + Spell* spell = new Spell(caster, spellInfo, triggerFlag); + spell->m_cast_count = _pendingSpellCastRequest->CastRequest.CastID; // set count of casts + spell->m_glyphIndex = _pendingSpellCastRequest->CastRequest.Misc; + spell->prepare(targets); + + _pendingSpellCastRequest.reset(); +} + +void Unit::ProcessItemCast(PendingSpellCastRequest const& castRequest, SpellCastTargets const& targets) +{ + Player* player = ToPlayer(); + if (!player || !castRequest.CastItemData.has_value()) + return; + + Item* item = player->GetUseableItemByPos(castRequest.CastItemData->BagSlot, castRequest.CastItemData->Slot); + if (!item) + { + player->SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, nullptr, nullptr); + return; + } + + if (item->GetGUID() != castRequest.CastItemData->CastItem) + { + player->SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, nullptr, nullptr); + return; + } + + ItemTemplate const* proto = item->GetTemplate(); + if (!proto) + { + player->SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, item, nullptr); + return; + } + + // some item classes can be used only in equipped state + if (proto->GetInventoryType() != INVTYPE_NON_EQUIP && !item->IsEquipped()) + { + player->SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, item, nullptr); + return; + } + + InventoryResult msg = player->CanUseItem(item); + if (msg != EQUIP_ERR_OK) + { + player->SendEquipError(msg, item, nullptr); + return; + } + + // only allow conjured consumable, bandage, poisons (all should have the 2^21 item flag set in DB) + if (proto->GetClass() == ITEM_CLASS_CONSUMABLE && !(proto->GetFlags() & ITEM_FLAG_IGNORE_DEFAULT_ARENA_RESTRICTIONS) && player->InArena()) + { + player->SendEquipError(EQUIP_ERR_NOT_DURING_ARENA_MATCH, item, nullptr); + return; + } + + // don't allow items banned in arena + if ((proto->GetFlags() & ITEM_FLAG_NOT_USEABLE_IN_ARENA) && player->InArena()) + { + player->SendEquipError(EQUIP_ERR_NOT_DURING_ARENA_MATCH, item, nullptr); + return; + } + + if (player->IsInCombat()) + { + for (ItemEffect const& effect : proto->Effects) + { + if (SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(effect.SpellID)) + { + if (!spellInfo->CanBeUsedInCombat()) + { + player->SendEquipError(EQUIP_ERR_NOT_IN_COMBAT, item, nullptr); + return; + } + } + } + } + + // check also BIND_ON_ACQUIRE and BIND_QUEST for .additem or .additemset case by GM (not binded at adding to inventory) + if (item->GetTemplate()->GetBonding() == BIND_ON_USE || item->GetTemplate()->GetBonding() == BIND_ON_ACQUIRE || item->GetTemplate()->GetBonding() == BIND_QUEST) + { + if (!item->IsSoulBound()) + { + item->SetState(ITEM_CHANGED, player); + item->SetBinding(true); + } + } + + player->RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::ItemUse); + + // Note: If script stop casting it must send appropriate data to client to prevent stuck item in gray state. + if (!sScriptMgr->OnItemUse(player, item, targets)) + { + // no script or script not process request by self + player->CastItemUseSpell(item, targets, castRequest.CastRequest.CastID, castRequest.CastRequest.Misc); + } +} + +void Unit::SetGameClientMovingMe(GameClient* gameClientMovingMe) +{ + _gameClientMovingMe = gameClientMovingMe; +} + +Stats& Unit::GetStats() const +{ + return *_stats; +} + +int32 Unit::GetStat(StatType statType) const +{ + if (statType < StatType::Strength) + return 0; + + return GetInt32Value(UNIT_FIELD_STAT0 + AsUnderlyingType(statType)); +} diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h index 437ebcfa01..4d8bbf00c9 100644 --- a/src/server/game/Entities/Unit/Unit.h +++ b/src/server/game/Entities/Unit/Unit.h @@ -837,6 +837,9 @@ class TC_GAME_API Unit : public WorldObject bool IsInPartyWith(Unit const* unit) const; bool IsInRaidWith(Unit const* unit) const; void GetPartyMembers(std::list &units); + // VIANO_GROUP_CONTEXT_V1 + bool IsVianoAdventurer() const; + uint32 GetVianoEffectiveGroupSize() const; bool IsContestedGuard() const; bool IsInSanctuary() const { return HasByteFlag(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PVP_FLAG, UNIT_BYTE2_FLAG_SANCTUARY); } bool IsPvP() const { return HasByteFlag(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PVP_FLAG, UNIT_BYTE2_FLAG_PVP); } diff --git a/src/server/game/Entities/Unit/Unit.h.bak-viano-group-context-v1-20261001-134352 b/src/server/game/Entities/Unit/Unit.h.bak-viano-group-context-v1-20261001-134352 new file mode 100644 index 0000000000..437ebcfa01 --- /dev/null +++ b/src/server/game/Entities/Unit/Unit.h.bak-viano-group-context-v1-20261001-134352 @@ -0,0 +1,1869 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef __UNIT_H +#define __UNIT_H + +#include "CombatManager.h" +#include "Object.h" +#include "SpellAuraDefines.h" +#include "SpellDefines.h" +#include "ThreatManager.h" +#include "Timer.h" +#include "UnitDefines.h" +#include "Util.h" + +#include +#include +#include +#include +#include + +constexpr uint32 WORLD_TRIGGER = 12999; + +constexpr uint8 MAX_SPELL_CHARM = 4; +constexpr uint8 MAX_SPELL_VEHICLE = 6; +constexpr uint8 MAX_SPELL_POSSESS = 8; +constexpr uint8 MAX_SPELL_CONTROL_BAR = 10; + +constexpr uint8 MAX_AGGRO_RESET_TIME = 10; // in seconds +constexpr float MAX_AGGRO_RADIUS = 45.0f; // yards + +enum VictimState +{ + VICTIMSTATE_INTACT = 0, // set when attacker misses + VICTIMSTATE_HIT = 1, // victim got clear/blocked hit + VICTIMSTATE_DODGE = 2, + VICTIMSTATE_PARRY = 3, + VICTIMSTATE_INTERRUPT = 4, + VICTIMSTATE_BLOCKS = 5, // unused? not set when blocked, even on full block + VICTIMSTATE_EVADES = 6, + VICTIMSTATE_IS_IMMUNE = 7, + VICTIMSTATE_DEFLECTS = 8 +}; + +//i would like to remove this: (it is defined in item.h +enum InventorySlot +{ + NULL_BAG = 0, + NULL_SLOT = 255 +}; + +struct AbstractPursuer; +struct CharmInfo; +struct FactionTemplateEntry; +struct LiquidData; +struct LiquidTypeEntry; +struct MountCapabilityEntry; +struct PendingSpellCastRequest; +struct SpellValue; + +class Aura; +class AuraApplication; +class AuraEffect; +class Creature; +class DynamicObject; +class GameClient; +class GameObject; +class Guardian; +class Item; +class Minion; +class MotionMaster; +class Pet; +class Spell; +class SpellCastTargets; +class SpellHistory; +class SpellInfo; +class Stats; +class Totem; +class Transport; +class TransportBase; +class UnitAI; +class UnitAura; +class Vehicle; +class VehicleJoinEvent; + +enum ProcFlags : uint32; +enum ProcFlagsHit : uint32; +enum ProcFlagsSpellPhase : uint32; +enum ProcFlagsSpellType : uint32; +enum ZLiquidStatus : uint32; +enum CharmType : uint8; + +enum class StatType : int8; + +namespace Movement +{ + class ExtraMovementStatusElement; + class MoveSpline; +} + +typedef std::list UnitList; + +class DispelableAura +{ + public: + DispelableAura(Aura* aura, int32 dispelChance, uint8 dispelCharges); + ~DispelableAura(); + + Aura* GetAura() const { return _aura; } + bool RollDispel() const; + uint8 GetDispelCharges() const { return _charges; } + + void IncrementCharges() { ++_charges; } + bool DecrementCharge(uint8 charges) + { + if (!_charges) + return false; + + _charges -= charges; + return _charges > 0; + } + + private: + Aura* _aura; + int32 _chance; + uint8 _charges; +}; +typedef std::vector DispelChargesList; + +typedef std::unordered_multimap SpellImmuneContainer; + +enum UnitModifierFlatType +{ + BASE_VALUE = 0, + TOTAL_VALUE = 1, + MODIFIER_TYPE_FLAT_END = 2 +}; + +enum UnitModifierPctType +{ + BASE_PCT = 0, + TOTAL_PCT = 1, + MODIFIER_TYPE_PCT_END = 2 +}; + +enum WeaponDamageRange +{ + MINDAMAGE, + MAXDAMAGE +}; + +enum UnitMods +{ + UNIT_MOD_HEALTH, + UNIT_MOD_MANA, // UNIT_MOD_MANA..UNIT_MOD_RUNIC_POWER must be in existed order, it's accessed by index values of Powers enum. + UNIT_MOD_RAGE, + UNIT_MOD_FOCUS, + UNIT_MOD_ENERGY, + UNIT_MOD_UNUSED, // Old UNIT_MOD_HAPPINESS + UNIT_MOD_RUNE, + UNIT_MOD_RUNIC_POWER, + UNIT_MOD_SOUL_SHARDS, + UNIT_MOD_ECLIPSE, + UNIT_MOD_HOLY_POWER, + UNIT_MOD_ALTERNATIVE, + UNIT_MOD_ARMOR, // UNIT_MOD_ARMOR..UNIT_MOD_RESISTANCE_ARCANE must be in existed order, it's accessed by index values of SpellSchools enum. + UNIT_MOD_RESISTANCE_HOLY, + UNIT_MOD_RESISTANCE_FIRE, + UNIT_MOD_RESISTANCE_NATURE, + UNIT_MOD_RESISTANCE_FROST, + UNIT_MOD_RESISTANCE_SHADOW, + UNIT_MOD_RESISTANCE_ARCANE, + UNIT_MOD_ATTACK_POWER, + UNIT_MOD_ATTACK_POWER_RANGED, + UNIT_MOD_DAMAGE_MAINHAND, + UNIT_MOD_DAMAGE_OFFHAND, + UNIT_MOD_DAMAGE_RANGED, + UNIT_MOD_END, + // synonyms + UNIT_MOD_RESISTANCE_START = UNIT_MOD_ARMOR, + UNIT_MOD_RESISTANCE_END = UNIT_MOD_RESISTANCE_ARCANE + 1, + UNIT_MOD_POWER_START = UNIT_MOD_MANA, + UNIT_MOD_POWER_END = UNIT_MOD_ALTERNATIVE + 1 +}; + +enum BaseModGroup +{ + CRIT_PERCENTAGE, + RANGED_CRIT_PERCENTAGE, + OFFHAND_CRIT_PERCENTAGE, + SHIELD_BLOCK_VALUE, + BASEMOD_END +}; + +enum BaseModType +{ + FLAT_MOD, + PCT_MOD, + MOD_END +}; + +enum DeathState +{ + ALIVE = 0, + JUST_DIED = 1, + CORPSE = 2, + DEAD = 3, + JUST_RESPAWNED = 4 +}; + +enum UnitState : uint32 +{ + UNIT_STATE_DIED = 0x00000001, // player has fake death aura + UNIT_STATE_MELEE_ATTACKING = 0x00000002, // player is melee attacking someone + UNIT_STATE_CHARMED = 0x00000004, // having any kind of charm aura on self + UNIT_STATE_STUNNED = 0x00000008, + UNIT_STATE_ROAMING = 0x00000010, + UNIT_STATE_CHASE = 0x00000020, + UNIT_STATE_FOCUSING = 0x00000040, + UNIT_STATE_FLEEING = 0x00000080, + UNIT_STATE_IN_FLIGHT = 0x00000100, // player is in flight mode + UNIT_STATE_FOLLOW = 0x00000200, + UNIT_STATE_ROOT = 0x00000400, + UNIT_STATE_CONFUSED = 0x00000800, + UNIT_STATE_DISTRACTED = 0x00001000, + UNIT_STATE_ISOLATED = 0x00002000, // area auras do not affect other players + UNIT_STATE_ATTACK_PLAYER = 0x00004000, + UNIT_STATE_CASTING = 0x00008000, + UNIT_STATE_POSSESSED = 0x00010000, // being possessed by another unit + UNIT_STATE_CHARGING = 0x00020000, + UNIT_STATE_JUMPING = 0x00040000, + UNIT_STATE_MOVE = 0x00100000, + UNIT_STATE_ROTATING = 0x00200000, + UNIT_STATE_EVADE = 0x00400000, + UNIT_STATE_ROAMING_MOVE = 0x00800000, + UNIT_STATE_CONFUSED_MOVE = 0x01000000, + UNIT_STATE_FLEEING_MOVE = 0x02000000, + UNIT_STATE_CHASE_MOVE = 0x04000000, + UNIT_STATE_FOLLOW_MOVE = 0x08000000, + UNIT_STATE_IGNORE_PATHFINDING = 0x10000000, // do not use pathfinding in any MovementGenerator + + UNIT_STATE_ALL_STATE_SUPPORTED = UNIT_STATE_DIED | UNIT_STATE_MELEE_ATTACKING | UNIT_STATE_CHARMED | UNIT_STATE_STUNNED | UNIT_STATE_ROAMING | UNIT_STATE_CHASE + | UNIT_STATE_FOCUSING | UNIT_STATE_FLEEING | UNIT_STATE_IN_FLIGHT | UNIT_STATE_FOLLOW | UNIT_STATE_ROOT | UNIT_STATE_CONFUSED + | UNIT_STATE_DISTRACTED | UNIT_STATE_ISOLATED | UNIT_STATE_ATTACK_PLAYER | UNIT_STATE_CASTING + | UNIT_STATE_POSSESSED | UNIT_STATE_CHARGING | UNIT_STATE_JUMPING | UNIT_STATE_MOVE | UNIT_STATE_ROTATING + | UNIT_STATE_EVADE | UNIT_STATE_ROAMING_MOVE | UNIT_STATE_CONFUSED_MOVE | UNIT_STATE_FLEEING_MOVE + | UNIT_STATE_CHASE_MOVE | UNIT_STATE_FOLLOW_MOVE | UNIT_STATE_IGNORE_PATHFINDING, + + UNIT_STATE_UNATTACKABLE = UNIT_STATE_IN_FLIGHT, + UNIT_STATE_MOVING = UNIT_STATE_ROAMING_MOVE | UNIT_STATE_CONFUSED_MOVE | UNIT_STATE_FLEEING_MOVE | UNIT_STATE_CHASE_MOVE | UNIT_STATE_FOLLOW_MOVE, + UNIT_STATE_CONTROLLED = UNIT_STATE_CONFUSED | UNIT_STATE_STUNNED | UNIT_STATE_FLEEING, + UNIT_STATE_CANT_CLIENT_CONTROL = UNIT_STATE_CHARMED | UNIT_STATE_FLEEING | UNIT_STATE_CONFUSED | UNIT_STATE_POSSESSED, + UNIT_STATE_LOST_CONTROL = UNIT_STATE_CONTROLLED | UNIT_STATE_POSSESSED | UNIT_STATE_JUMPING | UNIT_STATE_CHARGING, + UNIT_STATE_CANNOT_AUTOATTACK = UNIT_STATE_CONTROLLED | UNIT_STATE_CHARGING, + UNIT_STATE_SIGHTLESS = UNIT_STATE_LOST_CONTROL | UNIT_STATE_EVADE, + UNIT_STATE_CANNOT_TURN = UNIT_STATE_LOST_CONTROL | UNIT_STATE_ROTATING | UNIT_STATE_FOCUSING, + UNIT_STATE_NOT_MOVE = UNIT_STATE_ROOT | UNIT_STATE_STUNNED | UNIT_STATE_DIED | UNIT_STATE_DISTRACTED, + + UNIT_STATE_ALL_ERASABLE = UNIT_STATE_ALL_STATE_SUPPORTED & ~(UNIT_STATE_IGNORE_PATHFINDING), + UNIT_STATE_ALL_STATE = 0xffffffff +}; + +TC_GAME_API extern float baseMoveSpeed[MAX_MOVE_TYPE]; +TC_GAME_API extern float playerBaseMoveSpeed[MAX_MOVE_TYPE]; + +enum class MovementChangeType : uint8 +{ + INVALID, + + ROOT, + WATER_WALK, + SET_HOVER, + SET_CAN_FLY, + SET_CAN_TRANSITION_BETWEEN_SWIM_AND_FLY, + FEATHER_FALL, + GRAVITY_DISABLE, + + SPEED_CHANGE_WALK, + SPEED_CHANGE_RUN, + SPEED_CHANGE_RUN_BACK, + SPEED_CHANGE_SWIM, + SPEED_CHANGE_SWIM_BACK, + RATE_CHANGE_TURN, + SPEED_CHANGE_FLIGHT_SPEED, + SPEED_CHANGE_FLIGHT_BACK_SPEED, + RATE_CHANGE_PITCH, + + SET_COLLISION_HGT, + TELEPORT, + KNOCK_BACK +}; + +struct PlayerMovementPendingChange +{ + PlayerMovementPendingChange(); + + uint32 movementCounter = 0; + MovementChangeType movementChangeType = MovementChangeType::INVALID; + uint32 time; + + float newValue = 0.0f; // used if speed or height change + bool apply = false; // used if movement flag change + struct KnockbackInfo + { + float vcos = 0.0f; + float vsin = 0.0f; + float speedXY = 0.0f; + float speedZ = 0.0f; + } knockbackInfo; // used if knockback +}; + +enum CombatRating +{ + CR_WEAPON_SKILL = 0, + CR_DEFENSE_SKILL = 1, // Removed in 4.0.1 + CR_DODGE = 2, + CR_PARRY = 3, + CR_BLOCK = 4, + CR_HIT_MELEE = 5, + CR_HIT_RANGED = 6, + CR_HIT_SPELL = 7, + CR_CRIT_MELEE = 8, + CR_CRIT_RANGED = 9, + CR_CRIT_SPELL = 10, + CR_HIT_TAKEN_MELEE = 11, // Deprecated since Cataclysm + CR_HIT_TAKEN_RANGED = 12, // Deprecated since Cataclysm + CR_HIT_TAKEN_SPELL = 13, // Deprecated since Cataclysm + CR_RESILIENCE_CRIT_TAKEN = 14, + CR_RESILIENCE_PLAYER_DAMAGE_TAKEN = 15, + CR_CRIT_TAKEN_SPELL = 16, // Deprecated since Cataclysm + CR_HASTE_MELEE = 17, + CR_HASTE_RANGED = 18, + CR_HASTE_SPELL = 19, + CR_WEAPON_SKILL_MAINHAND = 20, + CR_WEAPON_SKILL_OFFHAND = 21, + CR_WEAPON_SKILL_RANGED = 22, + CR_EXPERTISE = 23, + CR_ARMOR_PENETRATION = 24, + CR_MASTERY = 25, +}; + +constexpr uint8 MAX_COMBAT_RATING = 26; + +enum DamageEffectType : uint8 +{ + DIRECT_DAMAGE = 0, // used for normal weapon damage (not for class abilities or spells) + SPELL_DIRECT_DAMAGE = 1, // spell/class abilities damage + DOT = 2, + HEAL = 3, + NODAMAGE = 4, // used also in case when damage applied to health but not applied to spell channelInterruptFlags/etc + SELF_DAMAGE = 5 +}; + +enum UnitTypeMask +{ + UNIT_MASK_NONE = 0x00000000, + UNIT_MASK_SUMMON = 0x00000001, + UNIT_MASK_MINION = 0x00000002, + UNIT_MASK_GUARDIAN = 0x00000004, + UNIT_MASK_TOTEM = 0x00000008, + UNIT_MASK_PET = 0x00000010, + UNIT_MASK_VEHICLE = 0x00000020, + UNIT_MASK_PUPPET = 0x00000040, + UNIT_MASK_HUNTER_PET = 0x00000080, + UNIT_MASK_CONTROLABLE_GUARDIAN = 0x00000100, + UNIT_MASK_ACCESSORY = 0x00000200 +}; + +struct DiminishingReturn +{ + DiminishingReturn() : stack(0), hitTime(0), hitCount(DIMINISHING_LEVEL_1) { } + + void Clear() + { + stack = 0; + hitTime = 0; + hitCount = DIMINISHING_LEVEL_1; + } + + uint16 stack; + uint32 hitTime; + uint32 hitCount; +}; + +enum MeleeHitOutcome : uint8 +{ + MELEE_HIT_EVADE, MELEE_HIT_MISS, MELEE_HIT_DODGE, MELEE_HIT_BLOCK, MELEE_HIT_PARRY, + MELEE_HIT_GLANCING, MELEE_HIT_CRIT, MELEE_HIT_CRUSHING, MELEE_HIT_NORMAL +}; + +class DispelInfo +{ + public: + explicit DispelInfo(WorldObject* dispeller, uint32 dispellerSpellId, uint8 chargesRemoved) : + _dispeller(dispeller), _dispellerSpell(dispellerSpellId), _chargesRemoved(chargesRemoved) { } + + WorldObject* GetDispeller() const { return _dispeller; } + uint32 GetDispellerSpellId() const { return _dispellerSpell; } + uint8 GetRemovedCharges() const { return _chargesRemoved; } + void SetRemovedCharges(uint8 amount) { _chargesRemoved = amount; } + private: + WorldObject* _dispeller; + uint32 _dispellerSpell; + uint8 _chargesRemoved; +}; + +struct CalcDamageInfo; +struct SpellNonMeleeDamage; + +class TC_GAME_API DamageInfo +{ + private: + Unit* const m_attacker; + Unit* const m_victim; + uint32 m_damage; + SpellInfo const* const m_spellInfo; + SpellSchoolMask const m_schoolMask; + DamageEffectType const m_damageType; + WeaponAttackType m_attackType; + uint32 m_absorb; + uint32 m_resist; + uint32 m_block; + ProcFlagsHit m_hitMask; + public: + DamageInfo(Unit* attacker, Unit* victim, uint32 damage, SpellInfo const* spellInfo, SpellSchoolMask schoolMask, DamageEffectType damageType, WeaponAttackType attackType); + explicit DamageInfo(CalcDamageInfo const& dmgInfo); + DamageInfo(SpellNonMeleeDamage const& spellNonMeleeDamage, DamageEffectType damageType, WeaponAttackType attackType, ProcFlagsHit hitMask); + + void ModifyDamage(int32 amount); + void AbsorbDamage(uint32 amount); + void ResistDamage(uint32 amount); + void BlockDamage(uint32 amount); + + Unit* GetAttacker() const { return m_attacker; } + Unit* GetVictim() const { return m_victim; } + SpellInfo const* GetSpellInfo() const { return m_spellInfo; } + SpellSchoolMask GetSchoolMask() const { return m_schoolMask; } + DamageEffectType GetDamageType() const { return m_damageType; } + WeaponAttackType GetAttackType() const { return m_attackType; } + uint32 GetDamage() const { return m_damage; } + uint32 GetAbsorb() const { return m_absorb; } + uint32 GetResist() const { return m_resist; } + uint32 GetBlock() const { return m_block; } + + ProcFlagsHit GetHitMask() const; +}; + +class TC_GAME_API HealInfo +{ + private: + Unit* const _healer; + Unit* const _target; + uint32 _heal; + uint32 _effectiveHeal; + uint32 _absorb; + SpellInfo const* const _spellInfo; + SpellSchoolMask const _schoolMask; + uint32 _hitMask; + + public: + HealInfo(Unit* healer, Unit* target, uint32 heal, SpellInfo const* spellInfo, SpellSchoolMask schoolMask); + + void AbsorbHeal(uint32 amount); + void SetEffectiveHeal(uint32 amount) { _effectiveHeal = amount; } + + Unit* GetHealer() const { return _healer; } + Unit* GetTarget() const { return _target; } + uint32 GetHeal() const { return _heal; } + uint32 GetEffectiveHeal() const { return _effectiveHeal; } + uint32 GetAbsorb() const { return _absorb; } + SpellInfo const* GetSpellInfo() const { return _spellInfo; }; + SpellSchoolMask GetSchoolMask() const { return _schoolMask; }; + + uint32 GetHitMask() const; +}; + +class TC_GAME_API ProcEventInfo +{ + public: + ProcEventInfo(Unit* actor, Unit* actionTarget, Unit* procTarget, ProcFlags typeMask, + ProcFlagsSpellType spellTypeMask, ProcFlagsSpellPhase spellPhaseMask, ProcFlagsHit hitMask, + Spell* spell, DamageInfo* damageInfo, HealInfo* healInfo); + + Unit* GetActor() { return _actor; } + Unit* GetActionTarget() const { return _actionTarget; } + Unit* GetProcTarget() const { return _procTarget; } + + ProcFlags GetTypeMask() const { return _typeMask; } + ProcFlagsSpellType GetSpellTypeMask() const { return _spellTypeMask; } + ProcFlagsSpellPhase GetSpellPhaseMask() const { return _spellPhaseMask; } + ProcFlagsHit GetHitMask() const { return _hitMask; } + + SpellInfo const* GetSpellInfo() const; + SpellSchoolMask GetSchoolMask() const; + + DamageInfo* GetDamageInfo() const { return _damageInfo; } + HealInfo* GetHealInfo() const { return _healInfo; } + + Spell const* GetProcSpell() const { return _spell; } + + private: + Unit* const _actor; + Unit* const _actionTarget; + Unit* const _procTarget; + ProcFlags _typeMask; + ProcFlagsSpellType _spellTypeMask; + ProcFlagsSpellPhase _spellPhaseMask; + ProcFlagsHit _hitMask; + Spell* _spell; + DamageInfo* _damageInfo; + HealInfo* _healInfo; +}; + +// Struct for use in Unit::CalculateMeleeDamage +// Need create structure like in SMSG_ATTACKERSTATEUPDATE opcode +struct CalcDamageInfo +{ + Unit* Attacker; + Unit* Target; + uint32 DamageSchoolMask; + uint32 Damage; + uint32 OriginalDamage; + uint32 Absorb; + uint32 Resist; + uint32 Blocked; + uint32 HitInfo; + uint32 TargetState; + uint32 RageGained; + + // Helpers + WeaponAttackType AttackType; // + ProcFlags ProcAttacker; + ProcFlags ProcVictim; + uint32 UnmitigatedDamage; // Used only for rage calculation + MeleeHitOutcome HitOutCome; /// @todo remove this field (need use TargetState) +}; + +// Spell damage info structure based on structure sending in SMSG_SPELLNONMELEEDAMAGELOG opcode +struct TC_GAME_API SpellNonMeleeDamage +{ + SpellNonMeleeDamage(Unit* _attacker, Unit* _target, uint32 _SpellID, uint32 _schoolMask) + : target(_target), attacker(_attacker), SpellID(_SpellID), damage(0), overkill(0), schoolMask(_schoolMask), + absorb(0), resist(0), periodicLog(false), unused(false), blocked(0), HitInfo(0), unmitigatedDamage(0), fullBlock(false) + { } + + Unit *target; + Unit *attacker; + uint32 SpellID; + uint32 damage; + uint32 overkill; + uint32 schoolMask; + uint32 absorb; + uint32 resist; + bool periodicLog; + bool unused; + uint32 blocked; + uint32 HitInfo; + // Used for help + uint32 unmitigatedDamage; + bool fullBlock; +}; + +struct SpellPeriodicAuraLogInfo +{ + SpellPeriodicAuraLogInfo(AuraEffect const* _auraEff, uint32 _damage, uint32 _overDamage, uint32 _absorb, uint32 _resist, float _multiplier, bool _critical) + : auraEff(_auraEff), damage(_damage), overDamage(_overDamage), absorb(_absorb), resist(_resist), multiplier(_multiplier), critical(_critical){ } + + AuraEffect const* auraEff; + uint32 damage; + uint32 overDamage; // overkill/overheal + uint32 absorb; + uint32 resist; + float multiplier; + bool critical; +}; + +ProcFlagsHit createProcHitMask(SpellNonMeleeDamage* damageInfo, SpellMissInfo missCondition); + +enum CurrentSpellTypes : uint8 +{ + CURRENT_MELEE_SPELL = 0, + CURRENT_GENERIC_SPELL = 1, + CURRENT_CHANNELED_SPELL = 2, + CURRENT_AUTOREPEAT_SPELL = 3 +}; + +constexpr uint8 CURRENT_FIRST_NON_MELEE_SPELL = 1; +constexpr uint8 CURRENT_MAX_SPELL = 4; + +typedef std::list SharedVisionList; + +// for clearing special attacks +constexpr uint32 REACTIVE_TIMER_START = 4000; + +enum ReactiveType +{ + REACTIVE_DEFENSE = 0, + REACTIVE_HUNTER_PARRY = 1, + REACTIVE_OVERPOWER = 2 +}; + +#define MAX_REACTIVE 3 + +enum PlayerTotemType +{ + SUMMON_TYPE_TOTEM_FIRE = 63, + SUMMON_TYPE_TOTEM_EARTH = 81, + SUMMON_TYPE_TOTEM_WATER = 82, + SUMMON_TYPE_TOTEM_AIR = 83 +}; + +struct PositionUpdateInfo +{ + void Reset() + { + Relocated = false; + Turned = false; + } + + bool Relocated = false; + bool Turned = false; +}; + +// delay time next attack to prevent client attack animation problems +constexpr uint32 ATTACK_DISPLAY_DELAY = 200; +constexpr float MAX_PLAYER_STEALTH_DETECT_RANGE = 30.0f; // max distance for detection targets by player + +class TC_GAME_API Unit : public WorldObject +{ + friend class WorldSession; + public: + typedef std::set AttackerSet; + typedef std::set ControlList; + typedef std::vector UnitVector; + + typedef std::multimap AuraMap; + typedef std::pair AuraMapBounds; + typedef std::pair AuraMapBoundsNonConst; + + typedef std::multimap AuraApplicationMap; + typedef std::pair AuraApplicationMapBounds; + typedef std::pair AuraApplicationMapBoundsNonConst; + + typedef std::multimap AuraStateAurasMap; + typedef std::pair AuraStateAurasMapBounds; + + typedef std::forward_list AuraEffectList; + typedef std::forward_list AuraList; + typedef std::forward_list AuraApplicationList; + + typedef std::vector> AuraApplicationProcContainer; + typedef std::vector FormationFollowerGUIDContainer; + + typedef std::map VisibleAuraMap; + + typedef std::unordered_map AurasBySpellIdMap; + + virtual ~Unit(); + + bool IsAIEnabled() const { return (i_AI != nullptr); } + void AIUpdateTick(uint32 diff); + UnitAI* GetAI() const { return i_AI.get(); } + void ScheduleAIChange(); + void PushAI(UnitAI* newAI); + bool PopAI(); + protected: + void SetAI(UnitAI* newAI); + UnitAI* GetTopAI() const { return i_AIs.empty() ? nullptr : i_AIs.top().get(); } + void RefreshAI(); + UnitAI* GetScheduledChangeAI(); + bool HasScheduledAIChange() const; + + public: + void AddToWorld() override; + void RemoveFromWorld() override; + + void CleanupBeforeRemoveFromMap(bool finalCleanup); + void CleanupsBeforeDelete(bool finalCleanup = true) override; // used in ~Creature/~Player (or before mass creature delete to remove cross-references to already deleted units) + + virtual bool IsAffectedByDiminishingReturns() const { return (GetCharmerOrOwnerPlayerOrPlayerItself() != nullptr); } + DiminishingLevels GetDiminishing(DiminishingGroup group) const; + void IncrDiminishing(SpellInfo const* auraSpellInfo, bool triggered); + bool ApplyDiminishingToDuration(SpellInfo const* auraSpellInfo, bool triggered, int32& duration, WorldObject* caster, DiminishingLevels previousLevel) const; + void ApplyDiminishingAura(DiminishingGroup group, bool apply); + void ClearDiminishings(); + + virtual void Update(uint32 time) override; + + void Heartbeat() override; + void TriggerAuraHeartbeat(); + + void setAttackTimer(WeaponAttackType type, uint32 time) { m_attackTimer[type] = time; } + void resetAttackTimer(WeaponAttackType type = BASE_ATTACK); + uint32 getAttackTimer(WeaponAttackType type) const { return m_attackTimer[type]; } + bool isAttackReady(WeaponAttackType type = BASE_ATTACK) const { return m_attackTimer[type] == 0; } + bool haveOffhandWeapon() const; + bool CanDualWield() const { return m_canDualWield; } + virtual void SetCanDualWield(bool value) { m_canDualWield = value; } + float GetCombatReach() const override { return m_floatValues[UNIT_FIELD_COMBATREACH]; } + float GetBoundaryRadius() const { return m_floatValues[UNIT_FIELD_BOUNDINGRADIUS]; } + bool IsWithinCombatRange(Unit const* obj, float dist2compare) const; + bool IsWithinMeleeRange(Unit const* obj) const { return IsWithinMeleeRangeAt(GetPosition(), obj); } + bool IsWithinMeleeRangeAt(Position const& pos, Unit const* obj) const; + bool IsWithinBoundaryRadius(const Unit* obj) const; + float GetMeleeRange(Unit const* target) const; + virtual SpellSchoolMask GetMeleeDamageSchoolMask(WeaponAttackType attackType = BASE_ATTACK) const = 0; + uint32 m_extraAttacks; + bool m_canDualWield; + + void _addAttacker(Unit* pAttacker); // must be called only from Unit::Attack(Unit*) + void _removeAttacker(Unit* pAttacker); // must be called only from Unit::AttackStop() + Unit* getAttackerForHelper() const; // If someone wants to help, who to give them + bool Attack(Unit* victim, bool meleeAttack); + void CastStop(uint32 except_spellid = 0); + bool AttackStop(); + void RemoveAllAttackers(); + AttackerSet const& getAttackers() const { return m_attackers; } + bool isAttackingPlayer() const; + Unit* GetVictim() const { return m_attacking; } + // Use this only when 100% sure there is a victim + Unit* EnsureVictim() const + { + ASSERT(m_attacking); + return m_attacking; + } + + void ValidateAttackersAndOwnTarget(); + void CombatStop(bool includingCast = false, bool mutualPvP = true, bool (*unitFilter)(Unit const* otherUnit) = nullptr); + void CombatStopWithPets(bool includingCast = false); + void StopAttackFaction(uint32 faction_id); + Unit* SelectNearbyTarget(Unit* exclude = nullptr, float dist = NOMINAL_MELEE_RANGE) const; + void SendMeleeAttackStop(Unit* victim = nullptr); + void SendMeleeAttackStart(Unit* victim); + + void AddUnitState(uint32 f) { m_state |= f; } + bool HasUnitState(const uint32 f) const { return (m_state & f) != 0; } + void ClearUnitState(uint32 f) { m_state &= ~f; } + bool CanFreeMove() const; + + uint32 HasUnitTypeMask(uint32 mask) const { return mask & m_unitTypeMask; } + void AddUnitTypeMask(uint32 mask) { m_unitTypeMask |= mask; } + bool IsSummon() const { return (m_unitTypeMask & UNIT_MASK_SUMMON) != 0; } + bool IsMinion() const { return (m_unitTypeMask & UNIT_MASK_MINION) != 0; } + bool IsGuardian() const { return (m_unitTypeMask & UNIT_MASK_GUARDIAN) != 0; } + bool IsPet() const { return (m_unitTypeMask & UNIT_MASK_PET) != 0; } + bool IsHunterPet() const{ return (m_unitTypeMask & UNIT_MASK_HUNTER_PET) != 0; } + bool IsTotem() const { return (m_unitTypeMask & UNIT_MASK_TOTEM) != 0; } + bool IsVehicle() const { return (m_unitTypeMask & UNIT_MASK_VEHICLE) != 0; } + bool IsControlableGuardian() const { return (m_unitTypeMask & UNIT_MASK_CONTROLABLE_GUARDIAN) != 0; } + + uint8 getLevel() const { return uint8(GetUInt32Value(UNIT_FIELD_LEVEL)); } + uint8 getLevelForTarget(WorldObject const* /*target*/) const override { return getLevel(); } + void SetLevel(uint8 lvl); + uint8 getRace() const { return GetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_RACE); } + uint32 getRaceMask() const { return 1 << (getRace()-1); } + uint8 getClass() const { return GetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_CLASS); } + uint32 getClassMask() const { return 1 << (getClass()-1); } + uint8 getGender() const { return GetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_GENDER); } + + uint32 GetArmor() const { return GetResistance(SPELL_SCHOOL_NORMAL); } + void SetArmor(int32 val) { SetResistance(SPELL_SCHOOL_NORMAL, val); } + + uint32 GetResistance(SpellSchools school) const { return GetUInt32Value(UNIT_FIELD_RESISTANCES + AsUnderlyingType(school)); } + uint32 GetResistance(SpellSchoolMask mask) const; + void SetResistance(SpellSchools school, int32 val) { SetStatInt32Value(UNIT_FIELD_RESISTANCES + AsUnderlyingType(school), val); } + static float CalculateAverageResistReduction(WorldObject const* caster, SpellSchoolMask schoolMask, Unit const* victim, SpellInfo const* spellInfo = nullptr); + + uint32 GetHealth() const { return GetUInt32Value(UNIT_FIELD_HEALTH); } + uint32 GetMaxHealth() const { return GetUInt32Value(UNIT_FIELD_MAXHEALTH); } + + bool IsFullHealth() const { return GetHealth() == GetMaxHealth(); } + bool HealthBelowPct(int32 pct) const { return GetHealth() < CountPctFromMaxHealth(pct); } + bool HealthBelowPctDamaged(int32 pct, uint32 damage) const { return int64(GetHealth()) - int64(damage) < int64(CountPctFromMaxHealth(pct)); } + bool HealthAbovePct(int32 pct) const { return GetHealth() > CountPctFromMaxHealth(pct); } + bool HealthAbovePctHealed(int32 pct, uint32 heal) const { return uint64(GetHealth()) + uint64(heal) > CountPctFromMaxHealth(pct); } + float GetHealthPct() const { return GetMaxHealth() ? 100.f * GetHealth() / GetMaxHealth() : 0.0f; } + uint32 CountPctFromMaxHealth(int32 pct) const { return CalculatePct(GetMaxHealth(), pct); } + uint32 CountPctFromCurHealth(int32 pct) const { return CalculatePct(GetHealth(), pct); } + + void SetHealth(uint32 val); + void SetMaxHealth(uint32 val); + inline void SetFullHealth() { SetHealth(GetMaxHealth()); } + int32 ModifyHealth(int32 val); + int32 GetHealthGain(int32 dVal); + + PowerType GetPowerType() const { return PowerType(GetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_POWER_TYPE)); } + void SetPowerType(PowerType power); + PowerType CalculateDisplayPowerType() const; + void UpdateDisplayPower(); + int32 GetPower(PowerType power) const; + int32 GetMinPower(PowerType power) const { return power == POWER_ECLIPSE ? -100 : 0; } + int32 GetMaxPower(PowerType power) const; + int32 CountPctFromMaxPower(PowerType power, int32 pct) const { return CalculatePct(GetMaxPower(power), pct); } + void SetPower(PowerType power, int32 val, bool withPowerUpdate = true); + void SetMaxPower(PowerType power, int32 val); + void SetPowerBarID(uint32 id) { _powerBarId = id; } + void Regenerate(PowerType powerType, uint32 diff); + void UpdatePowerRegeneration(PowerType powerType); + void RegisterPowerTypes(); + + inline void SetFullPower(PowerType power) { SetPower(power, GetMaxPower(power)); } + + // returns the change in power + int32 ModifyPower(PowerType power, int32 val, bool withPowerUpdate = true); + float GetPowerRegen(PowerType powerType, bool isInCombat) const; + + virtual void RegenerateHealth() { } + + uint32 GetBaseAttackTime(WeaponAttackType att) const; + void SetBaseAttackTime(WeaponAttackType att, uint32 val); + void UpdateAttackTimeField(WeaponAttackType att); + void ApplyAttackTimePercentMod(WeaponAttackType att, float val, bool apply); + void ApplyHasteRegenMod(float val, bool apply); + void ApplyCastTimePercentMod(float val, bool apply, bool withCastHaste = true, bool withCastSpeed = true); + + SheathState GetSheath() const { return SheathState(GetByteValue(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_SHEATH_STATE)); } + virtual void SetSheath(SheathState sheathed); + + // faction template id + uint32 GetFaction() const override { return GetUInt32Value(UNIT_FIELD_FACTIONTEMPLATE); } + void SetFaction(uint32 faction) override { SetUInt32Value(UNIT_FIELD_FACTIONTEMPLATE, faction); } + + bool IsInPartyWith(Unit const* unit) const; + bool IsInRaidWith(Unit const* unit) const; + void GetPartyMembers(std::list &units); + bool IsContestedGuard() const; + bool IsInSanctuary() const { return HasByteFlag(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PVP_FLAG, UNIT_BYTE2_FLAG_SANCTUARY); } + bool IsPvP() const { return HasByteFlag(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PVP_FLAG, UNIT_BYTE2_FLAG_PVP); } + bool IsFFAPvP() const { return HasByteFlag(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PVP_FLAG, UNIT_BYTE2_FLAG_FFA_PVP); } + virtual void SetPvP(bool state); + + uint32 GetCreatureType() const; + uint32 GetCreatureTypeMask() const; + + uint8 GetStandState() const { return GetByteValue(UNIT_FIELD_BYTES_1, UNIT_BYTES_1_OFFSET_STAND_STATE); } + bool IsSitState() const; + bool IsStandState() const; + void SetStandState(uint8 state); + + void SetAnimTier(AnimTier tier, bool immediate = true); + AnimTier GetAnimTier() const { return static_cast(GetByteValue(UNIT_FIELD_BYTES_1, UNIT_BYTES_1_OFFSET_ANIM_TIER)); } + + void SetStandFlags(uint8 flags) { SetByteFlag(UNIT_FIELD_BYTES_1, UNIT_BYTES_1_OFFSET_VIS_FLAG, flags); } + void RemoveStandFlags(uint8 flags) { RemoveByteFlag(UNIT_FIELD_BYTES_1, UNIT_BYTES_1_OFFSET_VIS_FLAG, flags); } + + bool IsMounted() const { return HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_MOUNT); } + uint32 GetMountID() const { return GetUInt32Value(UNIT_FIELD_MOUNTDISPLAYID); } + void Mount(uint32 mount, uint32 vehicleId = 0, uint32 creatureEntry = 0); + void Dismount(); + MountCapabilityEntry const* GetMountCapability(uint32 mountType) const; + void UpdateMountCapability(); + + void SendDurabilityLoss(Player* receiver, uint32 percent); + void PlayOneShotAnimKitId(uint16 animKitId); + + uint32 GetMaxSkillValueForLevel(Unit const* target = nullptr) const { return (target ? getLevelForTarget(target) : getLevel()) * 5; } + static void DealDamageMods(Unit const* victim, uint32& damage, uint32* absorb); + static uint32 DealDamage(Unit* attacker, Unit* victim, uint32 damage, uint32 unmitigatedDamage = 0, DamageEffectType damagetype = DIRECT_DAMAGE, SpellSchoolMask damageSchoolMask = SPELL_SCHOOL_MASK_NORMAL, SpellInfo const* spellProto = nullptr, bool durabilityLoss = true); + static void Kill(Unit* attacker, Unit* victim, bool durabilityLoss = true); + void KillSelf(bool durabilityLoss = true) { Unit::Kill(this, this, durabilityLoss); } + static void DealHeal(HealInfo& healInfo); + + static void ProcSkillsAndAuras(Unit* actor, Unit* actionTarget, ProcFlags typeMaskActor, ProcFlags typeMaskActionTarget, + ProcFlagsSpellType spellTypeMask, ProcFlagsSpellPhase spellPhaseMask, ProcFlagsHit hitMask, Spell* spell, + DamageInfo* damageInfo, HealInfo* healInfo); + + void GetProcAurasTriggeredOnEvent(AuraApplicationProcContainer& aurasTriggeringProc, AuraApplicationList* procAuras, ProcEventInfo& eventInfo); + void TriggerAurasProcOnEvent(Unit* actionTarget, ProcFlags typeMaskActor, ProcFlags typeMaskActionTarget, + ProcFlagsSpellType spellTypeMask, ProcFlagsSpellPhase spellPhaseMask, ProcFlagsHit hitMask, Spell* spell, + DamageInfo* damageInfo, HealInfo* healInfo); + void TriggerAurasProcOnEvent(ProcEventInfo& eventInfo, AuraApplicationProcContainer& procAuras); + + void HandleEmoteCommand(uint32 emoteId); + void AttackerStateUpdate(Unit* victim, WeaponAttackType attType = BASE_ATTACK, bool extra = false); + + static uint32 CalcMeleeAttackRageGain(Unit const* attacker, Unit const* victim, WeaponAttackType attType); + static uint32 CalcDamageTakenRageGain(Unit const* victim, uint32 damage); + void CalculateMeleeDamage(Unit* victim, CalcDamageInfo* damageInfo, WeaponAttackType attackType = BASE_ATTACK); + void DealMeleeDamage(CalcDamageInfo* damageInfo, bool durabilityLoss); + void HandleProcExtraAttackFor(Unit* victim); + + void CalculateSpellDamageTaken(SpellNonMeleeDamage* damageInfo, int32 damage, SpellInfo const* spellInfo, WeaponAttackType attackType = BASE_ATTACK, bool crit = false, bool blocked = false); + void DealSpellDamage(SpellNonMeleeDamage* damageInfo, bool durabilityLoss); + + // player or player's pet resilience (-1%) + uint32 GetDamageReduction(uint32 damage) const { return GetCombatRatingDamageReduction(CR_RESILIENCE_PLAYER_DAMAGE_TAKEN, 1.0f, 100.0f, damage); } + + virtual bool CanApplyResilience() const; + static void ApplyResilience(Unit const* victim, int32* damage); + + int32 CalculateAOEAvoidance(int32 damage, uint32 schoolMask, bool npcCaster) const; + + float MeleeSpellMissChance(Unit const* victim, WeaponAttackType attType, SpellInfo const* spellInfo = nullptr) const override; + SpellMissInfo MeleeSpellHitResult(Unit* victim, SpellInfo const* spellInfo) const override; + + float GetUnitDodgeChance(WeaponAttackType attType, Unit const* victim) const; + float GetUnitParryChance(WeaponAttackType attType, Unit const* victim) const; + float GetUnitBlockChance(Unit const* victim) const; + float GetMeleeMissChance(Unit const* victim) const; + float GetUnitCriticalChanceDone(WeaponAttackType attackType) const; + float GetUnitCriticalChanceTaken(WeaponAttackType attackType, float critDone) const; + float GetUnitCriticalChanceAgainst(WeaponAttackType attackType, Unit const* victim) const; + int32 GetMechanicResistChance(SpellInfo const* spellInfo) const; + bool CanUseAttackType(uint8 attacktype) const; + + virtual uint32 GetBlockPercent() const { return 30; } + + float GetWeaponProcChance() const; + float GetPPMProcChance(uint32 WeaponSpeed, float PPM, SpellInfo const* spellProto) const; + + MeleeHitOutcome RollMeleeOutcomeAgainst (const Unit* victim, WeaponAttackType attType) const; + + bool IsVendor() const { return HasFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_VENDOR); } + bool IsTrainer() const { return HasFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_TRAINER); } + bool IsQuestGiver() const { return HasFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_QUESTGIVER); } + bool IsGossip() const { return HasFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_GOSSIP); } + bool IsTaxi() const { return HasFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_FLIGHTMASTER); } + bool IsGuildMaster() const { return HasFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_PETITIONER); } + bool IsBattleMaster() const { return HasFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_BATTLEMASTER); } + bool IsBanker() const { return HasFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_BANKER); } + bool IsInnkeeper() const { return HasFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_INNKEEPER); } + bool IsSpiritHealer() const { return HasFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_SPIRITHEALER); } + bool IsSpiritGuide() const { return HasFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_SPIRITGUIDE); } + bool IsTabardDesigner()const { return HasFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_TABARDDESIGNER); } + bool IsAuctioner() const { return HasFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_AUCTIONEER); } + bool IsArmorer() const { return HasFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_REPAIR); } + bool IsServiceProvider() const; + bool IsSpiritService() const { return HasFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_SPIRITHEALER | UNIT_NPC_FLAG_SPIRITGUIDE); } + bool IsCritter() const { return GetCreatureType() == CREATURE_TYPE_CRITTER; } + + bool IsInFlight() const { return HasUnitState(UNIT_STATE_IN_FLIGHT); } + + /// ====================== THREAT & COMBAT ==================== + bool CanHaveThreatList() const { return m_threatManager.CanHaveThreatList(); } + // This value can be different from IsInCombat, for example: + // - when a projectile spell is midair against a creature (combat on launch - threat+aggro on impact) + // - when the creature has no targets left, but the AI has not yet ceased engaged logic + virtual bool IsEngaged() const { return IsInCombat(); } + bool IsEngagedBy(Unit const* who) const { return CanHaveThreatList() ? IsThreatenedBy(who) : IsInCombatWith(who); } + void EngageWithTarget(Unit* who); // Adds target to threat list if applicable, otherwise just sets combat state + // Combat handling + CombatManager& GetCombatManager() { return m_combatManager; } + CombatManager const& GetCombatManager() const { return m_combatManager; } + void AtTargetAttacked(Unit* target, bool canInitialAggro); + + bool IsImmuneToAll() const { return IsImmuneToPC() && IsImmuneToNPC(); } + void SetImmuneToAll(bool apply, bool keepCombat); + virtual void SetImmuneToAll(bool apply) { SetImmuneToAll(apply, false); } + bool IsImmuneToPC() const { return HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC); } + void SetImmuneToPC(bool apply, bool keepCombat); + virtual void SetImmuneToPC(bool apply) { SetImmuneToPC(apply, false); } + bool IsImmuneToNPC() const { return HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_NPC); } + void SetImmuneToNPC(bool apply, bool keepCombat); + virtual void SetImmuneToNPC(bool apply) { SetImmuneToNPC(apply, false); } + + bool IsInCombat() const { return HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IN_COMBAT); } + bool IsInCombatWith(Unit const* who) const { return who && m_combatManager.IsInCombatWith(who); } + void SetInCombatWith(Unit* enemy, bool addSecondUnitSuppressed = false) { if (enemy) m_combatManager.SetInCombatWith(enemy, addSecondUnitSuppressed); } + void ClearInCombat() { m_combatManager.EndAllCombat(); } + void UpdatePetCombatState(); + // Threat handling + bool IsThreatened() const; + bool IsThreatenedBy(Unit const* who) const { return who && m_threatManager.IsThreatenedBy(who, true); } + // Exposes the threat manager directly - be careful when interfacing with this + // As a general rule of thumb, any unit pointer MUST be null checked BEFORE passing it to threatmanager methods + // threatmanager will NOT null check your pointers for you - misuse = crash + ThreatManager& GetThreatManager() { return m_threatManager; } + ThreatManager const& GetThreatManager() const { return m_threatManager; } + + void SendClearTarget(); + + bool HasAuraTypeWithFamilyFlags(AuraType auraType, uint32 familyName, uint32 familyFlags) const; + bool virtual HasSpell(uint32 /*spellID*/) const { return false; } + bool HasBreakableByDamageAuraType(AuraType type, uint32 excludeAura = 0) const; + bool HasBreakableByDamageCrowdControlAura(Unit* excludeCasterChannel = nullptr) const; + + bool HasStealthAura() const { return HasAuraType(SPELL_AURA_MOD_STEALTH); } + bool HasInvisibilityAura() const { return HasAuraType(SPELL_AURA_MOD_INVISIBILITY); } + bool isFeared() const { return HasAuraType(SPELL_AURA_MOD_FEAR); } + bool isInRoots() const { return HasAuraType(SPELL_AURA_MOD_ROOT); } + bool IsPolymorphed() const; + + bool isFrozen() const; + + bool isTargetableForAttack(bool checkFakeDeath = true) const; + + bool IsInWater() const; + bool IsUnderWater() const; + bool IsOnOceanFloor() const; + bool isInAccessiblePlaceFor(Creature const* c) const; + + void SendHealSpellLog(HealInfo& healInfo, bool critical = false); + int32 HealBySpell(HealInfo& healInfo, bool critical = false); + void SendEnergizeSpellLog(Unit* victim, uint32 spellId, int32 damage, PowerType powerType); + void EnergizeBySpell(Unit* victim, uint32 spellId, int32 damage, PowerType powerType); + void EnergizeBySpell(Unit* victim, SpellInfo const* spellInfo, int32 damage, PowerType powerType); + + Aura* AddAura(uint32 spellId, Unit* target); + Aura* AddAura(SpellInfo const* spellInfo, uint8 effMask, Unit* target); + void SetAuraStack(uint32 spellId, Unit* target, uint32 stack); + void SendPlaySpellVisualKit(uint32 id, uint32 type, uint32 duration) const; + void SendPlaySpellVisual(uint32 spellVisualId, Unit const* target = nullptr, Optional targetPosition = {}, float travelSpeed = 0.f, uint16 missReason = 0, uint16 reflectStatus = 0, bool speedAsTime = false) const; + void CancelSpellMissiles(uint32 spellId, bool reverseMissile = false); + + void DeMorph(); + + void SendAttackStateUpdate(CalcDamageInfo* damageInfo); + void SendAttackStateUpdate(uint32 HitInfo, Unit* target, uint8 SwingType, SpellSchoolMask damageSchoolMask, uint32 Damage, uint32 AbsorbDamage, uint32 Resist, VictimState TargetState, uint32 BlockedAmount); + void SendSpellNonMeleeDamageLog(SpellNonMeleeDamage* log); + void SendSpellNonMeleeDamageLog(Unit* target, uint32 spellID, uint32 damage, SpellSchoolMask damageSchoolMask, uint32 absorbedDamage, uint32 resist, bool isPeriodic, uint32 blocked, bool criticalHit = false, bool split = false); + void SendPeriodicAuraLog(SpellPeriodicAuraLogInfo* pInfo); + void SendSpellDamageResist(Unit* target, uint32 spellId); + void SendSpellDamageImmune(Unit* target, uint32 spellId); + + void NearTeleportTo(Position const& pos, bool casting = false); + void NearTeleportTo(float x, float y, float z, float orientation, bool casting = false) { NearTeleportTo(Position(x, y, z, orientation), casting); } + void SendTeleportPacket(Position const& pos); + virtual bool UpdatePosition(float x, float y, float z, float ang, bool teleport = false); + // returns true if unit's position really changed + virtual bool UpdatePosition(const Position &pos, bool teleport = false); + void UpdateOrientation(float orientation); + void UpdateHeight(float newZ); + + void SendMoveKnockBack(Player* player, float speedXY, float speedZ, float vcos, float vsin); + void KnockbackFrom(float x, float y, float speedXY, float speedZ); + void JumpTo(float speedXY, float speedZ, bool forward = true, Optional dest = {}); + void JumpTo(WorldObject* obj, float speedZ, bool withOrientation = false); + + void MonsterMoveWithSpeed(float x, float y, float z, float speed, bool generatePath = false, bool forceDestination = false); + + bool IsPlayingHoverAnim() const { return m_updateFlag.PlayerHoverAnim; } + void SetPlayHoverAnim(bool enable, bool sendUpdate = true); + + bool IsGravityDisabled() const { return m_movementInfo.HasMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY); } + bool IsWalking() const { return m_movementInfo.HasMovementFlag(MOVEMENTFLAG_WALKING); } + bool IsHovering() const { return m_movementInfo.HasMovementFlag(MOVEMENTFLAG_HOVER); } + bool SetWalk(bool enable); + virtual bool SetDisableGravity(bool disable, bool updateAnimTier = true); + bool SetFall(bool enable); + bool SetSwim(bool enable); + virtual bool SetCanFly(bool enable); + virtual bool SetCanTransitionBetweenSwimAndFly(bool enable); + bool SetWaterWalking(bool enable); + bool SetFeatherFall(bool enable); + virtual bool SetHover(bool enable, bool updateAnimTier = true); + void SendSetVehicleRecId(uint32 vehicleId); + + void SetOrientationTowards(WorldObject const* target); + void SetFacingTo(float const ori, bool force = true); + void SetFacingToObject(WorldObject const* object, bool force = true); + void SetFacingToPoint(Position const& point, bool force = true); + + bool IsAlive() const { return (m_deathState == ALIVE); } + bool isDying() const { return (m_deathState == JUST_DIED); } + bool isDead() const { return (m_deathState == DEAD || m_deathState == CORPSE); } + DeathState getDeathState() const { return m_deathState; } + virtual void setDeathState(DeathState s); // overwrited in Creature/Player/Pet + + ObjectGuid GetOwnerGUID() const override { return GetGuidValue(UNIT_FIELD_SUMMONEDBY); } + void SetOwnerGUID(ObjectGuid owner); + ObjectGuid GetCreatorGUID() const { return GetGuidValue(UNIT_FIELD_CREATEDBY); } + void SetCreatorGUID(ObjectGuid creator) { SetGuidValue(UNIT_FIELD_CREATEDBY, creator); } + ObjectGuid GetMinionGUID() const { return GetGuidValue(UNIT_FIELD_SUMMON); } + void SetMinionGUID(ObjectGuid guid) { SetGuidValue(UNIT_FIELD_SUMMON, guid); } + void SetPetGUID(ObjectGuid guid) { m_SummonSlot[SUMMON_SLOT_PET] = guid; } + ObjectGuid GetPetGUID() const { return m_SummonSlot[SUMMON_SLOT_PET]; } + void SetCritterGUID(ObjectGuid guid) { SetGuidValue(UNIT_FIELD_CRITTER, guid); } + ObjectGuid GetCritterGUID() const { return GetGuidValue(UNIT_FIELD_CRITTER); } + ObjectGuid GetOwnerOrCreatorGUID() const { return GetOwnerGUID() ? GetOwnerGUID() : GetCreatorGUID(); } + + ObjectGuid GetCharmerGUID() const { return GetGuidValue(UNIT_FIELD_CHARMEDBY); } + Unit* GetCharmer() const { return m_charmer; } + + ObjectGuid GetCharmedGUID() const { return GetGuidValue(UNIT_FIELD_CHARM); } + Unit* GetCharmed() const { return m_charmed; } + + bool IsControlledByPlayer() const { return m_ControlledByPlayer; } + Player* GetControllingPlayer() const; + ObjectGuid GetCharmerOrOwnerGUID() const override { return IsCharmed() ? GetCharmerGUID() : GetOwnerGUID(); } + bool IsCharmedOwnedByPlayerOrPlayer() const { return GetCharmerOrOwnerOrOwnGUID().IsPlayer(); } + + Guardian* GetGuardianPet() const; + Minion* GetFirstMinion() const; + Unit* GetCharmerOrOwner() const { return IsCharmed() ? GetCharmer() : GetOwner(); } + + void SetMinion(Minion *minion, bool apply); + void GetAllMinionsByEntry(std::list& Minions, uint32 entry); + void RemoveAllMinionsByEntry(uint32 entry); + void SetCharm(Unit* target, bool apply); + Unit* GetNextRandomRaidMemberOrPet(float radius); + bool SetCharmedBy(Unit* charmer, CharmType type, AuraApplication const* aurApp = nullptr); + void RemoveCharmedBy(Unit* charmer); + void RestoreFaction(); + + ControlList m_Controlled; + Unit* GetFirstControlled() const; + void RemoveAllControlled(); + + bool IsCharmed() const { return !GetCharmerGUID().IsEmpty(); } + bool IsCharming() const { return !GetCharmedGUID().IsEmpty(); } + bool isPossessed() const { return HasUnitState(UNIT_STATE_POSSESSED); } + bool isPossessedByPlayer() const; + bool isPossessing() const; + bool isPossessing(Unit* u) const; + + CharmInfo* GetCharmInfo() { return m_charmInfo.get(); } + CharmInfo* InitCharmInfo(); + void DeleteCharmInfo(); + + // base client control of this unit (possess effects, vehicles and similar). Not affected by temporary CC. + bool IsCharmerOrSelfPlayer() const { return GetCharmerOrSelf()->IsPlayer(); } + Unit* GetCharmerOrSelf() const; + Player* GetCharmerOrSelfPlayer() const { return GetCharmerOrSelf()->ToPlayer();} + Unit* GetCharmedOrSelf() const { return IsCharming() ? GetCharmed() : const_cast(this); } + + // real time client control status of this unit (possess effects, vehicles and similar). For example, if this unit is a player temporarly under fear, it will return false. + bool IsMovedByClient() const { return _gameClientMovingMe != nullptr; } + bool IsMovedByServer() const { return !IsMovedByClient(); } + GameClient* GetGameClientMovingMe() const { return _gameClientMovingMe; } + void SetGameClientMovingMe(GameClient* gameClientMovingMe); + + SharedVisionList const& GetSharedVisionList() { return m_sharedVision; } + void AddPlayerToVision(Player* player); + void RemovePlayerFromVision(Player* player); + bool HasSharedVision() const { return !m_sharedVision.empty(); } + void RemoveBindSightAuras(); + void RemoveCharmAuras(); + + Pet* CreateTamedPetFrom(Creature* creatureTarget, uint32 spell_id = 0); + Pet* CreateTamedPetFrom(uint32 creatureEntry, uint32 spell_id = 0); + bool InitTamedPet(Pet* pet, uint8 level, uint32 spell_id); + + // aura apply/remove helpers - you should better not use these + Aura* _TryStackingOrRefreshingExistingAura(AuraCreateInfo& createInfo); + void _AddAura(UnitAura* aura, Unit* caster); + AuraApplication* _CreateAuraApplication(Aura* aura, uint8 effMask); + void _ApplyAuraEffect(Aura* aura, uint8 effIndex); + void _ApplyAura(AuraApplication* aurApp, uint8 effMask); + void _UnapplyAura(AuraApplicationMap::iterator& i, AuraRemoveFlags removeMode); + void _UnapplyAura(AuraApplication* aurApp, AuraRemoveFlags removeMode); + void _RemoveNoStackAurasDueToAura(Aura* aura, bool owned); + void _RegisterAuraEffect(AuraEffect* aurEff, bool apply); + + // m_ownedAuras container management + AuraMap & GetOwnedAuras() { return m_ownedAuras; } + AuraMap const& GetOwnedAuras() const { return m_ownedAuras; } + + void RemoveOwnedAura(AuraMap::iterator& i, AuraRemoveFlags removeMode = AuraRemoveFlags::ByDefault); + void RemoveOwnedAura(uint32 spellId, ObjectGuid casterGUID = ObjectGuid::Empty, uint8 reqEffMask = 0, AuraRemoveFlags removeMode = AuraRemoveFlags::ByDefault); + void RemoveOwnedAura(Aura* aura, AuraRemoveFlags removeMode = AuraRemoveFlags::ByDefault); + + Aura* GetOwnedAura(uint32 spellId, ObjectGuid casterGUID = ObjectGuid::Empty, ObjectGuid itemCasterGUID = ObjectGuid::Empty, uint8 reqEffMask = 0, Aura* except = nullptr) const; + + // m_appliedAuras container management + AuraApplicationMap & GetAppliedAuras() { return m_appliedAuras; } + AuraApplicationMap const& GetAppliedAuras() const { return m_appliedAuras; } + + void RemoveAura(AuraApplicationMap::iterator &i, AuraRemoveFlags mode = AuraRemoveFlags::ByDefault); + void RemoveAura(uint32 spellId, ObjectGuid casterGUID = ObjectGuid::Empty, uint8 reqEffMask = 0, AuraRemoveFlags removeMode = AuraRemoveFlags::ByDefault); + void RemoveAura(AuraApplication * aurApp, AuraRemoveFlags mode = AuraRemoveFlags::ByDefault); + void RemoveAura(Aura* aur, AuraRemoveFlags mode = AuraRemoveFlags::ByDefault); + + // Convenience methods removing auras by predicate + void RemoveAppliedAuras(std::function const& check, AuraRemoveFlags removeMode = AuraRemoveFlags::ByDefault); + void RemoveOwnedAuras(std::function const& check, AuraRemoveFlags removeMode = AuraRemoveFlags::ByDefault); + + // Optimized overloads taking advantage of map key + void RemoveAppliedAuras(uint32 spellId, std::function const& check, AuraRemoveFlags removeMode = AuraRemoveFlags::ByDefault); + void RemoveOwnedAuras(uint32 spellId, std::function const& check, AuraRemoveFlags removeMode = AuraRemoveFlags::ByDefault); + + void RemoveAurasByType(AuraType auraType, std::function const& check, AuraRemoveFlags removeMode = AuraRemoveFlags::ByDefault); + + void RemoveAurasDueToSpell(uint32 spellId, ObjectGuid casterGUID = ObjectGuid::Empty, uint8 reqEffMask = 0, AuraRemoveFlags removeMode = AuraRemoveFlags::ByDefault); + void RemoveAuraFromStack(uint32 spellId, ObjectGuid casterGUID = ObjectGuid::Empty, AuraRemoveFlags removeMode = AuraRemoveFlags::ByDefault); + void RemoveAurasDueToSpellByDispel(uint32 spellId, uint32 dispellerSpellId, ObjectGuid casterGUID, WorldObject* dispeller, uint8 chargesRemoved = 1); + void RemoveAurasDueToSpellBySteal(uint32 spellId, ObjectGuid casterGUID, WorldObject* stealer, int32 stolenCharges = 1); + void RemoveAurasDueToItemSpell(uint32 spellId, ObjectGuid castItemGuid); + void RemoveAurasByType(AuraType auraType, ObjectGuid casterGUID = ObjectGuid::Empty, Aura* except = nullptr, bool negative = true, bool positive = true); + void RemoveNotOwnSingleTargetAuras(bool onPhaseChange = false); + template + void RemoveAurasWithInterruptFlags(InterruptFlags flag, SpellInfo const* source = nullptr); + void RemoveAurasWithAttribute(uint32 flags); + void RemoveAurasWithFamily(SpellFamilyNames family, uint32 familyFlag1, uint32 familyFlag2, uint32 familyFlag3, ObjectGuid casterGUID); + void RemoveAurasWithMechanic(uint32 mechanicMaskToRemove, AuraRemoveFlags removeMode = AuraRemoveFlags::ByDefault, uint32 exceptSpellId = 0, bool withEffectMechanics = false); + void RemoveMovementImpairingAuras(bool withRoot); + void RemoveAurasByShapeShift(); + + void RemoveAreaAurasDueToLeaveWorld(); + void RemoveAllAuras(); + void RemoveArenaAuras(); + void RemoveAurasOnEvade(); + void RemoveAllAurasOnDeath(); + void RemoveAllAurasRequiringDeadTarget(); + void RemoveAllAurasExceptType(AuraType type); + void RemoveAllAurasExceptType(AuraType type1, AuraType type2); /// @todo: once we support variadic templates use them here + void RemoveAllGroupBuffsFromCaster(ObjectGuid casterGUID); + void DelayOwnedAuras(uint32 spellId, ObjectGuid caster, int32 delaytime); + + void _RemoveAllAuraStatMods(); + void _ApplyAllAuraStatMods(); + + AuraEffectList const& GetAuraEffectsByType(AuraType type) const { return m_modAuras[type]; } + AuraList & GetSingleCastAuras() { return m_scAuras; } + AuraList const& GetSingleCastAuras() const { return m_scAuras; } + bool HasSingleCastAuraOfSpell(uint32 spellId) const; + + AuraEffect* GetAuraEffect(uint32 spellId, uint8 effIndex, ObjectGuid casterGUID = ObjectGuid::Empty) const; + AuraEffect* GetAuraEffectOfRankedSpell(uint32 spellId, uint8 effIndex, ObjectGuid casterGUID = ObjectGuid::Empty) const; + AuraEffect* GetAuraEffect(AuraType type, SpellFamilyNames name, uint32 iconId, uint8 effIndex) const; // spell mustn't have familyflags + AuraEffect* GetAuraEffect(AuraType type, SpellFamilyNames family, uint32 familyFlag1, uint32 familyFlag2, uint32 familyFlag3, ObjectGuid casterGUID = ObjectGuid::Empty) const; + AuraEffect* GetDummyAuraEffect(SpellFamilyNames name, uint32 iconId, uint8 effIndex) const; + + AuraApplication * GetAuraApplication(uint32 spellId, ObjectGuid casterGUID = ObjectGuid::Empty, ObjectGuid itemCasterGUID = ObjectGuid::Empty, uint8 reqEffMask = 0, AuraApplication * except = nullptr) const; + Aura* GetAura(uint32 spellId, ObjectGuid casterGUID = ObjectGuid::Empty, ObjectGuid itemCasterGUID = ObjectGuid::Empty, uint8 reqEffMask = 0) const; + + AuraApplication * GetAuraApplicationOfRankedSpell(uint32 spellId, ObjectGuid casterGUID = ObjectGuid::Empty, ObjectGuid itemCasterGUID = ObjectGuid::Empty, uint8 reqEffMask = 0, AuraApplication * except = nullptr) const; + Aura* GetAuraOfRankedSpell(uint32 spellId, ObjectGuid casterGUID = ObjectGuid::Empty, ObjectGuid itemCasterGUID = ObjectGuid::Empty, uint8 reqEffMask = 0) const; + + void GetDispellableAuraList(WorldObject const* caster, uint32 dispelMask, DispelChargesList& dispelList, bool isReflect = false) const; + + bool HasAuraEffect(uint32 spellId, uint8 effIndex, ObjectGuid caster = ObjectGuid::Empty) const; + uint32 GetAuraCount(uint32 spellId) const; + bool HasAura(uint32 spellId, ObjectGuid casterGUID = ObjectGuid::Empty, ObjectGuid itemCasterGUID = ObjectGuid::Empty, uint8 reqEffMask = 0) const; + bool HasAuraType(AuraType auraType) const; + bool HasAuraTypeWithCaster(AuraType auraType, ObjectGuid caster) const; + bool HasAuraTypeWithMiscvalue(AuraType auraType, int32 miscValue) const; + bool HasAuraTypeWithAffectMask(AuraType auraType, SpellInfo const* affectedSpell) const; + bool HasAuraTypeWithValue(AuraType auraType, int32 value) const; + template + bool HasNegativeAuraWithInterruptFlag(InterruptFlags flag, ObjectGuid guid = ObjectGuid::Empty) const; + bool HasAuraWithMechanic(uint32 mechanicMask) const; + bool HasStrongerAuraWithDR(SpellInfo const* auraSpellInfo, Unit* caster, bool triggered) const; + + AuraEffect* IsScriptOverriden(SpellInfo const* spell, int32 script) const; + uint32 GetDiseasesByCaster(ObjectGuid casterGUID, bool remove = false); + uint32 GetDoTsByCaster(ObjectGuid casterGUID) const; + + int32 GetTotalAuraModifier(AuraType auraType) const; + float GetTotalAuraMultiplier(AuraType auraType) const; + int32 GetMaxPositiveAuraModifier(AuraType auraType) const; + int32 GetMaxNegativeAuraModifier(AuraType auraType) const; + + int32 GetTotalAuraModifier(AuraType auraType, std::function const& predicate) const; + float GetTotalAuraMultiplier(AuraType auraType, std::function const& predicate) const; + int32 GetMaxPositiveAuraModifier(AuraType auraType, std::function const& predicate) const; + int32 GetMaxNegativeAuraModifier(AuraType auraType, std::function const& predicate) const; + + int32 GetTotalAuraModifierByMiscMask(AuraType auraType, uint32 misc_mask) const; + float GetTotalAuraMultiplierByMiscMask(AuraType auraType, uint32 misc_mask) const; + int32 GetMaxPositiveAuraModifierByMiscMask(AuraType auraType, uint32 misc_mask, AuraEffect const* except = nullptr) const; + int32 GetMaxNegativeAuraModifierByMiscMask(AuraType auraType, uint32 misc_mask) const; + + int32 GetTotalAuraModifierByMiscValue(AuraType auraType, int32 misc_value) const; + float GetTotalAuraMultiplierByMiscValue(AuraType auraType, int32 misc_value) const; + int32 GetMaxPositiveAuraModifierByMiscValue(AuraType auraType, int32 misc_value) const; + int32 GetMaxNegativeAuraModifierByMiscValue(AuraType auraType, int32 misc_value) const; + + int32 GetTotalAuraModifierByAffectMask(AuraType auraType, SpellInfo const* affectedSpell) const; + float GetTotalAuraMultiplierByAffectMask(AuraType auraType, SpellInfo const* affectedSpell) const; + int32 GetMaxPositiveAuraModifierByAffectMask(AuraType auraType, SpellInfo const* affectedSpell) const; + int32 GetMaxNegativeAuraModifierByAffectMask(AuraType auraType, SpellInfo const* affectedSpell) const; + + void UpdateResistanceBuffModsMod(SpellSchools school); + void SetCreateHealth(uint32 val) { SetUInt32Value(UNIT_FIELD_BASE_HEALTH, val); } + uint32 GetCreateHealth() const { return GetUInt32Value(UNIT_FIELD_BASE_HEALTH); } + void SetCreateMana(uint32 val) { SetUInt32Value(UNIT_FIELD_BASE_MANA, val); } + uint32 GetCreateMana() const { return GetUInt32Value(UNIT_FIELD_BASE_MANA); } + int32 GetCreatePowerValue(PowerType power) const; + + ObjectGuid GetChannelObjectGuid() const { return GetGuidValue(UNIT_FIELD_CHANNEL_OBJECT); } + void SetChannelObjectGuid(ObjectGuid guid) { SetGuidValue(UNIT_FIELD_CHANNEL_OBJECT, guid); } + + void SetCurrentCastSpell(Spell* pSpell); + void InterruptSpell(CurrentSpellTypes spellType, bool withDelayed = true, bool withInstant = true); + void FinishSpell(CurrentSpellTypes spellType, bool ok = true); + + // set withDelayed to true to account delayed spells as cast + // delayed+channeled spells are always accounted as cast + // we can skip channeled or delayed checks using flags + bool IsNonMeleeSpellCast(bool withDelayed, bool skipChanneled = false, bool skipAutorepeat = false, bool isAutoshoot = false, bool skipInstant = true) const; + + // set withDelayed to true to interrupt delayed spells too + // delayed+channeled spells are always interrupted + void InterruptNonMeleeSpells(bool withDelayed, uint32 spellid = 0, bool withInstant = true); + + Spell* GetCurrentSpell(CurrentSpellTypes spellType) const { return m_currentSpells[spellType]; } + Spell* GetCurrentSpell(uint32 spellType) const { return m_currentSpells[spellType]; } + Spell* FindCurrentSpellBySpellId(uint32 spell_id) const; + int32 GetCurrentSpellCastTime(uint32 spell_id) const; + SpellInfo const* GetCastSpellInfo(SpellInfo const* spellInfo, TriggerCastFlags& triggerFlag) const; + + virtual bool HasSpellFocus(Spell const* /*focusSpell*/ = nullptr) const { return false; } + virtual bool IsMovementPreventedByCasting() const; + + SpellHistory* GetSpellHistory() { return m_spellHistory.get(); } + SpellHistory const* GetSpellHistory() const { return m_spellHistory.get(); } + + std::array m_SummonSlot; + std::array m_ObjectSlot; + + ShapeshiftForm GetShapeshiftForm() const { return ShapeshiftForm(GetByteValue(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_SHAPESHIFT_FORM)); } + void SetShapeshiftForm(ShapeshiftForm form); + + bool IsInFeralForm() const; + + bool IsInDisallowedMountForm() const; + + float m_modMeleeHitChance; + float m_modRangedHitChance; + float m_modSpellHitChance; + int32 m_baseSpellCritChance; + + std::array m_baseAttackSpeed; + std::array m_modAttackSpeedPct; + std::array m_attackTimer; + + // stat system + void HandleStatFlatModifier(UnitMods unitMod, UnitModifierFlatType modifierType, float amount, bool apply); + void ApplyStatPctModifier(UnitMods unitMod, UnitModifierPctType modifierType, float amount); + + void SetStatFlatModifier(UnitMods unitMod, UnitModifierFlatType modifierType, float val); + void SetStatPctModifier(UnitMods unitMod, UnitModifierPctType modifierType, float val); + + float GetFlatModifierValue(UnitMods unitMod, UnitModifierFlatType modifierType) const; + float GetPctModifierValue(UnitMods unitMod, UnitModifierPctType modifierType) const; + + void UpdateUnitMod(UnitMods unitMod); + + // only players have item requirements + virtual bool CheckAttackFitToAuraRequirement(WeaponAttackType /*attackType*/, AuraEffect const* /*aurEff*/) const { return true; } + + virtual void UpdateDamageDoneMods(WeaponAttackType attackType, int32 skipEnchantSlot = -1); + void UpdateAllDamageDoneMods(); + + void UpdateDamagePctDoneMods(WeaponAttackType attackType); + void UpdateAllDamagePctDoneMods(); + + float GetTotalAuraModValue(UnitMods unitMod) const; + SpellSchools GetSpellSchoolByAuraGroup(UnitMods unitMod) const; + PowerType GetPowerTypeByAuraGroup(UnitMods unitMod) const; + bool CanModifyStats() const { return m_canModifyStats; } + void SetCanModifyStats(bool modifyStats) { m_canModifyStats = modifyStats; } + virtual void UpdateResistances(uint32 school) = 0; + virtual void UpdateAllResistances(); + virtual void UpdateArmor() = 0; + virtual void UpdateMaxHealth() = 0; + virtual void UpdateMaxPower(PowerType power) = 0; + virtual uint32 GetPowerIndex(PowerType power) const = 0; + virtual void UpdateAttackPowerAndDamage(bool ranged = false) = 0; + virtual void UpdateDamagePhysical(WeaponAttackType attType); + float GetTotalAttackPowerValue(WeaponAttackType attType) const; + float GetWeaponDamageRange(WeaponAttackType attType, WeaponDamageRange type) const; + void SetBaseWeaponDamage(WeaponAttackType attType, WeaponDamageRange damageRange, float value) { m_weaponDamage[attType][damageRange] = value; } + virtual void CalculateMinMaxDamage(WeaponAttackType attType, bool normalized, bool addTotalPct, float& minDamage, float& maxDamage) const = 0; + uint32 CalculateDamage(WeaponAttackType attType, bool normalized, bool addTotalPct) const; + float GetAPMultiplier(WeaponAttackType attType, bool normalized) const; + + bool isInFrontInMap(Unit const* target, float distance, float arc = float(M_PI)) const; + bool isInBackInMap(Unit const* target, float distance, float arc = float(M_PI)) const; + + // Visibility system + bool IsVisible() const; + void SetVisible(bool x); + + // common function for visibility checks for player/creatures with detection code + void OnPhaseChange(); + void UpdateObjectVisibility(bool forced = true) override; + + SpellImmuneContainer m_spellImmune[MAX_SPELL_IMMUNITY]; + uint32 m_lastSanctuaryTime; + + VisibleAuraMap const* GetVisibleAuras() { return &m_visibleAuras; } + AuraApplication * GetVisibleAura(uint8 slot) const; + void SetVisibleAura(uint8 slot, AuraApplication * aur); + void RemoveVisibleAura(uint8 slot); + + bool HasInterruptFlag(SpellAuraInterruptFlags flags) const { return m_interruptMask.HasFlag(flags); } + bool HasInterruptFlag(SpellAuraInterruptFlags2 flags) const { return m_interruptMask2.HasFlag(flags); } + void AddInterruptMask(SpellAuraInterruptFlags flags, SpellAuraInterruptFlags2 flags2) + { + m_interruptMask |= flags; + m_interruptMask2 |= flags2; + } + void UpdateInterruptMask(); + + virtual float GetNativeObjectScale() const { return 1.0f; } + virtual void RecalculateObjectScale(); + uint32 GetDisplayId() const { return GetUInt32Value(UNIT_FIELD_DISPLAYID); } + virtual void SetDisplayId(uint32 modelId, bool setNative = false); + uint32 GetNativeDisplayId() const { return GetUInt32Value(UNIT_FIELD_NATIVEDISPLAYID); } + void RestoreDisplayId(); + void setTransForm(uint32 spellid) { m_transform = spellid;} + uint32 getTransForm() const { return m_transform;} + + // DynamicObject management + void _RegisterDynObject(DynamicObject* dynObj); + void _UnregisterDynObject(DynamicObject* dynObj); + DynamicObject* GetDynObject(uint32 spellId) const; + std::vector GetDynObjects(uint32 spellId) const; + void RemoveDynObject(uint32 spellId); + void RemoveAllDynObjects(); + + GameObject* GetGameObject(uint32 spellId) const; + std::vector GetGameObjects(uint32 spellId) const; + void AddGameObject(GameObject* gameObj); + void RemoveGameObject(GameObject* gameObj, bool del); + void RemoveGameObject(uint32 spellid, bool del); + void RemoveAllGameObjects(); + + void ModifyAuraState(AuraStateType flag, bool apply); + uint32 BuildAuraStateUpdateForTarget(Unit* target) const; + bool HasAuraState(AuraStateType flag, SpellInfo const* spellProto = nullptr, Unit const* Caster = nullptr) const; + void UnsummonAllTotems(); + bool IsMagnet() const; + Unit* GetMeleeHitRedirectTarget(Unit* victim, SpellInfo const* spellInfo = nullptr); + + int32 SpellBaseDamageBonusDone(SpellSchoolMask schoolMask, bool withSpellPowerPctMod = false) const; + int32 SpellDamageBonusDone(Unit* victim, SpellInfo const* spellProto, int32 pdamage, DamageEffectType damagetype, uint8 effIndex, uint32 stack = 1, Spell* spell = nullptr, AuraEffect const* aurEff = nullptr) const; + float SpellDamagePctDone(Unit* victim, SpellInfo const* spellProto, DamageEffectType damagetype) const; + int32 SpellDamageBonusTaken(Unit* caster, SpellInfo const* spellProto, int32 pdamage, DamageEffectType damagetype) const; + int32 SpellBaseHealingBonusDone(SpellSchoolMask schoolMask, bool withSpellPowerPctMod = false) const; + int32 SpellHealingBonusDone(Unit* victim, SpellInfo const* spellProto, int32 healamount, DamageEffectType damagetype, uint8 effIndex, uint32 stack = 1, Spell* spell = nullptr, AuraEffect const* aurEff = nullptr) const; + float SpellHealingPctDone(Unit* victim, SpellInfo const* spellProto) const; + int32 SpellHealingBonusTaken(Unit* caster, SpellInfo const* spellProto, int32 healamount, DamageEffectType damagetype) const; + + int32 MeleeDamageBonusDone(Unit* pVictim, int32 damage, WeaponAttackType attType, DamageEffectType damagetype, SpellInfo const* spellProto = nullptr, Mechanics mechanic = MECHANIC_NONE, SpellSchoolMask damageSchoolMask = SPELL_SCHOOL_MASK_NORMAL, Spell* spell = nullptr, AuraEffect const* aurEff = nullptr); + int32 MeleeDamageBonusTaken(Unit* attacker, int32 pdamage, WeaponAttackType attType, SpellInfo const* spellProto = nullptr, SpellSchoolMask damageSchoolMask = SPELL_SCHOOL_MASK_NORMAL); + + bool IsBlockCritical() const; + float SpellCritChanceDone(SpellInfo const* spellInfo, SpellSchoolMask schoolMask, WeaponAttackType attackType = BASE_ATTACK, bool isPeriodic = false) const; + float SpellCritChanceTaken(Unit const* caster, SpellInfo const* spellInfo, SpellSchoolMask schoolMask, float doneChance, WeaponAttackType attackType = BASE_ATTACK, bool isPeriodic = false) const; + static uint32 SpellCriticalDamageBonus(Unit const* caster, SpellInfo const* spellProto, uint32 damage, bool periodicDamage = false); + static uint32 SpellCriticalHealingBonus(Unit const* caster, uint32 damage); + + uint32 GetCastingTimeForBonus(SpellInfo const* spellProto, DamageEffectType damagetype, uint32 CastingTime) const; + float CalculateDefaultCoefficient(SpellInfo const* spellInfo, DamageEffectType damagetype) const; + + void ApplySpellImmune(uint32 spellId, uint32 op, uint32 type, bool apply); + bool IsImmunedToSpell(SpellInfo const* spellInfo, WorldObject const* caster, bool requireImmunityPurgesEffectAttribute = false) const; + uint32 GetSchoolImmunityMask() const; + uint32 GetDamageImmunityMask() const; + uint32 GetMechanicImmunityMask() const; + + bool IsImmunedToDamage(SpellSchoolMask meleeSchoolMask) const; + bool IsImmunedToDamage(SpellInfo const* spellInfo) const; + virtual bool IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index, WorldObject const* caster, bool requireImmunityPurgesEffectAttribute = false) const; // redefined in Creature + + static bool IsDamageReducedByArmor(SpellSchoolMask damageSchoolMask, SpellInfo const* spellInfo = nullptr); + static uint32 CalcArmorReducedDamage(Unit const* attacker, Unit* victim, const uint32 damage, SpellInfo const* spellInfo, WeaponAttackType attackType = MAX_ATTACK, uint8 attackerLevel = 0); + static uint32 CalcSpellResistedDamage(Unit const* attacker, Unit* victim, uint32 damage, SpellSchoolMask schoolMask, SpellInfo const* spellInfo); + static void CalcAbsorbResist(DamageInfo& damageInfo); + static void CalcHealAbsorb(HealInfo& healInfo); + + void UpdateSpeed(UnitMoveType mtype); + float GetSpeed(UnitMoveType mtype) const; + float GetSpeedRate(UnitMoveType mtype) const { return m_speed_rate[mtype]; } + void SetSpeed(UnitMoveType mtype, float newValue); + void SetSpeedRate(UnitMoveType mtype, float rate); + private: + void SetSpeedRateReal(UnitMoveType mtype, float rate); + + public: + // Makes the unit follow the given target. Use this function above using the MotionMaster::MoveFollow for default follow behaivior. + void FollowTarget(Unit* target); + + FormationFollowerGUIDContainer GetFormationFollowers() { return _formationFollowers; } + void AddFormationFollower(Unit* follower) { _formationFollowers.push_back(follower->GetGUID()); } + void RemoveFormationFollower(Unit* follower); + bool HasFormationFollower(Unit* follower) const; + + void PursuerAdded(PursuingType type, AbstractPursuer* pursuer) { _unitsPursuingMe[AsUnderlyingType(type)].insert(pursuer); }; + void PursuerRemoved(PursuingType type, AbstractPursuer* pursuer) { _unitsPursuingMe[AsUnderlyingType(type)].erase(pursuer); }; + + // Sets the target of all stored AbstractPursuers to nullptr, allowing the involved movement generators to do safe nullptr checks + void RemoveAllPursuers(); + + MotionMaster* GetMotionMaster() { return i_motionMaster.get(); } + MotionMaster const* GetMotionMaster() const { return i_motionMaster.get(); } + + bool IsStopped() const { return !(HasUnitState(UNIT_STATE_MOVING)); } + void StopMoving(); + void PauseMovement(uint32 timer = 0, uint8 slot = 0, bool forced = true); // timer in ms + void ResumeMovement(uint32 timer = 0, uint8 slot = 0); // timer in ms + + void AddUnitMovementFlag(uint32 f) { m_movementInfo.AddMovementFlag(f); } + void RemoveUnitMovementFlag(uint32 f) { m_movementInfo.RemoveMovementFlag(f); } + bool HasUnitMovementFlag(uint32 f) const { return m_movementInfo.HasMovementFlag(f); } + uint32 GetUnitMovementFlags() const { return m_movementInfo.GetMovementFlags(); } + void SetUnitMovementFlags(uint32 f) { m_movementInfo.SetMovementFlags(f); } + + void AddExtraUnitMovementFlag(uint16 f) { m_movementInfo.AddExtraMovementFlag(f); } + void RemoveExtraUnitMovementFlag(uint16 f) { m_movementInfo.RemoveExtraMovementFlag(f); } + bool HasExtraUnitMovementFlag(uint16 f) const { return m_movementInfo.HasExtraMovementFlag(f); } + uint16 GetExtraUnitMovementFlags() const { return m_movementInfo.GetExtraMovementFlags(); } + void SetExtraUnitMovementFlags(uint16 f) { m_movementInfo.SetExtraMovementFlags(f); } + bool IsSplineEnabled() const; + + void SetControlled(bool apply, UnitState state); + void ApplyControlStatesIfNeeded(); + + ///-----------Combo point system------------------- + void AddComboPoints(Unit* target, int8 count, Spell* spell = nullptr); + void GainSpellComboPoints(int8 count); + void ClearComboPoints(); + void SendComboPoints(); + void AddComboPointHolder(ObjectGuid lowguid) { m_ComboPointHolders.insert(lowguid); } + void RemoveComboPointHolder(ObjectGuid lowguid) { m_ComboPointHolders.erase(lowguid); } + void ClearComboPointHolders(); + uint8 GetComboPoints() const { return m_comboPoints; } + ObjectGuid GetComboTarget() const { return m_comboTarget; } + + ObjectGuid m_comboTarget; + int8 m_comboPoints; + + ///----------Pet responses methods----------------- + void SendPetActionFeedback (uint8 response, uint32 spellId = 0); + void SendPetActionSound(int32 action); + void SendPetAIReaction(ObjectGuid guid); + ///----------End of Pet responses methods---------- + + void PropagateSpeedChange(); + + // reactive attacks + void ClearAllReactives(); + void StartReactiveTimer(ReactiveType reactive) { m_reactiveTimer[reactive] = REACTIVE_TIMER_START;} + void UpdateReactives(uint32 p_time); + + // group updates + void UpdateAuraForGroup(uint8 slot); + + // proc trigger system + bool CanProc() const { return !m_procDeep; } + void SetCantProc(bool apply); + int32 GetProcChainLength() const { return m_procChainLength; } + + uint32 GetModelForForm(ShapeshiftForm form, uint32 spellId) const; + uint32 GetModelForTotem(PlayerTotemType totemType); + + friend class VehicleJoinEvent; + ObjectGuid LastCharmerGUID; + bool CreateVehicleKit(uint32 id, uint32 creatureEntry, bool loading = false); + void RemoveVehicleKit(bool onRemoveFromWorld = false); + Vehicle* GetVehicleKit()const { return m_vehicleKit.get(); } + Vehicle* GetVehicle() const { return m_vehicle; } + void SetVehicle(Vehicle* vehicle) { m_vehicle = vehicle; } + bool IsOnVehicle(Unit const* vehicle) const; + Unit* GetVehicleBase() const; + Creature* GetVehicleCreatureBase() const; + ObjectGuid GetTransGUID() const override; + /// Returns the transport this unit is on directly (if on vehicle and transport, return vehicle) + TransportBase* GetDirectTransport() const; + + bool m_ControlledByPlayer; + + bool HandleSpellClick(Unit* clicker, int8 seatId = -1); + void EnterVehicle(Unit* base, int8 seatId = -1); + void ExitVehicle(Position const* exitPosition = nullptr); + void ChangeSeat(int8 seatId, bool next = true); + + // Should only be called by AuraEffect::HandleAuraControlVehicle(AuraApplication const* auraApp, uint8 mode, bool apply) const; + void _ExitVehicle(Position const* exitPosition = nullptr); + void _EnterVehicle(Vehicle* vehicle, int8 seatId, AuraApplication const* aurApp = nullptr); + + void WriteMovementInfo(WorldPacket& data, Movement::ExtraMovementStatusElement* extras = nullptr, uint32* movementCounter = nullptr); + + bool isMoving() const { return m_movementInfo.HasMovementFlag(MOVEMENTFLAG_MASK_MOVING); } + bool isTurning() const { return m_movementInfo.HasMovementFlag(MOVEMENTFLAG_MASK_TURNING); } + virtual bool CanFly() const = 0; + bool IsFlying() const { return m_movementInfo.HasMovementFlag(MOVEMENTFLAG_FLYING | MOVEMENTFLAG_DISABLE_GRAVITY); } + bool IsFalling() const; + virtual bool CanEnterWater() const = 0; + virtual bool CanSwim() const; + + float GetHoverOffset() const { return HasUnitMovementFlag(MOVEMENTFLAG_HOVER) ? GetFloatValue(UNIT_FIELD_HOVERHEIGHT) : 0.0f; } + + uint32 GetMovementCounterAndInc() { return m_movementCounter++; } + uint32 GetMovementCounter() { return m_movementCounter; } + void ClearPendingMovementChangeForType(MovementChangeType changeType); + void AssignPendingMovementChange(MovementChangeType changeType, PlayerMovementPendingChange&& newChange); + bool HasPendingMovementChange() const { return !m_pendingMovementChanges.empty(); } + PlayerMovementPendingChange const* GetPendingMovementChange(MovementChangeType changeType) const; + void PurgeAndApplyPendingMovementChanges(bool informObservers = true); + + int32 RewardRage(uint32 baseRage, bool attacker); + + void OutDebugInfo() const; + virtual bool IsLoading() const { return false; } + bool IsDuringRemoveFromWorld() const {return m_duringRemoveFromWorld;} + + Pet* ToPet() { if (IsPet()) return reinterpret_cast(this); else return nullptr; } + Pet const* ToPet() const { if (IsPet()) return reinterpret_cast(this); else return nullptr; } + + Totem* ToTotem() { if (IsTotem()) return reinterpret_cast(this); else return nullptr; } + Totem const* ToTotem() const { if (IsTotem()) return reinterpret_cast(this); else return nullptr; } + + TempSummon* ToTempSummon() { if (IsSummon()) return reinterpret_cast(this); else return nullptr; } + TempSummon const* ToTempSummon() const { if (IsSummon()) return reinterpret_cast(this); else return nullptr; } + + ObjectGuid GetTarget() const { return GetGuidValue(UNIT_FIELD_TARGET); } + virtual void SetTarget(ObjectGuid /*guid*/) = 0; + + // Movement info + std::unique_ptr movespline; + + int32 GetHighestExclusiveSameEffectSpellGroupValue(AuraEffect const* aurEff, AuraType auraType, bool checkMiscValue = false, int32 miscValue = 0) const; + bool IsHighestExclusiveAura(Aura const* aura, bool removeOtherAuraApplications = false); + bool IsHighestExclusiveAuraEffect(SpellInfo const* spellInfo, AuraType auraType, int32 effectAmount, uint8 auraEffectMask, bool removeOtherAuraApplications = false); + + virtual void Talk(std::string const& text, ChatMsg msgType, Language language, float textRange, WorldObject const* target); + virtual void Say(std::string const& text, Language language, WorldObject const* target = nullptr); + virtual void Yell(std::string const& text, Language language, WorldObject const* target = nullptr); + virtual void TextEmote(std::string const& text, WorldObject const* target = nullptr, bool isBossEmote = false); + virtual void Whisper(std::string const& text, Language language, Player* target, bool isBossWhisper = false); + virtual void Talk(uint32 textId, ChatMsg msgType, float textRange, WorldObject const* target); + virtual void Say(uint32 textId, WorldObject const* target = nullptr); + virtual void Yell(uint32 textId, WorldObject const* target = nullptr); + virtual void TextEmote(uint32 textId, WorldObject const* target = nullptr, bool isBossEmote = false); + virtual void Whisper(uint32 textId, Player* target, bool isBossWhisper = false); + + float GetCollisionHeight() const override; + + // Queues up a spell cast request that has been received via packet and processes it whenever possible. + void RequestSpellCast(std::unique_ptr castRequest, SpellInfo const* spellInfo); + void CancelPendingCastRequest(); + bool CanRequestSpellCast(SpellInfo const* spell) const; + + void DestroyForPlayer(Player* target, bool onDeath = false) const override; + + protected: + explicit Unit (bool isWorldObject); + + void BuildValuesUpdate(uint8 updatetype, ByteBuffer* data, Player* target) const override; + + void _UpdateSpells(uint32 time); + void _DeleteRemovedAuras(); + + void _UpdateAutoRepeatSpell(); + + AttackerSet m_attackers; + Unit* m_attacking; + + DeathState m_deathState; + + int32 m_procDeep; // tracked for proc system correctness (what spells should proc what) + int32 m_procChainLength; // tracked to protect against infinite proc loops (hard limit, will disallow procs even if they should happen) + + + typedef std::list DynObjectList; + DynObjectList m_dynObj; + + typedef std::list GameObjectList; + GameObjectList m_gameObj; + + uint32 m_transform; + + std::array m_currentSpells; + + AuraMap m_ownedAuras; + AuraApplicationMap m_appliedAuras; + AuraList m_removedAuras; + AuraMap::iterator m_auraUpdateIterator; + uint32 m_removedAurasCount; + + AuraEffectList m_modAuras[TOTAL_AURAS]; + AuraList m_scAuras; // cast singlecast auras + AuraApplicationList m_interruptableAuras; // auras which have interrupt mask applied on unit + AuraStateAurasMap m_auraStateAuras; // Used for improve performance of aura state checks on aura apply/remove + EnumFlag m_interruptMask; + EnumFlag m_interruptMask2; + + float m_auraFlatModifiersGroup[UNIT_MOD_END][MODIFIER_TYPE_FLAT_END]; + float m_auraPctModifiersGroup[UNIT_MOD_END][MODIFIER_TYPE_PCT_END]; + float m_weaponDamage[MAX_ATTACK][2]; + bool m_canModifyStats; + VisibleAuraMap m_visibleAuras; + + std::array m_speed_rate; + + Unit* m_charmer; // Unit that is charming ME + Unit* m_charmed; // Unit that is being charmed BY ME + std::unique_ptr m_charmInfo; + SharedVisionList m_sharedVision; + GameClient* _gameClientMovingMe; + + std::unique_ptr i_motionMaster; + + std::array m_reactiveTimer; + + Vehicle* m_vehicle; + std::unique_ptr m_vehicleKit; + + uint32 m_unitTypeMask; + LiquidTypeEntry const* _lastLiquid; + + uint32 _powerBarId; + int32 _powerUpdateTimer; + int32 _healthRegenerationTimer; + std::array _powerFraction; + std::array _usedPowerTypes; + public: + // Returns an array that contains information about which power type is used at which power index. MAX_POWERS implies that a power at given index is not used. + std::array const& GetUsedPowerTypes() const { return _usedPowerTypes; } + + protected: + + bool IsAlwaysVisibleFor(WorldObject const* seer) const override; + bool IsAlwaysDetectableFor(WorldObject const* seer) const override; + + void DisableSpline(); + + void ProcessPositionDataChanged(PositionFullTerrainStatus const& data) override; + virtual void ProcessTerrainStatusUpdate(ZLiquidStatus oldLiquidStatus, Optional const& newLiquidData); + + // notifiers + virtual void AtEnterCombat(); + virtual void AtExitCombat(); + + virtual void AtEngage(Unit* /*target*/) {} + virtual void AtDisengage() {} + + void InterruptMovementBasedAuras(); + void CheckPendingMovementAcks(); + + uint32 GetPowerUpdateInterval() const { return IsPlayer() ? PLAYER_POWER_UPDATE_INTERVAL : UNIT_POWER_UPDATE_INTERVAL; } + private: + + void UpdateSplineMovement(uint32 t_diff); + void UpdateSplinePosition(); + void SendFlightSplineSyncUpdate(); + + // player or player's pet + float GetCombatRatingReduction(CombatRating cr) const; + uint32 GetCombatRatingDamageReduction(CombatRating cr, float rate, float cap, uint32 damage) const; + + void ProcSkillsAndReactives(bool isVictim, Unit* procTarget, ProcFlags typeMask, ProcFlagsHit hitMask, WeaponAttackType attType); + + protected: + void SetFeared(bool apply); + void SetConfused(bool apply); + void SetStunned(bool apply); + void SetRooted(bool apply, bool packetOnly = false); + + private: + uint32 m_state; // Even derived shouldn't modify + + std::array m_Diminishing; + + // Threat+combat management + bool m_isEngaged; + // Manage all Units that are threatened by us + friend class CombatManager; + CombatManager m_combatManager; + friend class ThreatManager; + ThreatManager m_threatManager; + + void UpdateCharmAI(); + void RestoreDisabledAI(); + typedef std::stack> UnitAIStack; + UnitAIStack i_AIs; + std::shared_ptr i_AI; + bool m_aiLocked; + + GuidSet m_ComboPointHolders; + + bool m_cleanupDone; // lock made to not add stuff after cleanup before delete + bool m_duringRemoveFromWorld; // lock made to not add stuff after begining removing from world + + uint32 _oldFactionId; ///< faction before charm + bool _isWalkingBeforeCharm; ///< Are we walking before we were charmed? + + std::unique_ptr m_spellHistory; + + PositionUpdateInfo _positionUpdateInfo; + + FormationFollowerGUIDContainer _formationFollowers; + + std::array, AsUnderlyingType(PursuingType::Max)> _unitsPursuingMe; + + std::unique_ptr _pendingSpellCastRequest; + void ProcessPendingSpellCastRequest(); + void ProcessItemCast(PendingSpellCastRequest const& castRequest, SpellCastTargets const& targets); + bool CanExecutePendingSpellCastRequest(SpellInfo const* spellInfo) const; + + /* Player Movement fields START*/ + + // when a player controls this unit, and when change is made to this unit which requires an ack from the client to be acted (change of speed for example), this movementCounter is incremented + uint32 m_movementCounter; + std::unordered_map m_pendingMovementChanges; + + /* Player Movement fields END*/ + + private: + std::unique_ptr _stats; + public: + Stats& GetStats() const; + int32 GetStat(StatType statType) const; +}; + +namespace Trinity +{ + // Binary predicate for sorting Units based on percent value of a power + class PowerPctOrderPred + { + public: + PowerPctOrderPred(PowerType power, bool ascending = true) : _power(power), _ascending(ascending) { } + + bool operator()(WorldObject const* objA, WorldObject const* objB) const + { + Unit const* a = objA->ToUnit(); + Unit const* b = objB->ToUnit(); + float rA = (a && a->GetMaxPower(_power)) ? float(a->GetPower(_power)) / float(a->GetMaxPower(_power)) : 0.0f; + float rB = (b && b->GetMaxPower(_power)) ? float(b->GetPower(_power)) / float(b->GetMaxPower(_power)) : 0.0f; + return _ascending ? rA < rB : rA > rB; + } + + bool operator()(Unit const* a, Unit const* b) const + { + float rA = a->GetMaxPower(_power) ? float(a->GetPower(_power)) / float(a->GetMaxPower(_power)) : 0.0f; + float rB = b->GetMaxPower(_power) ? float(b->GetPower(_power)) / float(b->GetMaxPower(_power)) : 0.0f; + return _ascending ? rA < rB : rA > rB; + } + + private: + PowerType const _power; + bool const _ascending; + }; + + // Binary predicate for sorting Units based on percent value of health + class HealthPctOrderPred + { + public: + HealthPctOrderPred(bool ascending = true) : _ascending(ascending) { } + + bool operator()(WorldObject const* objA, WorldObject const* objB) const + { + Unit const* a = objA->ToUnit(); + Unit const* b = objB->ToUnit(); + float rA = (a && a->GetMaxHealth()) ? float(a->GetHealth()) / float(a->GetMaxHealth()) : 0.0f; + float rB = (b && b->GetMaxHealth()) ? float(b->GetHealth()) / float(b->GetMaxHealth()) : 0.0f; + return _ascending ? rA < rB : rA > rB; + } + + bool operator() (Unit const* a, Unit const* b) const + { + float rA = a->GetMaxHealth() ? float(a->GetHealth()) / float(a->GetMaxHealth()) : 0.0f; + float rB = b->GetMaxHealth() ? float(b->GetHealth()) / float(b->GetMaxHealth()) : 0.0f; + return _ascending ? rA < rB : rA > rB; + } + + private: + bool const _ascending; + }; +} + +#endif diff --git a/src/server/game/Groups/Group.h b/src/server/game/Groups/Group.h index 5d2e051e1b..36fd452c4a 100644 --- a/src/server/game/Groups/Group.h +++ b/src/server/game/Groups/Group.h @@ -315,6 +315,7 @@ class TC_GAME_API Group void ChangeMembersGroup(ObjectGuid guid, uint8 group); void SetTargetIcon(uint8 id, ObjectGuid whoGuid, ObjectGuid targetGuid); + ObjectGuid GetTargetIcon(uint8 id) const { return id < TARGETICONCOUNT ? m_targetIcons[id] : ObjectGuid::Empty; } void SetGroupMemberFlag(ObjectGuid guid, bool apply, GroupMemberFlags flag); void RemoveUniqueGroupMemberFlag(GroupMemberFlags flag); diff --git a/src/server/game/Groups/Group.h.bak-viano-tank-v1-20261001-083107 b/src/server/game/Groups/Group.h.bak-viano-tank-v1-20261001-083107 new file mode 100644 index 0000000000..5d2e051e1b --- /dev/null +++ b/src/server/game/Groups/Group.h.bak-viano-tank-v1-20261001-083107 @@ -0,0 +1,439 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef TRINITYCORE_GROUP_H +#define TRINITYCORE_GROUP_H + +#include "DBCEnums.h" +#include "DatabaseEnvFwd.h" +#include "GroupRefManager.h" +#include "Loot.h" +#include "SharedDefines.h" +#include + +class Battlefield; +class Battleground; +class Creature; +class DynamicObject; +class InstanceSave; +class Map; +class Player; +class Unit; +class WorldObject; +class WorldPacket; +class WorldSession; + +struct MapEntry; + +#define MAXGROUPSIZE 5 +#define MAXRAIDSIZE 40 +#define MAX_RAID_SUBGROUPS MAXRAIDSIZE/MAXGROUPSIZE +#define TARGETICONCOUNT 8 +#define SPELL_RAID_MARKER 84996 + +enum RollVote +{ + PASS = 0, + NEED = 1, + GREED = 2, + DISENCHANT = 3, + NOT_EMITED_YET = 4, + NOT_VALID = 5 +}; + +enum GroupMemberOnlineStatus +{ + MEMBER_STATUS_OFFLINE = 0x0000, + MEMBER_STATUS_ONLINE = 0x0001, // Lua_UnitIsConnected + MEMBER_STATUS_PVP = 0x0002, // Lua_UnitIsPVP + MEMBER_STATUS_DEAD = 0x0004, // Lua_UnitIsDead + MEMBER_STATUS_GHOST = 0x0008, // Lua_UnitIsGhost + MEMBER_STATUS_PVP_FFA = 0x0010, // Lua_UnitIsPVPFreeForAll + MEMBER_STATUS_UNK3 = 0x0020, // used in calls from Lua_GetPlayerMapPosition/Lua_GetBattlefieldFlagPosition + MEMBER_STATUS_AFK = 0x0040, // Lua_UnitIsAFK + MEMBER_STATUS_DND = 0x0080 // Lua_UnitIsDND +}; + +enum GroupMemberFlags +{ + MEMBER_FLAG_ASSISTANT = 0x01, + MEMBER_FLAG_MAINTANK = 0x02, + MEMBER_FLAG_MAINASSIST = 0x04 +}; + +enum GroupMemberAssignment +{ + GROUP_ASSIGN_MAINTANK = 0, + GROUP_ASSIGN_MAINASSIST = 1 +}; + +enum GroupFlags +{ + GROUP_FLAG_NONE = 0x000, + GROUP_FLAG_FAKE_RAID = 0x001, + GROUP_FLAG_RAID = 0x002, + GROUP_FLAG_LFG_RESTRICTED = 0x004, // Script_HasLFGRestrictions() + GROUP_FLAG_LFG = 0x008, + GROUP_FLAG_DESTROYED = 0x010, + GROUP_FLAG_ONE_PERSON_PARTY = 0x020, // Script_IsOnePersonParty() + GROUP_FLAG_EVERYONE_ASSISTANT = 0x040, // Script_IsEveryoneAssistant() + + GROUP_MASK_BGRAID = GROUP_FLAG_FAKE_RAID | GROUP_FLAG_RAID, +}; + +enum GroupUpdateFlags +{ + GROUP_UPDATE_FLAG_NONE = 0x00000000, // nothing + GROUP_UPDATE_FLAG_STATUS = 0x00000001, // uint16 (GroupMemberStatusFlag) + GROUP_UPDATE_FLAG_CUR_HP = 0x00000002, // uint32 (HP) + GROUP_UPDATE_FLAG_MAX_HP = 0x00000004, // uint32 (HP) + GROUP_UPDATE_FLAG_POWER_TYPE = 0x00000008, // uint8 (PowerType) + GROUP_UPDATE_FLAG_CUR_POWER = 0x00000010, // int16 (power value) + GROUP_UPDATE_FLAG_MAX_POWER = 0x00000020, // int16 (power value) + GROUP_UPDATE_FLAG_LEVEL = 0x00000040, // uint16 (level value) + GROUP_UPDATE_FLAG_ZONE = 0x00000080, // uint16 (zone id) + GROUP_UPDATE_FLAG_WMO_GROUP_ID = 0x00000100, // int16 (WMOGroupID) + GROUP_UPDATE_FLAG_POSITION = 0x00000200, // uint16 (x), uint16 (y), uint16 (z) + GROUP_UPDATE_FLAG_AURAS = 0x00000400, // uint8 (unk), uint64 (mask), uint32 (count), for each bit set: uint32 (spell id) + uint16 (AuraFlags) (if has flags Scalable -> 3x int32 (bps)) + GROUP_UPDATE_FLAG_PET_GUID = 0x00000800, // uint64 (pet guid) + GROUP_UPDATE_FLAG_PET_NAME = 0x00001000, // cstring (name, nullptr terminated string) + GROUP_UPDATE_FLAG_PET_MODEL_ID = 0x00002000, // uint16 (model id) + GROUP_UPDATE_FLAG_PET_CUR_HP = 0x00004000, // uint32 (HP) + GROUP_UPDATE_FLAG_PET_MAX_HP = 0x00008000, // uint32 (HP) + GROUP_UPDATE_FLAG_PET_POWER_TYPE = 0x00010000, // uint8 (PowerType) + GROUP_UPDATE_FLAG_PET_CUR_POWER = 0x00020000, // uint16 (power value) + GROUP_UPDATE_FLAG_PET_MAX_POWER = 0x00040000, // uint16 (power value) + GROUP_UPDATE_FLAG_PET_AURAS = 0x00080000, // [see GROUP_UPDATE_FLAG_AURAS] + GROUP_UPDATE_FLAG_VEHICLE_SEAT = 0x00100000, // int32 (vehicle seat id) + GROUP_UPDATE_FLAG_PHASE = 0x00200000, // int32 (unk), uint32 (phase count), for (count) uint16(phaseId) + GROUP_UPDATE_FLAG_UNK400000 = 0x00400000, + GROUP_UPDATE_FLAG_UNK800000 = 0x00800000, + GROUP_UPDATE_FLAG_UNK1000000 = 0x01000000, + GROUP_UPDATE_FLAG_UNK2000000 = 0x02000000, + GROUP_UPDATE_FLAG_UNK4000000 = 0x04000000, + GROUP_UPDATE_FLAG_UNK8000000 = 0x08000000, + GROUP_UPDATE_FLAG_UNK10000000 = 0x10000000, + GROUP_UPDATE_FLAG_UNK20000000 = 0x20000000, + GROUP_UPDATE_FLAG_UNK40000000 = 0x40000000, + GROUP_UPDATE_FLAG_UNK80000000 = 0x80000000, + + GROUP_UPDATE_PET = GROUP_UPDATE_FLAG_PET_GUID | GROUP_UPDATE_FLAG_PET_NAME | GROUP_UPDATE_FLAG_PET_MODEL_ID | + GROUP_UPDATE_FLAG_PET_CUR_HP | GROUP_UPDATE_FLAG_PET_MAX_HP | GROUP_UPDATE_FLAG_PET_POWER_TYPE | + GROUP_UPDATE_FLAG_PET_CUR_POWER | GROUP_UPDATE_FLAG_PET_MAX_POWER | GROUP_UPDATE_FLAG_PET_AURAS, // all pet flags + GROUP_UPDATE_FULL = GROUP_UPDATE_FLAG_STATUS | GROUP_UPDATE_FLAG_CUR_HP | GROUP_UPDATE_FLAG_MAX_HP | + GROUP_UPDATE_FLAG_POWER_TYPE | GROUP_UPDATE_FLAG_CUR_POWER | GROUP_UPDATE_FLAG_MAX_POWER | + GROUP_UPDATE_FLAG_LEVEL | GROUP_UPDATE_FLAG_ZONE | GROUP_UPDATE_FLAG_WMO_GROUP_ID |GROUP_UPDATE_FLAG_POSITION | + GROUP_UPDATE_FLAG_AURAS | GROUP_UPDATE_FLAG_PHASE | GROUP_UPDATE_FLAG_UNK400000 | GROUP_UPDATE_FLAG_UNK800000 | + GROUP_UPDATE_FLAG_UNK1000000 | GROUP_UPDATE_FLAG_UNK2000000 | GROUP_UPDATE_FLAG_UNK4000000 | + GROUP_UPDATE_FLAG_UNK8000000 | GROUP_UPDATE_FLAG_UNK10000000 | GROUP_UPDATE_FLAG_UNK20000000 | GROUP_UPDATE_FLAG_UNK40000000 +}; + +class Roll : public LootValidatorRef +{ + public: + Roll(ObjectGuid _guid, LootItem const& li); + ~Roll(); + void setLoot(Loot* pLoot); + Loot* getLoot(); + void targetObjectBuildLink() override; + + ObjectGuid itemGUID; + uint32 itemid; + ItemRandomEnchantmentId itemRandomPropId; + uint32 itemRandomSuffix; + uint8 itemCount; + typedef std::map PlayerVote; + PlayerVote playerVote; //vote position correspond with player position (in group) + uint8 totalPlayersRolling; + uint8 totalNeed; + uint8 totalGreed; + uint8 totalPass; + uint8 itemSlot; + uint8 rollVoteMask; +}; + +struct InstanceGroupBind +{ + InstanceSave* save; + bool perm; + /* permanent InstanceGroupBinds exist if the leader has a permanent + PlayerInstanceBind for the same instance. */ + InstanceGroupBind() : save(nullptr), perm(false) { } +}; + +struct RaidMarkerInfo +{ + ObjectGuid summonerGuid; + ObjectGuid markerGuid; +}; + +typedef std::list RaidMarkerList; + +/** request member stats checken **/ +/// @todo uninvite people that not accepted invite +class TC_GAME_API Group +{ + public: + struct MemberSlot + { + ObjectGuid guid; + std::string name; + uint8 group; + uint8 flags; + uint8 roles; + uint32 guildId; + }; + typedef std::list MemberSlotList; + typedef MemberSlotList::const_iterator member_citerator; + + typedef std::unordered_map< uint32 /*mapId*/, InstanceGroupBind> BoundInstancesMap; + + struct GroupDisenchantInfo + { + ObjectGuid DisenchanterGUID; + uint32 MaxDisenchantSkillLevel; + + void Initialize() + { + DisenchanterGUID = ObjectGuid::Empty; + MaxDisenchantSkillLevel = 0; + } + }; + + protected: + typedef MemberSlotList::iterator member_witerator; + typedef std::set InvitesList; + + typedef std::vector> Rolls; + + public: + Group(); + ~Group(); + + // group manipulation methods + bool Create(Player* leader); + void LoadGroupFromDB(Field* field); + void LoadMemberFromDB(ObjectGuid::LowType guidLow, uint8 memberFlags, uint8 subgroup, uint8 roles); + bool AddInvite(Player* player); + void RemoveInvite(Player* player); + void RemoveAllInvites(); + bool AddLeaderInvite(Player* player); + bool AddMember(Player* player); + bool RemoveMember(ObjectGuid guid, const RemoveMethod &method = GROUP_REMOVEMETHOD_DEFAULT, ObjectGuid kicker = ObjectGuid::Empty, char const* reason = nullptr); + void ChangeLeader(ObjectGuid guid); + static void ConvertLeaderInstancesToGroup(Player* player, Group* group, bool switchLeader); + void SetLootMethod(LootMethod method); + void SetLooterGuid(ObjectGuid guid); + void SetMasterLooterGuid(ObjectGuid guid); + void UpdateLooterGuid(WorldObject* pLootedObject, bool ifneed = false); + void SetLootThreshold(ItemQualities threshold); + void Disband(bool hideDestroy = false); + void SetLfgRoles(ObjectGuid guid, uint8 roles); + uint8 GetLfgRoles(ObjectGuid guid); + void SetEveryoneIsAssistant(bool apply); + + void SetGroupMarkerMask(uint32 mask) { m_markerMask = mask; } + void AddGroupMarkerMask(uint32 mask) { m_markerMask |= mask; } + void RemoveGroupMarkerMask(uint32 mask) { if (mask == 0x20) m_markerMask = 0x20; m_markerMask &= ~mask; } + bool HasMarker(uint32 mask) { return (m_markerMask & mask) != 0; } + uint32 GetMarkerMask() { return m_markerMask; } + + DynamicObject* GetRaidMarkerBySpellId(uint32 spell); + void AddMarkerToList(ObjectGuid summonerGuid, ObjectGuid markerGuid) { m_raidMarkers.push_back({ summonerGuid, markerGuid }); } + void RemoveRaidMarkerFromList(ObjectGuid markerGuid); + void RemoveAllMarkerFromList() { m_raidMarkers.clear(); } + void RemoveMarker(); + + // properties accessories + bool IsFull() const; + bool isLFGGroup() const; + bool isLFRGroup() const; + bool isRaidGroup() const; + bool isBGGroup() const; + bool isBFGroup() const; + bool IsCreated() const; + ObjectGuid GetLeaderGUID() const; + ObjectGuid GetGUID() const; + ObjectGuid::LowType GetLowGUID() const; + const char * GetLeaderName() const; + LootMethod GetLootMethod() const; + ObjectGuid GetLooterGuid() const; + ObjectGuid GetMasterLooterGuid() const; + ItemQualities GetLootThreshold() const; + + uint32 GetDbStoreId() const { return m_dbStoreId; } + + // member manipulation methods + bool IsMember(ObjectGuid guid) const; + bool IsLeader(ObjectGuid guid) const; + ObjectGuid GetMemberGUID(const std::string& name); + uint8 GetMemberFlags(ObjectGuid guid) const; + bool IsAssistant(ObjectGuid guid) const + { + return (GetMemberFlags(guid) & MEMBER_FLAG_ASSISTANT) == MEMBER_FLAG_ASSISTANT; + } + + Player* GetInvited(ObjectGuid guid) const; + Player* GetInvited(const std::string& name) const; + + bool SameSubGroup(ObjectGuid guid1, ObjectGuid guid2) const; + bool SameSubGroup(ObjectGuid guid1, MemberSlot const* slot2) const; + bool SameSubGroup(Player const* member1, Player const* member2) const; + bool HasFreeSlotSubGroup(uint8 subgroup) const; + + MemberSlotList const& GetMemberSlots() const { return m_memberSlots; } + GroupReference* GetFirstMember() { return m_memberMgr.getFirst(); } + GroupReference const* GetFirstMember() const { return m_memberMgr.getFirst(); } + uint32 GetMembersCount() const { return m_memberSlots.size(); } + uint32 GetInviteeCount() const { return m_invitees.size(); } + GroupFlags GetGroupFlags() const { return m_groupFlags; } + + uint8 GetMemberGroup(ObjectGuid guid) const; + + void ConvertToLFG(); + void ConvertToLFR(); + void ConvertToRaid(); + void ConvertToGroup(); + + void SetBattlegroundGroup(Battleground* bg); + void SetBattlefieldGroup(Battlefield* bf); + GroupJoinBattlegroundResult CanJoinBattlegroundQueue(Battleground const* bgOrTemplate, BattlegroundQueueTypeId bgQueueTypeId, uint32 MinPlayerCount, uint32 MaxPlayerCount, bool isRated, uint32 arenaSlot); + + void ChangeMembersGroup(ObjectGuid guid, uint8 group); + void SetTargetIcon(uint8 id, ObjectGuid whoGuid, ObjectGuid targetGuid); + void SetGroupMemberFlag(ObjectGuid guid, bool apply, GroupMemberFlags flag); + void RemoveUniqueGroupMemberFlag(GroupMemberFlags flag); + + Difficulty GetDifficulty(bool isRaid) const; + Difficulty GetDungeonDifficulty() const; + Difficulty GetRaidDifficulty() const; + void SetDungeonDifficulty(Difficulty difficulty); + void SetRaidDifficulty(Difficulty difficulty); + bool InCombatToInstance(uint32 instanceId); + void ResetInstances(uint8 method, bool isRaid, Player* SendMsgTo); + + // -no description- + //void SendInit(WorldSession* session); + void SendTargetIconList(WorldSession* session); + void SendRaidMarkerUpdate(); + void SendRaidMarkerUpdateToPlayer(ObjectGuid playerGUID, bool remove = false); + void SendUpdate(); + void SendUpdateToPlayer(ObjectGuid playerGUID, MemberSlot* slot = nullptr); + void SendUpdateDestroyGroupToPlayer(Player* player); + void UpdatePlayerOutOfRange(Player* player); + + template + void BroadcastWorker(Worker& worker) + { + for (GroupReference* itr = GetFirstMember(); itr != nullptr; itr = itr->next()) + worker(itr->GetSource()); + } + + template + void BroadcastWorker(Worker const& worker) const + { + for (GroupReference const* itr = GetFirstMember(); itr != nullptr; itr = itr->next()) + worker(itr->GetSource()); + } + + void BroadcastPacket(WorldPacket const* packet, bool ignorePlayersInBGRaid, int group = -1, ObjectGuid ignoredPlayer = ObjectGuid::Empty); + void BroadcastAddonMessagePacket(WorldPacket const* packet, const std::string& prefix, bool ignorePlayersInBGRaid, int group = -1, uint64 ignore = 0); + void BroadcastReadyCheck(WorldPacket const* packet); + void OfflineReadyCheck(); + + /*********************************************************/ + /*** LOOT SYSTEM ***/ + /*********************************************************/ + + bool isRollLootActive() const; + void SendLootStartRoll(uint32 CountDown, uint32 mapid, Roll const& r); + void SendLootStartRollToPlayer(uint32 countDown, uint32 mapId, Player* p, bool canNeed, Roll const& r); + void SendLootRoll(ObjectGuid SourceGuid, ObjectGuid TargetGuid, int32 RollNumber, uint8 RollType, Roll const& r, bool autoPass = false); + void SendLootRollWon(ObjectGuid SourceGuid, ObjectGuid TargetGuid, int32 RollNumber, uint8 RollType, Roll const& r); + void SendLootAllPassed(Roll const& roll); + void SendLooter(Creature* creature, Player* pLooter); + void GroupLoot(Loot* loot, WorldObject* pLootedObject); + void NeedBeforeGreed(Loot* loot, WorldObject* pLootedObject); + void MasterLoot(Loot* loot, WorldObject* pLootedObject); + Rolls::iterator GetRoll(ObjectGuid Guid); + void CountTheRoll(Rolls::iterator roll, Map* allowedMap); + void CountRollVote(ObjectGuid playerGUID, ObjectGuid Guid, uint8 Choise); + void EndRoll(Loot* loot, Map* allowedMap); + + // related to disenchant rolls + void ResetMaxEnchantingLevel(); + + void LinkMember(GroupReference* pRef); + void DelinkMember(ObjectGuid guid); + + InstanceGroupBind* BindToInstance(InstanceSave* save, bool permanent, bool load = false); + void UnbindInstance(uint32 mapid, uint8 difficulty, bool unload = false); + InstanceGroupBind* GetBoundInstance(Player* player); + InstanceGroupBind* GetBoundInstance(Map* aMap); + InstanceGroupBind* GetBoundInstance(MapEntry const* mapEntry); + InstanceGroupBind* GetBoundInstance(Difficulty difficulty, uint32 mapId); + BoundInstancesMap& GetBoundInstances(Difficulty difficulty); + + // FG: evil hacks + void BroadcastGroupUpdate(void); + + // guild misc + bool IsGuildGroupFor(Player* player); + uint32 GetMembersCountOfGuild(uint32 guildId); + uint32 GetNeededMembersOfSameGuild(uint8 arenaType, Map const* map); + bool MemberLevelIsInRange(uint32 levelMin, uint32 levelMax); + float GetGuildXpRateForPlayer(Player* player); + void UpdateGuildFor(ObjectGuid guid, uint32 guildId); + + protected: + bool _setMembersGroup(ObjectGuid guid, uint8 group); + void _homebindIfInstance(Player* player); + + void _initRaidSubGroupsCounter(); + member_citerator _getMemberCSlot(ObjectGuid Guid) const; + member_witerator _getMemberWSlot(ObjectGuid Guid); + void SubGroupCounterIncrease(uint8 subgroup); + void SubGroupCounterDecrease(uint8 subgroup); + void ToggleGroupMemberFlag(member_witerator slot, uint8 flag, bool apply); + + MemberSlotList m_memberSlots; + GroupRefManager m_memberMgr; + InvitesList m_invitees; + ObjectGuid m_leaderGuid; + std::string m_leaderName; + GroupFlags m_groupFlags; + uint32 m_markerMask; + Difficulty m_dungeonDifficulty; + Difficulty m_raidDifficulty; + Battleground* m_bgGroup; + Battlefield* m_bfGroup; + ObjectGuid m_targetIcons[TARGETICONCOUNT]; + LootMethod m_lootMethod; + ItemQualities m_lootThreshold; + ObjectGuid m_looterGuid; + ObjectGuid m_masterLooterGuid; + Rolls RollId; + BoundInstancesMap m_boundInstances[MAX_DIFFICULTY]; + uint8* m_subGroupsCounts; + ObjectGuid m_guid; + uint32 m_counter; // used only in SMSG_GROUP_LIST + GroupDisenchantInfo m_disenchantInfo; + uint32 m_dbStoreId; // Represents the ID used in database (Can be reused by other groups if group was disbanded) + RaidMarkerList m_raidMarkers; +}; + +#endif diff --git a/src/server/scripts/Custom/viano_adventurers.cpp b/src/server/scripts/Custom/viano_adventurers.cpp index a3032d11e5..6acb067ae1 100644 --- a/src/server/scripts/Custom/viano_adventurers.cpp +++ b/src/server/scripts/Custom/viano_adventurers.cpp @@ -7,9 +7,12 @@ #include "ScriptedCreature.h" #include "ObjectAccessor.h" #include "Player.h" +#include "Group.h" #include "TemporarySummon.h" #include "MotionMaster.h" #include "Log.h" +#include +#include namespace VianoAdventurers { @@ -203,6 +206,7 @@ namespace VianoAdventurers ownerGuid = owner->GetGUID(); me->SetOwnerGUID(ownerGuid); + me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED); me->SetFaction(owner->GetFaction()); me->SetLevel(owner->getLevel()); me->UpdateLevelDependantStats(); @@ -507,10 +511,449 @@ namespace VianoAdventurers return new npc_horde_adventurer_meleeAI(creature); } }; + + // VIANO_TANK_V1 + class npc_horde_adventurer_tank : public CreatureScript + { + public: + npc_horde_adventurer_tank() : CreatureScript("npc_horde_adventurer_tank") { } + + struct npc_horde_adventurer_tankAI : public ScriptedAI + { + npc_horde_adventurer_tankAI(Creature* creature) : ScriptedAI(creature) { } + + static constexpr uint8 SKULL_ICON_ID = 7; + static constexpr float AUTO_PICKUP_RADIUS = 10.0f; + static constexpr uint32 TANK_UPDATE_INTERVAL = 500; + static constexpr uint32 AOE_DAMAGE_INTERVAL = 6000; + + ObjectGuid ownerGuid; + uint32 updateTimer = 0; + uint32 recoveryTimer = 1000; + uint32 aoeDamageTimer = 3000; + + Player* GetOwnerPlayer() const + { + if (!ownerGuid) + return nullptr; + + return ObjectAccessor::GetPlayer(*me, ownerGuid); + } + + bool IsValidTankTarget(Unit* unit) const + { + if (!unit || !unit->IsAlive()) + return false; + + // Dungeon-role bot: do not turn this into a PvP player hunter. + if (!unit->ToCreature()) + return false; + + return me->IsValidAttackTarget(unit); + } + + bool IsActivelyEngagedInCombat() const + { + if (Unit* victim = me->GetVictim()) + if (victim->IsAlive() && me->IsValidAttackTarget(victim)) + return true; + + for (Unit* attacker : me->getAttackers()) + if (attacker && attacker->IsAlive() && me->IsValidAttackTarget(attacker)) + return true; + + return false; + } + + void FollowOwner(Player* owner) + { + if (!owner) + return; + + me->GetMotionMaster()->MoveFollow(owner, FOLLOW_DISTANCE, FOLLOW_ANGLE); + } + + void HandleOutOfCombatRecovery(uint32 diff) + { + if (!me->IsAlive() || IsActivelyEngagedInCombat()) + { + recoveryTimer = 1000; + return; + } + + if (recoveryTimer > diff) + { + recoveryTimer -= diff; + return; + } + + recoveryTimer = 1000; + + uint32 maxHealth = me->GetMaxHealth(); + if (me->GetHealth() < maxHealth) + { + uint32 gain = maxHealth / 10; + if (!gain) + gain = 1; + + uint32 health = me->GetHealth() + gain; + me->SetHealth(health > maxHealth ? maxHealth : health); + } + + uint32 maxMana = me->GetMaxPower(POWER_MANA); + if (maxMana && me->GetPower(POWER_MANA) < maxMana) + { + uint32 gain = maxMana / 10; + if (!gain) + gain = 1; + + uint32 mana = me->GetPower(POWER_MANA) + gain; + me->SetPower(POWER_MANA, mana > maxMana ? maxMana : mana); + } + } + + Unit* GetSkullTarget(Player* owner) const + { + if (!owner) + return nullptr; + + Group* group = owner->GetGroup(); + if (!group) + return nullptr; + + ObjectGuid guid = group->GetTargetIcon(SKULL_ICON_ID); + if (!guid) + return nullptr; + + Unit* target = ObjectAccessor::GetUnit(*me, guid); + return IsValidTankTarget(target) ? target : nullptr; + } + + Unit* FindGroupAttacker(Player* owner) const + { + if (!owner) + return nullptr; + + std::list groupUnits; + owner->GetPartyMembers(groupUnits); + + for (Unit* member : groupUnits) + { + if (!member || !member->IsAlive() || !member->IsInMap(owner)) + continue; + + for (Unit* attacker : member->getAttackers()) + if (IsValidTankTarget(attacker)) + return attacker; + } + + return nullptr; + } + + Unit* SelectPrimaryTarget(Player* owner) + { + if (!owner) + return nullptr; + + // Skull is the only explicit permission for the tank to initiate + // a pull. It also overrides the current primary target mid-fight. + if (Unit* skull = GetSkullTarget(owner)) + return skull; + + // Existing combat takes precedence over player selection. + if (Unit* victim = me->GetVictim()) + if (IsValidTankTarget(victim)) + return victim; + + // Pick up anything already attacking the group. + if (Unit* attacker = FindGroupAttacker(owner)) + return attacker; + + // The owner already attacking something is also a valid combat signal. + if (Unit* victim = owner->GetVictim()) + if (IsValidTankTarget(victim)) + return victim; + + // GetVictim() is not always populated for players. + if (owner->IsInCombat()) + if (Unit* selected = owner->GetSelectedUnit()) + if (IsValidTankTarget(selected)) + return selected; + + return nullptr; + } + + bool IsSafeAutoPickup(Unit* enemy) const + { + return IsValidTankTarget(enemy) + && me->GetDistance(enemy) <= AUTO_PICKUP_RADIUS + && me->IsWithinLOSInMap(enemy); + } + + void ForceTankThreat(Unit* enemy) + { + if (!IsSafeAutoPickup(enemy)) + return; + + ThreatManager& threat = enemy->GetThreatManager(); + + // Match the current highest threat, then exceed it by 35%. + // This gives us taunt-like recovery without permanent Fixate. + threat.MatchUnitThreatToHighestThreat(me); + + float current = threat.GetThreat(me, true); + float bonus = current * 0.35f; + if (bonus < 1.0f) + bonus = 1.0f; + + threat.AddThreat(me, bonus, nullptr, true, true); + } + + void CollectRelevantEnemyGuids(Player* owner, std::vector& targets) const + { + if (!owner) + return; + + auto addTarget = [this, &targets](Unit* enemy) + { + if (!IsSafeAutoPickup(enemy)) + return; + + ObjectGuid const guid = enemy->GetGUID(); + if (std::find(targets.begin(), targets.end(), guid) == targets.end()) + targets.push_back(guid); + }; + + std::list groupUnits; + owner->GetPartyMembers(groupUnits); + + for (Unit* member : groupUnits) + { + if (!member || !member->IsAlive() || !member->IsInMap(owner)) + continue; + + for (Unit* attacker : member->getAttackers()) + addTarget(attacker); + } + } + + void RecoverGroupThreat(Player* owner) + { + if (!owner || !IsActivelyEngagedInCombat()) + return; + + if (Unit* victim = me->GetVictim()) + ForceTankThreat(victim); + + std::vector targets; + CollectRelevantEnemyGuids(owner, targets); + + for (ObjectGuid const& guid : targets) + { + if (Unit* enemy = ObjectAccessor::GetUnit(*me, guid)) + ForceTankThreat(enemy); + } + } + + void DoTankAoeDamage(Player* owner) + { + if (!owner || !IsActivelyEngagedInCombat()) + return; + + uint32 damage = uint32(me->getLevel()) * 2u; + if (!damage) + damage = 1; + + std::vector targets; + CollectRelevantEnemyGuids(owner, targets); + + for (ObjectGuid const& guid : targets) + { + Unit* enemy = ObjectAccessor::GetUnit(*me, guid); + if (!IsSafeAutoPickup(enemy)) + continue; + + Unit::DealDamage( + me, + enemy, + damage, + 0, + DIRECT_DAMAGE, + SPELL_SCHOOL_MASK_NORMAL, + nullptr, + false); + } + } + + void InitializeAI() override + { + ScriptedAI::InitializeAI(); + + TempSummon* summon = me->ToTempSummon(); + if (!summon) + { + me->DespawnOrUnsummon(); + return; + } + + Unit* summoner = summon->GetSummoner(); + Player* owner = summoner ? summoner->ToPlayer() : nullptr; + if (!owner) + { + me->DespawnOrUnsummon(); + return; + } + + ownerGuid = owner->GetGUID(); + + me->SetOwnerGUID(ownerGuid); + me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED); + me->SetFaction(owner->GetFaction()); + me->SetLevel(owner->getLevel()); + me->UpdateLevelDependantStats(); + + static constexpr uint32 HORDE_ADVENTURER_MODELS[] = + { + 30063, + 30064, + 30065, + 30066 + }; + + me->SetDisplayId(HORDE_ADVENTURER_MODELS[urand(0, 3)], true); + me->SetReactState(REACT_DEFENSIVE); + + FollowOwner(owner); + + TC_LOG_INFO( + "scripts", + "[VianoTank] initialized entry=%u owner='%s' level=%u health=%u armor=%u", + me->GetEntry(), + owner->GetName().c_str(), + uint32(me->getLevel()), + me->GetMaxHealth(), + me->GetArmor()); + } + + void Reset() override + { + updateTimer = 0; + recoveryTimer = 1000; + aoeDamageTimer = 3000; + + if (Player* owner = GetOwnerPlayer()) + if (!IsActivelyEngagedInCombat()) + FollowOwner(owner); + } + + void MoveInLineOfSight(Unit* /*who*/) override + { + // No autonomous proximity pulls. + } + + void UpdateAI(uint32 diff) override + { + HandleOutOfCombatRecovery(diff); + + if (aoeDamageTimer > diff) + aoeDamageTimer -= diff; + else + aoeDamageTimer = 0; + + if (updateTimer > diff) + { + updateTimer -= diff; + + if (me->GetVictim()) + DoMeleeAttackIfReady(); + + return; + } + + updateTimer = TANK_UPDATE_INTERVAL; + + Player* owner = GetOwnerPlayer(); + if (!owner || !owner->IsInWorld() || !me->IsInMap(owner)) + { + me->DespawnOrUnsummon(); + return; + } + + if (me->getLevel() != owner->getLevel()) + { + me->SetLevel(owner->getLevel()); + me->UpdateLevelDependantStats(); + } + + bool engaged = IsActivelyEngagedInCombat(); + + // Recovery teleport is strictly out-of-combat. + if (!engaged && me->GetDistance(owner) > TELEPORT_DISTANCE) + { + me->GetMotionMaster()->Clear(); + + me->NearTeleportTo( + owner->GetPositionX() + 1.5f, + owner->GetPositionY(), + owner->GetPositionZ(), + owner->GetOrientation()); + + FollowOwner(owner); + return; + } + + Unit* skullTarget = GetSkullTarget(owner); + bool const explicitSkullPull = skullTarget && !engaged; + Unit* desiredTarget = skullTarget ? skullTarget : SelectPrimaryTarget(owner); + + if (desiredTarget && me->GetVictim() != desiredTarget) + { + if (explicitSkullPull) + { + if (!me->IsWithinLOSInMap(desiredTarget)) + { + FollowOwner(owner); + return; + } + + me->SetInCombatWith(desiredTarget); + desiredTarget->SetInCombatWith(me); + } + + AttackStart(desiredTarget); + } + + engaged = IsActivelyEngagedInCombat(); + + if (engaged) + { + RecoverGroupThreat(owner); + + if (aoeDamageTimer == 0) + { + aoeDamageTimer = AOE_DAMAGE_INTERVAL; + DoTankAoeDamage(owner); + } + + if (me->GetVictim()) + DoMeleeAttackIfReady(); + } + else + FollowOwner(owner); + } + }; + + CreatureAI* GetAI(Creature* creature) const override + { + return new npc_horde_adventurer_tankAI(creature); + } + }; + } void AddSC_viano_adventurers() { using namespace VianoAdventurers; new npc_horde_adventurer_melee(); + new npc_horde_adventurer_tank(); } diff --git a/src/server/scripts/Custom/viano_adventurers.cpp.bak-player-controlled-20261001-010613 b/src/server/scripts/Custom/viano_adventurers.cpp.bak-player-controlled-20261001-010613 new file mode 100644 index 0000000000..a3032d11e5 --- /dev/null +++ b/src/server/scripts/Custom/viano_adventurers.cpp.bak-player-controlled-20261001-010613 @@ -0,0 +1,516 @@ +/* + * Viano custom dungeon adventurers + * Initial melee companion implementation. + */ + +#include "ScriptMgr.h" +#include "ScriptedCreature.h" +#include "ObjectAccessor.h" +#include "Player.h" +#include "TemporarySummon.h" +#include "MotionMaster.h" +#include "Log.h" + +namespace VianoAdventurers +{ + static constexpr float FOLLOW_DISTANCE = 2.5f; + static constexpr float FOLLOW_ANGLE = 3.14159265f; + static constexpr float TELEPORT_DISTANCE = 45.0f; + static constexpr uint32 UPDATE_INTERVAL = 500; + static constexpr uint32 DIAGNOSTIC_INTERVAL = 2000; + + // VIANO_DD_PROFILE_V1_5 + enum AdventurerSpells : uint32 + { + SPELL_ADVENTURER_MORTAL_STRIKE = 65926, + SPELL_ADVENTURER_OVERPOWER = 65924, + SPELL_ADVENTURER_BLADESTORM = 65947 + }; + + enum AdventurerEvents : uint32 + { + EVENT_ADVENTURER_MORTAL_STRIKE = 1, + EVENT_ADVENTURER_OVERPOWER, + EVENT_ADVENTURER_BLADESTORM + }; + + // VIANO_AI_DIAGNOSTICS_V1 + + class npc_horde_adventurer_melee : public CreatureScript + { + public: + npc_horde_adventurer_melee() : CreatureScript("npc_horde_adventurer_melee") { } + + struct npc_horde_adventurer_meleeAI : public ScriptedAI + { + npc_horde_adventurer_meleeAI(Creature* creature) : ScriptedAI(creature) { } + + ObjectGuid ownerGuid; + EventMap combatEvents; + uint32 updateTimer = 0; + uint32 diagnosticTimer = 0; + uint32 recoveryTimer = 1000; + Player* GetOwnerPlayer() const + { + if (!ownerGuid) + return nullptr; + + return ObjectAccessor::GetPlayer(*me, ownerGuid); + } + + void FollowOwner(Player* owner) + { + if (!owner) + return; + + me->GetMotionMaster()->MoveFollow(owner, FOLLOW_DISTANCE, FOLLOW_ANGLE); + } + + void ResetCombatEvents() + { + combatEvents.Reset(); + + combatEvents.ScheduleEvent(EVENT_ADVENTURER_MORTAL_STRIKE, 2 * IN_MILLISECONDS); + combatEvents.ScheduleEvent(EVENT_ADVENTURER_OVERPOWER, 5 * IN_MILLISECONDS); + combatEvents.ScheduleEvent(EVENT_ADVENTURER_BLADESTORM, 12 * IN_MILLISECONDS); + } + + void HandleCombatActions(uint32 diff) + { + Unit* victim = me->GetVictim(); + if (!victim || !victim->IsAlive()) + return; + + combatEvents.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + if (uint32 eventId = combatEvents.ExecuteEvent()) + { + switch (eventId) + { + case EVENT_ADVENTURER_MORTAL_STRIKE: + TC_LOG_INFO("scripts", "[VianoAI] cast Mortal Strike: target='%s'(%u)", + UnitName(victim), UnitEntry(victim)); + DoCastVictim(SPELL_ADVENTURER_MORTAL_STRIKE); + combatEvents.ScheduleEvent(EVENT_ADVENTURER_MORTAL_STRIKE, + urand(6 * IN_MILLISECONDS, 8 * IN_MILLISECONDS)); + return; + + case EVENT_ADVENTURER_OVERPOWER: + TC_LOG_INFO("scripts", "[VianoAI] cast Overpower: target='%s'(%u)", + UnitName(victim), UnitEntry(victim)); + DoCastVictim(SPELL_ADVENTURER_OVERPOWER); + combatEvents.ScheduleEvent(EVENT_ADVENTURER_OVERPOWER, + urand(10 * IN_MILLISECONDS, 14 * IN_MILLISECONDS)); + return; + + case EVENT_ADVENTURER_BLADESTORM: + TC_LOG_INFO("scripts", "[VianoAI] cast Bladestorm: target='%s'(%u)", + UnitName(victim), UnitEntry(victim)); + DoCastVictim(SPELL_ADVENTURER_BLADESTORM); + combatEvents.ScheduleEvent(EVENT_ADVENTURER_BLADESTORM, + urand(30 * IN_MILLISECONDS, 45 * IN_MILLISECONDS)); + return; + + default: + break; + } + } + + DoMeleeAttackIfReady(); + } + void HandleOutOfCombatRecovery(uint32 diff) + { + if (!me->IsAlive()) + { + recoveryTimer = 1000; + return; + } + + // Keine künstliche Regeneration während eines Kampfes. + if (me->IsInCombat()) + { + recoveryTimer = 1000; + return; + } + + if (recoveryTimer > diff) + { + recoveryTimer -= diff; + return; + } + + recoveryTimer = 1000; + + // 10 % der maximalen Lebenspunkte pro Sekunde. + uint32 maxHealth = me->GetMaxHealth(); + + if (me->GetHealth() < maxHealth) + { + uint32 healthGain = maxHealth / 10; + + if (healthGain == 0) + healthGain = 1; + + uint32 newHealth = me->GetHealth() + healthGain; + + if (newHealth > maxHealth) + newHealth = maxHealth; + + me->SetHealth(newHealth); + } + + // 10 % des maximalen Manas pro Sekunde. + uint32 maxMana = me->GetMaxPower(POWER_MANA); + + if (maxMana > 0 && me->GetPower(POWER_MANA) < maxMana) + { + uint32 manaGain = maxMana / 10; + + if (manaGain == 0) + manaGain = 1; + + uint32 newMana = me->GetPower(POWER_MANA) + manaGain; + + if (newMana > maxMana) + newMana = maxMana; + + me->SetPower(POWER_MANA, newMana); + } +} + + void InitializeAI() override + { + ScriptedAI::InitializeAI(); + + TempSummon* summon = me->ToTempSummon(); + if (!summon) + { + me->DespawnOrUnsummon(); + return; + } + + Unit* summoner = summon->GetSummoner(); + Player* owner = summoner ? summoner->ToPlayer() : nullptr; + if (!owner) + { + me->DespawnOrUnsummon(); + return; + } + + ownerGuid = owner->GetGUID(); + + me->SetOwnerGUID(ownerGuid); + me->SetFaction(owner->GetFaction()); + me->SetLevel(owner->getLevel()); + me->UpdateLevelDependantStats(); + + + static constexpr uint32 HORDE_ADVENTURER_MODELS[] = + { + 30063, + 30064, + 30065, + 30066 + }; + uint32 displayId = HORDE_ADVENTURER_MODELS[urand(0, 3)]; + + me->SetDisplayId(displayId, true); + + TC_LOG_INFO("scripts","[VianoAI] Horde adventurer display selected: %u", displayId); + + // REACT_PASSIVE lets AttackStart() briefly set a victim, but the + // core subsequently drops that victim again. We already suppress + // autonomous proximity aggro in MoveInLineOfSight(), so DEFENSIVE + // keeps an explicitly assigned assist target without making the + // adventurer independently pull nearby creatures. + me->SetReactState(REACT_DEFENSIVE); + + TC_LOG_INFO("scripts", + "[VianoAI] initialized me='%s' entry=%u owner='%s' ownerGuid=%s meFaction=%u ownerFaction=%u level=%u", + me->GetName().c_str(), me->GetEntry(), owner->GetName().c_str(), ownerGuid.ToString().c_str(), + me->GetFaction(), owner->GetFaction(), uint32(me->getLevel())); + + ResetCombatEvents(); + FollowOwner(owner); + } + + void Reset() override + { + ResetCombatEvents(); + + if (Player* owner = GetOwnerPlayer()) + FollowOwner(owner); + } + + void MoveInLineOfSight(Unit* /*who*/) override + { + } + + char const* UnitName(Unit* unit) const + { + return unit ? unit->GetName().c_str() : ""; + } + + uint32 UnitEntry(Unit* unit) const + { + return unit ? unit->GetEntry() : 0; + } + + bool IsValidCandidate(Unit* unit) const + { + return unit && unit->IsAlive() && me->IsValidAttackTarget(unit); + } + + void LogDiagnostics(Player* owner) + { + if (!owner) + return; + + Unit* ownerVictim = owner->GetVictim(); + Unit* selected = owner->GetSelectedUnit(); + Unit* meVictim = me->GetVictim(); + + Unit* firstOwnerAttacker = nullptr; + for (Unit* attacker : owner->getAttackers()) + { + if (attacker) + { + firstOwnerAttacker = attacker; + break; + } + } + + Unit* firstMeAttacker = nullptr; + for (Unit* attacker : me->getAttackers()) + { + if (attacker) + { + firstMeAttacker = attacker; + break; + } + } + + TC_LOG_INFO("scripts", + "[VianoAI] me='%s' entry=%u lvl=%u faction=%u react=%u alive=%u combat=%u hp=%u/%u victim='%s'(%u) attackers=%u distance=%.2f", + me->GetName().c_str(), me->GetEntry(), uint32(me->getLevel()), me->GetFaction(), uint32(me->GetReactState()), + me->IsAlive() ? 1u : 0u, me->IsInCombat() ? 1u : 0u, me->GetHealth(), me->GetMaxHealth(), + UnitName(meVictim), UnitEntry(meVictim), uint32(me->getAttackers().size()), me->GetDistance(owner)); + + TC_LOG_INFO("scripts", + "[VianoAI] owner='%s' lvl=%u faction=%u alive=%u combat=%u victim='%s'(%u) selected='%s'(%u) attackers=%u", + owner->GetName().c_str(), uint32(owner->getLevel()), owner->GetFaction(), owner->IsAlive() ? 1u : 0u, + owner->IsInCombat() ? 1u : 0u, UnitName(ownerVictim), UnitEntry(ownerVictim), UnitName(selected), UnitEntry(selected), + uint32(owner->getAttackers().size())); + + if (ownerVictim) + TC_LOG_INFO("scripts", "[VianoAI] ownerVictim='%s' entry=%u faction=%u validForBot=%u botCanAttack=%u ownerCanAttack=%u victim='%s'(%u)", + ownerVictim->GetName().c_str(), ownerVictim->GetEntry(), ownerVictim->GetFaction(), IsValidCandidate(ownerVictim) ? 1u : 0u, + me->IsValidAttackTarget(ownerVictim) ? 1u : 0u, owner->IsValidAttackTarget(ownerVictim) ? 1u : 0u, + UnitName(ownerVictim->GetVictim()), UnitEntry(ownerVictim->GetVictim())); + + if (selected) + TC_LOG_INFO("scripts", "[VianoAI] selected='%s' entry=%u faction=%u validForBot=%u botCanAttack=%u ownerCanAttack=%u victim='%s'(%u)", + selected->GetName().c_str(), selected->GetEntry(), selected->GetFaction(), IsValidCandidate(selected) ? 1u : 0u, + me->IsValidAttackTarget(selected) ? 1u : 0u, owner->IsValidAttackTarget(selected) ? 1u : 0u, + UnitName(selected->GetVictim()), UnitEntry(selected->GetVictim())); + + if (firstOwnerAttacker) + TC_LOG_INFO("scripts", "[VianoAI] firstOwnerAttacker='%s' entry=%u faction=%u validForBot=%u botCanAttack=%u ownerCanAttack=%u victim='%s'(%u)", + firstOwnerAttacker->GetName().c_str(), firstOwnerAttacker->GetEntry(), firstOwnerAttacker->GetFaction(), IsValidCandidate(firstOwnerAttacker) ? 1u : 0u, + me->IsValidAttackTarget(firstOwnerAttacker) ? 1u : 0u, owner->IsValidAttackTarget(firstOwnerAttacker) ? 1u : 0u, + UnitName(firstOwnerAttacker->GetVictim()), UnitEntry(firstOwnerAttacker->GetVictim())); + + if (firstMeAttacker) + TC_LOG_INFO("scripts", "[VianoAI] firstMeAttacker='%s' entry=%u faction=%u victim='%s'(%u)", + firstMeAttacker->GetName().c_str(), firstMeAttacker->GetEntry(), firstMeAttacker->GetFaction(), + UnitName(firstMeAttacker->GetVictim()), UnitEntry(firstMeAttacker->GetVictim())); + } + + bool IsActivelyEngagedInCombat() const + { + // IsInCombat() alone is intentionally not enough here: the Core + // may keep the combat flag set after the bot has no live opponent. + if (Unit* victim = me->GetVictim()) + if (IsValidCandidate(victim)) + return true; + + for (Unit* attacker : me->getAttackers()) + if (IsValidCandidate(attacker)) + return true; + + return false; + } + + Unit* SelectGroupCombatTarget(Player* owner) + { + if (!owner) + return nullptr; + + std::list partyMembers; + owner->GetPartyMembers(partyMembers); + + // In Vcore GetPartyMembers() returns living members of the same + // party/subgroup on the same map, plus their living guardian pets. + // An enemy attacking one of them is therefore already part of the + // group's current fight. + for (Unit* member : partyMembers) + { + if (!member || !member->IsAlive()) + continue; + + for (Unit* attacker : member->getAttackers()) + if (IsValidCandidate(attacker)) + return attacker; + } + + return nullptr; + } + + Unit* SelectAssistTarget(Player* owner) + { + if (!owner) + return nullptr; + + // 1. An active owner victim is an explicit target instruction. + if (Unit* target = owner->GetVictim()) + if (IsValidCandidate(target)) + return target; + + // During an already running fight, an explicitly selected living + // hostile target may also be treated as an instruction. Merely + // selecting an enemy out of combat still must not pull it. + if (owner->IsInCombat()) + { + if (Unit* target = owner->GetSelectedUnit()) + if (IsValidCandidate(target)) + return target; + } + + // 2. Keep the bot's own valid current victim. + if (Unit* target = me->GetVictim()) + if (IsValidCandidate(target)) + return target; + + // 3. Continue the group's fight even if the owner is a healer and + // currently targets a friendly tank or another party member. + if (Unit* target = SelectGroupCombatTarget(owner)) + return target; + + // 4. Finally, defend the bot itself if something is attacking it. + for (Unit* attacker : me->getAttackers()) + if (IsValidCandidate(attacker)) + return attacker; + + return nullptr; + } + + void UpdateAI(uint32 diff) override + { + HandleOutOfCombatRecovery(diff); + if (diagnosticTimer > diff) + diagnosticTimer -= diff; + else + diagnosticTimer = 0; + + if (updateTimer > diff) + { + updateTimer -= diff; + + // VIANO_TARGET_CONTROL_V1_4 + // Keep the explicitly assigned victim. UpdateVictim() may + // select another creature from the threat list. + if (me->GetVictim()) + HandleCombatActions(diff); + + return; + } + + updateTimer = UPDATE_INTERVAL; + + Player* owner = GetOwnerPlayer(); + if (!owner || !owner->IsInWorld() || !me->IsInMap(owner)) + { + TC_LOG_INFO("scripts", "[VianoAI] despawn: owner invalid or not on same map (ownerFound=%u)", owner ? 1u : 0u); + me->DespawnOrUnsummon(); + return; + } + + if (diagnosticTimer == 0) + { + diagnosticTimer = DIAGNOSTIC_INTERVAL; + LogDiagnostics(owner); + } + + if (me->getLevel() != owner->getLevel()) + { + me->SetLevel(owner->getLevel()); + me->UpdateLevelDependantStats(); + } + + if (me->GetDistance(owner) > TELEPORT_DISTANCE && !IsActivelyEngagedInCombat()) + { + // Keep the recovery teleport for geometry/jump problems, even + // if the group is already fighting elsewhere. Only the bot's + // own active engagement blocks teleporting. + me->GetMotionMaster()->Clear(); + + me->NearTeleportTo( + owner->GetPositionX() + 1.5f, + owner->GetPositionY(), + owner->GetPositionZ(), + owner->GetOrientation() + ); + + FollowOwner(owner); + return; + } + + Unit* desiredTarget = SelectAssistTarget(owner); + + if (desiredTarget) + { + if (me->GetVictim() != desiredTarget) + { + TC_LOG_INFO("scripts", + "[VianoAI] AttackStart requested: target='%s' entry=%u targetFaction=%u valid=%u meCombatBefore=%u", + desiredTarget->GetName().c_str(), desiredTarget->GetEntry(), desiredTarget->GetFaction(), + me->IsValidAttackTarget(desiredTarget) ? 1u : 0u, me->IsInCombat() ? 1u : 0u); + + AttackStart(desiredTarget); + + Unit* afterVictim = me->GetVictim(); + TC_LOG_INFO("scripts", "[VianoAI] AttackStart result: meCombatAfter=%u victim='%s'(%u)", + me->IsInCombat() ? 1u : 0u, UnitName(afterVictim), UnitEntry(afterVictim)); + } + } + else if (!IsActivelyEngagedInCombat()) + { + // A stale Core combat flag must not prevent recovery/following. + // We deliberately do not clear the flag here; OOC regeneration + // will be handled as a separate change. + FollowOwner(owner); + } + + // VIANO_TARGET_CONTROL_V1_4 + // Keep the explicitly assigned victim. UpdateVictim() may + // select another creature from the threat list. + if (me->GetVictim()) + HandleCombatActions(diff); + } + }; + + CreatureAI* GetAI(Creature* creature) const override + { + return new npc_horde_adventurer_meleeAI(creature); + } + }; +} + +void AddSC_viano_adventurers() +{ + using namespace VianoAdventurers; + new npc_horde_adventurer_melee(); +} diff --git a/src/server/scripts/Custom/viano_adventurers.cpp.bak-tank-attacker-snapshot-20261001-114726 b/src/server/scripts/Custom/viano_adventurers.cpp.bak-tank-attacker-snapshot-20261001-114726 new file mode 100644 index 0000000000..5d668ad151 --- /dev/null +++ b/src/server/scripts/Custom/viano_adventurers.cpp.bak-tank-attacker-snapshot-20261001-114726 @@ -0,0 +1,956 @@ +/* + * Viano custom dungeon adventurers + * Initial melee companion implementation. + */ + +#include "ScriptMgr.h" +#include "ScriptedCreature.h" +#include "ObjectAccessor.h" +#include "Player.h" +#include "Group.h" +#include "TemporarySummon.h" +#include "MotionMaster.h" +#include "Log.h" + +namespace VianoAdventurers +{ + static constexpr float FOLLOW_DISTANCE = 2.5f; + static constexpr float FOLLOW_ANGLE = 3.14159265f; + static constexpr float TELEPORT_DISTANCE = 45.0f; + static constexpr uint32 UPDATE_INTERVAL = 500; + static constexpr uint32 DIAGNOSTIC_INTERVAL = 2000; + + // VIANO_DD_PROFILE_V1_5 + enum AdventurerSpells : uint32 + { + SPELL_ADVENTURER_MORTAL_STRIKE = 65926, + SPELL_ADVENTURER_OVERPOWER = 65924, + SPELL_ADVENTURER_BLADESTORM = 65947 + }; + + enum AdventurerEvents : uint32 + { + EVENT_ADVENTURER_MORTAL_STRIKE = 1, + EVENT_ADVENTURER_OVERPOWER, + EVENT_ADVENTURER_BLADESTORM + }; + + // VIANO_AI_DIAGNOSTICS_V1 + + class npc_horde_adventurer_melee : public CreatureScript + { + public: + npc_horde_adventurer_melee() : CreatureScript("npc_horde_adventurer_melee") { } + + struct npc_horde_adventurer_meleeAI : public ScriptedAI + { + npc_horde_adventurer_meleeAI(Creature* creature) : ScriptedAI(creature) { } + + ObjectGuid ownerGuid; + EventMap combatEvents; + uint32 updateTimer = 0; + uint32 diagnosticTimer = 0; + uint32 recoveryTimer = 1000; + Player* GetOwnerPlayer() const + { + if (!ownerGuid) + return nullptr; + + return ObjectAccessor::GetPlayer(*me, ownerGuid); + } + + void FollowOwner(Player* owner) + { + if (!owner) + return; + + me->GetMotionMaster()->MoveFollow(owner, FOLLOW_DISTANCE, FOLLOW_ANGLE); + } + + void ResetCombatEvents() + { + combatEvents.Reset(); + + combatEvents.ScheduleEvent(EVENT_ADVENTURER_MORTAL_STRIKE, 2 * IN_MILLISECONDS); + combatEvents.ScheduleEvent(EVENT_ADVENTURER_OVERPOWER, 5 * IN_MILLISECONDS); + combatEvents.ScheduleEvent(EVENT_ADVENTURER_BLADESTORM, 12 * IN_MILLISECONDS); + } + + void HandleCombatActions(uint32 diff) + { + Unit* victim = me->GetVictim(); + if (!victim || !victim->IsAlive()) + return; + + combatEvents.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + if (uint32 eventId = combatEvents.ExecuteEvent()) + { + switch (eventId) + { + case EVENT_ADVENTURER_MORTAL_STRIKE: + TC_LOG_INFO("scripts", "[VianoAI] cast Mortal Strike: target='%s'(%u)", + UnitName(victim), UnitEntry(victim)); + DoCastVictim(SPELL_ADVENTURER_MORTAL_STRIKE); + combatEvents.ScheduleEvent(EVENT_ADVENTURER_MORTAL_STRIKE, + urand(6 * IN_MILLISECONDS, 8 * IN_MILLISECONDS)); + return; + + case EVENT_ADVENTURER_OVERPOWER: + TC_LOG_INFO("scripts", "[VianoAI] cast Overpower: target='%s'(%u)", + UnitName(victim), UnitEntry(victim)); + DoCastVictim(SPELL_ADVENTURER_OVERPOWER); + combatEvents.ScheduleEvent(EVENT_ADVENTURER_OVERPOWER, + urand(10 * IN_MILLISECONDS, 14 * IN_MILLISECONDS)); + return; + + case EVENT_ADVENTURER_BLADESTORM: + TC_LOG_INFO("scripts", "[VianoAI] cast Bladestorm: target='%s'(%u)", + UnitName(victim), UnitEntry(victim)); + DoCastVictim(SPELL_ADVENTURER_BLADESTORM); + combatEvents.ScheduleEvent(EVENT_ADVENTURER_BLADESTORM, + urand(30 * IN_MILLISECONDS, 45 * IN_MILLISECONDS)); + return; + + default: + break; + } + } + + DoMeleeAttackIfReady(); + } + void HandleOutOfCombatRecovery(uint32 diff) + { + if (!me->IsAlive()) + { + recoveryTimer = 1000; + return; + } + + // Keine künstliche Regeneration während eines Kampfes. + if (me->IsInCombat()) + { + recoveryTimer = 1000; + return; + } + + if (recoveryTimer > diff) + { + recoveryTimer -= diff; + return; + } + + recoveryTimer = 1000; + + // 10 % der maximalen Lebenspunkte pro Sekunde. + uint32 maxHealth = me->GetMaxHealth(); + + if (me->GetHealth() < maxHealth) + { + uint32 healthGain = maxHealth / 10; + + if (healthGain == 0) + healthGain = 1; + + uint32 newHealth = me->GetHealth() + healthGain; + + if (newHealth > maxHealth) + newHealth = maxHealth; + + me->SetHealth(newHealth); + } + + // 10 % des maximalen Manas pro Sekunde. + uint32 maxMana = me->GetMaxPower(POWER_MANA); + + if (maxMana > 0 && me->GetPower(POWER_MANA) < maxMana) + { + uint32 manaGain = maxMana / 10; + + if (manaGain == 0) + manaGain = 1; + + uint32 newMana = me->GetPower(POWER_MANA) + manaGain; + + if (newMana > maxMana) + newMana = maxMana; + + me->SetPower(POWER_MANA, newMana); + } +} + + void InitializeAI() override + { + ScriptedAI::InitializeAI(); + + TempSummon* summon = me->ToTempSummon(); + if (!summon) + { + me->DespawnOrUnsummon(); + return; + } + + Unit* summoner = summon->GetSummoner(); + Player* owner = summoner ? summoner->ToPlayer() : nullptr; + if (!owner) + { + me->DespawnOrUnsummon(); + return; + } + + ownerGuid = owner->GetGUID(); + + me->SetOwnerGUID(ownerGuid); + me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED); + me->SetFaction(owner->GetFaction()); + me->SetLevel(owner->getLevel()); + me->UpdateLevelDependantStats(); + + + static constexpr uint32 HORDE_ADVENTURER_MODELS[] = + { + 30063, + 30064, + 30065, + 30066 + }; + uint32 displayId = HORDE_ADVENTURER_MODELS[urand(0, 3)]; + + me->SetDisplayId(displayId, true); + + TC_LOG_INFO("scripts","[VianoAI] Horde adventurer display selected: %u", displayId); + + // REACT_PASSIVE lets AttackStart() briefly set a victim, but the + // core subsequently drops that victim again. We already suppress + // autonomous proximity aggro in MoveInLineOfSight(), so DEFENSIVE + // keeps an explicitly assigned assist target without making the + // adventurer independently pull nearby creatures. + me->SetReactState(REACT_DEFENSIVE); + + TC_LOG_INFO("scripts", + "[VianoAI] initialized me='%s' entry=%u owner='%s' ownerGuid=%s meFaction=%u ownerFaction=%u level=%u", + me->GetName().c_str(), me->GetEntry(), owner->GetName().c_str(), ownerGuid.ToString().c_str(), + me->GetFaction(), owner->GetFaction(), uint32(me->getLevel())); + + ResetCombatEvents(); + FollowOwner(owner); + } + + void Reset() override + { + ResetCombatEvents(); + + if (Player* owner = GetOwnerPlayer()) + FollowOwner(owner); + } + + void MoveInLineOfSight(Unit* /*who*/) override + { + } + + char const* UnitName(Unit* unit) const + { + return unit ? unit->GetName().c_str() : ""; + } + + uint32 UnitEntry(Unit* unit) const + { + return unit ? unit->GetEntry() : 0; + } + + bool IsValidCandidate(Unit* unit) const + { + return unit && unit->IsAlive() && me->IsValidAttackTarget(unit); + } + + void LogDiagnostics(Player* owner) + { + if (!owner) + return; + + Unit* ownerVictim = owner->GetVictim(); + Unit* selected = owner->GetSelectedUnit(); + Unit* meVictim = me->GetVictim(); + + Unit* firstOwnerAttacker = nullptr; + for (Unit* attacker : owner->getAttackers()) + { + if (attacker) + { + firstOwnerAttacker = attacker; + break; + } + } + + Unit* firstMeAttacker = nullptr; + for (Unit* attacker : me->getAttackers()) + { + if (attacker) + { + firstMeAttacker = attacker; + break; + } + } + + TC_LOG_INFO("scripts", + "[VianoAI] me='%s' entry=%u lvl=%u faction=%u react=%u alive=%u combat=%u hp=%u/%u victim='%s'(%u) attackers=%u distance=%.2f", + me->GetName().c_str(), me->GetEntry(), uint32(me->getLevel()), me->GetFaction(), uint32(me->GetReactState()), + me->IsAlive() ? 1u : 0u, me->IsInCombat() ? 1u : 0u, me->GetHealth(), me->GetMaxHealth(), + UnitName(meVictim), UnitEntry(meVictim), uint32(me->getAttackers().size()), me->GetDistance(owner)); + + TC_LOG_INFO("scripts", + "[VianoAI] owner='%s' lvl=%u faction=%u alive=%u combat=%u victim='%s'(%u) selected='%s'(%u) attackers=%u", + owner->GetName().c_str(), uint32(owner->getLevel()), owner->GetFaction(), owner->IsAlive() ? 1u : 0u, + owner->IsInCombat() ? 1u : 0u, UnitName(ownerVictim), UnitEntry(ownerVictim), UnitName(selected), UnitEntry(selected), + uint32(owner->getAttackers().size())); + + if (ownerVictim) + TC_LOG_INFO("scripts", "[VianoAI] ownerVictim='%s' entry=%u faction=%u validForBot=%u botCanAttack=%u ownerCanAttack=%u victim='%s'(%u)", + ownerVictim->GetName().c_str(), ownerVictim->GetEntry(), ownerVictim->GetFaction(), IsValidCandidate(ownerVictim) ? 1u : 0u, + me->IsValidAttackTarget(ownerVictim) ? 1u : 0u, owner->IsValidAttackTarget(ownerVictim) ? 1u : 0u, + UnitName(ownerVictim->GetVictim()), UnitEntry(ownerVictim->GetVictim())); + + if (selected) + TC_LOG_INFO("scripts", "[VianoAI] selected='%s' entry=%u faction=%u validForBot=%u botCanAttack=%u ownerCanAttack=%u victim='%s'(%u)", + selected->GetName().c_str(), selected->GetEntry(), selected->GetFaction(), IsValidCandidate(selected) ? 1u : 0u, + me->IsValidAttackTarget(selected) ? 1u : 0u, owner->IsValidAttackTarget(selected) ? 1u : 0u, + UnitName(selected->GetVictim()), UnitEntry(selected->GetVictim())); + + if (firstOwnerAttacker) + TC_LOG_INFO("scripts", "[VianoAI] firstOwnerAttacker='%s' entry=%u faction=%u validForBot=%u botCanAttack=%u ownerCanAttack=%u victim='%s'(%u)", + firstOwnerAttacker->GetName().c_str(), firstOwnerAttacker->GetEntry(), firstOwnerAttacker->GetFaction(), IsValidCandidate(firstOwnerAttacker) ? 1u : 0u, + me->IsValidAttackTarget(firstOwnerAttacker) ? 1u : 0u, owner->IsValidAttackTarget(firstOwnerAttacker) ? 1u : 0u, + UnitName(firstOwnerAttacker->GetVictim()), UnitEntry(firstOwnerAttacker->GetVictim())); + + if (firstMeAttacker) + TC_LOG_INFO("scripts", "[VianoAI] firstMeAttacker='%s' entry=%u faction=%u victim='%s'(%u)", + firstMeAttacker->GetName().c_str(), firstMeAttacker->GetEntry(), firstMeAttacker->GetFaction(), + UnitName(firstMeAttacker->GetVictim()), UnitEntry(firstMeAttacker->GetVictim())); + } + + bool IsActivelyEngagedInCombat() const + { + // IsInCombat() alone is intentionally not enough here: the Core + // may keep the combat flag set after the bot has no live opponent. + if (Unit* victim = me->GetVictim()) + if (IsValidCandidate(victim)) + return true; + + for (Unit* attacker : me->getAttackers()) + if (IsValidCandidate(attacker)) + return true; + + return false; + } + + Unit* SelectGroupCombatTarget(Player* owner) + { + if (!owner) + return nullptr; + + std::list partyMembers; + owner->GetPartyMembers(partyMembers); + + // In Vcore GetPartyMembers() returns living members of the same + // party/subgroup on the same map, plus their living guardian pets. + // An enemy attacking one of them is therefore already part of the + // group's current fight. + for (Unit* member : partyMembers) + { + if (!member || !member->IsAlive()) + continue; + + for (Unit* attacker : member->getAttackers()) + if (IsValidCandidate(attacker)) + return attacker; + } + + return nullptr; + } + + Unit* SelectAssistTarget(Player* owner) + { + if (!owner) + return nullptr; + + // 1. An active owner victim is an explicit target instruction. + if (Unit* target = owner->GetVictim()) + if (IsValidCandidate(target)) + return target; + + // During an already running fight, an explicitly selected living + // hostile target may also be treated as an instruction. Merely + // selecting an enemy out of combat still must not pull it. + if (owner->IsInCombat()) + { + if (Unit* target = owner->GetSelectedUnit()) + if (IsValidCandidate(target)) + return target; + } + + // 2. Keep the bot's own valid current victim. + if (Unit* target = me->GetVictim()) + if (IsValidCandidate(target)) + return target; + + // 3. Continue the group's fight even if the owner is a healer and + // currently targets a friendly tank or another party member. + if (Unit* target = SelectGroupCombatTarget(owner)) + return target; + + // 4. Finally, defend the bot itself if something is attacking it. + for (Unit* attacker : me->getAttackers()) + if (IsValidCandidate(attacker)) + return attacker; + + return nullptr; + } + + void UpdateAI(uint32 diff) override + { + HandleOutOfCombatRecovery(diff); + if (diagnosticTimer > diff) + diagnosticTimer -= diff; + else + diagnosticTimer = 0; + + if (updateTimer > diff) + { + updateTimer -= diff; + + // VIANO_TARGET_CONTROL_V1_4 + // Keep the explicitly assigned victim. UpdateVictim() may + // select another creature from the threat list. + if (me->GetVictim()) + HandleCombatActions(diff); + + return; + } + + updateTimer = UPDATE_INTERVAL; + + Player* owner = GetOwnerPlayer(); + if (!owner || !owner->IsInWorld() || !me->IsInMap(owner)) + { + TC_LOG_INFO("scripts", "[VianoAI] despawn: owner invalid or not on same map (ownerFound=%u)", owner ? 1u : 0u); + me->DespawnOrUnsummon(); + return; + } + + if (diagnosticTimer == 0) + { + diagnosticTimer = DIAGNOSTIC_INTERVAL; + LogDiagnostics(owner); + } + + if (me->getLevel() != owner->getLevel()) + { + me->SetLevel(owner->getLevel()); + me->UpdateLevelDependantStats(); + } + + if (me->GetDistance(owner) > TELEPORT_DISTANCE && !IsActivelyEngagedInCombat()) + { + // Keep the recovery teleport for geometry/jump problems, even + // if the group is already fighting elsewhere. Only the bot's + // own active engagement blocks teleporting. + me->GetMotionMaster()->Clear(); + + me->NearTeleportTo( + owner->GetPositionX() + 1.5f, + owner->GetPositionY(), + owner->GetPositionZ(), + owner->GetOrientation() + ); + + FollowOwner(owner); + return; + } + + Unit* desiredTarget = SelectAssistTarget(owner); + + if (desiredTarget) + { + if (me->GetVictim() != desiredTarget) + { + TC_LOG_INFO("scripts", + "[VianoAI] AttackStart requested: target='%s' entry=%u targetFaction=%u valid=%u meCombatBefore=%u", + desiredTarget->GetName().c_str(), desiredTarget->GetEntry(), desiredTarget->GetFaction(), + me->IsValidAttackTarget(desiredTarget) ? 1u : 0u, me->IsInCombat() ? 1u : 0u); + + AttackStart(desiredTarget); + + Unit* afterVictim = me->GetVictim(); + TC_LOG_INFO("scripts", "[VianoAI] AttackStart result: meCombatAfter=%u victim='%s'(%u)", + me->IsInCombat() ? 1u : 0u, UnitName(afterVictim), UnitEntry(afterVictim)); + } + } + else if (!IsActivelyEngagedInCombat()) + { + // A stale Core combat flag must not prevent recovery/following. + // We deliberately do not clear the flag here; OOC regeneration + // will be handled as a separate change. + FollowOwner(owner); + } + + // VIANO_TARGET_CONTROL_V1_4 + // Keep the explicitly assigned victim. UpdateVictim() may + // select another creature from the threat list. + if (me->GetVictim()) + HandleCombatActions(diff); + } + }; + + CreatureAI* GetAI(Creature* creature) const override + { + return new npc_horde_adventurer_meleeAI(creature); + } + }; + + // VIANO_TANK_V1 + class npc_horde_adventurer_tank : public CreatureScript + { + public: + npc_horde_adventurer_tank() : CreatureScript("npc_horde_adventurer_tank") { } + + struct npc_horde_adventurer_tankAI : public ScriptedAI + { + npc_horde_adventurer_tankAI(Creature* creature) : ScriptedAI(creature) { } + + static constexpr uint8 SKULL_ICON_ID = 7; + static constexpr float AUTO_PICKUP_RADIUS = 10.0f; + static constexpr uint32 TANK_UPDATE_INTERVAL = 500; + static constexpr uint32 AOE_DAMAGE_INTERVAL = 6000; + + ObjectGuid ownerGuid; + uint32 updateTimer = 0; + uint32 recoveryTimer = 1000; + uint32 aoeDamageTimer = 3000; + + Player* GetOwnerPlayer() const + { + if (!ownerGuid) + return nullptr; + + return ObjectAccessor::GetPlayer(*me, ownerGuid); + } + + bool IsValidTankTarget(Unit* unit) const + { + if (!unit || !unit->IsAlive()) + return false; + + // Dungeon-role bot: do not turn this into a PvP player hunter. + if (!unit->ToCreature()) + return false; + + return me->IsValidAttackTarget(unit); + } + + bool IsActivelyEngagedInCombat() const + { + if (Unit* victim = me->GetVictim()) + if (victim->IsAlive() && me->IsValidAttackTarget(victim)) + return true; + + for (Unit* attacker : me->getAttackers()) + if (attacker && attacker->IsAlive() && me->IsValidAttackTarget(attacker)) + return true; + + return false; + } + + void FollowOwner(Player* owner) + { + if (!owner) + return; + + me->GetMotionMaster()->MoveFollow(owner, FOLLOW_DISTANCE, FOLLOW_ANGLE); + } + + void HandleOutOfCombatRecovery(uint32 diff) + { + if (!me->IsAlive() || IsActivelyEngagedInCombat()) + { + recoveryTimer = 1000; + return; + } + + if (recoveryTimer > diff) + { + recoveryTimer -= diff; + return; + } + + recoveryTimer = 1000; + + uint32 maxHealth = me->GetMaxHealth(); + if (me->GetHealth() < maxHealth) + { + uint32 gain = maxHealth / 10; + if (!gain) + gain = 1; + + uint32 health = me->GetHealth() + gain; + me->SetHealth(health > maxHealth ? maxHealth : health); + } + + uint32 maxMana = me->GetMaxPower(POWER_MANA); + if (maxMana && me->GetPower(POWER_MANA) < maxMana) + { + uint32 gain = maxMana / 10; + if (!gain) + gain = 1; + + uint32 mana = me->GetPower(POWER_MANA) + gain; + me->SetPower(POWER_MANA, mana > maxMana ? maxMana : mana); + } + } + + Unit* GetSkullTarget(Player* owner) const + { + if (!owner) + return nullptr; + + Group* group = owner->GetGroup(); + if (!group) + return nullptr; + + ObjectGuid guid = group->GetTargetIcon(SKULL_ICON_ID); + if (!guid) + return nullptr; + + Unit* target = ObjectAccessor::GetUnit(*me, guid); + return IsValidTankTarget(target) ? target : nullptr; + } + + Unit* FindGroupAttacker(Player* owner) const + { + if (!owner) + return nullptr; + + if (Group* group = owner->GetGroup()) + { + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !member->IsAlive() || !member->IsInMap(owner)) + continue; + + for (Unit* attacker : member->getAttackers()) + if (IsValidTankTarget(attacker)) + return attacker; + } + } + else + { + for (Unit* attacker : owner->getAttackers()) + if (IsValidTankTarget(attacker)) + return attacker; + } + + for (Unit* attacker : me->getAttackers()) + if (IsValidTankTarget(attacker)) + return attacker; + + return nullptr; + } + + Unit* SelectPrimaryTarget(Player* owner) + { + if (!owner) + return nullptr; + + // Skull is the only explicit permission for the tank to initiate + // a pull. It also overrides the current primary target mid-fight. + if (Unit* skull = GetSkullTarget(owner)) + return skull; + + // Existing combat takes precedence over player selection. + if (Unit* victim = me->GetVictim()) + if (IsValidTankTarget(victim)) + return victim; + + // Pick up anything already attacking the group. + if (Unit* attacker = FindGroupAttacker(owner)) + return attacker; + + // The owner already attacking something is also a valid combat signal. + if (Unit* victim = owner->GetVictim()) + if (IsValidTankTarget(victim)) + return victim; + + // GetVictim() is not always populated for players. + if (owner->IsInCombat()) + if (Unit* selected = owner->GetSelectedUnit()) + if (IsValidTankTarget(selected)) + return selected; + + return nullptr; + } + + bool IsSafeAutoPickup(Unit* enemy) const + { + return IsValidTankTarget(enemy) + && me->GetDistance(enemy) <= AUTO_PICKUP_RADIUS + && me->IsWithinLOSInMap(enemy); + } + + void ForceTankThreat(Unit* enemy) + { + if (!IsSafeAutoPickup(enemy)) + return; + + ThreatManager& threat = enemy->GetThreatManager(); + + // Match the current highest threat, then exceed it by 35%. + // This gives us taunt-like recovery without permanent Fixate. + threat.MatchUnitThreatToHighestThreat(me); + + float current = threat.GetThreat(me, true); + float bonus = current * 0.35f; + if (bonus < 1.0f) + bonus = 1.0f; + + threat.AddThreat(me, bonus, nullptr, true, true); + } + + void RecoverGroupThreat(Player* owner) + { + if (!owner || !IsActivelyEngagedInCombat()) + return; + + // Strong single-target threat on the current primary victim. + if (Unit* victim = me->GetVictim()) + ForceTankThreat(victim); + + // AoE pickup: only enemies already attacking real group members, + // and only inside the configured 10 yard + LOS safety envelope. + if (Group* group = owner->GetGroup()) + { + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !member->IsAlive() || !member->IsInMap(owner)) + continue; + + for (Unit* attacker : member->getAttackers()) + ForceTankThreat(attacker); + } + } + else + { + for (Unit* attacker : owner->getAttackers()) + ForceTankThreat(attacker); + } + + for (Unit* attacker : me->getAttackers()) + ForceTankThreat(attacker); + } + + void DoTankAoeDamage(Player* owner) + { + if (!owner || !IsActivelyEngagedInCombat()) + return; + + uint32 damage = uint32(me->getLevel()) * 2u; + if (!damage) + damage = 1; + + auto hit = [this, damage](Unit* enemy) + { + if (!IsSafeAutoPickup(enemy)) + return; + + Unit::DealDamage( + me, + enemy, + damage, + 0, + DIRECT_DAMAGE, + SPELL_SCHOOL_MASK_NORMAL, + nullptr, + false); + }; + + if (Group* group = owner->GetGroup()) + { + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !member->IsAlive() || !member->IsInMap(owner)) + continue; + + for (Unit* attacker : member->getAttackers()) + hit(attacker); + } + } + else + { + for (Unit* attacker : owner->getAttackers()) + hit(attacker); + } + + for (Unit* attacker : me->getAttackers()) + hit(attacker); + } + + void InitializeAI() override + { + ScriptedAI::InitializeAI(); + + TempSummon* summon = me->ToTempSummon(); + if (!summon) + { + me->DespawnOrUnsummon(); + return; + } + + Unit* summoner = summon->GetSummoner(); + Player* owner = summoner ? summoner->ToPlayer() : nullptr; + if (!owner) + { + me->DespawnOrUnsummon(); + return; + } + + ownerGuid = owner->GetGUID(); + + me->SetOwnerGUID(ownerGuid); + me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED); + me->SetFaction(owner->GetFaction()); + me->SetLevel(owner->getLevel()); + me->UpdateLevelDependantStats(); + + static constexpr uint32 HORDE_ADVENTURER_MODELS[] = + { + 30063, + 30064, + 30065, + 30066 + }; + + me->SetDisplayId(HORDE_ADVENTURER_MODELS[urand(0, 3)], true); + me->SetReactState(REACT_DEFENSIVE); + + FollowOwner(owner); + + TC_LOG_INFO( + "scripts", + "[VianoTank] initialized entry=%u owner='%s' level=%u health=%u armor=%u", + me->GetEntry(), + owner->GetName().c_str(), + uint32(me->getLevel()), + me->GetMaxHealth(), + me->GetArmor()); + } + + void Reset() override + { + updateTimer = 0; + recoveryTimer = 1000; + aoeDamageTimer = 3000; + + if (Player* owner = GetOwnerPlayer()) + if (!IsActivelyEngagedInCombat()) + FollowOwner(owner); + } + + void MoveInLineOfSight(Unit* /*who*/) override + { + // No autonomous proximity pulls. + } + + void UpdateAI(uint32 diff) override + { + HandleOutOfCombatRecovery(diff); + + if (aoeDamageTimer > diff) + aoeDamageTimer -= diff; + else + aoeDamageTimer = 0; + + if (updateTimer > diff) + { + updateTimer -= diff; + + if (me->GetVictim()) + DoMeleeAttackIfReady(); + + return; + } + + updateTimer = TANK_UPDATE_INTERVAL; + + Player* owner = GetOwnerPlayer(); + if (!owner || !owner->IsInWorld() || !me->IsInMap(owner)) + { + me->DespawnOrUnsummon(); + return; + } + + if (me->getLevel() != owner->getLevel()) + { + me->SetLevel(owner->getLevel()); + me->UpdateLevelDependantStats(); + } + + bool engaged = IsActivelyEngagedInCombat(); + + // Recovery teleport is strictly out-of-combat. + if (!engaged && me->GetDistance(owner) > TELEPORT_DISTANCE) + { + me->GetMotionMaster()->Clear(); + + me->NearTeleportTo( + owner->GetPositionX() + 1.5f, + owner->GetPositionY(), + owner->GetPositionZ(), + owner->GetOrientation()); + + FollowOwner(owner); + return; + } + + Unit* desiredTarget = SelectPrimaryTarget(owner); + + if (desiredTarget && me->GetVictim() != desiredTarget) + AttackStart(desiredTarget); + + engaged = IsActivelyEngagedInCombat(); + + if (engaged) + { + RecoverGroupThreat(owner); + + if (aoeDamageTimer == 0) + { + aoeDamageTimer = AOE_DAMAGE_INTERVAL; + DoTankAoeDamage(owner); + } + + if (me->GetVictim()) + DoMeleeAttackIfReady(); + } + else + FollowOwner(owner); + } + }; + + CreatureAI* GetAI(Creature* creature) const override + { + return new npc_horde_adventurer_tankAI(creature); + } + }; + +} + +void AddSC_viano_adventurers() +{ + using namespace VianoAdventurers; + new npc_horde_adventurer_melee(); + new npc_horde_adventurer_tank(); +} diff --git a/src/server/scripts/Custom/viano_adventurers.cpp.bak-viano-group-context-v1-20261001-134352 b/src/server/scripts/Custom/viano_adventurers.cpp.bak-viano-group-context-v1-20261001-134352 new file mode 100644 index 0000000000..8ce77f0bf8 --- /dev/null +++ b/src/server/scripts/Custom/viano_adventurers.cpp.bak-viano-group-context-v1-20261001-134352 @@ -0,0 +1,964 @@ +/* + * Viano custom dungeon adventurers + * Initial melee companion implementation. + */ + +#include "ScriptMgr.h" +#include "ScriptedCreature.h" +#include "ObjectAccessor.h" +#include "Player.h" +#include "Group.h" +#include "TemporarySummon.h" +#include "MotionMaster.h" +#include "Log.h" +#include +#include + +namespace VianoAdventurers +{ + static constexpr float FOLLOW_DISTANCE = 2.5f; + static constexpr float FOLLOW_ANGLE = 3.14159265f; + static constexpr float TELEPORT_DISTANCE = 45.0f; + static constexpr uint32 UPDATE_INTERVAL = 500; + static constexpr uint32 DIAGNOSTIC_INTERVAL = 2000; + + // VIANO_DD_PROFILE_V1_5 + enum AdventurerSpells : uint32 + { + SPELL_ADVENTURER_MORTAL_STRIKE = 65926, + SPELL_ADVENTURER_OVERPOWER = 65924, + SPELL_ADVENTURER_BLADESTORM = 65947 + }; + + enum AdventurerEvents : uint32 + { + EVENT_ADVENTURER_MORTAL_STRIKE = 1, + EVENT_ADVENTURER_OVERPOWER, + EVENT_ADVENTURER_BLADESTORM + }; + + // VIANO_AI_DIAGNOSTICS_V1 + + class npc_horde_adventurer_melee : public CreatureScript + { + public: + npc_horde_adventurer_melee() : CreatureScript("npc_horde_adventurer_melee") { } + + struct npc_horde_adventurer_meleeAI : public ScriptedAI + { + npc_horde_adventurer_meleeAI(Creature* creature) : ScriptedAI(creature) { } + + ObjectGuid ownerGuid; + EventMap combatEvents; + uint32 updateTimer = 0; + uint32 diagnosticTimer = 0; + uint32 recoveryTimer = 1000; + Player* GetOwnerPlayer() const + { + if (!ownerGuid) + return nullptr; + + return ObjectAccessor::GetPlayer(*me, ownerGuid); + } + + void FollowOwner(Player* owner) + { + if (!owner) + return; + + me->GetMotionMaster()->MoveFollow(owner, FOLLOW_DISTANCE, FOLLOW_ANGLE); + } + + void ResetCombatEvents() + { + combatEvents.Reset(); + + combatEvents.ScheduleEvent(EVENT_ADVENTURER_MORTAL_STRIKE, 2 * IN_MILLISECONDS); + combatEvents.ScheduleEvent(EVENT_ADVENTURER_OVERPOWER, 5 * IN_MILLISECONDS); + combatEvents.ScheduleEvent(EVENT_ADVENTURER_BLADESTORM, 12 * IN_MILLISECONDS); + } + + void HandleCombatActions(uint32 diff) + { + Unit* victim = me->GetVictim(); + if (!victim || !victim->IsAlive()) + return; + + combatEvents.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + if (uint32 eventId = combatEvents.ExecuteEvent()) + { + switch (eventId) + { + case EVENT_ADVENTURER_MORTAL_STRIKE: + TC_LOG_INFO("scripts", "[VianoAI] cast Mortal Strike: target='%s'(%u)", + UnitName(victim), UnitEntry(victim)); + DoCastVictim(SPELL_ADVENTURER_MORTAL_STRIKE); + combatEvents.ScheduleEvent(EVENT_ADVENTURER_MORTAL_STRIKE, + urand(6 * IN_MILLISECONDS, 8 * IN_MILLISECONDS)); + return; + + case EVENT_ADVENTURER_OVERPOWER: + TC_LOG_INFO("scripts", "[VianoAI] cast Overpower: target='%s'(%u)", + UnitName(victim), UnitEntry(victim)); + DoCastVictim(SPELL_ADVENTURER_OVERPOWER); + combatEvents.ScheduleEvent(EVENT_ADVENTURER_OVERPOWER, + urand(10 * IN_MILLISECONDS, 14 * IN_MILLISECONDS)); + return; + + case EVENT_ADVENTURER_BLADESTORM: + TC_LOG_INFO("scripts", "[VianoAI] cast Bladestorm: target='%s'(%u)", + UnitName(victim), UnitEntry(victim)); + DoCastVictim(SPELL_ADVENTURER_BLADESTORM); + combatEvents.ScheduleEvent(EVENT_ADVENTURER_BLADESTORM, + urand(30 * IN_MILLISECONDS, 45 * IN_MILLISECONDS)); + return; + + default: + break; + } + } + + DoMeleeAttackIfReady(); + } + void HandleOutOfCombatRecovery(uint32 diff) + { + if (!me->IsAlive()) + { + recoveryTimer = 1000; + return; + } + + // Keine künstliche Regeneration während eines Kampfes. + if (me->IsInCombat()) + { + recoveryTimer = 1000; + return; + } + + if (recoveryTimer > diff) + { + recoveryTimer -= diff; + return; + } + + recoveryTimer = 1000; + + // 10 % der maximalen Lebenspunkte pro Sekunde. + uint32 maxHealth = me->GetMaxHealth(); + + if (me->GetHealth() < maxHealth) + { + uint32 healthGain = maxHealth / 10; + + if (healthGain == 0) + healthGain = 1; + + uint32 newHealth = me->GetHealth() + healthGain; + + if (newHealth > maxHealth) + newHealth = maxHealth; + + me->SetHealth(newHealth); + } + + // 10 % des maximalen Manas pro Sekunde. + uint32 maxMana = me->GetMaxPower(POWER_MANA); + + if (maxMana > 0 && me->GetPower(POWER_MANA) < maxMana) + { + uint32 manaGain = maxMana / 10; + + if (manaGain == 0) + manaGain = 1; + + uint32 newMana = me->GetPower(POWER_MANA) + manaGain; + + if (newMana > maxMana) + newMana = maxMana; + + me->SetPower(POWER_MANA, newMana); + } +} + + void InitializeAI() override + { + ScriptedAI::InitializeAI(); + + TempSummon* summon = me->ToTempSummon(); + if (!summon) + { + me->DespawnOrUnsummon(); + return; + } + + Unit* summoner = summon->GetSummoner(); + Player* owner = summoner ? summoner->ToPlayer() : nullptr; + if (!owner) + { + me->DespawnOrUnsummon(); + return; + } + + ownerGuid = owner->GetGUID(); + + me->SetOwnerGUID(ownerGuid); + me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED); + me->SetFaction(owner->GetFaction()); + me->SetLevel(owner->getLevel()); + me->UpdateLevelDependantStats(); + + + static constexpr uint32 HORDE_ADVENTURER_MODELS[] = + { + 30063, + 30064, + 30065, + 30066 + }; + uint32 displayId = HORDE_ADVENTURER_MODELS[urand(0, 3)]; + + me->SetDisplayId(displayId, true); + + TC_LOG_INFO("scripts","[VianoAI] Horde adventurer display selected: %u", displayId); + + // REACT_PASSIVE lets AttackStart() briefly set a victim, but the + // core subsequently drops that victim again. We already suppress + // autonomous proximity aggro in MoveInLineOfSight(), so DEFENSIVE + // keeps an explicitly assigned assist target without making the + // adventurer independently pull nearby creatures. + me->SetReactState(REACT_DEFENSIVE); + + TC_LOG_INFO("scripts", + "[VianoAI] initialized me='%s' entry=%u owner='%s' ownerGuid=%s meFaction=%u ownerFaction=%u level=%u", + me->GetName().c_str(), me->GetEntry(), owner->GetName().c_str(), ownerGuid.ToString().c_str(), + me->GetFaction(), owner->GetFaction(), uint32(me->getLevel())); + + ResetCombatEvents(); + FollowOwner(owner); + } + + void Reset() override + { + ResetCombatEvents(); + + if (Player* owner = GetOwnerPlayer()) + FollowOwner(owner); + } + + void MoveInLineOfSight(Unit* /*who*/) override + { + } + + char const* UnitName(Unit* unit) const + { + return unit ? unit->GetName().c_str() : ""; + } + + uint32 UnitEntry(Unit* unit) const + { + return unit ? unit->GetEntry() : 0; + } + + bool IsValidCandidate(Unit* unit) const + { + return unit && unit->IsAlive() && me->IsValidAttackTarget(unit); + } + + void LogDiagnostics(Player* owner) + { + if (!owner) + return; + + Unit* ownerVictim = owner->GetVictim(); + Unit* selected = owner->GetSelectedUnit(); + Unit* meVictim = me->GetVictim(); + + Unit* firstOwnerAttacker = nullptr; + for (Unit* attacker : owner->getAttackers()) + { + if (attacker) + { + firstOwnerAttacker = attacker; + break; + } + } + + Unit* firstMeAttacker = nullptr; + for (Unit* attacker : me->getAttackers()) + { + if (attacker) + { + firstMeAttacker = attacker; + break; + } + } + + TC_LOG_INFO("scripts", + "[VianoAI] me='%s' entry=%u lvl=%u faction=%u react=%u alive=%u combat=%u hp=%u/%u victim='%s'(%u) attackers=%u distance=%.2f", + me->GetName().c_str(), me->GetEntry(), uint32(me->getLevel()), me->GetFaction(), uint32(me->GetReactState()), + me->IsAlive() ? 1u : 0u, me->IsInCombat() ? 1u : 0u, me->GetHealth(), me->GetMaxHealth(), + UnitName(meVictim), UnitEntry(meVictim), uint32(me->getAttackers().size()), me->GetDistance(owner)); + + TC_LOG_INFO("scripts", + "[VianoAI] owner='%s' lvl=%u faction=%u alive=%u combat=%u victim='%s'(%u) selected='%s'(%u) attackers=%u", + owner->GetName().c_str(), uint32(owner->getLevel()), owner->GetFaction(), owner->IsAlive() ? 1u : 0u, + owner->IsInCombat() ? 1u : 0u, UnitName(ownerVictim), UnitEntry(ownerVictim), UnitName(selected), UnitEntry(selected), + uint32(owner->getAttackers().size())); + + if (ownerVictim) + TC_LOG_INFO("scripts", "[VianoAI] ownerVictim='%s' entry=%u faction=%u validForBot=%u botCanAttack=%u ownerCanAttack=%u victim='%s'(%u)", + ownerVictim->GetName().c_str(), ownerVictim->GetEntry(), ownerVictim->GetFaction(), IsValidCandidate(ownerVictim) ? 1u : 0u, + me->IsValidAttackTarget(ownerVictim) ? 1u : 0u, owner->IsValidAttackTarget(ownerVictim) ? 1u : 0u, + UnitName(ownerVictim->GetVictim()), UnitEntry(ownerVictim->GetVictim())); + + if (selected) + TC_LOG_INFO("scripts", "[VianoAI] selected='%s' entry=%u faction=%u validForBot=%u botCanAttack=%u ownerCanAttack=%u victim='%s'(%u)", + selected->GetName().c_str(), selected->GetEntry(), selected->GetFaction(), IsValidCandidate(selected) ? 1u : 0u, + me->IsValidAttackTarget(selected) ? 1u : 0u, owner->IsValidAttackTarget(selected) ? 1u : 0u, + UnitName(selected->GetVictim()), UnitEntry(selected->GetVictim())); + + if (firstOwnerAttacker) + TC_LOG_INFO("scripts", "[VianoAI] firstOwnerAttacker='%s' entry=%u faction=%u validForBot=%u botCanAttack=%u ownerCanAttack=%u victim='%s'(%u)", + firstOwnerAttacker->GetName().c_str(), firstOwnerAttacker->GetEntry(), firstOwnerAttacker->GetFaction(), IsValidCandidate(firstOwnerAttacker) ? 1u : 0u, + me->IsValidAttackTarget(firstOwnerAttacker) ? 1u : 0u, owner->IsValidAttackTarget(firstOwnerAttacker) ? 1u : 0u, + UnitName(firstOwnerAttacker->GetVictim()), UnitEntry(firstOwnerAttacker->GetVictim())); + + if (firstMeAttacker) + TC_LOG_INFO("scripts", "[VianoAI] firstMeAttacker='%s' entry=%u faction=%u victim='%s'(%u)", + firstMeAttacker->GetName().c_str(), firstMeAttacker->GetEntry(), firstMeAttacker->GetFaction(), + UnitName(firstMeAttacker->GetVictim()), UnitEntry(firstMeAttacker->GetVictim())); + } + + bool IsActivelyEngagedInCombat() const + { + // IsInCombat() alone is intentionally not enough here: the Core + // may keep the combat flag set after the bot has no live opponent. + if (Unit* victim = me->GetVictim()) + if (IsValidCandidate(victim)) + return true; + + for (Unit* attacker : me->getAttackers()) + if (IsValidCandidate(attacker)) + return true; + + return false; + } + + Unit* SelectGroupCombatTarget(Player* owner) + { + if (!owner) + return nullptr; + + std::list partyMembers; + owner->GetPartyMembers(partyMembers); + + // In Vcore GetPartyMembers() returns living members of the same + // party/subgroup on the same map, plus their living guardian pets. + // An enemy attacking one of them is therefore already part of the + // group's current fight. + for (Unit* member : partyMembers) + { + if (!member || !member->IsAlive()) + continue; + + for (Unit* attacker : member->getAttackers()) + if (IsValidCandidate(attacker)) + return attacker; + } + + return nullptr; + } + + Unit* SelectAssistTarget(Player* owner) + { + if (!owner) + return nullptr; + + // 1. An active owner victim is an explicit target instruction. + if (Unit* target = owner->GetVictim()) + if (IsValidCandidate(target)) + return target; + + // During an already running fight, an explicitly selected living + // hostile target may also be treated as an instruction. Merely + // selecting an enemy out of combat still must not pull it. + if (owner->IsInCombat()) + { + if (Unit* target = owner->GetSelectedUnit()) + if (IsValidCandidate(target)) + return target; + } + + // 2. Keep the bot's own valid current victim. + if (Unit* target = me->GetVictim()) + if (IsValidCandidate(target)) + return target; + + // 3. Continue the group's fight even if the owner is a healer and + // currently targets a friendly tank or another party member. + if (Unit* target = SelectGroupCombatTarget(owner)) + return target; + + // 4. Finally, defend the bot itself if something is attacking it. + for (Unit* attacker : me->getAttackers()) + if (IsValidCandidate(attacker)) + return attacker; + + return nullptr; + } + + void UpdateAI(uint32 diff) override + { + HandleOutOfCombatRecovery(diff); + if (diagnosticTimer > diff) + diagnosticTimer -= diff; + else + diagnosticTimer = 0; + + if (updateTimer > diff) + { + updateTimer -= diff; + + // VIANO_TARGET_CONTROL_V1_4 + // Keep the explicitly assigned victim. UpdateVictim() may + // select another creature from the threat list. + if (me->GetVictim()) + HandleCombatActions(diff); + + return; + } + + updateTimer = UPDATE_INTERVAL; + + Player* owner = GetOwnerPlayer(); + if (!owner || !owner->IsInWorld() || !me->IsInMap(owner)) + { + TC_LOG_INFO("scripts", "[VianoAI] despawn: owner invalid or not on same map (ownerFound=%u)", owner ? 1u : 0u); + me->DespawnOrUnsummon(); + return; + } + + if (diagnosticTimer == 0) + { + diagnosticTimer = DIAGNOSTIC_INTERVAL; + LogDiagnostics(owner); + } + + if (me->getLevel() != owner->getLevel()) + { + me->SetLevel(owner->getLevel()); + me->UpdateLevelDependantStats(); + } + + if (me->GetDistance(owner) > TELEPORT_DISTANCE && !IsActivelyEngagedInCombat()) + { + // Keep the recovery teleport for geometry/jump problems, even + // if the group is already fighting elsewhere. Only the bot's + // own active engagement blocks teleporting. + me->GetMotionMaster()->Clear(); + + me->NearTeleportTo( + owner->GetPositionX() + 1.5f, + owner->GetPositionY(), + owner->GetPositionZ(), + owner->GetOrientation() + ); + + FollowOwner(owner); + return; + } + + Unit* desiredTarget = SelectAssistTarget(owner); + + if (desiredTarget) + { + if (me->GetVictim() != desiredTarget) + { + TC_LOG_INFO("scripts", + "[VianoAI] AttackStart requested: target='%s' entry=%u targetFaction=%u valid=%u meCombatBefore=%u", + desiredTarget->GetName().c_str(), desiredTarget->GetEntry(), desiredTarget->GetFaction(), + me->IsValidAttackTarget(desiredTarget) ? 1u : 0u, me->IsInCombat() ? 1u : 0u); + + AttackStart(desiredTarget); + + Unit* afterVictim = me->GetVictim(); + TC_LOG_INFO("scripts", "[VianoAI] AttackStart result: meCombatAfter=%u victim='%s'(%u)", + me->IsInCombat() ? 1u : 0u, UnitName(afterVictim), UnitEntry(afterVictim)); + } + } + else if (!IsActivelyEngagedInCombat()) + { + // A stale Core combat flag must not prevent recovery/following. + // We deliberately do not clear the flag here; OOC regeneration + // will be handled as a separate change. + FollowOwner(owner); + } + + // VIANO_TARGET_CONTROL_V1_4 + // Keep the explicitly assigned victim. UpdateVictim() may + // select another creature from the threat list. + if (me->GetVictim()) + HandleCombatActions(diff); + } + }; + + CreatureAI* GetAI(Creature* creature) const override + { + return new npc_horde_adventurer_meleeAI(creature); + } + }; + + // VIANO_TANK_V1 + class npc_horde_adventurer_tank : public CreatureScript + { + public: + npc_horde_adventurer_tank() : CreatureScript("npc_horde_adventurer_tank") { } + + struct npc_horde_adventurer_tankAI : public ScriptedAI + { + npc_horde_adventurer_tankAI(Creature* creature) : ScriptedAI(creature) { } + + static constexpr uint8 SKULL_ICON_ID = 7; + static constexpr float AUTO_PICKUP_RADIUS = 10.0f; + static constexpr uint32 TANK_UPDATE_INTERVAL = 500; + static constexpr uint32 AOE_DAMAGE_INTERVAL = 6000; + + ObjectGuid ownerGuid; + uint32 updateTimer = 0; + uint32 recoveryTimer = 1000; + uint32 aoeDamageTimer = 3000; + + Player* GetOwnerPlayer() const + { + if (!ownerGuid) + return nullptr; + + return ObjectAccessor::GetPlayer(*me, ownerGuid); + } + + bool IsValidTankTarget(Unit* unit) const + { + if (!unit || !unit->IsAlive()) + return false; + + // Dungeon-role bot: do not turn this into a PvP player hunter. + if (!unit->ToCreature()) + return false; + + return me->IsValidAttackTarget(unit); + } + + bool IsActivelyEngagedInCombat() const + { + if (Unit* victim = me->GetVictim()) + if (victim->IsAlive() && me->IsValidAttackTarget(victim)) + return true; + + for (Unit* attacker : me->getAttackers()) + if (attacker && attacker->IsAlive() && me->IsValidAttackTarget(attacker)) + return true; + + return false; + } + + void FollowOwner(Player* owner) + { + if (!owner) + return; + + me->GetMotionMaster()->MoveFollow(owner, FOLLOW_DISTANCE, FOLLOW_ANGLE); + } + + void HandleOutOfCombatRecovery(uint32 diff) + { + if (!me->IsAlive() || IsActivelyEngagedInCombat()) + { + recoveryTimer = 1000; + return; + } + + if (recoveryTimer > diff) + { + recoveryTimer -= diff; + return; + } + + recoveryTimer = 1000; + + uint32 maxHealth = me->GetMaxHealth(); + if (me->GetHealth() < maxHealth) + { + uint32 gain = maxHealth / 10; + if (!gain) + gain = 1; + + uint32 health = me->GetHealth() + gain; + me->SetHealth(health > maxHealth ? maxHealth : health); + } + + uint32 maxMana = me->GetMaxPower(POWER_MANA); + if (maxMana && me->GetPower(POWER_MANA) < maxMana) + { + uint32 gain = maxMana / 10; + if (!gain) + gain = 1; + + uint32 mana = me->GetPower(POWER_MANA) + gain; + me->SetPower(POWER_MANA, mana > maxMana ? maxMana : mana); + } + } + + Unit* GetSkullTarget(Player* owner) const + { + if (!owner) + return nullptr; + + Group* group = owner->GetGroup(); + if (!group) + return nullptr; + + ObjectGuid guid = group->GetTargetIcon(SKULL_ICON_ID); + if (!guid) + return nullptr; + + Unit* target = ObjectAccessor::GetUnit(*me, guid); + return IsValidTankTarget(target) ? target : nullptr; + } + + Unit* FindGroupAttacker(Player* owner) const + { + if (!owner) + return nullptr; + + if (Group* group = owner->GetGroup()) + { + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !member->IsAlive() || !member->IsInMap(owner)) + continue; + + for (Unit* attacker : member->getAttackers()) + if (IsValidTankTarget(attacker)) + return attacker; + } + } + else + { + for (Unit* attacker : owner->getAttackers()) + if (IsValidTankTarget(attacker)) + return attacker; + } + + for (Unit* attacker : me->getAttackers()) + if (IsValidTankTarget(attacker)) + return attacker; + + return nullptr; + } + + Unit* SelectPrimaryTarget(Player* owner) + { + if (!owner) + return nullptr; + + // Skull is the only explicit permission for the tank to initiate + // a pull. It also overrides the current primary target mid-fight. + if (Unit* skull = GetSkullTarget(owner)) + return skull; + + // Existing combat takes precedence over player selection. + if (Unit* victim = me->GetVictim()) + if (IsValidTankTarget(victim)) + return victim; + + // Pick up anything already attacking the group. + if (Unit* attacker = FindGroupAttacker(owner)) + return attacker; + + // The owner already attacking something is also a valid combat signal. + if (Unit* victim = owner->GetVictim()) + if (IsValidTankTarget(victim)) + return victim; + + // GetVictim() is not always populated for players. + if (owner->IsInCombat()) + if (Unit* selected = owner->GetSelectedUnit()) + if (IsValidTankTarget(selected)) + return selected; + + return nullptr; + } + + bool IsSafeAutoPickup(Unit* enemy) const + { + return IsValidTankTarget(enemy) + && me->GetDistance(enemy) <= AUTO_PICKUP_RADIUS + && me->IsWithinLOSInMap(enemy); + } + + void ForceTankThreat(Unit* enemy) + { + if (!IsSafeAutoPickup(enemy)) + return; + + ThreatManager& threat = enemy->GetThreatManager(); + + // Match the current highest threat, then exceed it by 35%. + // This gives us taunt-like recovery without permanent Fixate. + threat.MatchUnitThreatToHighestThreat(me); + + float current = threat.GetThreat(me, true); + float bonus = current * 0.35f; + if (bonus < 1.0f) + bonus = 1.0f; + + threat.AddThreat(me, bonus, nullptr, true, true); + } + + void CollectRelevantEnemyGuids(Player* owner, std::vector& targets) const + { + if (!owner) + return; + + auto addTarget = [this, &targets](Unit* enemy) + { + if (!IsSafeAutoPickup(enemy)) + return; + + ObjectGuid const guid = enemy->GetGUID(); + + if (std::find(targets.begin(), targets.end(), guid) == targets.end()) + targets.push_back(guid); + }; + + if (Group* group = owner->GetGroup()) + { + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !member->IsAlive() || !member->IsInMap(owner)) + continue; + + for (Unit* attacker : member->getAttackers()) + addTarget(attacker); + } + } + else + { + for (Unit* attacker : owner->getAttackers()) + addTarget(attacker); + } + + for (Unit* attacker : me->getAttackers()) + addTarget(attacker); + } + + void RecoverGroupThreat(Player* owner) + { + if (!owner || !IsActivelyEngagedInCombat()) + return; + + if (Unit* victim = me->GetVictim()) + ForceTankThreat(victim); + + std::vector targets; + CollectRelevantEnemyGuids(owner, targets); + + for (ObjectGuid const& guid : targets) + { + if (Unit* enemy = ObjectAccessor::GetUnit(*me, guid)) + ForceTankThreat(enemy); + } + } + + void DoTankAoeDamage(Player* owner) + { + if (!owner || !IsActivelyEngagedInCombat()) + return; + + uint32 damage = uint32(me->getLevel()) * 2u; + if (!damage) + damage = 1; + + std::vector targets; + CollectRelevantEnemyGuids(owner, targets); + + for (ObjectGuid const& guid : targets) + { + Unit* enemy = ObjectAccessor::GetUnit(*me, guid); + if (!IsSafeAutoPickup(enemy)) + continue; + + Unit::DealDamage( + me, + enemy, + damage, + 0, + DIRECT_DAMAGE, + SPELL_SCHOOL_MASK_NORMAL, + nullptr, + false); + } + } + + void InitializeAI() override + { + ScriptedAI::InitializeAI(); + + TempSummon* summon = me->ToTempSummon(); + if (!summon) + { + me->DespawnOrUnsummon(); + return; + } + + Unit* summoner = summon->GetSummoner(); + Player* owner = summoner ? summoner->ToPlayer() : nullptr; + if (!owner) + { + me->DespawnOrUnsummon(); + return; + } + + ownerGuid = owner->GetGUID(); + + me->SetOwnerGUID(ownerGuid); + me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED); + me->SetFaction(owner->GetFaction()); + me->SetLevel(owner->getLevel()); + me->UpdateLevelDependantStats(); + + static constexpr uint32 HORDE_ADVENTURER_MODELS[] = + { + 30063, + 30064, + 30065, + 30066 + }; + + me->SetDisplayId(HORDE_ADVENTURER_MODELS[urand(0, 3)], true); + me->SetReactState(REACT_DEFENSIVE); + + FollowOwner(owner); + + TC_LOG_INFO( + "scripts", + "[VianoTank] initialized entry=%u owner='%s' level=%u health=%u armor=%u", + me->GetEntry(), + owner->GetName().c_str(), + uint32(me->getLevel()), + me->GetMaxHealth(), + me->GetArmor()); + } + + void Reset() override + { + updateTimer = 0; + recoveryTimer = 1000; + aoeDamageTimer = 3000; + + if (Player* owner = GetOwnerPlayer()) + if (!IsActivelyEngagedInCombat()) + FollowOwner(owner); + } + + void MoveInLineOfSight(Unit* /*who*/) override + { + // No autonomous proximity pulls. + } + + void UpdateAI(uint32 diff) override + { + HandleOutOfCombatRecovery(diff); + + if (aoeDamageTimer > diff) + aoeDamageTimer -= diff; + else + aoeDamageTimer = 0; + + if (updateTimer > diff) + { + updateTimer -= diff; + + if (me->GetVictim()) + DoMeleeAttackIfReady(); + + return; + } + + updateTimer = TANK_UPDATE_INTERVAL; + + Player* owner = GetOwnerPlayer(); + if (!owner || !owner->IsInWorld() || !me->IsInMap(owner)) + { + me->DespawnOrUnsummon(); + return; + } + + if (me->getLevel() != owner->getLevel()) + { + me->SetLevel(owner->getLevel()); + me->UpdateLevelDependantStats(); + } + + bool engaged = IsActivelyEngagedInCombat(); + + // Recovery teleport is strictly out-of-combat. + if (!engaged && me->GetDistance(owner) > TELEPORT_DISTANCE) + { + me->GetMotionMaster()->Clear(); + + me->NearTeleportTo( + owner->GetPositionX() + 1.5f, + owner->GetPositionY(), + owner->GetPositionZ(), + owner->GetOrientation()); + + FollowOwner(owner); + return; + } + + Unit* desiredTarget = SelectPrimaryTarget(owner); + + if (desiredTarget && me->GetVictim() != desiredTarget) + AttackStart(desiredTarget); + + engaged = IsActivelyEngagedInCombat(); + + if (engaged) + { + RecoverGroupThreat(owner); + + if (aoeDamageTimer == 0) + { + aoeDamageTimer = AOE_DAMAGE_INTERVAL; + DoTankAoeDamage(owner); + } + + if (me->GetVictim()) + DoMeleeAttackIfReady(); + } + else + FollowOwner(owner); + } + }; + + CreatureAI* GetAI(Creature* creature) const override + { + return new npc_horde_adventurer_tankAI(creature); + } + }; + +} + +void AddSC_viano_adventurers() +{ + using namespace VianoAdventurers; + new npc_horde_adventurer_melee(); + new npc_horde_adventurer_tank(); +} diff --git a/src/server/scripts/Custom/viano_adventurers.cpp.bak-viano-tank-v1-20261001-083107 b/src/server/scripts/Custom/viano_adventurers.cpp.bak-viano-tank-v1-20261001-083107 new file mode 100644 index 0000000000..8f4fe95e21 --- /dev/null +++ b/src/server/scripts/Custom/viano_adventurers.cpp.bak-viano-tank-v1-20261001-083107 @@ -0,0 +1,517 @@ +/* + * Viano custom dungeon adventurers + * Initial melee companion implementation. + */ + +#include "ScriptMgr.h" +#include "ScriptedCreature.h" +#include "ObjectAccessor.h" +#include "Player.h" +#include "TemporarySummon.h" +#include "MotionMaster.h" +#include "Log.h" + +namespace VianoAdventurers +{ + static constexpr float FOLLOW_DISTANCE = 2.5f; + static constexpr float FOLLOW_ANGLE = 3.14159265f; + static constexpr float TELEPORT_DISTANCE = 45.0f; + static constexpr uint32 UPDATE_INTERVAL = 500; + static constexpr uint32 DIAGNOSTIC_INTERVAL = 2000; + + // VIANO_DD_PROFILE_V1_5 + enum AdventurerSpells : uint32 + { + SPELL_ADVENTURER_MORTAL_STRIKE = 65926, + SPELL_ADVENTURER_OVERPOWER = 65924, + SPELL_ADVENTURER_BLADESTORM = 65947 + }; + + enum AdventurerEvents : uint32 + { + EVENT_ADVENTURER_MORTAL_STRIKE = 1, + EVENT_ADVENTURER_OVERPOWER, + EVENT_ADVENTURER_BLADESTORM + }; + + // VIANO_AI_DIAGNOSTICS_V1 + + class npc_horde_adventurer_melee : public CreatureScript + { + public: + npc_horde_adventurer_melee() : CreatureScript("npc_horde_adventurer_melee") { } + + struct npc_horde_adventurer_meleeAI : public ScriptedAI + { + npc_horde_adventurer_meleeAI(Creature* creature) : ScriptedAI(creature) { } + + ObjectGuid ownerGuid; + EventMap combatEvents; + uint32 updateTimer = 0; + uint32 diagnosticTimer = 0; + uint32 recoveryTimer = 1000; + Player* GetOwnerPlayer() const + { + if (!ownerGuid) + return nullptr; + + return ObjectAccessor::GetPlayer(*me, ownerGuid); + } + + void FollowOwner(Player* owner) + { + if (!owner) + return; + + me->GetMotionMaster()->MoveFollow(owner, FOLLOW_DISTANCE, FOLLOW_ANGLE); + } + + void ResetCombatEvents() + { + combatEvents.Reset(); + + combatEvents.ScheduleEvent(EVENT_ADVENTURER_MORTAL_STRIKE, 2 * IN_MILLISECONDS); + combatEvents.ScheduleEvent(EVENT_ADVENTURER_OVERPOWER, 5 * IN_MILLISECONDS); + combatEvents.ScheduleEvent(EVENT_ADVENTURER_BLADESTORM, 12 * IN_MILLISECONDS); + } + + void HandleCombatActions(uint32 diff) + { + Unit* victim = me->GetVictim(); + if (!victim || !victim->IsAlive()) + return; + + combatEvents.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + if (uint32 eventId = combatEvents.ExecuteEvent()) + { + switch (eventId) + { + case EVENT_ADVENTURER_MORTAL_STRIKE: + TC_LOG_INFO("scripts", "[VianoAI] cast Mortal Strike: target='%s'(%u)", + UnitName(victim), UnitEntry(victim)); + DoCastVictim(SPELL_ADVENTURER_MORTAL_STRIKE); + combatEvents.ScheduleEvent(EVENT_ADVENTURER_MORTAL_STRIKE, + urand(6 * IN_MILLISECONDS, 8 * IN_MILLISECONDS)); + return; + + case EVENT_ADVENTURER_OVERPOWER: + TC_LOG_INFO("scripts", "[VianoAI] cast Overpower: target='%s'(%u)", + UnitName(victim), UnitEntry(victim)); + DoCastVictim(SPELL_ADVENTURER_OVERPOWER); + combatEvents.ScheduleEvent(EVENT_ADVENTURER_OVERPOWER, + urand(10 * IN_MILLISECONDS, 14 * IN_MILLISECONDS)); + return; + + case EVENT_ADVENTURER_BLADESTORM: + TC_LOG_INFO("scripts", "[VianoAI] cast Bladestorm: target='%s'(%u)", + UnitName(victim), UnitEntry(victim)); + DoCastVictim(SPELL_ADVENTURER_BLADESTORM); + combatEvents.ScheduleEvent(EVENT_ADVENTURER_BLADESTORM, + urand(30 * IN_MILLISECONDS, 45 * IN_MILLISECONDS)); + return; + + default: + break; + } + } + + DoMeleeAttackIfReady(); + } + void HandleOutOfCombatRecovery(uint32 diff) + { + if (!me->IsAlive()) + { + recoveryTimer = 1000; + return; + } + + // Keine künstliche Regeneration während eines Kampfes. + if (me->IsInCombat()) + { + recoveryTimer = 1000; + return; + } + + if (recoveryTimer > diff) + { + recoveryTimer -= diff; + return; + } + + recoveryTimer = 1000; + + // 10 % der maximalen Lebenspunkte pro Sekunde. + uint32 maxHealth = me->GetMaxHealth(); + + if (me->GetHealth() < maxHealth) + { + uint32 healthGain = maxHealth / 10; + + if (healthGain == 0) + healthGain = 1; + + uint32 newHealth = me->GetHealth() + healthGain; + + if (newHealth > maxHealth) + newHealth = maxHealth; + + me->SetHealth(newHealth); + } + + // 10 % des maximalen Manas pro Sekunde. + uint32 maxMana = me->GetMaxPower(POWER_MANA); + + if (maxMana > 0 && me->GetPower(POWER_MANA) < maxMana) + { + uint32 manaGain = maxMana / 10; + + if (manaGain == 0) + manaGain = 1; + + uint32 newMana = me->GetPower(POWER_MANA) + manaGain; + + if (newMana > maxMana) + newMana = maxMana; + + me->SetPower(POWER_MANA, newMana); + } +} + + void InitializeAI() override + { + ScriptedAI::InitializeAI(); + + TempSummon* summon = me->ToTempSummon(); + if (!summon) + { + me->DespawnOrUnsummon(); + return; + } + + Unit* summoner = summon->GetSummoner(); + Player* owner = summoner ? summoner->ToPlayer() : nullptr; + if (!owner) + { + me->DespawnOrUnsummon(); + return; + } + + ownerGuid = owner->GetGUID(); + + me->SetOwnerGUID(ownerGuid); + me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED); + me->SetFaction(owner->GetFaction()); + me->SetLevel(owner->getLevel()); + me->UpdateLevelDependantStats(); + + + static constexpr uint32 HORDE_ADVENTURER_MODELS[] = + { + 30063, + 30064, + 30065, + 30066 + }; + uint32 displayId = HORDE_ADVENTURER_MODELS[urand(0, 3)]; + + me->SetDisplayId(displayId, true); + + TC_LOG_INFO("scripts","[VianoAI] Horde adventurer display selected: %u", displayId); + + // REACT_PASSIVE lets AttackStart() briefly set a victim, but the + // core subsequently drops that victim again. We already suppress + // autonomous proximity aggro in MoveInLineOfSight(), so DEFENSIVE + // keeps an explicitly assigned assist target without making the + // adventurer independently pull nearby creatures. + me->SetReactState(REACT_DEFENSIVE); + + TC_LOG_INFO("scripts", + "[VianoAI] initialized me='%s' entry=%u owner='%s' ownerGuid=%s meFaction=%u ownerFaction=%u level=%u", + me->GetName().c_str(), me->GetEntry(), owner->GetName().c_str(), ownerGuid.ToString().c_str(), + me->GetFaction(), owner->GetFaction(), uint32(me->getLevel())); + + ResetCombatEvents(); + FollowOwner(owner); + } + + void Reset() override + { + ResetCombatEvents(); + + if (Player* owner = GetOwnerPlayer()) + FollowOwner(owner); + } + + void MoveInLineOfSight(Unit* /*who*/) override + { + } + + char const* UnitName(Unit* unit) const + { + return unit ? unit->GetName().c_str() : ""; + } + + uint32 UnitEntry(Unit* unit) const + { + return unit ? unit->GetEntry() : 0; + } + + bool IsValidCandidate(Unit* unit) const + { + return unit && unit->IsAlive() && me->IsValidAttackTarget(unit); + } + + void LogDiagnostics(Player* owner) + { + if (!owner) + return; + + Unit* ownerVictim = owner->GetVictim(); + Unit* selected = owner->GetSelectedUnit(); + Unit* meVictim = me->GetVictim(); + + Unit* firstOwnerAttacker = nullptr; + for (Unit* attacker : owner->getAttackers()) + { + if (attacker) + { + firstOwnerAttacker = attacker; + break; + } + } + + Unit* firstMeAttacker = nullptr; + for (Unit* attacker : me->getAttackers()) + { + if (attacker) + { + firstMeAttacker = attacker; + break; + } + } + + TC_LOG_INFO("scripts", + "[VianoAI] me='%s' entry=%u lvl=%u faction=%u react=%u alive=%u combat=%u hp=%u/%u victim='%s'(%u) attackers=%u distance=%.2f", + me->GetName().c_str(), me->GetEntry(), uint32(me->getLevel()), me->GetFaction(), uint32(me->GetReactState()), + me->IsAlive() ? 1u : 0u, me->IsInCombat() ? 1u : 0u, me->GetHealth(), me->GetMaxHealth(), + UnitName(meVictim), UnitEntry(meVictim), uint32(me->getAttackers().size()), me->GetDistance(owner)); + + TC_LOG_INFO("scripts", + "[VianoAI] owner='%s' lvl=%u faction=%u alive=%u combat=%u victim='%s'(%u) selected='%s'(%u) attackers=%u", + owner->GetName().c_str(), uint32(owner->getLevel()), owner->GetFaction(), owner->IsAlive() ? 1u : 0u, + owner->IsInCombat() ? 1u : 0u, UnitName(ownerVictim), UnitEntry(ownerVictim), UnitName(selected), UnitEntry(selected), + uint32(owner->getAttackers().size())); + + if (ownerVictim) + TC_LOG_INFO("scripts", "[VianoAI] ownerVictim='%s' entry=%u faction=%u validForBot=%u botCanAttack=%u ownerCanAttack=%u victim='%s'(%u)", + ownerVictim->GetName().c_str(), ownerVictim->GetEntry(), ownerVictim->GetFaction(), IsValidCandidate(ownerVictim) ? 1u : 0u, + me->IsValidAttackTarget(ownerVictim) ? 1u : 0u, owner->IsValidAttackTarget(ownerVictim) ? 1u : 0u, + UnitName(ownerVictim->GetVictim()), UnitEntry(ownerVictim->GetVictim())); + + if (selected) + TC_LOG_INFO("scripts", "[VianoAI] selected='%s' entry=%u faction=%u validForBot=%u botCanAttack=%u ownerCanAttack=%u victim='%s'(%u)", + selected->GetName().c_str(), selected->GetEntry(), selected->GetFaction(), IsValidCandidate(selected) ? 1u : 0u, + me->IsValidAttackTarget(selected) ? 1u : 0u, owner->IsValidAttackTarget(selected) ? 1u : 0u, + UnitName(selected->GetVictim()), UnitEntry(selected->GetVictim())); + + if (firstOwnerAttacker) + TC_LOG_INFO("scripts", "[VianoAI] firstOwnerAttacker='%s' entry=%u faction=%u validForBot=%u botCanAttack=%u ownerCanAttack=%u victim='%s'(%u)", + firstOwnerAttacker->GetName().c_str(), firstOwnerAttacker->GetEntry(), firstOwnerAttacker->GetFaction(), IsValidCandidate(firstOwnerAttacker) ? 1u : 0u, + me->IsValidAttackTarget(firstOwnerAttacker) ? 1u : 0u, owner->IsValidAttackTarget(firstOwnerAttacker) ? 1u : 0u, + UnitName(firstOwnerAttacker->GetVictim()), UnitEntry(firstOwnerAttacker->GetVictim())); + + if (firstMeAttacker) + TC_LOG_INFO("scripts", "[VianoAI] firstMeAttacker='%s' entry=%u faction=%u victim='%s'(%u)", + firstMeAttacker->GetName().c_str(), firstMeAttacker->GetEntry(), firstMeAttacker->GetFaction(), + UnitName(firstMeAttacker->GetVictim()), UnitEntry(firstMeAttacker->GetVictim())); + } + + bool IsActivelyEngagedInCombat() const + { + // IsInCombat() alone is intentionally not enough here: the Core + // may keep the combat flag set after the bot has no live opponent. + if (Unit* victim = me->GetVictim()) + if (IsValidCandidate(victim)) + return true; + + for (Unit* attacker : me->getAttackers()) + if (IsValidCandidate(attacker)) + return true; + + return false; + } + + Unit* SelectGroupCombatTarget(Player* owner) + { + if (!owner) + return nullptr; + + std::list partyMembers; + owner->GetPartyMembers(partyMembers); + + // In Vcore GetPartyMembers() returns living members of the same + // party/subgroup on the same map, plus their living guardian pets. + // An enemy attacking one of them is therefore already part of the + // group's current fight. + for (Unit* member : partyMembers) + { + if (!member || !member->IsAlive()) + continue; + + for (Unit* attacker : member->getAttackers()) + if (IsValidCandidate(attacker)) + return attacker; + } + + return nullptr; + } + + Unit* SelectAssistTarget(Player* owner) + { + if (!owner) + return nullptr; + + // 1. An active owner victim is an explicit target instruction. + if (Unit* target = owner->GetVictim()) + if (IsValidCandidate(target)) + return target; + + // During an already running fight, an explicitly selected living + // hostile target may also be treated as an instruction. Merely + // selecting an enemy out of combat still must not pull it. + if (owner->IsInCombat()) + { + if (Unit* target = owner->GetSelectedUnit()) + if (IsValidCandidate(target)) + return target; + } + + // 2. Keep the bot's own valid current victim. + if (Unit* target = me->GetVictim()) + if (IsValidCandidate(target)) + return target; + + // 3. Continue the group's fight even if the owner is a healer and + // currently targets a friendly tank or another party member. + if (Unit* target = SelectGroupCombatTarget(owner)) + return target; + + // 4. Finally, defend the bot itself if something is attacking it. + for (Unit* attacker : me->getAttackers()) + if (IsValidCandidate(attacker)) + return attacker; + + return nullptr; + } + + void UpdateAI(uint32 diff) override + { + HandleOutOfCombatRecovery(diff); + if (diagnosticTimer > diff) + diagnosticTimer -= diff; + else + diagnosticTimer = 0; + + if (updateTimer > diff) + { + updateTimer -= diff; + + // VIANO_TARGET_CONTROL_V1_4 + // Keep the explicitly assigned victim. UpdateVictim() may + // select another creature from the threat list. + if (me->GetVictim()) + HandleCombatActions(diff); + + return; + } + + updateTimer = UPDATE_INTERVAL; + + Player* owner = GetOwnerPlayer(); + if (!owner || !owner->IsInWorld() || !me->IsInMap(owner)) + { + TC_LOG_INFO("scripts", "[VianoAI] despawn: owner invalid or not on same map (ownerFound=%u)", owner ? 1u : 0u); + me->DespawnOrUnsummon(); + return; + } + + if (diagnosticTimer == 0) + { + diagnosticTimer = DIAGNOSTIC_INTERVAL; + LogDiagnostics(owner); + } + + if (me->getLevel() != owner->getLevel()) + { + me->SetLevel(owner->getLevel()); + me->UpdateLevelDependantStats(); + } + + if (me->GetDistance(owner) > TELEPORT_DISTANCE && !IsActivelyEngagedInCombat()) + { + // Keep the recovery teleport for geometry/jump problems, even + // if the group is already fighting elsewhere. Only the bot's + // own active engagement blocks teleporting. + me->GetMotionMaster()->Clear(); + + me->NearTeleportTo( + owner->GetPositionX() + 1.5f, + owner->GetPositionY(), + owner->GetPositionZ(), + owner->GetOrientation() + ); + + FollowOwner(owner); + return; + } + + Unit* desiredTarget = SelectAssistTarget(owner); + + if (desiredTarget) + { + if (me->GetVictim() != desiredTarget) + { + TC_LOG_INFO("scripts", + "[VianoAI] AttackStart requested: target='%s' entry=%u targetFaction=%u valid=%u meCombatBefore=%u", + desiredTarget->GetName().c_str(), desiredTarget->GetEntry(), desiredTarget->GetFaction(), + me->IsValidAttackTarget(desiredTarget) ? 1u : 0u, me->IsInCombat() ? 1u : 0u); + + AttackStart(desiredTarget); + + Unit* afterVictim = me->GetVictim(); + TC_LOG_INFO("scripts", "[VianoAI] AttackStart result: meCombatAfter=%u victim='%s'(%u)", + me->IsInCombat() ? 1u : 0u, UnitName(afterVictim), UnitEntry(afterVictim)); + } + } + else if (!IsActivelyEngagedInCombat()) + { + // A stale Core combat flag must not prevent recovery/following. + // We deliberately do not clear the flag here; OOC regeneration + // will be handled as a separate change. + FollowOwner(owner); + } + + // VIANO_TARGET_CONTROL_V1_4 + // Keep the explicitly assigned victim. UpdateVictim() may + // select another creature from the threat list. + if (me->GetVictim()) + HandleCombatActions(diff); + } + }; + + CreatureAI* GetAI(Creature* creature) const override + { + return new npc_horde_adventurer_meleeAI(creature); + } + }; +} + +void AddSC_viano_adventurers() +{ + using namespace VianoAdventurers; + new npc_horde_adventurer_melee(); +} diff --git a/src/server/scripts/Custom/viano_bot_commands.cpp b/src/server/scripts/Custom/viano_bot_commands.cpp index decd1afe75..458cc25bcf 100644 --- a/src/server/scripts/Custom/viano_bot_commands.cpp +++ b/src/server/scripts/Custom/viano_bot_commands.cpp @@ -23,6 +23,10 @@ namespace VianoBotCommands { + // VIANO_GROUP_CONTEXT_V1 + static constexpr bool VIANO_ENFORCE_GROUP_LIMIT = false; + static constexpr uint32 VIANO_MAX_EFFECTIVE_GROUP_SIZE = 5; + static char const* BoolText(bool value) { return value ? "yes" : "no"; @@ -364,6 +368,71 @@ namespace VianoBotCommands return true; } + + // VIANO_BOT_SUMMON_COMMAND_V1 + static bool HandleSummon(ChatHandler* handler, char const* args) + { + Player* player = GetCommandPlayer(handler); + if (!player) + return true; + + if (VIANO_ENFORCE_GROUP_LIMIT + && player->GetVianoEffectiveGroupSize() >= VIANO_MAX_EFFECTIVE_GROUP_SIZE) + { + handler->PSendSysMessage( + "Viano effective group is full (%u/%u).", + player->GetVianoEffectiveGroupSize(), + VIANO_MAX_EFFECTIVE_GROUP_SIZE); + return true; + } + + std::string role = args ? args : ""; + std::string::size_type first = role.find_first_not_of(" \t"); + std::string::size_type last = role.find_last_not_of(" \t"); + + if (first == std::string::npos) + { + handler->SendSysMessage("Usage: .viano bot summon melee|tank"); + return true; + } + + role = role.substr(first, last - first + 1); + + uint32 entry = 0; + if (role == "melee") + entry = 900001; + else if (role == "tank") + entry = 900002; + else + { + handler->SendSysMessage("Unknown role. Usage: .viano bot summon melee|tank"); + return true; + } + + Creature* summon = player->SummonCreature( + entry, + player->GetPositionX() + 1.5f, + player->GetPositionY(), + player->GetPositionZ(), + player->GetOrientation(), + TEMPSUMMON_MANUAL_DESPAWN, + 0); + + if (!summon) + { + handler->PSendSysMessage("Failed to summon Viano bot entry=%u.", entry); + return true; + } + + handler->PSendSysMessage( + "Summoned Viano bot role=%s entry=%u guid=%s", + role.c_str(), + entry, + GuidText(summon->GetGUID()).c_str()); + + return true; + } + class VianoBotCommandScript : public CommandScript { public: @@ -378,6 +447,7 @@ namespace VianoBotCommands { "combat", rbac::RBAC_PERM_COMMAND_GM, false, &HandleCombat, "" }, { "owner", rbac::RBAC_PERM_COMMAND_GM, false, &HandleOwner, "" }, { "role", rbac::RBAC_PERM_COMMAND_GM, false, &HandleRole, "" }, + { "summon", rbac::RBAC_PERM_COMMAND_GM, false, &HandleSummon, "" }, }; static std::vector vianoCommands = diff --git a/src/server/scripts/Custom/viano_bot_commands.cpp.bak-viano-group-context-v1-20261001-134352 b/src/server/scripts/Custom/viano_bot_commands.cpp.bak-viano-group-context-v1-20261001-134352 new file mode 100644 index 0000000000..d57f99a3bb --- /dev/null +++ b/src/server/scripts/Custom/viano_bot_commands.cpp.bak-viano-group-context-v1-20261001-134352 @@ -0,0 +1,457 @@ +/* + * Viano bot GM diagnostics. + * + * Read-only commands: + * .viano bot info + * .viano bot relations + * .viano bot combat + * .viano bot owner + * .viano bot role + */ + +#include "ScriptMgr.h" +#include "Chat.h" +#include "Creature.h" +#include "ObjectAccessor.h" +#include "ObjectMgr.h" +#include "Player.h" +#include "RBAC.h" +#include "TemporarySummon.h" +#include "Unit.h" + +#include + +namespace VianoBotCommands +{ + static char const* BoolText(bool value) + { + return value ? "yes" : "no"; + } + + static std::string GuidText(ObjectGuid const& guid) + { + return guid ? guid.ToString() : std::string(""); + } + + static char const* UnitName(Unit const* unit) + { + return unit ? unit->GetName().c_str() : ""; + } + + static uint32 UnitEntry(Unit const* unit) + { + if (Creature const* creature = unit ? unit->ToCreature() : nullptr) + return creature->GetEntry(); + + return 0; + } + + static std::string GetScriptName(Creature const* creature) + { + if (!creature) + return {}; + + if (CreatureTemplate const* creatureTemplate = creature->GetCreatureTemplate()) + return sObjectMgr->GetScriptName(creatureTemplate->ScriptID); + + return {}; + } + + static bool IsVianoBot(Creature const* creature) + { + if (!creature) + return false; + + std::string const scriptName = GetScriptName(creature); + + return scriptName.rfind("npc_horde_adventurer_", 0) == 0 + || scriptName.rfind("npc_alliance_adventurer_", 0) == 0; + } + + static Creature* GetSelectedVianoBot(ChatHandler* handler) + { + if (!handler || !handler->GetSession()) + return nullptr; + + Player* player = handler->GetSession()->GetPlayer(); + if (!player) + return nullptr; + + Unit* selected = player->GetSelectedUnit(); + Creature* creature = selected ? selected->ToCreature() : nullptr; + + if (!creature) + { + handler->SendSysMessage("Select a Viano bot creature first."); + return nullptr; + } + + if (!IsVianoBot(creature)) + { + handler->PSendSysMessage( + "Selected creature is not a recognized Viano bot. entry=%u script='%s'", + creature->GetEntry(), GetScriptName(creature).c_str()); + return nullptr; + } + + return creature; + } + + static Player* GetCommandPlayer(ChatHandler* handler) + { + return handler && handler->GetSession() ? handler->GetSession()->GetPlayer() : nullptr; + } + + static Unit* GetResolvedController(Creature* bot) + { + return bot ? bot->GetCharmerOrOwnerOrCreatorOrSelf() : nullptr; + } + + static char const* DetectRole(Creature const* bot) + { + std::string const scriptName = GetScriptName(bot); + + if (scriptName.find("_melee") != std::string::npos) + return "damage-melee"; + if (scriptName.find("_tank") != std::string::npos) + return "tank"; + if (scriptName.find("_healer") != std::string::npos) + return "healer"; + if (scriptName.find("_ranged") != std::string::npos) + return "damage-ranged"; + + return "unknown"; + } + + static void PrintHeader(ChatHandler* handler, Creature* bot, char const* section) + { + handler->PSendSysMessage( + "[VianoBot:%s] name='%s' entry=%u guid=%s script='%s'", + section, + bot->GetName().c_str(), + bot->GetEntry(), + GuidText(bot->GetGUID()).c_str(), + GetScriptName(bot).c_str()); + } + + static bool HandleInfo(ChatHandler* handler, char const* /*args*/) + { + Creature* bot = GetSelectedVianoBot(handler); + if (!bot) + return true; + + Player* gm = GetCommandPlayer(handler); + Unit* victim = bot->GetVictim(); + + PrintHeader(handler, bot, "info"); + + handler->PSendSysMessage( + "level=%u alive=%s health=%u/%u faction=%u react=%u combat=%s attackers=%u", + uint32(bot->getLevel()), + BoolText(bot->IsAlive()), + bot->GetHealth(), + bot->GetMaxHealth(), + bot->GetFaction(), + uint32(bot->GetReactState()), + BoolText(bot->IsInCombat()), + uint32(bot->getAttackers().size())); + + handler->PSendSysMessage( + "unitFlags=0x%08X immuneToPC=%s immuneToNPC=%s selectable=%s", + bot->GetUInt32Value(UNIT_FIELD_FLAGS), + BoolText(bot->IsImmuneToPC()), + BoolText(bot->IsImmuneToNPC()), + BoolText(!bot->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE))); + + handler->PSendSysMessage( + "victim='%s' entry=%u victimGuid=%s", + UnitName(victim), + UnitEntry(victim), + victim ? GuidText(victim->GetGUID()).c_str() : ""); + + if (gm) + handler->PSendSysMessage( + "distanceToGM=%.2f gmAssistTarget=%s gmAttackTarget=%s", + bot->GetDistance(gm), + BoolText(gm->IsValidAssistTarget(bot)), + BoolText(gm->IsValidAttackTarget(bot))); + + return true; + } + + static bool HandleRelations(ChatHandler* handler, char const* /*args*/) + { + Creature* bot = GetSelectedVianoBot(handler); + if (!bot) + return true; + + Player* player = GetCommandPlayer(handler); + if (!player) + return true; + + PrintHeader(handler, bot, "relations"); + + handler->PSendSysMessage( + "player='%s' playerGuid=%s botFaction=%u playerFaction=%u", + player->GetName().c_str(), + GuidText(player->GetGUID()).c_str(), + bot->GetFaction(), + player->GetFaction()); + + handler->PSendSysMessage( + "player->bot: party=%s raid=%s assist=%s attack=%s reaction=%d", + BoolText(player->IsInPartyWith(bot)), + BoolText(player->IsInRaidWith(bot)), + BoolText(player->IsValidAssistTarget(bot)), + BoolText(player->IsValidAttackTarget(bot)), + int32(player->GetReactionTo(bot))); + + handler->PSendSysMessage( + "bot->player: party=%s raid=%s assist=%s attack=%s reaction=%d", + BoolText(bot->IsInPartyWith(player)), + BoolText(bot->IsInRaidWith(player)), + BoolText(bot->IsValidAssistTarget(player)), + BoolText(bot->IsValidAttackTarget(player)), + int32(bot->GetReactionTo(player))); + + handler->PSendSysMessage( + "bot immuneToPC=%s immuneToNPC=%s unitFlags=0x%08X", + BoolText(bot->IsImmuneToPC()), + BoolText(bot->IsImmuneToNPC()), + bot->GetUInt32Value(UNIT_FIELD_FLAGS)); + + return true; + } + + static bool HandleCombat(ChatHandler* handler, char const* /*args*/) + { + Creature* bot = GetSelectedVianoBot(handler); + if (!bot) + return true; + + Player* player = GetCommandPlayer(handler); + Unit* victim = bot->GetVictim(); + + PrintHeader(handler, bot, "combat"); + + handler->PSendSysMessage( + "botCombat=%s victim='%s' entry=%u attackers=%u casting=%s", + BoolText(bot->IsInCombat()), + UnitName(victim), + UnitEntry(victim), + uint32(bot->getAttackers().size()), + BoolText(bot->HasUnitState(UNIT_STATE_CASTING))); + + uint32 index = 0; + for (Unit* attacker : bot->getAttackers()) + { + if (!attacker) + continue; + + handler->PSendSysMessage( + "attacker[%u]='%s' entry=%u guid=%s alive=%s victim='%s'(%u)", + index++, + attacker->GetName().c_str(), + UnitEntry(attacker), + GuidText(attacker->GetGUID()).c_str(), + BoolText(attacker->IsAlive()), + UnitName(attacker->GetVictim()), + UnitEntry(attacker->GetVictim())); + } + + if (index == 0) + handler->SendSysMessage("attackers="); + + if (player) + { + Unit* playerVictim = player->GetVictim(); + Unit* selected = player->GetSelectedUnit(); + + handler->PSendSysMessage( + "playerCombat=%s playerVictim='%s'(%u) selected='%s'(%u)", + BoolText(player->IsInCombat()), + UnitName(playerVictim), + UnitEntry(playerVictim), + UnitName(selected), + UnitEntry(selected)); + } + + return true; + } + + static bool HandleOwner(ChatHandler* handler, char const* /*args*/) + { + Creature* bot = GetSelectedVianoBot(handler); + if (!bot) + return true; + + Unit* resolved = GetResolvedController(bot); + Unit* owner = bot->GetOwner(); + Unit* creator = bot->GetCreatorGUID() + ? ObjectAccessor::GetUnit(*bot, bot->GetCreatorGUID()) + : nullptr; + Unit* charmer = bot->GetCharmer(); + + PrintHeader(handler, bot, "owner"); + + handler->PSendSysMessage( + "OwnerGUID=%s CreatorGUID=%s CharmerGUID=%s", + GuidText(bot->GetOwnerGUID()).c_str(), + GuidText(bot->GetCreatorGUID()).c_str(), + GuidText(bot->GetCharmerGUID()).c_str()); + + handler->PSendSysMessage( + "owner='%s'(%u) creator='%s'(%u) charmer='%s'(%u)", + UnitName(owner), UnitEntry(owner), + UnitName(creator), UnitEntry(creator), + UnitName(charmer), UnitEntry(charmer)); + + handler->PSendSysMessage( + "resolvedController='%s' entry=%u guid=%s typeId=%u", + UnitName(resolved), + UnitEntry(resolved), + resolved ? GuidText(resolved->GetGUID()).c_str() : "", + resolved ? uint32(resolved->GetTypeId()) : 0u); + + if (TempSummon* summon = bot->ToTempSummon()) + { + Unit* summoner = summon->GetSummoner(); + handler->PSendSysMessage( + "tempSummon=yes summoner='%s' entry=%u guid=%s", + UnitName(summoner), + UnitEntry(summoner), + summoner ? GuidText(summoner->GetGUID()).c_str() : ""); + } + else + handler->SendSysMessage("tempSummon=no"); + + return true; + } + + static bool HandleRole(ChatHandler* handler, char const* /*args*/) + { + Creature* bot = GetSelectedVianoBot(handler); + if (!bot) + return true; + + Player* player = GetCommandPlayer(handler); + Unit* victim = bot->GetVictim(); + + PrintHeader(handler, bot, "role"); + + handler->PSendSysMessage( + "role=%s react=%u level=%u combat=%s victim='%s'(%u)", + DetectRole(bot), + uint32(bot->GetReactState()), + uint32(bot->getLevel()), + BoolText(bot->IsInCombat()), + UnitName(victim), + UnitEntry(victim)); + + if (player) + { + handler->PSendSysMessage( + "ownerCandidate='%s' distance=%.2f canAssistBot=%s canAttackBot=%s", + player->GetName().c_str(), + bot->GetDistance(player), + BoolText(player->IsValidAssistTarget(bot)), + BoolText(player->IsValidAttackTarget(bot))); + } + + handler->SendSysMessage( + "role-specific AI state/cooldowns can be added here as tank/healer roles are implemented."); + + return true; + } + + + // VIANO_BOT_SUMMON_COMMAND_V1 + static bool HandleSummon(ChatHandler* handler, char const* args) + { + Player* player = GetCommandPlayer(handler); + if (!player) + return true; + + std::string role = args ? args : ""; + std::string::size_type first = role.find_first_not_of(" \t"); + std::string::size_type last = role.find_last_not_of(" \t"); + + if (first == std::string::npos) + { + handler->SendSysMessage("Usage: .viano bot summon melee|tank"); + return true; + } + + role = role.substr(first, last - first + 1); + + uint32 entry = 0; + if (role == "melee") + entry = 900001; + else if (role == "tank") + entry = 900002; + else + { + handler->SendSysMessage("Unknown role. Usage: .viano bot summon melee|tank"); + return true; + } + + Creature* summon = player->SummonCreature( + entry, + player->GetPositionX() + 1.5f, + player->GetPositionY(), + player->GetPositionZ(), + player->GetOrientation(), + TEMPSUMMON_MANUAL_DESPAWN, + 0); + + if (!summon) + { + handler->PSendSysMessage("Failed to summon Viano bot entry=%u.", entry); + return true; + } + + handler->PSendSysMessage( + "Summoned Viano bot role=%s entry=%u guid=%s", + role.c_str(), + entry, + GuidText(summon->GetGUID()).c_str()); + + return true; + } + + class VianoBotCommandScript : public CommandScript + { + public: + VianoBotCommandScript() : CommandScript("VianoBotCommandScript") { } + + std::vector GetCommands() const override + { + static std::vector botCommands = + { + { "info", rbac::RBAC_PERM_COMMAND_GM, false, &HandleInfo, "" }, + { "relations", rbac::RBAC_PERM_COMMAND_GM, false, &HandleRelations, "" }, + { "combat", rbac::RBAC_PERM_COMMAND_GM, false, &HandleCombat, "" }, + { "owner", rbac::RBAC_PERM_COMMAND_GM, false, &HandleOwner, "" }, + { "role", rbac::RBAC_PERM_COMMAND_GM, false, &HandleRole, "" }, + { "summon", rbac::RBAC_PERM_COMMAND_GM, false, &HandleSummon, "" }, + }; + + static std::vector vianoCommands = + { + { "bot", rbac::RBAC_PERM_COMMAND_GM, false, nullptr, "", botCommands }, + }; + + static std::vector commandTable = + { + { "viano", rbac::RBAC_PERM_COMMAND_GM, false, nullptr, "", vianoCommands }, + }; + + return commandTable; + } + }; +} + +void AddSC_viano_bot_commands() +{ + new VianoBotCommands::VianoBotCommandScript(); +} diff --git a/src/server/scripts/Custom/viano_bot_commands.cpp.bak-viano-tank-v1-20261001-083107 b/src/server/scripts/Custom/viano_bot_commands.cpp.bak-viano-tank-v1-20261001-083107 new file mode 100644 index 0000000000..decd1afe75 --- /dev/null +++ b/src/server/scripts/Custom/viano_bot_commands.cpp.bak-viano-tank-v1-20261001-083107 @@ -0,0 +1,401 @@ +/* + * Viano bot GM diagnostics. + * + * Read-only commands: + * .viano bot info + * .viano bot relations + * .viano bot combat + * .viano bot owner + * .viano bot role + */ + +#include "ScriptMgr.h" +#include "Chat.h" +#include "Creature.h" +#include "ObjectAccessor.h" +#include "ObjectMgr.h" +#include "Player.h" +#include "RBAC.h" +#include "TemporarySummon.h" +#include "Unit.h" + +#include + +namespace VianoBotCommands +{ + static char const* BoolText(bool value) + { + return value ? "yes" : "no"; + } + + static std::string GuidText(ObjectGuid const& guid) + { + return guid ? guid.ToString() : std::string(""); + } + + static char const* UnitName(Unit const* unit) + { + return unit ? unit->GetName().c_str() : ""; + } + + static uint32 UnitEntry(Unit const* unit) + { + if (Creature const* creature = unit ? unit->ToCreature() : nullptr) + return creature->GetEntry(); + + return 0; + } + + static std::string GetScriptName(Creature const* creature) + { + if (!creature) + return {}; + + if (CreatureTemplate const* creatureTemplate = creature->GetCreatureTemplate()) + return sObjectMgr->GetScriptName(creatureTemplate->ScriptID); + + return {}; + } + + static bool IsVianoBot(Creature const* creature) + { + if (!creature) + return false; + + std::string const scriptName = GetScriptName(creature); + + return scriptName.rfind("npc_horde_adventurer_", 0) == 0 + || scriptName.rfind("npc_alliance_adventurer_", 0) == 0; + } + + static Creature* GetSelectedVianoBot(ChatHandler* handler) + { + if (!handler || !handler->GetSession()) + return nullptr; + + Player* player = handler->GetSession()->GetPlayer(); + if (!player) + return nullptr; + + Unit* selected = player->GetSelectedUnit(); + Creature* creature = selected ? selected->ToCreature() : nullptr; + + if (!creature) + { + handler->SendSysMessage("Select a Viano bot creature first."); + return nullptr; + } + + if (!IsVianoBot(creature)) + { + handler->PSendSysMessage( + "Selected creature is not a recognized Viano bot. entry=%u script='%s'", + creature->GetEntry(), GetScriptName(creature).c_str()); + return nullptr; + } + + return creature; + } + + static Player* GetCommandPlayer(ChatHandler* handler) + { + return handler && handler->GetSession() ? handler->GetSession()->GetPlayer() : nullptr; + } + + static Unit* GetResolvedController(Creature* bot) + { + return bot ? bot->GetCharmerOrOwnerOrCreatorOrSelf() : nullptr; + } + + static char const* DetectRole(Creature const* bot) + { + std::string const scriptName = GetScriptName(bot); + + if (scriptName.find("_melee") != std::string::npos) + return "damage-melee"; + if (scriptName.find("_tank") != std::string::npos) + return "tank"; + if (scriptName.find("_healer") != std::string::npos) + return "healer"; + if (scriptName.find("_ranged") != std::string::npos) + return "damage-ranged"; + + return "unknown"; + } + + static void PrintHeader(ChatHandler* handler, Creature* bot, char const* section) + { + handler->PSendSysMessage( + "[VianoBot:%s] name='%s' entry=%u guid=%s script='%s'", + section, + bot->GetName().c_str(), + bot->GetEntry(), + GuidText(bot->GetGUID()).c_str(), + GetScriptName(bot).c_str()); + } + + static bool HandleInfo(ChatHandler* handler, char const* /*args*/) + { + Creature* bot = GetSelectedVianoBot(handler); + if (!bot) + return true; + + Player* gm = GetCommandPlayer(handler); + Unit* victim = bot->GetVictim(); + + PrintHeader(handler, bot, "info"); + + handler->PSendSysMessage( + "level=%u alive=%s health=%u/%u faction=%u react=%u combat=%s attackers=%u", + uint32(bot->getLevel()), + BoolText(bot->IsAlive()), + bot->GetHealth(), + bot->GetMaxHealth(), + bot->GetFaction(), + uint32(bot->GetReactState()), + BoolText(bot->IsInCombat()), + uint32(bot->getAttackers().size())); + + handler->PSendSysMessage( + "unitFlags=0x%08X immuneToPC=%s immuneToNPC=%s selectable=%s", + bot->GetUInt32Value(UNIT_FIELD_FLAGS), + BoolText(bot->IsImmuneToPC()), + BoolText(bot->IsImmuneToNPC()), + BoolText(!bot->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE))); + + handler->PSendSysMessage( + "victim='%s' entry=%u victimGuid=%s", + UnitName(victim), + UnitEntry(victim), + victim ? GuidText(victim->GetGUID()).c_str() : ""); + + if (gm) + handler->PSendSysMessage( + "distanceToGM=%.2f gmAssistTarget=%s gmAttackTarget=%s", + bot->GetDistance(gm), + BoolText(gm->IsValidAssistTarget(bot)), + BoolText(gm->IsValidAttackTarget(bot))); + + return true; + } + + static bool HandleRelations(ChatHandler* handler, char const* /*args*/) + { + Creature* bot = GetSelectedVianoBot(handler); + if (!bot) + return true; + + Player* player = GetCommandPlayer(handler); + if (!player) + return true; + + PrintHeader(handler, bot, "relations"); + + handler->PSendSysMessage( + "player='%s' playerGuid=%s botFaction=%u playerFaction=%u", + player->GetName().c_str(), + GuidText(player->GetGUID()).c_str(), + bot->GetFaction(), + player->GetFaction()); + + handler->PSendSysMessage( + "player->bot: party=%s raid=%s assist=%s attack=%s reaction=%d", + BoolText(player->IsInPartyWith(bot)), + BoolText(player->IsInRaidWith(bot)), + BoolText(player->IsValidAssistTarget(bot)), + BoolText(player->IsValidAttackTarget(bot)), + int32(player->GetReactionTo(bot))); + + handler->PSendSysMessage( + "bot->player: party=%s raid=%s assist=%s attack=%s reaction=%d", + BoolText(bot->IsInPartyWith(player)), + BoolText(bot->IsInRaidWith(player)), + BoolText(bot->IsValidAssistTarget(player)), + BoolText(bot->IsValidAttackTarget(player)), + int32(bot->GetReactionTo(player))); + + handler->PSendSysMessage( + "bot immuneToPC=%s immuneToNPC=%s unitFlags=0x%08X", + BoolText(bot->IsImmuneToPC()), + BoolText(bot->IsImmuneToNPC()), + bot->GetUInt32Value(UNIT_FIELD_FLAGS)); + + return true; + } + + static bool HandleCombat(ChatHandler* handler, char const* /*args*/) + { + Creature* bot = GetSelectedVianoBot(handler); + if (!bot) + return true; + + Player* player = GetCommandPlayer(handler); + Unit* victim = bot->GetVictim(); + + PrintHeader(handler, bot, "combat"); + + handler->PSendSysMessage( + "botCombat=%s victim='%s' entry=%u attackers=%u casting=%s", + BoolText(bot->IsInCombat()), + UnitName(victim), + UnitEntry(victim), + uint32(bot->getAttackers().size()), + BoolText(bot->HasUnitState(UNIT_STATE_CASTING))); + + uint32 index = 0; + for (Unit* attacker : bot->getAttackers()) + { + if (!attacker) + continue; + + handler->PSendSysMessage( + "attacker[%u]='%s' entry=%u guid=%s alive=%s victim='%s'(%u)", + index++, + attacker->GetName().c_str(), + UnitEntry(attacker), + GuidText(attacker->GetGUID()).c_str(), + BoolText(attacker->IsAlive()), + UnitName(attacker->GetVictim()), + UnitEntry(attacker->GetVictim())); + } + + if (index == 0) + handler->SendSysMessage("attackers="); + + if (player) + { + Unit* playerVictim = player->GetVictim(); + Unit* selected = player->GetSelectedUnit(); + + handler->PSendSysMessage( + "playerCombat=%s playerVictim='%s'(%u) selected='%s'(%u)", + BoolText(player->IsInCombat()), + UnitName(playerVictim), + UnitEntry(playerVictim), + UnitName(selected), + UnitEntry(selected)); + } + + return true; + } + + static bool HandleOwner(ChatHandler* handler, char const* /*args*/) + { + Creature* bot = GetSelectedVianoBot(handler); + if (!bot) + return true; + + Unit* resolved = GetResolvedController(bot); + Unit* owner = bot->GetOwner(); + Unit* creator = bot->GetCreatorGUID() + ? ObjectAccessor::GetUnit(*bot, bot->GetCreatorGUID()) + : nullptr; + Unit* charmer = bot->GetCharmer(); + + PrintHeader(handler, bot, "owner"); + + handler->PSendSysMessage( + "OwnerGUID=%s CreatorGUID=%s CharmerGUID=%s", + GuidText(bot->GetOwnerGUID()).c_str(), + GuidText(bot->GetCreatorGUID()).c_str(), + GuidText(bot->GetCharmerGUID()).c_str()); + + handler->PSendSysMessage( + "owner='%s'(%u) creator='%s'(%u) charmer='%s'(%u)", + UnitName(owner), UnitEntry(owner), + UnitName(creator), UnitEntry(creator), + UnitName(charmer), UnitEntry(charmer)); + + handler->PSendSysMessage( + "resolvedController='%s' entry=%u guid=%s typeId=%u", + UnitName(resolved), + UnitEntry(resolved), + resolved ? GuidText(resolved->GetGUID()).c_str() : "", + resolved ? uint32(resolved->GetTypeId()) : 0u); + + if (TempSummon* summon = bot->ToTempSummon()) + { + Unit* summoner = summon->GetSummoner(); + handler->PSendSysMessage( + "tempSummon=yes summoner='%s' entry=%u guid=%s", + UnitName(summoner), + UnitEntry(summoner), + summoner ? GuidText(summoner->GetGUID()).c_str() : ""); + } + else + handler->SendSysMessage("tempSummon=no"); + + return true; + } + + static bool HandleRole(ChatHandler* handler, char const* /*args*/) + { + Creature* bot = GetSelectedVianoBot(handler); + if (!bot) + return true; + + Player* player = GetCommandPlayer(handler); + Unit* victim = bot->GetVictim(); + + PrintHeader(handler, bot, "role"); + + handler->PSendSysMessage( + "role=%s react=%u level=%u combat=%s victim='%s'(%u)", + DetectRole(bot), + uint32(bot->GetReactState()), + uint32(bot->getLevel()), + BoolText(bot->IsInCombat()), + UnitName(victim), + UnitEntry(victim)); + + if (player) + { + handler->PSendSysMessage( + "ownerCandidate='%s' distance=%.2f canAssistBot=%s canAttackBot=%s", + player->GetName().c_str(), + bot->GetDistance(player), + BoolText(player->IsValidAssistTarget(bot)), + BoolText(player->IsValidAttackTarget(bot))); + } + + handler->SendSysMessage( + "role-specific AI state/cooldowns can be added here as tank/healer roles are implemented."); + + return true; + } + + class VianoBotCommandScript : public CommandScript + { + public: + VianoBotCommandScript() : CommandScript("VianoBotCommandScript") { } + + std::vector GetCommands() const override + { + static std::vector botCommands = + { + { "info", rbac::RBAC_PERM_COMMAND_GM, false, &HandleInfo, "" }, + { "relations", rbac::RBAC_PERM_COMMAND_GM, false, &HandleRelations, "" }, + { "combat", rbac::RBAC_PERM_COMMAND_GM, false, &HandleCombat, "" }, + { "owner", rbac::RBAC_PERM_COMMAND_GM, false, &HandleOwner, "" }, + { "role", rbac::RBAC_PERM_COMMAND_GM, false, &HandleRole, "" }, + }; + + static std::vector vianoCommands = + { + { "bot", rbac::RBAC_PERM_COMMAND_GM, false, nullptr, "", botCommands }, + }; + + static std::vector commandTable = + { + { "viano", rbac::RBAC_PERM_COMMAND_GM, false, nullptr, "", vianoCommands }, + }; + + return commandTable; + } + }; +} + +void AddSC_viano_bot_commands() +{ + new VianoBotCommands::VianoBotCommandScript(); +}