refactor(Core/Combat): Port TrinityCore heap-based threat system (#24715)
Co-authored-by: blinkysc <blinkysc@users.noreply.github.com> Co-authored-by: Treeston <treeston.mmoc@gmail.com> Co-authored-by: killerwife <killerwife@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -221,16 +221,12 @@ bool AssistDelayEvent::Execute(uint64 /*e_time*/, uint32 /*p_time*/)
|
||||
if (assistant && assistant->CanAssistTo(m_owner, victim))
|
||||
{
|
||||
assistant->SetNoCallAssistance(true);
|
||||
assistant->CombatStart(victim);
|
||||
if (assistant->IsAIEnabled)
|
||||
{
|
||||
assistant->AI()->AttackStart(victim);
|
||||
assistant->EngageWithTarget(victim);
|
||||
|
||||
// When nearby mobs aggro from another mob's initial call for assistance
|
||||
// their leash timers become linked and attacking one will keep the rest from evading.
|
||||
if (assistant->GetVictim())
|
||||
assistant->SetLastLeashExtensionTimePtr(m_owner->GetLastLeashExtensionTimePtr());
|
||||
}
|
||||
// When nearby mobs aggro from another mob's initial call for assistance
|
||||
// their leash timers become linked and attacking one will keep the rest from evading.
|
||||
if (assistant->GetVictim())
|
||||
assistant->SetLastLeashExtensionTimePtr(m_owner->GetLastLeashExtensionTimePtr());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -267,7 +263,7 @@ Creature::Creature(): Unit(), MovableMapObject(), m_groupLootTimer(0), lootingGr
|
||||
m_transportCheckTimer(1000), lootPickPocketRestoreTime(0), m_combatPulseTime(0), m_combatPulseDelay(0), m_reactState(REACT_AGGRESSIVE), m_defaultMovementType(IDLE_MOTION_TYPE),
|
||||
m_spawnId(0), m_equipmentId(0), m_originalEquipmentId(0), m_alreadyCallForHelp(false), m_AlreadyCallAssistance(false),
|
||||
m_AlreadySearchedAssistance(false), m_regenHealth(true), m_regenPower(true), m_AI_locked(false), m_meleeDamageSchoolMask(SPELL_SCHOOL_MASK_NORMAL), m_originalEntry(0), _gossipMenuId(0), m_moveInLineOfSightDisabled(false), m_moveInLineOfSightStrictlyDisabled(false),
|
||||
m_homePosition(), m_transportHomePosition(), m_creatureInfo(nullptr), m_creatureData(nullptr), m_detectionDistance(20.0f),_sparringPct(0.0f), m_waypointID(0), m_path_id(0), m_formation(nullptr), m_lastLeashExtensionTime(nullptr), m_cannotReachTimer(0),
|
||||
m_homePosition(), m_transportHomePosition(), m_creatureInfo(nullptr), m_creatureData(nullptr), m_detectionDistance(20.0f),_sparringPct(0.0f), m_waypointID(0), m_path_id(0), m_formation(nullptr), m_lastLeashExtensionTime(nullptr),
|
||||
_isMissingSwimmingFlagOutOfCombat(false), m_assistanceTimer(0), _playerDamageReq(0), _damagedByPlayer(false), _isCombatMovementAllowed(true)
|
||||
{
|
||||
m_regenTimer = CREATURE_REGEN_INTERVAL;
|
||||
@@ -529,6 +525,8 @@ bool Creature::InitEntry(uint32 Entry, const CreatureData* data)
|
||||
for (uint8 i = 0; i < MAX_CREATURE_SPELLS; ++i)
|
||||
m_spells[i] = GetCreatureTemplate()->spells[i];
|
||||
|
||||
GetThreatMgr().Initialize();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -722,6 +720,8 @@ void Creature::Update(uint32 diff)
|
||||
if (!IsAlive())
|
||||
break;
|
||||
|
||||
GetThreatMgr().Update(diff);
|
||||
|
||||
// if creature is charmed, switch to charmed AI
|
||||
if (NeedChangeAI)
|
||||
{
|
||||
@@ -865,45 +865,7 @@ void Creature::Update(uint32 diff)
|
||||
m_regenTimer += CREATURE_REGEN_INTERVAL;
|
||||
}
|
||||
|
||||
if (CanNotReachTarget() && !IsInEvadeMode())
|
||||
{
|
||||
m_cannotReachTimer += diff;
|
||||
if (m_cannotReachTimer >= (sWorld->getIntConfig(CONFIG_NPC_EVADE_IF_NOT_REACHABLE) * IN_MILLISECONDS))
|
||||
{
|
||||
Player* cannotReachPlayer = ObjectAccessor::GetPlayer(*this, m_cannotReachTarget);
|
||||
if (cannotReachPlayer && IsEngagedBy(cannotReachPlayer) && IsAIEnabled && AI()->OnTeleportUnreacheablePlayer(cannotReachPlayer))
|
||||
{
|
||||
SetCannotReachTarget();
|
||||
}
|
||||
else if (!GetMap()->IsRaid())
|
||||
{
|
||||
auto EnterEvade = [&]()
|
||||
{
|
||||
if (CreatureAI* ai = AI())
|
||||
{
|
||||
ai->EnterEvadeMode(CreatureAI::EvadeReason::EVADE_REASON_NO_PATH);
|
||||
}
|
||||
};
|
||||
|
||||
if (GetThreatMgr().GetThreatListSize() <= 1)
|
||||
{
|
||||
EnterEvade();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (HostileReference* ref = GetThreatMgr().GetOnlineContainer().getReferenceByTarget(m_cannotReachTarget))
|
||||
{
|
||||
ref->removeReference();
|
||||
SetCannotReachTarget();
|
||||
}
|
||||
else
|
||||
{
|
||||
EnterEvade();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Evade timer is now handled by CombatManager::Update() which calls UnitAI::EvadeTimerExpired()
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -2792,6 +2754,80 @@ void Creature::SetInCombatWithZone()
|
||||
AI()->DoZoneInCombat();
|
||||
}
|
||||
|
||||
void Creature::AtEngage(Unit* target)
|
||||
{
|
||||
Unit::AtEngage(target);
|
||||
|
||||
// If we somehow engage in combat with a player while immune, remove immunity so they can fight back
|
||||
if (target && IsImmuneToPC() && target->GetCharmerOrOwnerPlayerOrPlayerItself())
|
||||
SetImmuneToPC(false);
|
||||
|
||||
if (!IsStandState())
|
||||
SetStandState(UNIT_STAND_STATE_STAND);
|
||||
|
||||
if (!(GetCreatureTemplate()->type_flags & CREATURE_TYPE_FLAG_ALLOW_MOUNTED_COMBAT))
|
||||
Dismount();
|
||||
|
||||
RefreshSwimmingFlag();
|
||||
|
||||
if (IsPet() || IsGuardian()) // update pets' speed for catchup OOC speed
|
||||
{
|
||||
UpdateSpeed(MOVE_RUN, true);
|
||||
UpdateSpeed(MOVE_SWIM, true);
|
||||
UpdateSpeed(MOVE_FLIGHT, true);
|
||||
}
|
||||
|
||||
MovementGeneratorType const movetype = GetMotionMaster()->GetCurrentMovementGeneratorType();
|
||||
if (movetype == WAYPOINT_MOTION_TYPE || movetype == ESCORT_MOTION_TYPE || (IsAIEnabled && AI()->IsEscorted()))
|
||||
{
|
||||
SetHomePosition(GetPosition());
|
||||
|
||||
// if its a vehicle, set the home position of every creature passenger at engage
|
||||
// so that they are in combat range if hostile
|
||||
if (Vehicle* vehicle = GetVehicleKit())
|
||||
{
|
||||
for (auto& seat : vehicle->Seats)
|
||||
if (Unit* passenger = ObjectAccessor::GetUnit(*this, seat.second.Passenger.Guid))
|
||||
if (Creature* creature = passenger->ToCreature())
|
||||
creature->SetHomePosition(GetPosition());
|
||||
}
|
||||
}
|
||||
|
||||
if (CreatureAI* ai = AI())
|
||||
{
|
||||
ai->JustEngagedWith(target);
|
||||
UpdateLeashExtensionTime();
|
||||
|
||||
if (CreatureGroup* formation = GetFormation())
|
||||
formation->MemberEngagingTarget(this, target);
|
||||
|
||||
sScriptMgr->OnUnitEnterCombat(this, target);
|
||||
}
|
||||
}
|
||||
|
||||
void Creature::AtDisengage()
|
||||
{
|
||||
Unit::AtDisengage();
|
||||
|
||||
ClearUnitState(UNIT_STATE_ATTACK_PLAYER);
|
||||
if (IsAlive() && HasDynamicFlag(UNIT_DYNFLAG_TAPPED))
|
||||
ReplaceAllDynamicFlags(GetCreatureTemplate()->dynamicflags);
|
||||
|
||||
if (IsPet() || IsGuardian()) // update pets' speed for catchup OOC speed
|
||||
{
|
||||
UpdateSpeed(MOVE_RUN, true);
|
||||
UpdateSpeed(MOVE_SWIM, true);
|
||||
UpdateSpeed(MOVE_FLIGHT, true);
|
||||
}
|
||||
}
|
||||
|
||||
bool Creature::IsEngaged() const
|
||||
{
|
||||
if (CreatureAI const* ai = AI())
|
||||
return ai->IsEngaged();
|
||||
return false;
|
||||
}
|
||||
|
||||
void Creature::ProhibitSpellSchool(SpellSchoolMask idSchoolMask, uint32 unTimeMs)
|
||||
{
|
||||
for (uint8 i = SPELL_SCHOOL_NORMAL; i < MAX_SPELL_SCHOOL; ++i)
|
||||
@@ -3393,7 +3429,9 @@ void Creature::SetDisplayFromModel(uint32 modelIdx)
|
||||
|
||||
void Creature::SetTarget(ObjectGuid guid)
|
||||
{
|
||||
if (!_focusSpell)
|
||||
if (_focusSpell)
|
||||
_spellFocusTarget = guid;
|
||||
else
|
||||
SetGuidValue(UNIT_FIELD_TARGET, guid);
|
||||
}
|
||||
|
||||
@@ -3404,6 +3442,7 @@ void Creature::FocusTarget(Spell const* focusSpell, WorldObject const* target)
|
||||
return;
|
||||
|
||||
_focusSpell = focusSpell;
|
||||
_spellFocusTarget = GetGuidValue(UNIT_FIELD_TARGET);
|
||||
|
||||
SetGuidValue(UNIT_FIELD_TARGET, this == target ? ObjectGuid::Empty : target->GetGUID());
|
||||
if (focusSpell->GetSpellInfo()->HasAttribute(SPELL_ATTR5_AI_DOESNT_FACE_TARGET))
|
||||
@@ -3433,10 +3472,8 @@ void Creature::ReleaseFocus(Spell const* focusSpell)
|
||||
return;
|
||||
|
||||
_focusSpell = nullptr;
|
||||
if (Unit* victim = GetVictim())
|
||||
SetGuidValue(UNIT_FIELD_TARGET, victim->GetGUID());
|
||||
else
|
||||
SetGuidValue(UNIT_FIELD_TARGET, ObjectGuid::Empty);
|
||||
SetGuidValue(UNIT_FIELD_TARGET, _spellFocusTarget);
|
||||
_spellFocusTarget.Clear();
|
||||
|
||||
if (focusSpell->GetSpellInfo()->HasAttribute(SPELL_ATTR5_AI_DOESNT_FACE_TARGET))
|
||||
ClearUnitState(UNIT_STATE_ROTATING);
|
||||
@@ -3509,16 +3546,19 @@ bool Creature::IsMovementPreventedByCasting() const
|
||||
void Creature::SetCannotReachTarget(ObjectGuid const& cannotReach)
|
||||
{
|
||||
if (cannotReach == m_cannotReachTarget)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_cannotReachTarget = cannotReach;
|
||||
m_cannotReachTimer = 0;
|
||||
|
||||
if (cannotReach)
|
||||
{
|
||||
LOG_DEBUG("entities.unit", "Creature::SetCannotReachTarget() called with true. Details: {}", GetDebugInfo());
|
||||
LOG_DEBUG("entities.unit", "Creature::SetCannotReachTarget() called with target {}. Details: {}", cannotReach.ToString(), GetDebugInfo());
|
||||
if (!GetCombatManager().IsInEvadeMode())
|
||||
GetCombatManager().StartEvadeTimer();
|
||||
}
|
||||
else
|
||||
{
|
||||
GetCombatManager().StopEvade();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3529,12 +3569,7 @@ bool Creature::CanNotReachTarget() const
|
||||
|
||||
bool Creature::IsNotReachableAndNeedRegen() const
|
||||
{
|
||||
if (CanNotReachTarget())
|
||||
{
|
||||
return m_cannotReachTimer >= (sWorld->getIntConfig(CONFIG_NPC_REGEN_TIME_IF_NOT_REACHABLE_IN_RAID) * IN_MILLISECONDS);
|
||||
}
|
||||
|
||||
return false;
|
||||
return CanNotReachTarget() && GetCombatManager().IsEvadeRegen();
|
||||
}
|
||||
|
||||
std::shared_ptr<time_t> const& Creature::GetLastLeashExtensionTimePtr() const
|
||||
|
||||
@@ -313,6 +313,11 @@ public:
|
||||
|
||||
void SetInCombatWithZone();
|
||||
|
||||
// Engagement callbacks (called from CreatureAI::EngagementStart/EngagementOver)
|
||||
void AtEngage(Unit* target) override;
|
||||
void AtDisengage() override;
|
||||
[[nodiscard]] bool IsEngaged() const override;
|
||||
|
||||
[[nodiscard]] bool hasQuest(uint32 quest_id) const override;
|
||||
[[nodiscard]] bool hasInvolvedQuest(uint32 quest_id) const override;
|
||||
|
||||
@@ -330,6 +335,7 @@ public:
|
||||
|
||||
void SetCannotReachTarget(ObjectGuid const& target = ObjectGuid::Empty);
|
||||
[[nodiscard]] bool CanNotReachTarget() const;
|
||||
[[nodiscard]] ObjectGuid const& GetCannotReachTarget() const { return m_cannotReachTarget; }
|
||||
[[nodiscard]] bool IsNotReachableAndNeedRegen() const;
|
||||
|
||||
void SetPosition(float x, float y, float z, float o);
|
||||
@@ -521,9 +527,9 @@ private:
|
||||
mutable std::shared_ptr<time_t> m_lastLeashExtensionTime;
|
||||
|
||||
ObjectGuid m_cannotReachTarget;
|
||||
uint32 m_cannotReachTimer;
|
||||
|
||||
Spell const* _focusSpell; ///> Locks the target during spell cast for proper facing
|
||||
ObjectGuid _spellFocusTarget; ///> Saved target during spell focus for restoration
|
||||
|
||||
CreatureTextRepeatGroup m_textRepeat;
|
||||
|
||||
|
||||
@@ -227,9 +227,9 @@ void CreatureGroup::MemberEngagingTarget(Creature* member, Unit* target)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pMember->IsValidAttackTarget(target) && pMember->AI())
|
||||
if (pMember->IsValidAttackTarget(target))
|
||||
{
|
||||
pMember->AI()->AttackStart(target);
|
||||
pMember->EngageWithTarget(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2318,6 +2318,7 @@ bool Pet::Create(ObjectGuid::LowType guidlow, Map* map, uint32 phaseMask, uint32
|
||||
// Force regen flag for player pets, just like we do for players themselves
|
||||
SetUnitFlag2(UNIT_FLAG2_REGENERATE_POWER);
|
||||
SetSheath(SHEATH_STATE_MELEE);
|
||||
GetThreatMgr().Initialize();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2161,7 +2161,7 @@ void Player::SetInWater(bool apply)
|
||||
// remove auras that need water/land
|
||||
RemoveAurasWithInterruptFlags(apply ? AURA_INTERRUPT_FLAG_NOT_ABOVEWATER : AURA_INTERRUPT_FLAG_NOT_UNDERWATER);
|
||||
|
||||
getHostileRefMgr().updateThreatTables();
|
||||
GetThreatMgr().EvaluateSuppressed();
|
||||
|
||||
if (InstanceScript* instance = GetInstanceScript())
|
||||
instance->OnPlayerInWaterStateUpdate(this, apply);
|
||||
@@ -2203,7 +2203,6 @@ void Player::SetGameMaster(bool on)
|
||||
{
|
||||
if (GetSession()->IsGMAccount())
|
||||
pet->SetFaction(FACTION_FRIENDLY);
|
||||
pet->getHostileRefMgr().setOnlineOfflineState(false);
|
||||
}
|
||||
if (HasByteFlag(UNIT_FIELD_BYTES_2, 1, UNIT_BYTE2_FLAG_FFA_PVP))
|
||||
{
|
||||
@@ -2212,7 +2211,6 @@ void Player::SetGameMaster(bool on)
|
||||
}
|
||||
ResetContestedPvP();
|
||||
|
||||
getHostileRefMgr().setOnlineOfflineState(false);
|
||||
CombatStopWithPets();
|
||||
|
||||
SetPhaseMask(uint32(PHASEMASK_ANYWHERE), false); // see and visible in all phases
|
||||
@@ -2234,10 +2232,7 @@ void Player::SetGameMaster(bool on)
|
||||
RemoveUnitFlag2(UNIT_FLAG2_ALLOW_CHEAT_SPELLS);
|
||||
|
||||
if (Pet* pet = GetPet())
|
||||
{
|
||||
pet->SetFaction(GetFaction());
|
||||
pet->getHostileRefMgr().setOnlineOfflineState(true);
|
||||
}
|
||||
|
||||
// restore FFA PvP Server state
|
||||
if (sWorld->IsFFAPvPRealm())
|
||||
@@ -2251,7 +2246,6 @@ void Player::SetGameMaster(bool on)
|
||||
// restore FFA PvP area state, remove not allowed for GM mounts
|
||||
UpdateArea(m_areaUpdateId);
|
||||
|
||||
getHostileRefMgr().setOnlineOfflineState(true);
|
||||
SetServerSideVisibilityDetect(SERVERSIDE_VISIBILITY_GM, SEC_PLAYER);
|
||||
}
|
||||
|
||||
@@ -2268,7 +2262,6 @@ void Player::SetGMVisible(bool on)
|
||||
m_ExtraFlags &= ~PLAYER_EXTRA_GM_INVISIBLE;
|
||||
SetServerSideVisibility(SERVERSIDE_VISIBILITY_GM, SEC_PLAYER);
|
||||
|
||||
getHostileRefMgr().setOnlineOfflineState(false);
|
||||
CombatStopWithPets();
|
||||
}
|
||||
else
|
||||
@@ -10380,7 +10373,6 @@ void Player::CleanupAfterTaxiFlight()
|
||||
m_taxi.ClearTaxiDestinations(); // not destinations, clear source node
|
||||
Dismount();
|
||||
RemoveUnitFlag(UNIT_FLAG_DISABLE_MOVE | UNIT_FLAG_TAXI_FLIGHT);
|
||||
getHostileRefMgr().setOnlineOfflineState(true);
|
||||
}
|
||||
|
||||
void Player::ContinueTaxiFlight()
|
||||
|
||||
@@ -402,13 +402,11 @@ void Player::Update(uint32 p_time)
|
||||
// != GetCharmGUID())))
|
||||
RemovePet(pet, PET_SAVE_NOT_IN_SLOT, true);
|
||||
|
||||
// pussywizard:
|
||||
if (m_hostileReferenceCheckTimer <= p_time)
|
||||
{
|
||||
m_hostileReferenceCheckTimer = 15000;
|
||||
if (!GetMap()->IsDungeon())
|
||||
getHostileRefMgr().deleteReferencesOutOfRange(
|
||||
GetVisibilityRange());
|
||||
GetCombatManager().EndCombatBeyondRange(GetVisibilityRange(), true);
|
||||
}
|
||||
else
|
||||
m_hostileReferenceCheckTimer -= p_time;
|
||||
@@ -1238,7 +1236,8 @@ void Player::UpdateArea(uint32 newArea)
|
||||
{
|
||||
SetByteFlag(UNIT_FIELD_BYTES_2, 1, UNIT_BYTE2_FLAG_SANCTUARY);
|
||||
pvpInfo.IsInNoPvPArea = true;
|
||||
CombatStopWithPets();
|
||||
if (!duel && GetCombatManager().HasPvPCombat())
|
||||
CombatStopWithPets();
|
||||
}
|
||||
else
|
||||
RemoveByteFlag(UNIT_FIELD_BYTES_2, 1, UNIT_BYTE2_FLAG_SANCTUARY);
|
||||
|
||||
@@ -329,11 +329,11 @@ Unit::Unit() : WorldObject(),
|
||||
m_removedAurasCount(0),
|
||||
i_motionMaster(new MotionMaster(this)),
|
||||
m_regenTimer(0),
|
||||
m_ThreatMgr(this),
|
||||
m_threatManager(this),
|
||||
m_combatManager(this),
|
||||
m_vehicle(nullptr),
|
||||
m_vehicleKit(nullptr),
|
||||
m_unitTypeMask(UNIT_MASK_NONE),
|
||||
m_HostileRefMgr(this),
|
||||
m_comboTarget(nullptr),
|
||||
m_comboPoints(0)
|
||||
{
|
||||
@@ -406,16 +406,11 @@ Unit::Unit() : WorldObject(),
|
||||
m_CombatTimer = 0;
|
||||
m_lastManaUse = 0;
|
||||
|
||||
for (uint8 i = 0; i < MAX_SPELL_SCHOOL; ++i)
|
||||
m_threatModifier[i] = 1.0f;
|
||||
|
||||
for (uint8 i = 0; i < MAX_MOVE_TYPE; ++i)
|
||||
m_speed_rate[i] = 1.0f;
|
||||
|
||||
m_charmInfo = nullptr;
|
||||
|
||||
_redirectThreatInfo = RedirectThreatInfo();
|
||||
|
||||
// remove aurastates allowing special moves
|
||||
for (uint8 i = 0; i < MAX_REACTIVE; ++i)
|
||||
m_reactiveTimer[i] = 0;
|
||||
@@ -439,6 +434,7 @@ Unit::Unit() : WorldObject(),
|
||||
_oldFactionId = 0;
|
||||
|
||||
_isWalkingBeforeCharm = false;
|
||||
_isCombatDisallowed = false;
|
||||
|
||||
_lastExtraAttackSpell = 0;
|
||||
}
|
||||
@@ -529,16 +525,13 @@ void Unit::Update(uint32 p_time)
|
||||
|
||||
_UpdateSpells( p_time );
|
||||
|
||||
if (CanHaveThreatList() && GetThreatMgr().isNeedUpdateToClient(p_time))
|
||||
SendThreatListUpdate();
|
||||
|
||||
// update combat timer only for players and pets (only pets with PetAI)
|
||||
if (IsInCombat() && (IsPlayer() || ((IsPet() || HasUnitTypeMask(UNIT_MASK_CONTROLLABLE_GUARDIAN)) && IsControlledByPlayer())))
|
||||
{
|
||||
// Check UNIT_STATE_MELEE_ATTACKING or UNIT_STATE_CHASE (without UNIT_STATE_FOLLOW in this case) so pets can reach far away
|
||||
// targets without stopping half way there and running off.
|
||||
// These flags are reset after target dies or another command is given.
|
||||
if (m_HostileRefMgr.IsEmpty())
|
||||
if (!GetCombatManager().HasCombat())
|
||||
{
|
||||
// m_CombatTimer set at aura start and it will be freeze until aura removing
|
||||
if (m_CombatTimer <= p_time)
|
||||
@@ -548,6 +541,8 @@ void Unit::Update(uint32 p_time)
|
||||
}
|
||||
}
|
||||
|
||||
m_combatManager.Update(p_time);
|
||||
|
||||
_lastDamagedTargetGuid = ObjectGuid::Empty;
|
||||
if (_lastExtraAttackSpell)
|
||||
{
|
||||
@@ -1197,9 +1192,6 @@ uint32 Unit::DealDamage(Unit* attacker, Unit* victim, uint32 damage, CleanDamage
|
||||
|
||||
if (attacker && attacker != victim)
|
||||
{
|
||||
if (spellProto && victim->CanHaveThreatList() && !victim->HasUnitState(UNIT_STATE_EVADE) && !victim->IsInCombatWith(attacker))
|
||||
victim->CombatStart(attacker, !(spellProto->AttributesEx3 & SPELL_ATTR3_SUPPRESS_TARGET_PROCS));
|
||||
|
||||
victim->AddThreat(attacker, float(damage), damageSchoolMask, spellProto);
|
||||
}
|
||||
}
|
||||
@@ -2701,7 +2693,7 @@ void Unit::AttackerStateUpdate(Unit* victim, WeaponAttackType attType /*= BASE_A
|
||||
// CombatStart puts the target into stand state, so we need to cache sit state here to know if we should crit later
|
||||
const bool sittingVictim = victim->IsPlayer() && (victim->IsSitState() || victim->getStandState() == UNIT_STAND_STATE_SLEEP);
|
||||
|
||||
CombatStart(victim);
|
||||
AtTargetAttacked(victim, true);
|
||||
RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_MELEE_ATTACK);
|
||||
|
||||
if (attType != BASE_ATTACK && attType != OFF_ATTACK)
|
||||
@@ -7348,14 +7340,10 @@ bool Unit::Attack(Unit* victim, bool meleeAttack)
|
||||
// set position before any AI calls/assistance
|
||||
//if (IsCreature())
|
||||
// ToCreature()->SetCombatStartPosition(GetPositionX(), GetPositionY(), GetPositionZ());
|
||||
if (creature && !(IsControllableGuardian() && IsControlledByPlayer()))
|
||||
if (creature && !IsControlledByPlayer())
|
||||
{
|
||||
// should not let player enter combat by right clicking target - doesn't helps
|
||||
SetInCombatWith(victim);
|
||||
if (victim->IsPlayer())
|
||||
victim->SetInCombatWith(this);
|
||||
|
||||
AddThreat(victim, 0.0f);
|
||||
EngageWithTarget(victim);
|
||||
|
||||
creature->SendAIReaction(AI_REACTION_HOSTILE);
|
||||
|
||||
@@ -7415,7 +7403,7 @@ bool Unit::AttackStop()
|
||||
return true;
|
||||
}
|
||||
|
||||
void Unit::CombatStop(bool includingCast)
|
||||
void Unit::CombatStop(bool includingCast, bool mutualPvP)
|
||||
{
|
||||
if (includingCast && IsNonMeleeSpellCast(false))
|
||||
InterruptNonMeleeSpells(false);
|
||||
@@ -7426,7 +7414,14 @@ void Unit::CombatStop(bool includingCast)
|
||||
ToPlayer()->SendAttackSwingCancelAttack(); // melee and ranged forced attack cancel
|
||||
if (Creature* pCreature = ToCreature())
|
||||
pCreature->ClearLastLeashExtensionTimePtr();
|
||||
ClearInCombat();
|
||||
|
||||
if (mutualPvP)
|
||||
ClearInCombat();
|
||||
else
|
||||
{
|
||||
GetCombatManager().EndAllPvECombat();
|
||||
GetCombatManager().SuppressPvPCombat();
|
||||
}
|
||||
|
||||
// xinef: just in case
|
||||
if (IsPetInCombat() && !IsPlayer())
|
||||
@@ -7441,6 +7436,16 @@ void Unit::CombatStopWithPets(bool includingCast)
|
||||
(*itr)->CombatStop(includingCast);
|
||||
}
|
||||
|
||||
void Unit::EngageWithTarget(Unit* who)
|
||||
{
|
||||
if (!who)
|
||||
return;
|
||||
if (CanHaveThreatList())
|
||||
m_threatManager.AddThreat(who, 0.0f, nullptr, true, true);
|
||||
else
|
||||
SetInCombatWith(who);
|
||||
}
|
||||
|
||||
bool Unit::isAttackingPlayer() const
|
||||
{
|
||||
if (HasUnitState(UNIT_STATE_ATTACK_PLAYER))
|
||||
@@ -7460,6 +7465,38 @@ bool Unit::isAttackingPlayer() const
|
||||
return false;
|
||||
}
|
||||
|
||||
void Unit::UpdatePetCombatState()
|
||||
{
|
||||
ASSERT(!IsPet()); // player pets do not use UNIT_FLAG_PET_IN_COMBAT for this purpose
|
||||
|
||||
bool state = false;
|
||||
for (Unit* minion : m_Controlled)
|
||||
if (minion->IsInCombat())
|
||||
{
|
||||
state = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (state)
|
||||
SetUnitFlag(UNIT_FLAG_PET_IN_COMBAT);
|
||||
else
|
||||
RemoveUnitFlag(UNIT_FLAG_PET_IN_COMBAT);
|
||||
}
|
||||
|
||||
void Unit::AtExitCombat()
|
||||
{
|
||||
RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_LEAVE_COMBAT);
|
||||
}
|
||||
|
||||
void Unit::AtEngage(Unit* /*target*/)
|
||||
{
|
||||
if (HasUnitState(UNIT_STATE_DISTRACTED))
|
||||
{
|
||||
ClearUnitState(UNIT_STATE_DISTRACTED);
|
||||
GetMotionMaster()->MovementExpiredOnSlot(MOTION_SLOT_CONTROLLED, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Remove all units in m_attackers list and send them AttackStop()
|
||||
*/
|
||||
@@ -8262,13 +8299,10 @@ void Unit::SendEnergizeSpellLog(Unit* victim, uint32 spellID, uint32 damage, Pow
|
||||
|
||||
void Unit::EnergizeBySpell(Unit* victim, uint32 spellID, uint32 damage, Powers powerType)
|
||||
{
|
||||
int32 gainedPower = victim->ModifyPower(powerType, damage, false);
|
||||
victim->ModifyPower(powerType, damage, false);
|
||||
|
||||
if (powerType != POWER_HAPPINESS && gainedPower)
|
||||
{
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellID);
|
||||
victim->getHostileRefMgr().threatAssist(this, float(gainedPower) * 0.5f, spellInfo);
|
||||
}
|
||||
if (SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellID))
|
||||
victim->GetThreatMgr().ForwardThreatForAssistingMe(this, float(damage) / 2.0f, spellInfo, true);
|
||||
|
||||
SendEnergizeSpellLog(victim, spellID, damage, powerType);
|
||||
}
|
||||
@@ -10519,245 +10553,56 @@ void Unit::Dismount()
|
||||
}
|
||||
}
|
||||
|
||||
void Unit::SetInCombatWith(Unit* enemy, uint32 duration)
|
||||
{
|
||||
// Xinef: Dont allow to start combat with triggers
|
||||
if (enemy->IsCreature() && enemy->ToCreature()->IsTrigger())
|
||||
return;
|
||||
|
||||
Unit* eOwner = enemy->GetCharmerOrOwnerOrSelf();
|
||||
if (eOwner->IsPvP() || eOwner->IsFFAPvP())
|
||||
{
|
||||
SetInCombatState(true, enemy, duration);
|
||||
return;
|
||||
}
|
||||
|
||||
// check for duel
|
||||
if (eOwner->IsPlayer() && eOwner->ToPlayer()->duel)
|
||||
{
|
||||
Unit const* myOwner = GetCharmerOrOwnerOrSelf();
|
||||
if (((Player const*)eOwner)->duel->Opponent == myOwner)
|
||||
{
|
||||
SetInCombatState(true, enemy, duration);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
SetInCombatState(false, enemy, duration);
|
||||
}
|
||||
|
||||
void Unit::SetImmuneToPC(bool apply, bool keepCombat)
|
||||
{
|
||||
(void)keepCombat;
|
||||
if (apply)
|
||||
{
|
||||
SetUnitFlag(UNIT_FLAG_IMMUNE_TO_PC);
|
||||
if (!keepCombat)
|
||||
{
|
||||
std::vector<CombatReference*> toEnd;
|
||||
for (auto const& pair : m_combatManager.GetPvECombatRefs())
|
||||
if (pair.second->GetOther(this)->HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED))
|
||||
toEnd.push_back(pair.second);
|
||||
for (auto const& pair : m_combatManager.GetPvPCombatRefs())
|
||||
toEnd.push_back(pair.second);
|
||||
for (CombatReference* ref : toEnd)
|
||||
ref->EndCombat();
|
||||
}
|
||||
}
|
||||
else
|
||||
RemoveUnitFlag(UNIT_FLAG_IMMUNE_TO_PC);
|
||||
}
|
||||
|
||||
void Unit::SetImmuneToNPC(bool apply, bool keepCombat)
|
||||
{
|
||||
(void)keepCombat;
|
||||
if (apply)
|
||||
{
|
||||
SetUnitFlag(UNIT_FLAG_IMMUNE_TO_NPC);
|
||||
if (!keepCombat)
|
||||
{
|
||||
std::vector<CombatReference*> toEnd;
|
||||
for (auto const& pair : m_combatManager.GetPvECombatRefs())
|
||||
if (!pair.second->GetOther(this)->HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED))
|
||||
toEnd.push_back(pair.second);
|
||||
for (CombatReference* ref : toEnd)
|
||||
ref->EndCombat();
|
||||
}
|
||||
}
|
||||
else
|
||||
RemoveUnitFlag(UNIT_FLAG_IMMUNE_TO_NPC);
|
||||
}
|
||||
|
||||
void Unit::CombatStart(Unit* victim, bool initialAggro)
|
||||
{
|
||||
// Xinef: Dont allow to start combat with triggers
|
||||
if (victim->IsCreature() && victim->ToCreature()->IsTrigger())
|
||||
return;
|
||||
|
||||
if (initialAggro)
|
||||
{
|
||||
// Make player victim stand up automatically
|
||||
if (victim->getStandState() && victim->IsPlayer())
|
||||
{
|
||||
victim->SetStandState(UNIT_STAND_STATE_STAND);
|
||||
}
|
||||
|
||||
if (!victim->IsInCombat() && !victim->IsPlayer() && !victim->ToCreature()->HasReactState(REACT_PASSIVE) && victim->ToCreature()->IsAIEnabled)
|
||||
{
|
||||
if (victim->IsPet())
|
||||
victim->ToCreature()->AI()->AttackedBy(this); // PetAI has special handler before AttackStart()
|
||||
else
|
||||
{
|
||||
victim->ToCreature()->AI()->AttackStart(this);
|
||||
// if the target is an NPC with a pet or minion, pet should react.
|
||||
if (Unit* victimControlledUnit = victim->GetFirstControlled())
|
||||
{
|
||||
victimControlledUnit->SetInCombatWith(this);
|
||||
SetInCombatWith(victimControlledUnit);
|
||||
victimControlledUnit->AddThreat(this, 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
// if unit has an owner, put owner in combat.
|
||||
if (Unit* victimOwner = victim->GetOwner())
|
||||
{
|
||||
if (!(victimOwner->IsInCombatWith(this)))
|
||||
{
|
||||
/* warding off to not take over aggro for no reason
|
||||
Using only AddThreat causes delay in attack */
|
||||
if (!victimOwner->IsInCombat() && victimOwner->IsAIEnabled)
|
||||
{
|
||||
victimOwner->ToCreature()->AI()->AttackStart(this);
|
||||
}
|
||||
victimOwner->SetInCombatWith(this);
|
||||
SetInCombatWith(victimOwner);
|
||||
victimOwner->AddThreat(this, 0.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool alreadyInCombat = IsInCombat();
|
||||
|
||||
SetInCombatWith(victim);
|
||||
victim->SetInCombatWith(this);
|
||||
|
||||
// Update leash timer when attacking creatures
|
||||
if (victim->IsCreature() && this != victim)
|
||||
victim->ToCreature()->UpdateLeashExtensionTime();
|
||||
|
||||
// Xinef: If pet started combat - put owner in combat
|
||||
if (!alreadyInCombat && IsInCombat())
|
||||
{
|
||||
if (Unit* owner = GetOwner())
|
||||
{
|
||||
owner->SetInCombatWith(victim);
|
||||
victim->SetInCombatWith(owner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Unit* who = victim->GetCharmerOrOwnerOrSelf();
|
||||
if (who->IsPlayer())
|
||||
SetContestedPvP(who->ToPlayer());
|
||||
|
||||
Player* player = GetCharmerOrOwnerPlayerOrPlayerItself();
|
||||
if (player && who->IsPvP() && (!who->IsPlayer() || !player->duel || player->duel->Opponent != who))
|
||||
{
|
||||
player->UpdatePvP(true);
|
||||
player->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_ENTER_PVP_COMBAT);
|
||||
}
|
||||
}
|
||||
|
||||
void Unit::CombatStartOnCast(Unit* target, bool initialAggro, uint32 duration)
|
||||
{
|
||||
// Xinef: Dont allow to start combat with triggers
|
||||
if (target->IsCreature() && target->ToCreature()->IsTrigger())
|
||||
return;
|
||||
|
||||
if (initialAggro)
|
||||
{
|
||||
SetInCombatWith(target, duration);
|
||||
|
||||
// Xinef: If pet started combat - put owner in combat
|
||||
if (Unit* owner = GetOwner())
|
||||
owner->SetInCombatWith(target, duration);
|
||||
|
||||
// Update leash timer when attacking creatures
|
||||
if (target->IsCreature())
|
||||
target->ToCreature()->UpdateLeashExtensionTime();
|
||||
else if (ToCreature()) // Reset leash if it is a spell caster, else it may evade inbetween casts
|
||||
ToCreature()->UpdateLeashExtensionTime();
|
||||
}
|
||||
|
||||
Unit* who = target->GetCharmerOrOwnerOrSelf();
|
||||
if (who->IsPlayer())
|
||||
SetContestedPvP(who->ToPlayer());
|
||||
|
||||
Player* player = GetCharmerOrOwnerPlayerOrPlayerItself();
|
||||
if (player && who->IsPvP() && (!who->IsPlayer() || !player->duel || player->duel->Opponent != who))
|
||||
{
|
||||
player->UpdatePvP(true);
|
||||
player->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_ENTER_PVP_COMBAT);
|
||||
}
|
||||
}
|
||||
|
||||
void Unit::SetInCombatState(bool PvP, Unit* enemy, uint32 duration)
|
||||
{
|
||||
// only alive units can be in combat
|
||||
if (!IsAlive())
|
||||
return;
|
||||
|
||||
if (PvP)
|
||||
m_CombatTimer = std::max<uint32>(GetCombatTimer(), std::max<uint32>(5500, duration));
|
||||
else if (duration)
|
||||
m_CombatTimer = std::max<uint32>(GetCombatTimer(), duration);
|
||||
|
||||
if (HasUnitState(UNIT_STATE_EVADE) || GetCreatureType() == CREATURE_TYPE_NON_COMBAT_PET)
|
||||
return;
|
||||
|
||||
// xinef: if we somehow engage in combat (scripts, dunno) with player, remove this flag so he can fight back
|
||||
if (IsCreature() && enemy && IsImmuneToPC() && enemy->GetCharmerOrOwnerPlayerOrPlayerItself())
|
||||
SetImmuneToPC(false); // unit has engaged in combat, remove immunity so players can fight back
|
||||
|
||||
if (IsInCombat())
|
||||
return;
|
||||
|
||||
SetUnitFlag(UNIT_FLAG_IN_COMBAT);
|
||||
|
||||
if (Creature* creature = ToCreature())
|
||||
{
|
||||
// Set home position at place of engaging combat for escorted creatures
|
||||
if ((IsAIEnabled && creature->AI()->IsEscorted()) ||
|
||||
GetMotionMaster()->GetCurrentMovementGeneratorType() == WAYPOINT_MOTION_TYPE ||
|
||||
GetMotionMaster()->GetCurrentMovementGeneratorType() == ESCORT_MOTION_TYPE)
|
||||
creature->SetHomePosition(GetPositionX(), GetPositionY(), GetPositionZ(), GetOrientation());
|
||||
|
||||
if (enemy)
|
||||
{
|
||||
creature->UpdateLeashExtensionTime();
|
||||
|
||||
if (IsAIEnabled)
|
||||
creature->AI()->JustEngagedWith(enemy);
|
||||
|
||||
if (creature->GetFormation())
|
||||
creature->GetFormation()->MemberEngagingTarget(creature, enemy);
|
||||
|
||||
sScriptMgr->OnUnitEnterCombat(creature, enemy);
|
||||
}
|
||||
|
||||
creature->RefreshSwimmingFlag();
|
||||
|
||||
if (IsPet())
|
||||
{
|
||||
UpdateSpeed(MOVE_RUN, true);
|
||||
UpdateSpeed(MOVE_SWIM, true);
|
||||
UpdateSpeed(MOVE_FLIGHT, true);
|
||||
}
|
||||
|
||||
if (!(creature->GetCreatureTemplate()->type_flags & CREATURE_TYPE_FLAG_ALLOW_MOUNTED_COMBAT))
|
||||
Dismount();
|
||||
if (!IsStandState()) // pussywizard: already done in CombatStart(target, initialAggro) for the target, but when aggro'ing from MoveInLOS CombatStart is not called!
|
||||
SetStandState(UNIT_STAND_STATE_STAND);
|
||||
}
|
||||
|
||||
for (Unit::ControlSet::iterator itr = m_Controlled.begin(); itr != m_Controlled.end();)
|
||||
{
|
||||
Unit* controlled = *itr;
|
||||
++itr;
|
||||
|
||||
// Xinef: Dont set combat for passive units, they will evade in next update...
|
||||
if (controlled->IsCreature() && controlled->ToCreature()->HasReactState(REACT_PASSIVE))
|
||||
continue;
|
||||
|
||||
controlled->SetInCombatState(PvP, enemy, duration);
|
||||
}
|
||||
|
||||
if (Player* player = this->ToPlayer())
|
||||
{
|
||||
sScriptMgr->OnPlayerEnterCombat(player, enemy);
|
||||
}
|
||||
}
|
||||
|
||||
void Unit::ClearInCombat()
|
||||
{
|
||||
m_CombatTimer = 0;
|
||||
RemoveUnitFlag(UNIT_FLAG_IN_COMBAT);
|
||||
// Let CombatManager handle flag removal through UpdateOwnerCombatState
|
||||
// so that AtExitCombat/AtDisengage callbacks properly fire.
|
||||
GetCombatManager().EndAllCombat();
|
||||
// If flag is still set (e.g. set by old code path without CombatReference),
|
||||
// remove it directly.
|
||||
if (IsInCombat())
|
||||
RemoveUnitFlag(UNIT_FLAG_IN_COMBAT);
|
||||
|
||||
// Player's state will be cleared in Player::UpdateContestedPvP
|
||||
if (Creature* creature = ToCreature())
|
||||
@@ -11454,8 +11299,6 @@ void Unit::setDeathState(DeathState s, bool despawn)
|
||||
if (s != DeathState::Alive && s != DeathState::JustRespawned)
|
||||
{
|
||||
CombatStop();
|
||||
GetThreatMgr().ClearAllThreat();
|
||||
getHostileRefMgr().deleteReferences();
|
||||
ClearComboPointHolders(); // any combo points pointed to unit lost at it death
|
||||
|
||||
if (IsNonMeleeSpellCast(false))
|
||||
@@ -11508,118 +11351,56 @@ void Unit::setDeathState(DeathState s, bool despawn)
|
||||
########################################*/
|
||||
bool Unit::CanHaveThreatList(bool skipAliveCheck) const
|
||||
{
|
||||
// only creatures can have threat list
|
||||
if (!IsCreature())
|
||||
// Delegate to ThreatManager's cached value for consistency
|
||||
if (!m_threatManager.CanHaveThreatList())
|
||||
return false;
|
||||
|
||||
// only alive units can have threat list
|
||||
if (!skipAliveCheck && !IsAlive())
|
||||
return false;
|
||||
|
||||
// totems can not have threat list
|
||||
if (ToCreature()->IsTotem())
|
||||
return false;
|
||||
|
||||
// vehicles can not have threat list
|
||||
// vehicles can not have threat list in battlegrounds
|
||||
if (ToCreature()->IsVehicle() && GetMap()->IsBattlegroundOrArena())
|
||||
return false;
|
||||
|
||||
// summons can not have a threat list, unless they are controlled by a creature
|
||||
if (HasUnitTypeMask(UNIT_MASK_MINION | UNIT_MASK_GUARDIAN | UNIT_MASK_CONTROLLABLE_GUARDIAN) && ((Pet*)this)->GetOwnerGUID().IsPlayer())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//======================================================================
|
||||
|
||||
float Unit::ApplyTotalThreatModifier(float fThreat, SpellSchoolMask schoolMask)
|
||||
{
|
||||
if (!HasThreatAura() || fThreat < 0)
|
||||
return fThreat;
|
||||
|
||||
SpellSchools school = GetFirstSchoolInMask(schoolMask);
|
||||
|
||||
return fThreat * m_threatModifier[school];
|
||||
}
|
||||
|
||||
//======================================================================
|
||||
|
||||
void Unit::AddThreat(Unit* victim, float fThreat, SpellSchoolMask schoolMask, SpellInfo const* threatSpell)
|
||||
void Unit::AddThreat(Unit* victim, float fThreat, SpellSchoolMask /*schoolMask*/, SpellInfo const* threatSpell)
|
||||
{
|
||||
// Only mobs can manage threat lists
|
||||
if (CanHaveThreatList() && !HasUnitState(UNIT_STATE_EVADE))
|
||||
{
|
||||
m_ThreatMgr.AddThreat(victim, fThreat, schoolMask, threatSpell);
|
||||
m_threatManager.AddThreat(victim, fThreat, threatSpell);
|
||||
}
|
||||
}
|
||||
|
||||
//======================================================================
|
||||
|
||||
void Unit::TauntApply(Unit* taunter)
|
||||
void Unit::AtTargetAttacked(Unit* target, bool canInitialAggro)
|
||||
{
|
||||
ASSERT(IsCreature());
|
||||
|
||||
if (!taunter || (taunter->IsPlayer() && taunter->ToPlayer()->IsGameMaster()))
|
||||
if (!target->IsEngaged() && !canInitialAggro)
|
||||
return;
|
||||
|
||||
if (!CanHaveThreatList())
|
||||
return;
|
||||
target->EngageWithTarget(this);
|
||||
if (Unit* targetOwner = target->GetCharmerOrOwner())
|
||||
targetOwner->EngageWithTarget(this);
|
||||
|
||||
Creature* creature = ToCreature();
|
||||
// Patch 3.0.8: All player spells which cause a creature to become aggressive
|
||||
// to you will now also immediately cause the creature to be tapped.
|
||||
if (Creature* creature = target->ToCreature())
|
||||
if (!creature->hasLootRecipient() && IsPlayer())
|
||||
creature->SetLootRecipient(this);
|
||||
|
||||
if (creature->HasReactState(REACT_PASSIVE))
|
||||
return;
|
||||
|
||||
Unit* target = GetVictim();
|
||||
if (target && target == taunter)
|
||||
return;
|
||||
|
||||
SetInFront(taunter);
|
||||
SetGuidValue(UNIT_FIELD_TARGET, taunter->GetGUID());
|
||||
|
||||
if (creature->IsAIEnabled)
|
||||
creature->AI()->AttackStart(taunter);
|
||||
|
||||
//m_ThreatMgr.tauntApply(taunter);
|
||||
}
|
||||
|
||||
//======================================================================
|
||||
|
||||
void Unit::TauntFadeOut(Unit* taunter)
|
||||
{
|
||||
ASSERT(IsCreature());
|
||||
|
||||
if (!taunter || (taunter->IsPlayer() && taunter->ToPlayer()->IsGameMaster()))
|
||||
return;
|
||||
|
||||
if (!CanHaveThreatList())
|
||||
return;
|
||||
|
||||
Creature* creature = ToCreature();
|
||||
|
||||
if (creature->HasReactState(REACT_PASSIVE))
|
||||
return;
|
||||
|
||||
Unit* target = GetVictim();
|
||||
if (!target || target != taunter)
|
||||
return;
|
||||
|
||||
if (m_ThreatMgr.isThreatListEmpty())
|
||||
Player* myPlayerOwner = GetCharmerOrOwnerPlayerOrPlayerItself();
|
||||
Player* targetPlayerOwner = target->GetCharmerOrOwnerPlayerOrPlayerItself();
|
||||
if (myPlayerOwner && targetPlayerOwner && !(myPlayerOwner->duel && myPlayerOwner->duel->Opponent == targetPlayerOwner))
|
||||
{
|
||||
if (creature->IsAIEnabled)
|
||||
creature->AI()->EnterEvadeMode(CreatureAI::EVADE_REASON_NO_HOSTILES);
|
||||
return;
|
||||
}
|
||||
|
||||
target = creature->SelectVictim(); // might have more taunt auras remaining
|
||||
|
||||
if (target && target != taunter)
|
||||
{
|
||||
SetGuidValue(UNIT_FIELD_TARGET, target->GetGUID());
|
||||
SetInFront(target);
|
||||
if (creature->IsAIEnabled)
|
||||
creature->AI()->AttackStart(target);
|
||||
myPlayerOwner->UpdatePvP(true);
|
||||
myPlayerOwner->SetContestedPvP(targetPlayerOwner);
|
||||
myPlayerOwner->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_ENTER_PVP_COMBAT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11633,21 +11414,11 @@ Unit* Creature::SelectVictim()
|
||||
|
||||
Unit* target = nullptr;
|
||||
|
||||
// First checking if we have some taunt on us
|
||||
AuraEffectList const& tauntAuras = GetAuraEffectsByType(SPELL_AURA_MOD_TAUNT);
|
||||
if (!tauntAuras.empty())
|
||||
for (Unit::AuraEffectList::const_reverse_iterator itr = tauntAuras.rbegin(); itr != tauntAuras.rend(); ++itr)
|
||||
if (Unit* caster = (*itr)->GetCaster())
|
||||
if (CanCreatureAttack(caster) && !caster->HasAuraTypeWithCaster(SPELL_AURA_IGNORED, GetGUID()))
|
||||
{
|
||||
target = caster;
|
||||
break;
|
||||
}
|
||||
|
||||
// Taunt handling is now done inside ThreatManager via TauntState
|
||||
if (CanHaveThreatList())
|
||||
{
|
||||
if (!target && !m_ThreatMgr.isThreatListEmpty())
|
||||
target = m_ThreatMgr.getHostileTarget();
|
||||
if (!m_threatManager.IsThreatListEmpty())
|
||||
target = m_threatManager.GetCurrentVictim();
|
||||
}
|
||||
else if (!HasReactState(REACT_PASSIVE))
|
||||
{
|
||||
@@ -11671,9 +11442,10 @@ Unit* Creature::SelectVictim()
|
||||
else
|
||||
return nullptr;
|
||||
|
||||
if (target && CanCreatureAttack(target))
|
||||
if (target && _IsTargetAcceptable(target) && CanCreatureAttack(target))
|
||||
{
|
||||
SetInFront(target);
|
||||
if (!HasSpellFocus())
|
||||
SetInFront(target);
|
||||
return target;
|
||||
}
|
||||
|
||||
@@ -12776,8 +12548,6 @@ void Unit::CleanupBeforeRemoveFromMap(bool finalCleanup)
|
||||
CombatStop();
|
||||
ClearComboPoints();
|
||||
ClearComboPointHolders();
|
||||
GetThreatMgr().ClearAllThreat();
|
||||
getHostileRefMgr().deleteReferences();
|
||||
GetMotionMaster()->Clear(false); // remove different non-standard movement generators.
|
||||
}
|
||||
|
||||
@@ -14290,7 +14060,6 @@ void Unit::Kill(Unit* killer, Unit* victim, bool durabilityLoss, WeaponAttackTyp
|
||||
|
||||
// Stop attacks
|
||||
victim->CombatStop();
|
||||
victim->getHostileRefMgr().deleteReferences();
|
||||
|
||||
// restore for use at real death
|
||||
victim->SetUInt32Value(PLAYER_SELF_RES_SPELL, ressSpellId);
|
||||
@@ -14352,8 +14121,6 @@ void Unit::Kill(Unit* killer, Unit* victim, bool durabilityLoss, WeaponAttackTyp
|
||||
|
||||
if (!creature->IsPet() && creature->GetLootMode() > 0)
|
||||
{
|
||||
creature->GetThreatMgr().ClearAllThreat();
|
||||
|
||||
// must be after setDeathState which resets dynamic flags
|
||||
if (!creature->loot.isLooted())
|
||||
{
|
||||
@@ -14814,10 +14581,6 @@ bool Unit::SetCharmedBy(Unit* charmer, CharmType type, AuraApplication const* au
|
||||
CastStop();
|
||||
AttackStop();
|
||||
|
||||
// Xinef: dont reset threat and combat, put them on offline list, moved down after faction changes
|
||||
// CombatStop(); /// @todo: CombatStop(true) may cause crash (interrupt spells)
|
||||
// DeleteThreatList();
|
||||
|
||||
Player* playerCharmer = charmer->ToPlayer();
|
||||
|
||||
// Charmer stop charming
|
||||
@@ -15027,6 +14790,10 @@ void Unit::RemoveCharmedBy(Unit* charmer)
|
||||
ASSERT(type != CHARM_TYPE_POSSESS || charmer->IsPlayer());
|
||||
ASSERT(type != CHARM_TYPE_VEHICLE || (IsCreature() && IsVehicle()));
|
||||
|
||||
// Vehicle should not attack its passenger after he exits the seat
|
||||
if (type != CHARM_TYPE_VEHICLE)
|
||||
LastCharmerGUID = charmer->GetGUID();
|
||||
|
||||
charmer->SetCharm(this, false);
|
||||
|
||||
StopAttackingInvalidTarget();
|
||||
@@ -15478,41 +15245,29 @@ void Unit::SetPhaseMask(uint32 newPhaseMask, bool update)
|
||||
if (!sScriptMgr->CanSetPhaseMask(this, newPhaseMask, update))
|
||||
return;
|
||||
|
||||
// modify hostile references for new phasemask, some special cases deal with hostile references themselves
|
||||
if (IsCreature() || (!ToPlayer()->IsGameMaster() && !ToPlayer()->GetSession()->PlayerLogout()))
|
||||
{
|
||||
HostileRefMgr& refMgr = getHostileRefMgr();
|
||||
HostileReference* ref = refMgr.getFirst();
|
||||
|
||||
while (ref)
|
||||
{
|
||||
if (Unit* unit = ref->GetSource()->GetOwner())
|
||||
if (Creature* creature = unit->ToCreature())
|
||||
refMgr.setOnlineOfflineState(creature, creature->InSamePhase(newPhaseMask));
|
||||
|
||||
ref = ref->next();
|
||||
}
|
||||
|
||||
// modify threat lists for new phasemask
|
||||
if (!IsPlayer())
|
||||
{
|
||||
ThreatContainer::StorageType threatList = GetThreatMgr().GetThreatList();
|
||||
ThreatContainer::StorageType offlineThreatList = GetThreatMgr().GetOfflineThreatList();
|
||||
|
||||
// merge expects sorted lists
|
||||
threatList.sort();
|
||||
offlineThreatList.sort();
|
||||
threatList.merge(offlineThreatList);
|
||||
|
||||
for (ThreatContainer::StorageType::const_iterator itr = threatList.begin(); itr != threatList.end(); ++itr)
|
||||
if (Unit* unit = (*itr)->getTarget())
|
||||
unit->getHostileRefMgr().setOnlineOfflineState(ToCreature(), unit->InSamePhase(newPhaseMask));
|
||||
}
|
||||
}
|
||||
// Phase-related threat updates are done AFTER the phase change below
|
||||
}
|
||||
|
||||
WorldObject::SetPhaseMask(newPhaseMask, false);
|
||||
|
||||
// Now update threat online states with the new phase mask applied
|
||||
if (IsCreature() || (IsPlayer() && !ToPlayer()->IsGameMaster() && !ToPlayer()->GetSession()->PlayerLogout()))
|
||||
{
|
||||
// Update online state for units that have me on their threat list
|
||||
for (auto const& pair : GetThreatMgr().GetThreatenedByMeList())
|
||||
{
|
||||
if (ThreatReference* ref = pair.second)
|
||||
ref->UpdateOffline();
|
||||
}
|
||||
|
||||
// Update online state for units on my threat list
|
||||
if (!IsPlayer())
|
||||
{
|
||||
for (ThreatReference* ref : GetThreatMgr().GetModifiableThreatList())
|
||||
ref->UpdateOffline();
|
||||
}
|
||||
}
|
||||
|
||||
if (!IsInWorld())
|
||||
{
|
||||
return;
|
||||
@@ -15655,11 +15410,6 @@ uint32 Unit::GetModelForForm(ShapeshiftForm form, uint32 spellId)
|
||||
return modelid;
|
||||
}
|
||||
|
||||
Unit* Unit::GetRedirectThreatTarget() const
|
||||
{
|
||||
return _redirectThreatInfo.GetTargetGUID() ? ObjectAccessor::GetUnit(*this, _redirectThreatInfo.GetTargetGUID()) : nullptr;
|
||||
}
|
||||
|
||||
bool Unit::IsAttackSpeedOverridenShapeShift() const
|
||||
{
|
||||
// Mirroring clientside gameplay logic
|
||||
@@ -16221,64 +15971,6 @@ void Unit::UpdateHeight(float newZ)
|
||||
GetVehicleKit()->RelocatePassengers();
|
||||
}
|
||||
|
||||
void Unit::SendThreatListUpdate()
|
||||
{
|
||||
if (!GetThreatMgr().isThreatListEmpty())
|
||||
{
|
||||
uint32 count = GetThreatMgr().GetThreatList().size();
|
||||
|
||||
//LOG_DEBUG("entities.unit", "WORLD: Send SMSG_THREAT_UPDATE Message");
|
||||
WorldPacket data(SMSG_THREAT_UPDATE, 8 + count * 8);
|
||||
data << GetPackGUID();
|
||||
data << uint32(count);
|
||||
ThreatContainer::StorageType const& tlist = GetThreatMgr().GetThreatList();
|
||||
for (ThreatContainer::StorageType::const_iterator itr = tlist.begin(); itr != tlist.end(); ++itr)
|
||||
{
|
||||
data << (*itr)->getUnitGuid().WriteAsPacked();
|
||||
data << uint32((*itr)->GetThreat() * 100);
|
||||
}
|
||||
SendMessageToSet(&data, false);
|
||||
}
|
||||
}
|
||||
|
||||
void Unit::SendChangeCurrentVictimOpcode(HostileReference* pHostileReference)
|
||||
{
|
||||
if (!GetThreatMgr().isThreatListEmpty())
|
||||
{
|
||||
uint32 count = GetThreatMgr().GetThreatList().size();
|
||||
|
||||
LOG_DEBUG("entities.unit", "WORLD: Send SMSG_HIGHEST_THREAT_UPDATE Message");
|
||||
WorldPacket data(SMSG_HIGHEST_THREAT_UPDATE, 8 + 8 + count * 8);
|
||||
data << GetPackGUID();
|
||||
data << pHostileReference->getUnitGuid().WriteAsPacked();
|
||||
data << uint32(count);
|
||||
ThreatContainer::StorageType const& tlist = GetThreatMgr().GetThreatList();
|
||||
for (ThreatContainer::StorageType::const_iterator itr = tlist.begin(); itr != tlist.end(); ++itr)
|
||||
{
|
||||
data << (*itr)->getUnitGuid().WriteAsPacked();
|
||||
data << uint32((*itr)->GetThreat() * 100);
|
||||
}
|
||||
SendMessageToSet(&data, false);
|
||||
}
|
||||
}
|
||||
|
||||
void Unit::SendClearThreatListOpcode()
|
||||
{
|
||||
LOG_DEBUG("entities.unit", "WORLD: Send SMSG_THREAT_CLEAR Message");
|
||||
WorldPacket data(SMSG_THREAT_CLEAR, 8);
|
||||
data << GetPackGUID();
|
||||
SendMessageToSet(&data, false);
|
||||
}
|
||||
|
||||
void Unit::SendRemoveFromThreatListOpcode(HostileReference* pHostileReference)
|
||||
{
|
||||
LOG_DEBUG("entities.unit", "WORLD: Send SMSG_THREAT_REMOVE Message");
|
||||
WorldPacket data(SMSG_THREAT_REMOVE, 8 + 8);
|
||||
data << GetPackGUID();
|
||||
data << pHostileReference->getUnitGuid().WriteAsPacked();
|
||||
SendMessageToSet(&data, false);
|
||||
}
|
||||
|
||||
void Unit::RewardRage(uint32 damage, uint32 weaponSpeedHitFactor, bool attacker)
|
||||
{
|
||||
// Rage formulae https://wowwiki-archive.fandom.com/wiki/Rage#Formulae
|
||||
@@ -16340,7 +16032,13 @@ void Unit::StopAttackFaction(uint32 faction_id)
|
||||
++itr;
|
||||
}
|
||||
|
||||
getHostileRefMgr().deleteReferencesForFaction(faction_id);
|
||||
// End combat and threat references with creatures in this faction
|
||||
std::vector<CombatReference*> 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 (ControlSet::const_iterator itr = m_Controlled.begin(); itr != m_Controlled.end(); ++itr)
|
||||
(*itr)->StopAttackFaction(faction_id);
|
||||
@@ -17300,20 +16998,14 @@ DisplayRace Unit::GetDisplayRaceFromModelId(uint32 modelId) const
|
||||
// Check if unit in combat with specific unit
|
||||
bool Unit::IsInCombatWith(Unit const* who) const
|
||||
{
|
||||
// Check target exists
|
||||
if (!who)
|
||||
return false;
|
||||
// Search in threat list
|
||||
ObjectGuid guid = who->GetGUID();
|
||||
for (ThreatContainer::StorageType::const_iterator i = m_ThreatMgr.GetThreatList().begin(); i != m_ThreatMgr.GetThreatList().end(); ++i)
|
||||
{
|
||||
HostileReference* ref = (*i);
|
||||
// Return true if the unit matches
|
||||
if (ref && ref->getUnitGuid() == guid)
|
||||
return true;
|
||||
}
|
||||
// Nothing found, false.
|
||||
return false;
|
||||
return GetCombatManager().IsInCombatWith(who);
|
||||
}
|
||||
|
||||
bool Unit::IsThreatened() const
|
||||
{
|
||||
return !m_threatManager.IsThreatListEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
#include "EnumFlag.h"
|
||||
#include "EventProcessor.h"
|
||||
#include "HostileRefMgr.h"
|
||||
#include "CombatManager.h"
|
||||
#include "ItemTemplate.h"
|
||||
#include "MotionMaster.h"
|
||||
#include "Object.h"
|
||||
@@ -28,7 +28,7 @@
|
||||
#include "SharedDefines.h"
|
||||
#include "SpellAuraDefines.h"
|
||||
#include "SpellDefines.h"
|
||||
#include "ThreatMgr.h"
|
||||
#include "ThreatManager.h"
|
||||
#include "UnitDefines.h"
|
||||
#include "UnitUtils.h"
|
||||
#include <functional>
|
||||
@@ -902,7 +902,7 @@ public:
|
||||
[[nodiscard]] bool isAttackingPlayer() const;
|
||||
[[nodiscard]] Unit* GetVictim() const { return m_attacking; }
|
||||
|
||||
void CombatStop(bool includingCast = false);
|
||||
void CombatStop(bool includingCast = false, bool mutualPvP = true);
|
||||
void CombatStopWithPets(bool includingCast = false);
|
||||
void StopAttackFaction(uint32 faction_id);
|
||||
void StopAttackingInvalidTarget();
|
||||
@@ -928,17 +928,15 @@ public:
|
||||
void SetImmuneToNPC(bool apply, bool keepCombat = false);
|
||||
bool IsImmuneToNPC() const { return HasUnitFlag(UNIT_FLAG_IMMUNE_TO_NPC); }
|
||||
|
||||
bool IsEngaged() const { return IsInCombat(); }
|
||||
bool IsEngagedBy(Unit const* who) const { return IsInCombatWith(who); }
|
||||
virtual bool IsEngaged() const { return IsInCombat(); }
|
||||
bool IsEngagedBy(Unit const* who) const { return CanHaveThreatList() ? IsThreatenedBy(who) : IsInCombatWith(who); }
|
||||
void EngageWithTarget(Unit* who);
|
||||
|
||||
[[nodiscard]] bool IsInCombat() const { return HasUnitFlag(UNIT_FLAG_IN_COMBAT); }
|
||||
bool IsInCombatWith(Unit const* who) const;
|
||||
|
||||
[[nodiscard]] bool IsPetInCombat() const { return HasUnitFlag(UNIT_FLAG_PET_IN_COMBAT); }
|
||||
void CombatStart(Unit* target, bool initialAggro = true);
|
||||
void CombatStartOnCast(Unit* target, bool initialAggro = true, uint32 duration = 0);
|
||||
void SetInCombatState(bool PvP, Unit* enemy = nullptr, uint32 duration = 0);
|
||||
void SetInCombatWith(Unit* enemy, uint32 duration = 0);
|
||||
void SetInCombatWith(Unit* enemy, bool addSecondUnitSuppressed = false) { if (enemy) m_combatManager.SetInCombatWith(enemy, addSecondUnitSuppressed); }
|
||||
void ClearInCombat();
|
||||
void ClearInPetCombat();
|
||||
[[nodiscard]] uint32 GetCombatTimer() const { return m_CombatTimer; }
|
||||
@@ -947,21 +945,28 @@ public:
|
||||
// Threat related methods
|
||||
[[nodiscard]] bool CanHaveThreatList(bool skipAliveCheck = false) const;
|
||||
void AddThreat(Unit* victim, float fThreat, SpellSchoolMask schoolMask = SPELL_SCHOOL_MASK_NORMAL, SpellInfo const* threatSpell = nullptr);
|
||||
float ApplyTotalThreatModifier(float fThreat, SpellSchoolMask schoolMask = SPELL_SCHOOL_MASK_NORMAL);
|
||||
void TauntApply(Unit* victim);
|
||||
void TauntFadeOut(Unit* taunter);
|
||||
ThreatMgr& GetThreatMgr() { return m_ThreatMgr; }
|
||||
ThreatMgr const& GetThreatMgr() const { return m_ThreatMgr; }
|
||||
void addHatedBy(HostileReference* pHostileReference) { m_HostileRefMgr.insertFirst(pHostileReference); };
|
||||
void removeHatedBy(HostileReference* /*pHostileReference*/) { /* nothing to do yet */ }
|
||||
HostileRefMgr& getHostileRefMgr() { return m_HostileRefMgr; }
|
||||
void AtTargetAttacked(Unit* target, bool canInitialAggro);
|
||||
|
||||
// Redirect Threat
|
||||
void SetRedirectThreat(ObjectGuid guid, uint32 pct) { _redirectThreatInfo.Set(guid, pct); }
|
||||
void ResetRedirectThreat() { SetRedirectThreat(ObjectGuid::Empty, 0); }
|
||||
void ModifyRedirectThreat(int32 amount) { _redirectThreatInfo.ModifyThreatPct(amount); }
|
||||
uint32 GetRedirectThreatPercent() { return _redirectThreatInfo.GetThreatPct(); }
|
||||
[[nodiscard]] Unit* GetRedirectThreatTarget() const;
|
||||
// ThreatManager/CombatManager accessors
|
||||
ThreatManager& GetThreatMgr() { return m_threatManager; }
|
||||
ThreatManager const& GetThreatMgr() const { return m_threatManager; }
|
||||
CombatManager& GetCombatManager() { return m_combatManager; }
|
||||
CombatManager const& GetCombatManager() const { return m_combatManager; }
|
||||
|
||||
// Combat state methods
|
||||
[[nodiscard]] bool IsThreatened() const;
|
||||
[[nodiscard]] bool IsThreatenedBy(Unit const* who) const { return who && m_threatManager.IsThreatenedBy(who, true); }
|
||||
void UpdatePetCombatState();
|
||||
|
||||
virtual void AtEnterCombat() {}
|
||||
virtual void AtExitCombat();
|
||||
|
||||
// Engagement callbacks - override in Creature for creature-specific behavior
|
||||
virtual void AtEngage(Unit* target);
|
||||
virtual void AtDisengage() {}
|
||||
|
||||
[[nodiscard]] bool IsCombatDisallowed() const { return _isCombatDisallowed; }
|
||||
void SetIsCombatDisallowed(bool value) { _isCombatDisallowed = value; }
|
||||
|
||||
void SetLastDamagedTargetGuid(ObjectGuid const& guid) { _lastDamagedTargetGuid = guid; }
|
||||
[[nodiscard]] ObjectGuid const& GetLastDamagedTargetGuid() const { return _lastDamagedTargetGuid; }
|
||||
@@ -1818,7 +1823,7 @@ public:
|
||||
[[nodiscard]] bool HasIgnoreTargetResistAura() const { return HasAuraType(SPELL_AURA_MOD_IGNORE_TARGET_RESIST); }
|
||||
[[nodiscard]] bool HasIncreaseMountedSpeedAura() const { return HasAuraType(SPELL_AURA_MOD_INCREASE_MOUNTED_SPEED); }
|
||||
[[nodiscard]] bool HasIncreaseMountedFlightSpeedAura() const { return HasAuraType(SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED); }
|
||||
[[nodiscard]] bool HasThreatAura() const { return HasAuraType(SPELL_AURA_MOD_THREAT); }
|
||||
|
||||
[[nodiscard]] bool HasAttackerSpellCritChanceAura() const { return HasAuraType(SPELL_AURA_MOD_ATTACKER_SPELL_CRIT_CHANCE); }
|
||||
[[nodiscard]] bool HasUnattackableAura() const { return HasAuraType(SPELL_AURA_MOD_UNATTACKABLE); }
|
||||
[[nodiscard]] bool HasHealthRegenInCombatAura() const { return HasAuraType(SPELL_AURA_MOD_HEALTH_REGEN_IN_COMBAT); }
|
||||
@@ -2044,10 +2049,6 @@ public:
|
||||
void SendMovementFeatherFall(Player* sendTo);
|
||||
void SendMovementHover(Player* sendTo);
|
||||
|
||||
void SendChangeCurrentVictimOpcode(HostileReference* pHostileReference);
|
||||
void SendClearThreatListOpcode();
|
||||
void SendRemoveFromThreatListOpcode(HostileReference* pHostileReference);
|
||||
void SendThreatListUpdate();
|
||||
void SendClearTarget();
|
||||
|
||||
// Misc functions
|
||||
@@ -2077,7 +2078,6 @@ public:
|
||||
float m_modSpellHitChance;
|
||||
int32 m_baseSpellCritChance;
|
||||
|
||||
float m_threatModifier[MAX_SPELL_SCHOOL];
|
||||
float m_modAttackSpeedPct[3];
|
||||
|
||||
SpellImmuneList m_spellImmune[MAX_SPELL_IMMUNITY];
|
||||
@@ -2087,6 +2087,7 @@ public:
|
||||
typedef std::set<PetAura const*> PetAuraSet;
|
||||
PetAuraSet m_petAuras;
|
||||
|
||||
ObjectGuid LastCharmerGUID;
|
||||
bool IsAIEnabled;
|
||||
bool NeedChangeAI;
|
||||
|
||||
@@ -2184,7 +2185,8 @@ protected:
|
||||
uint32 m_reactiveTimer[MAX_REACTIVE];
|
||||
int32 m_regenTimer;
|
||||
|
||||
ThreatMgr m_ThreatMgr;
|
||||
ThreatManager m_threatManager;
|
||||
CombatManager m_combatManager;
|
||||
typedef std::map<ObjectGuid, float> CharmThreatMap;
|
||||
CharmThreatMap _charmThreatInfo;
|
||||
|
||||
@@ -2225,8 +2227,6 @@ private:
|
||||
//TimeTrackerSmall m_movesplineTimer;
|
||||
|
||||
Diminishing m_Diminishing;
|
||||
// Manage all Units that are threatened by us
|
||||
HostileRefMgr m_HostileRefMgr;
|
||||
|
||||
std::unordered_set<AbstractFollower*> m_followingMe;
|
||||
|
||||
@@ -2234,13 +2234,12 @@ private:
|
||||
int8 m_comboPoints;
|
||||
std::unordered_set<Unit*> m_ComboPointHolders;
|
||||
|
||||
RedirectThreatInfo _redirectThreatInfo;
|
||||
|
||||
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?
|
||||
bool _isCombatDisallowed;
|
||||
|
||||
uint32 _lastExtraAttackSpell;
|
||||
std::unordered_map<ObjectGuid /*guid*/, uint32 /*count*/> extraAttacksTargets;
|
||||
|
||||
@@ -445,6 +445,10 @@ bool Vehicle::AddPassenger(Unit* unit, int8 seatId)
|
||||
init.SetTransportEnter();
|
||||
init.Launch();
|
||||
|
||||
// Transfer threat from passenger to vehicle
|
||||
for (auto const& [guid, threatRef] : unit->GetThreatMgr().GetThreatenedByMeList())
|
||||
threatRef->GetOwner()->GetThreatMgr().AddThreat(_me, threatRef->GetThreat(), nullptr, true, true);
|
||||
|
||||
if (_me->IsCreature())
|
||||
{
|
||||
if (_me->ToCreature()->IsAIEnabled)
|
||||
|
||||
Reference in New Issue
Block a user