mirror of
https://github.com/liyunfan1223/azerothcore-wotlk.git
synced 2026-08-07 07:17:57 +00:00
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:
@@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user