[9581] Fixed apply damage reduction to melee/ranged damage.
[getmangos.git] / src / game / ThreatManager.cpp
blob389d691a550342ebe87d6ca77cfd414968dae46b
1 /*
2 * Copyright (C) 2005-2010 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"
20 #include "Unit.h"
21 #include "Creature.h"
22 #include "CreatureAI.h"
23 #include "Map.h"
24 #include "Player.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
36 if(!pThreat)
37 return 0.0f;
39 if (pThreatSpell)
41 if (Player* modOwner = pHatedUnit->GetSpellModOwner())
42 modOwner->ApplySpellMod(pThreatSpell->Id, SPELLMOD_THREAT, pThreat);
44 if(crit)
45 pThreat *= pHatedUnit->GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_CRITICAL_THREAT,schoolMask);
48 float threat = pHatedUnit->ApplyTotalThreatModifier(pThreat, schoolMask);
49 return threat;
52 //============================================================
53 //================= HostileReference ==========================
54 //============================================================
56 HostileReference::HostileReference(Unit* pUnit, ThreatManager *pThreatManager, float pThreat)
58 iThreat = pThreat;
59 iTempThreatModifyer = 0.0f;
60 link(pUnit, pThreatManager);
61 iUnitGuid = pUnit->GetGUID();
62 iOnline = true;
63 iAccessible = true;
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)
93 if(getSource())
94 getSource()->processThreatEvent(&pThreatRefStatusChangeEvent);
97 //============================================================
99 void HostileReference::addThreat(float pMod)
101 iThreat += pMod;
102 // the threat is changed. Source and target unit have to be availabe
103 // if the link was cut before relink it again
104 if(!isOnline())
105 updateOnlineStatus();
106 if(pMod != 0.0f)
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()
125 bool online = false;
126 bool accessible = false;
128 if(!isValid())
130 Unit* target = ObjectAccessor::GetUnit(*getSourceUnit(), getUnitGuid());
131 if(target)
132 link(target, getSource());
134 // only check for online status if
135 // ref is valid
136 // target is no player or not gamemaster
137 // target is not in flight
138 if(isValid() &&
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);
144 if(!online)
146 if(creature->AI()->canReachByRangeAttack(getTarget()))
147 online = true; // not accessable but stays online
149 else
150 accessible = true;
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)
164 iOnline = pIsOnline;
165 if(!iOnline)
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()
192 invalidate();
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)
213 (*i)->unlink();
214 delete (*i);
216 iThreatList.clear();
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)
229 result = (*i);
230 break;
234 return result;
237 //============================================================
238 // Add the threat, if we find the reference
240 HostileReference* ThreatContainer::addThreat(Unit* pVictim, float pThreat)
242 HostileReference* ref = getReferenceByTarget(pVictim);
243 if(ref)
244 ref->addThreat(pThreat);
245 return ref;
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);
273 iDirty = false;
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;
283 bool found = false;
284 bool noPriorityTargetFound = false;
286 ThreatList::const_iterator lastRef = iThreatList.end();
287 lastRef--;
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)) )
299 if(iter != lastRef)
301 // current victim is a second choice target, so don't compare threat with it below
302 if(currentRef == pCurrentVictim)
303 pCurrentVictim = NULL;
304 ++iter;
305 continue;
307 else
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();
312 continue;
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
324 found = true;
325 break;
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
336 else // select any
338 found = true;
339 break;
342 ++iter;
344 if(!found)
345 currentRef = NULL;
347 return currentRef;
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);
367 iUpdateNeed = false;
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.)
379 // not to self
380 if (pVictim == getOwner())
381 return;
383 // not to GM
384 if (!pVictim || (pVictim->GetTypeId() == TYPEID_PLAYER && ((Player*)pVictim)->isGameMaster()) )
385 return;
387 // not to dead and not for dead
388 if(!pVictim->isAlive() || !getOwner()->isAlive() )
389 return;
391 assert(getOwner()->GetTypeId()== TYPEID_UNIT);
393 float threat = ThreatCalcHelper::calcThreat(pVictim, iOwner, pThreat, crit, schoolMask, pThreatSpell);
395 HostileReference* ref = iThreatContainer.addThreat(pVictim, threat);
396 // Ref is online
397 if (ref)
398 iUpdateNeed = true;
399 // Ref is not in the online refs, search the offline refs next
400 else
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
409 iUpdateNeed = true;
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);
420 iUpdateNeed = true;
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)
437 float threat = 0.0f;
438 HostileReference* ref = iThreatContainer.getReferenceByTarget(pVictim);
439 if(!ref && pAlsoSearchOfflineList)
440 ref = iThreatOfflineContainer.getReferenceByTarget(pVictim);
441 if(ref)
442 threat = ref->getThreat();
443 return threat;
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());
458 iUpdateNeed = true;
464 //============================================================
466 void ThreatManager::tauntFadeOut(Unit *pTaunter)
468 if(HostileReference* ref = iThreatContainer.getReferenceByTarget(pTaunter))
470 ref->resetTempThreat();
471 iUpdateNeed = true;
475 //============================================================
477 void ThreatManager::setCurrentVictim(HostileReference* pHostileReference)
479 // including NULL==NULL case
480 if (pHostileReference == iCurrentVictim)
481 return;
483 if (pHostileReference)
484 iOwner->SendHighestThreatUpdate(pHostileReference);
486 iCurrentVictim = pHostileReference;
487 iUpdateNeed = true;
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
506 break;
507 case UEV_THREAT_REF_ONLINE_STATUS:
508 if(!hostileReference->isOnline())
510 if (hostileReference == getCurrentVictim())
512 setCurrentVictim(NULL);
513 setDirty(true);
515 iOwner->SendThreatRemove(hostileReference);
516 iThreatContainer.remove(hostileReference);
517 iUpdateNeed = true;
518 iThreatOfflineContainer.addReference(hostileReference);
520 else
522 if(getCurrentVictim() && hostileReference->getThreat() > (1.1f * getCurrentVictim()->getThreat()))
523 setDirty(true);
524 iThreatContainer.addReference(hostileReference);
525 iUpdateNeed = true;
526 iThreatOfflineContainer.remove(hostileReference);
528 break;
529 case UEV_THREAT_REF_REMOVE_FROM_LIST:
530 if (hostileReference == getCurrentVictim())
532 setCurrentVictim(NULL);
533 setDirty(true);
535 if(hostileReference->isOnline())
537 iOwner->SendThreatRemove(hostileReference);
538 iThreatContainer.remove(hostileReference);
539 iUpdateNeed = true;
541 else
542 iThreatOfflineContainer.remove(hostileReference);
543 break;
547 void ThreatManager::UpdateForClient(uint32 diff)
549 if (!iUpdateNeed || isThreatListEmpty())
550 return;
552 iUpdateTimer.Update(diff);
553 if (iUpdateTimer.Passed())
555 iOwner->SendThreatUpdate();
556 iUpdateTimer.Reset(THREAT_UPDATE_INTERVAL);
557 iUpdateNeed = false;