aboutsummaryrefslogtreecommitdiff
path: root/src/server/game/Combat/ThreatManager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/server/game/Combat/ThreatManager.cpp')
-rw-r--r--src/server/game/Combat/ThreatManager.cpp553
1 files changed, 553 insertions, 0 deletions
diff --git a/src/server/game/Combat/ThreatManager.cpp b/src/server/game/Combat/ThreatManager.cpp
new file mode 100644
index 00000000000..52f02f0f66d
--- /dev/null
+++ b/src/server/game/Combat/ThreatManager.cpp
@@ -0,0 +1,553 @@
+/*
+ * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
+ *
+ * Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "ThreatManager.h"
+#include "Unit.h"
+#include "Creature.h"
+#include "CreatureAI.h"
+#include "Map.h"
+#include "Player.h"
+#include "ObjectAccessor.h"
+#include "UnitEvents.h"
+#include "SpellAuras.h"
+
+//==============================================================
+//================= ThreatCalcHelper ===========================
+//==============================================================
+
+// The pHatingUnit is not used yet
+float ThreatCalcHelper::calcThreat(Unit* pHatedUnit, Unit* /*pHatingUnit*/, float fThreat, SpellSchoolMask schoolMask, SpellEntry const *pThreatSpell)
+{
+ if (pThreatSpell)
+ if (Player* modOwner = pHatedUnit->GetSpellModOwner())
+ modOwner->ApplySpellMod(pThreatSpell->Id, SPELLMOD_THREAT, fThreat);
+
+ return pHatedUnit->ApplyTotalThreatModifier(fThreat, schoolMask);
+}
+
+//============================================================
+//================= HostileReference ==========================
+//============================================================
+
+HostileReference::HostileReference(Unit* pUnit, ThreatManager *pThreatManager, float fThreat)
+{
+ iThreat = fThreat;
+ iTempThreatModifier = 0.0f;
+ link(pUnit, pThreatManager);
+ iUnitGuid = pUnit->GetGUID();
+ iOnline = true;
+ iAccessible = true;
+}
+
+//============================================================
+// Tell our refTo (target) object that we have a link
+void HostileReference::targetObjectBuildLink()
+{
+ getTarget()->addHatedBy(this);
+}
+
+//============================================================
+// Tell our refTo (taget) object, that the link is cut
+void HostileReference::targetObjectDestroyLink()
+{
+ getTarget()->removeHatedBy(this);
+}
+
+//============================================================
+// Tell our refFrom (source) object, that the link is cut (Target destroyed)
+
+void HostileReference::sourceObjectDestroyLink()
+{
+ setOnlineOfflineState(false);
+}
+
+//============================================================
+// Inform the source, that the status of the reference changed
+
+void HostileReference::fireStatusChanged(ThreatRefStatusChangeEvent& pThreatRefStatusChangeEvent)
+{
+ if (getSource())
+ getSource()->processThreatEvent(&pThreatRefStatusChangeEvent);
+}
+
+//============================================================
+
+void HostileReference::addThreat(float fModThreat)
+{
+ iThreat += fModThreat;
+ // the threat is changed. Source and target unit have to be available
+ // if the link was cut before relink it again
+ if (!isOnline())
+ updateOnlineStatus();
+ if (fModThreat != 0.0f)
+ {
+ ThreatRefStatusChangeEvent event(UEV_THREAT_REF_THREAT_CHANGE, this, fModThreat);
+ fireStatusChanged(event);
+ }
+
+ if (isValid() && fModThreat >= 0.0f)
+ {
+ Unit* victim_owner = getTarget()->GetCharmerOrOwner();
+ if (victim_owner && victim_owner->isAlive())
+ getSource()->addThreat(victim_owner, 0.0f); // create a threat to the owner of a pet, if the pet attacks
+ }
+}
+
+//============================================================
+// check, if source can reach target and set the status
+
+void HostileReference::updateOnlineStatus()
+{
+ bool online = false;
+ bool accessible = false;
+
+ if (!isValid())
+ if (Unit* target = ObjectAccessor::GetUnit(*getSourceUnit(), getUnitGuid()))
+ link(target, getSource());
+
+ // only check for online status if
+ // ref is valid
+ // target is no player or not gamemaster
+ // target is not in flight
+ if (isValid() &&
+ ((getTarget()->GetTypeId() != TYPEID_PLAYER || !((Player*)getTarget())->isGameMaster()) ||
+ !getTarget()->hasUnitState(UNIT_STAT_IN_FLIGHT)))
+ {
+ Creature* creature = getSourceUnit()->ToCreature();
+ online = getTarget()->isInAccessiblePlaceFor(creature);
+ if (!online)
+ {
+ if (creature->IsWithinCombatRange(getTarget(), creature->m_CombatDistance))
+ online = true; // not accessible but stays online
+ }
+ else
+ accessible = true;
+
+ }
+ setAccessibleState(accessible);
+ setOnlineOfflineState(online);
+}
+
+//============================================================
+// set the status and fire the event on status change
+
+void HostileReference::setOnlineOfflineState(bool pIsOnline)
+{
+ if (iOnline != pIsOnline)
+ {
+ iOnline = pIsOnline;
+ if (!iOnline)
+ setAccessibleState(false); // if not online that not accessable as well
+
+ ThreatRefStatusChangeEvent event(UEV_THREAT_REF_ONLINE_STATUS, this);
+ fireStatusChanged(event);
+ }
+}
+
+//============================================================
+
+void HostileReference::setAccessibleState(bool pIsAccessible)
+{
+ if (iAccessible != pIsAccessible)
+ {
+ iAccessible = pIsAccessible;
+
+ ThreatRefStatusChangeEvent event(UEV_THREAT_REF_ASSECCIBLE_STATUS, this);
+ fireStatusChanged(event);
+ }
+}
+
+//============================================================
+// prepare the reference for deleting
+// this is called be the target
+
+void HostileReference::removeReference()
+{
+ invalidate();
+
+ ThreatRefStatusChangeEvent event(UEV_THREAT_REF_REMOVE_FROM_LIST, this);
+ fireStatusChanged(event);
+}
+
+//============================================================
+
+Unit* HostileReference::getSourceUnit()
+{
+ return (getSource()->getOwner());
+}
+
+//============================================================
+//================ ThreatContainer ===========================
+//============================================================
+
+void ThreatContainer::clearReferences()
+{
+ for (std::list<HostileReference*>::const_iterator i = iThreatList.begin(); i != iThreatList.end(); ++i)
+ {
+ (*i)->unlink();
+ delete (*i);
+ }
+ iThreatList.clear();
+}
+
+//============================================================
+// Return the HostileReference of NULL, if not found
+HostileReference* ThreatContainer::getReferenceByTarget(Unit* pVictim)
+{
+ HostileReference* result = NULL;
+
+ uint64 guid = pVictim->GetGUID();
+ for (std::list<HostileReference*>::const_iterator i = iThreatList.begin(); i != iThreatList.end(); ++i)
+ {
+ if ((*i)->getUnitGuid() == guid)
+ {
+ result = (*i);
+ break;
+ }
+ }
+
+ return result;
+}
+
+//============================================================
+// Add the threat, if we find the reference
+
+HostileReference* ThreatContainer::addThreat(Unit* pVictim, float fThreat)
+{
+ HostileReference* ref = getReferenceByTarget(pVictim);
+ if (ref)
+ ref->addThreat(fThreat);
+ return ref;
+}
+
+//============================================================
+
+void ThreatContainer::modifyThreatPercent(Unit *pVictim, int32 iPercent)
+{
+ if (HostileReference* ref = getReferenceByTarget(pVictim))
+ ref->addThreatPercent(iPercent);
+}
+
+//============================================================
+// Check if the list is dirty and sort if necessary
+
+void ThreatContainer::update()
+{
+ if (iDirty && iThreatList.size() >1)
+ {
+ iThreatList.sort(Trinity::ThreatOrderPred());
+ }
+ iDirty = false;
+}
+
+//============================================================
+// return the next best victim
+// could be the current victim
+
+HostileReference* ThreatContainer::selectNextVictim(Creature* pAttacker, HostileReference* pCurrentVictim)
+{
+ HostileReference* currentRef = NULL;
+ bool found = false;
+ bool noPriorityTargetFound = false;
+
+ std::list<HostileReference*>::const_iterator lastRef = iThreatList.end();
+ lastRef--;
+
+ for (std::list<HostileReference*>::const_iterator iter = iThreatList.begin(); iter != iThreatList.end() && !found;)
+ {
+ currentRef = (*iter);
+
+ Unit* target = currentRef->getTarget();
+ assert(target); // if the ref has status online the target must be there !
+
+ // some units are prefered in comparison to others
+ if (!noPriorityTargetFound && (target->IsImmunedToDamage(pAttacker->GetMeleeDamageSchoolMask()) || target->HasNegativeAuraWithInterruptFlag(AURA_INTERRUPT_FLAG_TAKE_DAMAGE)))
+ {
+ if (iter != lastRef)
+ {
+ // current victim is a second choice target, so don't compare threat with it below
+ if (currentRef == pCurrentVictim)
+ pCurrentVictim = NULL;
+ ++iter;
+ continue;
+ }
+ else
+ {
+ // 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.
+ noPriorityTargetFound = true;
+ iter = iThreatList.begin();
+ continue;
+ }
+ }
+
+ if (pAttacker->canCreatureAttack(target)) // skip non attackable currently targets
+ {
+ if (pCurrentVictim) // select 1.3/1.1 better target in comparison current target
+ {
+ // list sorted and and we check current target, then this is best case
+ if (pCurrentVictim == currentRef || currentRef->getThreat() <= 1.1f * pCurrentVictim->getThreat())
+ {
+ currentRef = pCurrentVictim; // for second case
+ found = true;
+ break;
+ }
+
+ if (currentRef->getThreat() > 1.3f * pCurrentVictim->getThreat() ||
+ currentRef->getThreat() > 1.1f * pCurrentVictim->getThreat() &&
+ pAttacker->IsWithinMeleeRange(target))
+ { //implement 110% threat rule for targets in melee range
+ found = true; //and 130% rule for targets in ranged distances
+ break; //for selecting alive targets
+ }
+ }
+ else // select any
+ {
+ found = true;
+ break;
+ }
+ }
+ ++iter;
+ }
+ if (!found)
+ currentRef = NULL;
+
+ return currentRef;
+}
+
+//============================================================
+//=================== ThreatManager ==========================
+//============================================================
+
+ThreatManager::ThreatManager(Unit* owner) : iCurrentVictim(NULL), iOwner(owner), iUpdateTimer(THREAT_UPDATE_INTERVAL)
+{
+}
+
+//============================================================
+
+void ThreatManager::clearReferences()
+{
+ iThreatContainer.clearReferences();
+ iThreatOfflineContainer.clearReferences();
+ iCurrentVictim = NULL;
+ iUpdateTimer = THREAT_UPDATE_INTERVAL;
+}
+
+//============================================================
+
+void ThreatManager::addThreat(Unit* pVictim, float fThreat, SpellSchoolMask schoolMask, SpellEntry const *pThreatSpell)
+{
+ //function deals with adding threat and adding players and pets into ThreatList
+ //mobs, NPCs, guards have ThreatList and HateOfflineList
+ //players and pets have only InHateListOf
+ //HateOfflineList is used co contain unattackable victims (in-flight, in-water, GM etc.)
+
+ // not to self
+ if (pVictim == getOwner())
+ return;
+
+ // not to GM
+ if (!pVictim || (pVictim->GetTypeId() == TYPEID_PLAYER && pVictim->ToPlayer()->isGameMaster()))
+ return;
+
+ // not to dead and not for dead
+ if (!pVictim->isAlive() || !getOwner()->isAlive())
+ return;
+
+ assert(getOwner()->GetTypeId() == TYPEID_UNIT);
+
+ float threat = ThreatCalcHelper::calcThreat(pVictim, iOwner, fThreat, schoolMask, pThreatSpell);
+
+ // must check > 0.0f, otherwise dead loop
+ if (threat > 0.0f && pVictim->GetReducedThreatPercent())
+ {
+ uint32 reducedThreadPercent = pVictim->GetReducedThreatPercent();
+
+ Unit *unit = pVictim->GetMisdirectionTarget();
+ if (unit)
+ if (Aura* pAura = unit->GetAura(63326)) // Glyph of Vigilance
+ reducedThreadPercent += pAura->GetSpellProto()->EffectBasePoints[0];
+
+ float reducedThreat = threat * reducedThreadPercent / 100;
+ threat -= reducedThreat;
+ if (unit)
+ _addThreat(unit, reducedThreat);
+ }
+
+ _addThreat(pVictim, threat);
+}
+
+void ThreatManager::_addThreat(Unit *pVictim, float fThreat)
+{
+ HostileReference* ref = iThreatContainer.addThreat(pVictim, fThreat);
+ // Ref is not in the online refs, search the offline refs next
+ if (!ref)
+ ref = iThreatOfflineContainer.addThreat(pVictim, fThreat);
+
+ if (!ref) // there was no ref => create a new one
+ {
+ // threat has to be 0 here
+ HostileReference* hostilReference = new HostileReference(pVictim, this, 0);
+ iThreatContainer.addReference(hostilReference);
+ hostilReference->addThreat(fThreat); // now we add the real threat
+ if (pVictim->GetTypeId() == TYPEID_PLAYER && pVictim->ToPlayer()->isGameMaster())
+ hostilReference->setOnlineOfflineState(false); // GM is always offline
+ }
+}
+
+//============================================================
+
+void ThreatManager::modifyThreatPercent(Unit *pVictim, int32 iPercent)
+{
+ iThreatContainer.modifyThreatPercent(pVictim, iPercent);
+}
+
+//============================================================
+
+Unit* ThreatManager::getHostilTarget()
+{
+ iThreatContainer.update();
+ HostileReference* nextVictim = iThreatContainer.selectNextVictim(getOwner()->ToCreature(), getCurrentVictim());
+ setCurrentVictim(nextVictim);
+ return getCurrentVictim() != NULL ? getCurrentVictim()->getTarget() : NULL;
+}
+
+//============================================================
+
+float ThreatManager::getThreat(Unit *pVictim, bool pAlsoSearchOfflineList)
+{
+ float threat = 0.0f;
+ HostileReference* ref = iThreatContainer.getReferenceByTarget(pVictim);
+ if (!ref && pAlsoSearchOfflineList)
+ ref = iThreatOfflineContainer.getReferenceByTarget(pVictim);
+ if (ref)
+ threat = ref->getThreat();
+ return threat;
+}
+
+//============================================================
+
+void ThreatManager::tauntApply(Unit* pTaunter)
+{
+ HostileReference* ref = iThreatContainer.getReferenceByTarget(pTaunter);
+ if (getCurrentVictim() && ref && (ref->getThreat() < getCurrentVictim()->getThreat()))
+ {
+ if (ref->getTempThreatModifier() == 0.0f) // Ok, temp threat is unused
+ ref->setTempThreat(getCurrentVictim()->getThreat());
+ }
+}
+
+//============================================================
+
+void ThreatManager::tauntFadeOut(Unit *pTaunter)
+{
+ HostileReference* ref = iThreatContainer.getReferenceByTarget(pTaunter);
+ if (ref)
+ ref->resetTempThreat();
+}
+
+//============================================================
+
+void ThreatManager::setCurrentVictim(HostileReference* pHostileReference)
+{
+ if (pHostileReference && pHostileReference != iCurrentVictim)
+ {
+ iOwner->SendChangeCurrentVictimOpcode(pHostileReference);
+ }
+ iCurrentVictim = pHostileReference;
+}
+
+//============================================================
+// The hated unit is gone, dead or deleted
+// return true, if the event is consumed
+
+void ThreatManager::processThreatEvent(ThreatRefStatusChangeEvent* threatRefStatusChangeEvent)
+{
+ threatRefStatusChangeEvent->setThreatManager(this); // now we can set the threat manager
+
+ HostileReference* hostilReference = threatRefStatusChangeEvent->getReference();
+
+ switch(threatRefStatusChangeEvent->getType())
+ {
+ case UEV_THREAT_REF_THREAT_CHANGE:
+ if ((getCurrentVictim() == hostilReference && threatRefStatusChangeEvent->getFValue()<0.0f) ||
+ (getCurrentVictim() != hostilReference && threatRefStatusChangeEvent->getFValue()>0.0f))
+ setDirty(true); // the order in the threat list might have changed
+ break;
+ case UEV_THREAT_REF_ONLINE_STATUS:
+ if (!hostilReference->isOnline())
+ {
+ if (hostilReference == getCurrentVictim())
+ {
+ setCurrentVictim(NULL);
+ setDirty(true);
+ }
+ iThreatContainer.remove(hostilReference);
+ iThreatOfflineContainer.addReference(hostilReference);
+ }
+ else
+ {
+ if (getCurrentVictim() && hostilReference->getThreat() > (1.1f * getCurrentVictim()->getThreat()))
+ setDirty(true);
+ iThreatContainer.addReference(hostilReference);
+ iThreatOfflineContainer.remove(hostilReference);
+ }
+ break;
+ case UEV_THREAT_REF_REMOVE_FROM_LIST:
+ if (hostilReference == getCurrentVictim())
+ {
+ setCurrentVictim(NULL);
+ setDirty(true);
+ }
+ iOwner->SendRemoveFromThreatListOpcode(hostilReference);
+ if (hostilReference->isOnline())
+ iThreatContainer.remove(hostilReference);
+ else
+ iThreatOfflineContainer.remove(hostilReference);
+ break;
+ }
+}
+
+bool ThreatManager::isNeedUpdateToClient(uint32 time)
+{
+ if (isThreatListEmpty())
+ return false;
+ if (time >= iUpdateTimer)
+ {
+ iUpdateTimer = THREAT_UPDATE_INTERVAL;
+ return true;
+ }
+ iUpdateTimer -= time;
+ return false;
+}
+
+// Reset all aggro without modifying the threadlist.
+void ThreatManager::resetAllAggro()
+{
+ std::list<HostileReference*> &threatlist = getThreatList();
+ if (threatlist.empty())
+ return;
+
+ for (std::list<HostileReference*>::iterator itr = threatlist.begin(); itr != threatlist.end(); ++itr)
+ {
+ (*itr)->setThreat(0);
+ }
+
+ setDirty(true);
+}