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

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();
}
/**