diff options
Diffstat (limited to 'src/server/game/Combat/CombatManager.cpp')
-rw-r--r-- | src/server/game/Combat/CombatManager.cpp | 350 |
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; +} |