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:
blinkysc
2026-03-18 13:36:59 -05:00
committed by GitHub
parent c80a0f1fad
commit 984baa92dd
101 changed files with 7045 additions and 2659 deletions
+99 -64
View File
@@ -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
+7 -1
View File
@@ -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);
}
}
}
+1
View File
@@ -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;
}
+1 -9
View File
@@ -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);
+157 -465
View File
@@ -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();
}
/**
+33 -34
View File
@@ -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)