aboutsummaryrefslogtreecommitdiff
path: root/src/server/game/Combat/CombatManager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/server/game/Combat/CombatManager.cpp')
-rw-r--r--src/server/game/Combat/CombatManager.cpp350
1 files changed, 350 insertions, 0 deletions
diff --git a/src/server/game/Combat/CombatManager.cpp b/src/server/game/Combat/CombatManager.cpp
new file mode 100644
index 00000000000..3bd5f70cd7e
--- /dev/null
+++ b/src/server/game/Combat/CombatManager.cpp
@@ -0,0 +1,350 @@
+/*
+ * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "CombatManager.h"
+#include "Creature.h"
+#include "CreatureAI.h"
+#include "Player.h"
+
+/*static*/ bool CombatManager::CanBeginCombat(Unit const* a, Unit const* b)
+{
+ // Checks combat validity before initial reference creation.
+ // For the combat to be valid...
+ // ...the two units need to be different
+ if (a == b)
+ return false;
+ // ...the two units need to be in the world
+ if (!a->IsInWorld() || !b->IsInWorld())
+ return false;
+ // ...the two units need to both be alive
+ if (!a->IsAlive() || !b->IsAlive())
+ return false;
+ // ...the two units need to be on the same map
+ if (a->GetMap() != b->GetMap())
+ return false;
+ // ...the two units need to be in the same phase
+ if (!WorldObject::InSamePhase(a, b))
+ return false;
+ if (a->HasUnitState(UNIT_STATE_EVADE) || b->HasUnitState(UNIT_STATE_EVADE))
+ return false;
+ if (a->HasUnitState(UNIT_STATE_IN_FLIGHT) || b->HasUnitState(UNIT_STATE_IN_FLIGHT))
+ return false;
+ if (a->IsControlledByPlayer() || b->IsControlledByPlayer())
+ { // PvSomething, only block friendly fire
+ if (a->IsFriendlyTo(b) || b->IsFriendlyTo(a))
+ return false;
+ }
+ else
+ { // CvC, need hostile reaction to start a fight
+ if (!a->IsHostileTo(b) && !b->IsHostileTo(a))
+ return false;
+ }
+ Player const* playerA = a->GetCharmerOrOwnerPlayerOrPlayerItself();
+ Player const* playerB = b->GetCharmerOrOwnerPlayerOrPlayerItself();
+ // ...neither of the two units must be (owned by) a player with .gm on
+ if ((playerA && playerA->IsGameMaster()) || (playerB && playerB->IsGameMaster()))
+ return false;
+ return true;
+}
+
+void CombatReference::EndCombat()
+{
+ // sequencing matters here - AI might do nasty stuff, so make sure refs are in a consistent state before you hand off!
+
+ // first, get rid of any threat that still exists...
+ first->GetThreatManager().ClearThreat(second);
+ second->GetThreatManager().ClearThreat(first);
+
+ // ...then, remove the references from both managers...
+ first->GetCombatManager().PurgeReference(second->GetGUID(), _isPvP);
+ second->GetCombatManager().PurgeReference(first->GetGUID(), _isPvP);
+
+ // ...update the combat state, which will potentially remove IN_COMBAT...
+ bool const needFirstAI = first->GetCombatManager().UpdateOwnerCombatState();
+ bool const needSecondAI = second->GetCombatManager().UpdateOwnerCombatState();
+
+ // ...and if that happened, also notify the AI of it...
+ if (needFirstAI && first->IsAIEnabled)
+ first->GetAI()->JustExitedCombat();
+ if (needSecondAI && second->IsAIEnabled)
+ second->GetAI()->JustExitedCombat();
+
+ // ...and finally clean up the reference object
+ delete this;
+}
+
+bool PvPCombatReference::Update(uint32 tdiff)
+{
+ if (_combatTimer <= tdiff)
+ return false;
+ _combatTimer -= tdiff;
+ return true;
+}
+
+void PvPCombatReference::Refresh()
+{
+ _combatTimer = PVP_COMBAT_TIMEOUT;
+
+ bool needFirstAI = false, needSecondAI = false;
+ if (_suppressFirst)
+ {
+ _suppressFirst = false;
+ needFirstAI = first->GetCombatManager().UpdateOwnerCombatState();
+ }
+ if (_suppressSecond)
+ {
+ _suppressSecond = false;
+ needSecondAI = second->GetCombatManager().UpdateOwnerCombatState();
+ }
+
+ if (needFirstAI)
+ CombatManager::NotifyAICombat(first, second);
+ if (needSecondAI)
+ CombatManager::NotifyAICombat(second, first);
+}
+
+void PvPCombatReference::SuppressFor(Unit* who)
+{
+ Suppress(who);
+ if (who->GetCombatManager().UpdateOwnerCombatState())
+ if (who->IsAIEnabled)
+ who->GetAI()->JustExitedCombat();
+}
+
+void CombatManager::Update(uint32 tdiff)
+{
+ auto it = _pvpRefs.begin(), end = _pvpRefs.end();
+ while (it != end)
+ {
+ PvPCombatReference* const ref = it->second;
+ if (ref->first == _owner && !ref->Update(tdiff)) // only update if we're the first unit involved (otherwise double decrement)
+ {
+ it = _pvpRefs.erase(it), end = _pvpRefs.end(); // remove it from our refs first to prevent invalidation
+ ref->EndCombat(); // this will remove it from the other side
+ }
+ else
+ ++it;
+ }
+}
+
+bool CombatManager::HasPvPCombat() const
+{
+ for (auto const& pair : _pvpRefs)
+ if (!pair.second->IsSuppressedFor(_owner))
+ return true;
+ return false;
+}
+
+Unit* CombatManager::GetAnyTarget() const
+{
+ if (!_pveRefs.empty())
+ return _pveRefs.begin()->second->GetOther(_owner);
+ for (auto const& pair : _pvpRefs)
+ if (!pair.second->IsSuppressedFor(_owner))
+ return pair.second->GetOther(_owner);
+ return nullptr;
+}
+
+bool CombatManager::SetInCombatWith(Unit* who)
+{
+ // Are we already in combat? If yes, refresh pvp combat
+ auto it = _pvpRefs.find(who->GetGUID());
+ if (it != _pvpRefs.end())
+ {
+ it->second->Refresh();
+ return true;
+ }
+ else if (_pveRefs.find(who->GetGUID()) != _pveRefs.end())
+ return true;
+
+ // Otherwise, check validity...
+ if (!CombatManager::CanBeginCombat(_owner, who))
+ return false;
+
+ // ...then create new reference
+ CombatReference* ref;
+ if (_owner->IsControlledByPlayer() && who->IsControlledByPlayer())
+ ref = new PvPCombatReference(_owner, who);
+ else
+ ref = new CombatReference(_owner, who);
+
+ // ...and insert it into both managers
+ PutReference(who->GetGUID(), ref);
+ who->GetCombatManager().PutReference(_owner->GetGUID(), ref);
+
+ // now, sequencing is important - first we update the combat state, which will set both units in combat and do non-AI combat start stuff
+ bool const needSelfAI = UpdateOwnerCombatState();
+ bool const needOtherAI = who->GetCombatManager().UpdateOwnerCombatState();
+
+ // then, we finally notify the AI (if necessary) and let it safely do whatever it feels like
+ if (needSelfAI)
+ NotifyAICombat(_owner, who);
+ if (needOtherAI)
+ NotifyAICombat(who, _owner);
+ return true;
+}
+
+bool CombatManager::IsInCombatWith(ObjectGuid const& guid) const
+{
+ return (_pveRefs.find(guid) != _pveRefs.end()) || (_pvpRefs.find(guid) != _pvpRefs.end());
+}
+
+bool CombatManager::IsInCombatWith(Unit const* who) const
+{
+ return IsInCombatWith(who->GetGUID());
+}
+
+void CombatManager::InheritCombatStatesFrom(Unit const* who)
+{
+ CombatManager const& mgr = who->GetCombatManager();
+ for (auto& ref : mgr._pveRefs)
+ {
+ if (!IsInCombatWith(ref.first))
+ {
+ Unit* target = ref.second->GetOther(who);
+ if ((_owner->IsImmuneToPC() && target->HasUnitFlag(UNIT_FLAG_PVP_ATTACKABLE)) ||
+ (_owner->IsImmuneToNPC() && !target->HasUnitFlag(UNIT_FLAG_PVP_ATTACKABLE)))
+ continue;
+ SetInCombatWith(target);
+ }
+ }
+ for (auto& ref : mgr._pvpRefs)
+ {
+ if (!IsInCombatWith(ref.first))
+ {
+ Unit* target = ref.second->GetOther(who);
+ if ((_owner->IsImmuneToPC() && target->HasUnitFlag(UNIT_FLAG_PVP_ATTACKABLE)) ||
+ (_owner->IsImmuneToNPC() && !target->HasUnitFlag(UNIT_FLAG_PVP_ATTACKABLE)))
+ continue;
+ SetInCombatWith(target);
+ }
+ }
+}
+
+void CombatManager::EndCombatBeyondRange(float range, bool includingPvP)
+{
+ auto it = _pveRefs.begin(), end = _pveRefs.end();
+ while (it != end)
+ {
+ CombatReference* const ref = it->second;
+ if (!ref->first->IsWithinDistInMap(ref->second, range))
+ {
+ it = _pveRefs.erase(it), end = _pveRefs.end(); // erase manually here to avoid iterator invalidation
+ ref->EndCombat();
+ }
+ else
+ ++it;
+ }
+
+ if (!includingPvP)
+ return;
+
+ auto it2 = _pvpRefs.begin(), end2 = _pvpRefs.end();
+ while (it2 != end2)
+ {
+ CombatReference* const ref = it2->second;
+ if (!ref->first->IsWithinDistInMap(ref->second, range))
+ {
+ it2 = _pvpRefs.erase(it2), end2 = _pvpRefs.end(); // erase manually here to avoid iterator invalidation
+ ref->EndCombat();
+ }
+ else
+ ++it2;
+ }
+}
+
+void CombatManager::SuppressPvPCombat()
+{
+ for (auto const& pair : _pvpRefs)
+ pair.second->Suppress(_owner);
+ if (UpdateOwnerCombatState())
+ if (_owner->IsAIEnabled)
+ _owner->GetAI()->JustExitedCombat();
+}
+
+void CombatManager::EndAllPvECombat()
+{
+ // cannot have threat without combat
+ _owner->GetThreatManager().RemoveMeFromThreatLists();
+ _owner->GetThreatManager().ClearAllThreat();
+ while (!_pveRefs.empty())
+ _pveRefs.begin()->second->EndCombat();
+}
+
+void CombatManager::EndAllPvPCombat()
+{
+ while (!_pvpRefs.empty())
+ _pvpRefs.begin()->second->EndCombat();
+}
+
+/*static*/ void CombatManager::NotifyAICombat(Unit* me, Unit* other)
+{
+ if (!me->IsAIEnabled)
+ return;
+ me->GetAI()->JustEnteredCombat(other);
+
+ if (Creature* cMe = me->ToCreature())
+ if (!cMe->CanHaveThreatList())
+ cMe->AI()->JustEngagedWith(other);
+}
+
+void CombatManager::PutReference(ObjectGuid const& guid, CombatReference* ref)
+{
+ if (ref->_isPvP)
+ {
+ auto& inMap = _pvpRefs[guid];
+ ASSERT(!inMap && "Duplicate combat state detected - memory leak!");
+ inMap = static_cast<PvPCombatReference*>(ref);
+ }
+ else
+ {
+ auto& inMap = _pveRefs[guid];
+ ASSERT(!inMap && "Duplicate combat state detected - memory leak!");
+ inMap = ref;
+ }
+}
+
+void CombatManager::PurgeReference(ObjectGuid const& guid, bool pvp)
+{
+ if (pvp)
+ _pvpRefs.erase(guid);
+ else
+ _pveRefs.erase(guid);
+}
+
+bool CombatManager::UpdateOwnerCombatState() const
+{
+ bool const combatState = HasCombat();
+ if (combatState == _owner->IsInCombat())
+ return false;
+
+ if (combatState)
+ {
+ _owner->AddUnitFlag(UNIT_FLAG_IN_COMBAT);
+ _owner->AtEnterCombat();
+ }
+ else
+ {
+ _owner->RemoveUnitFlag(UNIT_FLAG_IN_COMBAT);
+ _owner->AtExitCombat();
+ }
+
+ if (Unit* master = _owner->GetCharmerOrOwner())
+ master->UpdatePetCombatState();
+
+ return true;
+}