2 * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 #include "ThreatManager.h"
22 #include "CreatureAI.h"
25 #include "ObjectAccessor.h"
26 #include "UnitEvents.h"
28 //==============================================================
29 //================= ThreatCalcHelper ===========================
30 //==============================================================
32 // The pHatingUnit is not used yet
33 float ThreatCalcHelper::calcThreat(Unit
* pHatedUnit
, Unit
* pHatingUnit
, float pThreat
, bool crit
, SpellSchoolMask schoolMask
, SpellEntry
const *pThreatSpell
)
35 // all flat mods applied early
41 if (Player
* modOwner
= pHatedUnit
->GetSpellModOwner())
42 modOwner
->ApplySpellMod(pThreatSpell
->Id
, SPELLMOD_THREAT
, pThreat
);
45 pThreat
*= pHatedUnit
->GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_CRITICAL_THREAT
,schoolMask
);
48 float threat
= pHatedUnit
->ApplyTotalThreatModifier(pThreat
, schoolMask
);
52 //============================================================
53 //================= HostileReference ==========================
54 //============================================================
56 HostileReference::HostileReference(Unit
* pUnit
, ThreatManager
*pThreatManager
, float pThreat
)
59 iTempThreatModifyer
= 0.0f
;
60 link(pUnit
, pThreatManager
);
61 iUnitGuid
= pUnit
->GetGUID();
66 //============================================================
67 // Tell our refTo (target) object that we have a link
68 void HostileReference::targetObjectBuildLink()
70 getTarget()->addHatedBy(this);
73 //============================================================
74 // Tell our refTo (taget) object, that the link is cut
75 void HostileReference::targetObjectDestroyLink()
77 getTarget()->removeHatedBy(this);
80 //============================================================
81 // Tell our refFrom (source) object, that the link is cut (Target destroyed)
83 void HostileReference::sourceObjectDestroyLink()
85 setOnlineOfflineState(false);
88 //============================================================
89 // Inform the source, that the status of the reference changed
91 void HostileReference::fireStatusChanged(ThreatRefStatusChangeEvent
& pThreatRefStatusChangeEvent
)
94 getSource()->processThreatEvent(&pThreatRefStatusChangeEvent
);
97 //============================================================
99 void HostileReference::addThreat(float pMod
)
102 // the threat is changed. Source and target unit have to be availabe
103 // if the link was cut before relink it again
105 updateOnlineStatus();
108 ThreatRefStatusChangeEvent
event(UEV_THREAT_REF_THREAT_CHANGE
, this, pMod
);
109 fireStatusChanged(event
);
112 if(isValid() && pMod
>= 0)
114 Unit
* victim_owner
= getTarget()->GetOwner();
115 if(victim_owner
&& victim_owner
->isAlive())
116 getSource()->addThreat(victim_owner
, 0.0f
); // create a threat to the owner of a pet, if the pet attacks
120 //============================================================
121 // check, if source can reach target and set the status
123 void HostileReference::updateOnlineStatus()
126 bool accessible
= false;
130 Unit
* target
= ObjectAccessor::GetUnit(*getSourceUnit(), getUnitGuid());
132 link(target
, getSource());
134 // only check for online status if
136 // target is no player or not gamemaster
137 // target is not in flight
139 ((getTarget()->GetTypeId() != TYPEID_PLAYER
|| !((Player
*)getTarget())->isGameMaster()) ||
140 !getTarget()->hasUnitState(UNIT_STAT_IN_FLIGHT
)))
142 Creature
* creature
= (Creature
* ) getSourceUnit();
143 online
= getTarget()->isInAccessablePlaceFor(creature
);
146 if(creature
->AI()->canReachByRangeAttack(getTarget()))
147 online
= true; // not accessable but stays online
153 setAccessibleState(accessible
);
154 setOnlineOfflineState(online
);
157 //============================================================
158 // set the status and fire the event on status change
160 void HostileReference::setOnlineOfflineState(bool pIsOnline
)
162 if(iOnline
!= pIsOnline
)
166 setAccessibleState(false); // if not online that not accessable as well
168 ThreatRefStatusChangeEvent
event(UEV_THREAT_REF_ONLINE_STATUS
, this);
169 fireStatusChanged(event
);
173 //============================================================
175 void HostileReference::setAccessibleState(bool pIsAccessible
)
177 if(iAccessible
!= pIsAccessible
)
179 iAccessible
= pIsAccessible
;
181 ThreatRefStatusChangeEvent
event(UEV_THREAT_REF_ASSECCIBLE_STATUS
, this);
182 fireStatusChanged(event
);
186 //============================================================
187 // prepare the reference for deleting
188 // this is called be the target
190 void HostileReference::removeReference()
194 ThreatRefStatusChangeEvent
event(UEV_THREAT_REF_REMOVE_FROM_LIST
, this);
195 fireStatusChanged(event
);
198 //============================================================
200 Unit
* HostileReference::getSourceUnit()
202 return (getSource()->getOwner());
205 //============================================================
206 //================ ThreatContainer ===========================
207 //============================================================
209 void ThreatContainer::clearReferences()
211 for(ThreatList::const_iterator i
= iThreatList
.begin(); i
!= iThreatList
.end(); ++i
)
219 //============================================================
220 // Return the HostileReference of NULL, if not found
221 HostileReference
* ThreatContainer::getReferenceByTarget(Unit
* pVictim
)
223 HostileReference
* result
= NULL
;
224 uint64 guid
= pVictim
->GetGUID();
225 for(ThreatList::const_iterator i
= iThreatList
.begin(); i
!= iThreatList
.end(); ++i
)
227 if((*i
)->getUnitGuid() == guid
)
237 //============================================================
238 // Add the threat, if we find the reference
240 HostileReference
* ThreatContainer::addThreat(Unit
* pVictim
, float pThreat
)
242 HostileReference
* ref
= getReferenceByTarget(pVictim
);
244 ref
->addThreat(pThreat
);
248 //============================================================
250 void ThreatContainer::modifyThreatPercent(Unit
*pVictim
, int32 pPercent
)
252 if(HostileReference
* ref
= getReferenceByTarget(pVictim
))
253 ref
->addThreatPercent(pPercent
);
256 //============================================================
258 bool HostileReferenceSortPredicate(const HostileReference
* lhs
, const HostileReference
* rhs
)
260 // std::list::sort ordering predicate must be: (Pred(x,y)&&Pred(y,x))==false
261 return lhs
->getThreat() > rhs
->getThreat(); // reverse sorting
264 //============================================================
265 // Check if the list is dirty and sort if necessary
267 void ThreatContainer::update()
269 if(iDirty
&& iThreatList
.size() >1)
271 iThreatList
.sort(HostileReferenceSortPredicate
);
276 //============================================================
277 // return the next best victim
278 // could be the current victim
280 HostileReference
* ThreatContainer::selectNextVictim(Creature
* pAttacker
, HostileReference
* pCurrentVictim
)
282 HostileReference
* currentRef
= NULL
;
284 bool noPriorityTargetFound
= false;
286 ThreatList::const_iterator lastRef
= iThreatList
.end();
289 for(ThreatList::const_iterator iter
= iThreatList
.begin(); iter
!= iThreatList
.end() && !found
;)
291 currentRef
= (*iter
);
293 Unit
* target
= currentRef
->getTarget();
294 assert(target
); // if the ref has status online the target must be there !
296 // some units are prefered in comparison to others
297 if(!noPriorityTargetFound
&& (target
->IsImmunedToDamage(pAttacker
->GetMeleeDamageSchoolMask()) || target
->hasNegativeAuraWithInterruptFlag(AURA_INTERRUPT_FLAG_DAMAGE
)) )
301 // current victim is a second choice target, so don't compare threat with it below
302 if(currentRef
== pCurrentVictim
)
303 pCurrentVictim
= NULL
;
309 // if we reached to this point, everyone in the threatlist is a second choice target. In such a situation the target with the highest threat should be attacked.
310 noPriorityTargetFound
= true;
311 iter
= iThreatList
.begin();
316 if(!pAttacker
->IsOutOfThreatArea(target
)) // skip non attackable currently targets
318 if(pCurrentVictim
) // select 1.3/1.1 better target in comparison current target
320 // list sorted and and we check current target, then this is best case
321 if(pCurrentVictim
== currentRef
|| currentRef
->getThreat() <= 1.1f
* pCurrentVictim
->getThreat() )
323 currentRef
= pCurrentVictim
; // for second case
328 if (currentRef
->getThreat() > 1.3f
* pCurrentVictim
->getThreat() ||
329 (currentRef
->getThreat() > 1.1f
* pCurrentVictim
->getThreat() &&
330 pAttacker
->IsWithinDistInMap(target
, ATTACK_DISTANCE
)) )
331 { //implement 110% threat rule for targets in melee range
332 found
= true; //and 130% rule for targets in ranged distances
333 break; //for selecting alive targets
350 //============================================================
351 //=================== ThreatManager ==========================
352 //============================================================
354 ThreatManager::ThreatManager(Unit
* owner
)
355 : iCurrentVictim(NULL
), iOwner(owner
), iUpdateTimer(THREAT_UPDATE_INTERVAL
), iUpdateNeed(false)
359 //============================================================
361 void ThreatManager::clearReferences()
363 iThreatContainer
.clearReferences();
364 iThreatOfflineContainer
.clearReferences();
365 iCurrentVictim
= NULL
;
366 iUpdateTimer
.Reset(THREAT_UPDATE_INTERVAL
);
370 //============================================================
372 void ThreatManager::addThreat(Unit
* pVictim
, float pThreat
, bool crit
, SpellSchoolMask schoolMask
, SpellEntry
const *pThreatSpell
)
374 //function deals with adding threat and adding players and pets into ThreatList
375 //mobs, NPCs, guards have ThreatList and HateOfflineList
376 //players and pets have only InHateListOf
377 //HateOfflineList is used co contain unattackable victims (in-flight, in-water, GM etc.)
380 if (pVictim
== getOwner())
384 if (!pVictim
|| (pVictim
->GetTypeId() == TYPEID_PLAYER
&& ((Player
*)pVictim
)->isGameMaster()) )
387 // not to dead and not for dead
388 if(!pVictim
->isAlive() || !getOwner()->isAlive() )
391 assert(getOwner()->GetTypeId()== TYPEID_UNIT
);
393 float threat
= ThreatCalcHelper::calcThreat(pVictim
, iOwner
, pThreat
, crit
, schoolMask
, pThreatSpell
);
395 HostileReference
* ref
= iThreatContainer
.addThreat(pVictim
, threat
);
399 // Ref is not in the online refs, search the offline refs next
401 ref
= iThreatOfflineContainer
.addThreat(pVictim
, threat
);
403 if(!ref
) // there was no ref => create a new one
405 // threat has to be 0 here
406 HostileReference
* hostileReference
= new HostileReference(pVictim
, this, 0);
407 iThreatContainer
.addReference(hostileReference
);
408 hostileReference
->addThreat(threat
); // now we add the real threat
410 if(pVictim
->GetTypeId() == TYPEID_PLAYER
&& ((Player
*)pVictim
)->isGameMaster())
411 hostileReference
->setOnlineOfflineState(false); // GM is always offline
415 //============================================================
417 void ThreatManager::modifyThreatPercent(Unit
*pVictim
, int32 pPercent
)
419 iThreatContainer
.modifyThreatPercent(pVictim
, pPercent
);
423 //============================================================
425 Unit
* ThreatManager::getHostileTarget()
427 iThreatContainer
.update();
428 HostileReference
* nextVictim
= iThreatContainer
.selectNextVictim((Creature
*) getOwner(), getCurrentVictim());
429 setCurrentVictim(nextVictim
);
430 return getCurrentVictim() != NULL
? getCurrentVictim()->getTarget() : NULL
;
433 //============================================================
435 float ThreatManager::getThreat(Unit
*pVictim
, bool pAlsoSearchOfflineList
)
438 HostileReference
* ref
= iThreatContainer
.getReferenceByTarget(pVictim
);
439 if(!ref
&& pAlsoSearchOfflineList
)
440 ref
= iThreatOfflineContainer
.getReferenceByTarget(pVictim
);
442 threat
= ref
->getThreat();
446 //============================================================
448 void ThreatManager::tauntApply(Unit
* pTaunter
)
450 if(HostileReference
* ref
= iThreatContainer
.getReferenceByTarget(pTaunter
))
452 if(getCurrentVictim() && (ref
->getThreat() < getCurrentVictim()->getThreat()))
454 // Ok, temp threat is unused
455 if(ref
->getTempThreatModifyer() == 0.0f
)
457 ref
->setTempThreat(getCurrentVictim()->getThreat());
464 //============================================================
466 void ThreatManager::tauntFadeOut(Unit
*pTaunter
)
468 if(HostileReference
* ref
= iThreatContainer
.getReferenceByTarget(pTaunter
))
470 ref
->resetTempThreat();
475 //============================================================
477 void ThreatManager::setCurrentVictim(HostileReference
* pHostileReference
)
479 // including NULL==NULL case
480 if (pHostileReference
== iCurrentVictim
)
483 if (pHostileReference
)
484 iOwner
->SendHighestThreatUpdate(pHostileReference
);
486 iCurrentVictim
= pHostileReference
;
490 //============================================================
491 // The hated unit is gone, dead or deleted
492 // return true, if the event is consumed
494 void ThreatManager::processThreatEvent(ThreatRefStatusChangeEvent
* threatRefStatusChangeEvent
)
496 threatRefStatusChangeEvent
->setThreatManager(this); // now we can set the threat manager
498 HostileReference
* hostileReference
= threatRefStatusChangeEvent
->getReference();
500 switch(threatRefStatusChangeEvent
->getType())
502 case UEV_THREAT_REF_THREAT_CHANGE
:
503 if((getCurrentVictim() == hostileReference
&& threatRefStatusChangeEvent
->getFValue()<0.0f
) ||
504 (getCurrentVictim() != hostileReference
&& threatRefStatusChangeEvent
->getFValue()>0.0f
))
505 setDirty(true); // the order in the threat list might have changed
507 case UEV_THREAT_REF_ONLINE_STATUS
:
508 if(!hostileReference
->isOnline())
510 if (hostileReference
== getCurrentVictim())
512 setCurrentVictim(NULL
);
515 iThreatContainer
.remove(hostileReference
);
517 iThreatOfflineContainer
.addReference(hostileReference
);
521 if(getCurrentVictim() && hostileReference
->getThreat() > (1.1f
* getCurrentVictim()->getThreat()))
523 iThreatContainer
.addReference(hostileReference
);
525 iThreatOfflineContainer
.remove(hostileReference
);
528 case UEV_THREAT_REF_REMOVE_FROM_LIST
:
529 if (hostileReference
== getCurrentVictim())
531 setCurrentVictim(NULL
);
534 iOwner
->SendThreatRemove(hostileReference
);
535 if(hostileReference
->isOnline())
537 iThreatContainer
.remove(hostileReference
);
541 iThreatOfflineContainer
.remove(hostileReference
);
546 void ThreatManager::UpdateForClient(uint32 diff
)
548 if (!iUpdateNeed
|| isThreatListEmpty())
551 iUpdateTimer
.Update(diff
);
552 if (iUpdateTimer
.Passed())
554 iOwner
->SendThreatUpdate();
555 iUpdateTimer
.Reset(THREAT_UPDATE_INTERVAL
);