TrinityCore
Loading...
Searching...
No Matches
CombatManager.cpp
Go to the documentation of this file.
1/*
2 * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the
6 * Free Software Foundation; either version 2 of the License, or (at your
7 * option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12 * more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "CombatManager.h"
19#include "Creature.h"
20#include "CreatureAI.h"
21#include "MapUtils.h"
22#include "Player.h"
23
24/*static*/ bool CombatManager::CanBeginCombat(Unit const* a, Unit const* b)
25{
26 // Checks combat validity before initial reference creation.
27 // For the combat to be valid...
28 // ...the two units need to be different
29 if (a == b)
30 return false;
31 // ...the two units need to be in the world
32 if (!a->IsInWorld() || !b->IsInWorld())
33 return false;
34 // ...the two units need to both be alive
35 if (!a->IsAlive() || !b->IsAlive())
36 return false;
37 // ...the two units need to be on the same map
38 if (a->GetMap() != b->GetMap())
39 return false;
40 // ...the two units need to be in the same phase
41 if (!WorldObject::InSamePhase(a, b))
42 return false;
44 return false;
46 return false;
47 // ... both units must be allowed to enter combat
49 return false;
50 if (a->IsFriendlyTo(b) || b->IsFriendlyTo(a))
51 return false;
54 // ...neither of the two units must be (owned by) a player with .gm on
55 if ((playerA && playerA->IsGameMaster()) || (playerB && playerB->IsGameMaster()))
56 return false;
57 return true;
58}
59
61{
62 // sequencing matters here - AI might do nasty stuff, so make sure refs are in a consistent state before you hand off!
63
64 // first, get rid of any threat that still exists...
67
68 // ...then, remove the references from both managers...
71
72 // ...update the combat state, which will potentially remove IN_COMBAT...
73 bool const needFirstAI = first->GetCombatManager().UpdateOwnerCombatState();
74 bool const needSecondAI = second->GetCombatManager().UpdateOwnerCombatState();
75
76 // ...and if that happened, also notify the AI of it...
77 if (needFirstAI)
78 if (UnitAI* firstAI = first->GetAI())
79 firstAI->JustExitedCombat();
80 if (needSecondAI)
81 if (UnitAI* secondAI = second->GetAI())
82 secondAI->JustExitedCombat();
83
84 // ...and finally clean up the reference object
85 delete this;
86}
87
89{
90 bool needFirstAI = false, needSecondAI = false;
92 {
93 _suppressFirst = false;
95 }
97 {
98 _suppressSecond = false;
100 }
101
102 if (needFirstAI)
104 if (needSecondAI)
106}
107
109{
110 Suppress(who);
112 if (UnitAI* ai = who->GetAI())
113 ai->JustExitedCombat();
114}
115
117{
118 if (_combatTimer <= tdiff)
119 return false;
120 _combatTimer -= tdiff;
121 return true;
122}
123
128
129CombatManager::CombatManager(Unit* owner) : _owner(owner)
130{
131}
132
134{
135 ASSERT(_pveRefs.empty(), "CombatManager::~CombatManager - %s: we still have %zu PvE combat references, one of them is with %s", _owner->GetGUID().ToString().c_str(), _pveRefs.size(), _pveRefs.begin()->first.ToString().c_str());
136 ASSERT(_pvpRefs.empty(), "CombatManager::~CombatManager - %s: we still have %zu PvP combat references, one of them is with %s", _owner->GetGUID().ToString().c_str(), _pvpRefs.size(), _pvpRefs.begin()->first.ToString().c_str());
137}
138
140{
141 auto it = _pvpRefs.begin(), end = _pvpRefs.end();
142 while (it != end)
143 {
144 PvPCombatReference* const ref = it->second;
145 if (ref->first == _owner && !ref->Update(tdiff)) // only update if we're the first unit involved (otherwise double decrement)
146 {
147 it = _pvpRefs.erase(it), end = _pvpRefs.end(); // remove it from our refs first to prevent invalidation
148 ref->EndCombat(); // this will remove it from the other side
149 }
150 else
151 ++it;
152 }
153}
154
156{
157 for (auto const& [guid, ref] : _pveRefs)
158 if (!ref->IsSuppressedFor(_owner))
159 return true;
160 return false;
161}
162
164{
165 for (std::pair<ObjectGuid const, CombatReference*> const& reference : _pveRefs)
166 if (!reference.second->IsSuppressedFor(_owner) && reference.second->GetOther(_owner)->GetTypeId() == TYPEID_PLAYER)
167 return true;
168
169 return false;
170}
171
173{
174 for (auto const& pair : _pvpRefs)
175 if (!pair.second->IsSuppressedFor(_owner))
176 return true;
177 return false;
178}
179
181{
182 for (auto const& pair : _pveRefs)
183 if (!pair.second->IsSuppressedFor(_owner))
184 return pair.second->GetOther(_owner);
185 for (auto const& pair : _pvpRefs)
186 if (!pair.second->IsSuppressedFor(_owner))
187 return pair.second->GetOther(_owner);
188 return nullptr;
189}
190
191bool CombatManager::SetInCombatWith(Unit* who, bool addSecondUnitSuppressed)
192{
193 // Are we already in combat? If yes, refresh pvp combat
195 {
196 existingPvpRef->RefreshTimer();
197 existingPvpRef->Refresh();
198 return true;
199 }
201 {
202 existingPveRef->Refresh();
203 return true;
204 }
205
206 // Otherwise, check validity...
208 return false;
209
210 // ...then create new reference
211 CombatReference* ref;
213 ref = new PvPCombatReference(_owner, who);
214 else
215 ref = new CombatReference(_owner, who);
216
217 if (addSecondUnitSuppressed)
218 ref->Suppress(who);
219
220 // ...and insert it into both managers
221 PutReference(who->GetGUID(), ref);
223
224 // now, sequencing is important - first we update the combat state, which will set both units in combat and do non-AI combat start stuff
225 bool const needSelfAI = UpdateOwnerCombatState();
226 bool const needOtherAI = who->GetCombatManager().UpdateOwnerCombatState();
227
228 // then, we finally notify the AI (if necessary) and let it safely do whatever it feels like
229 if (needSelfAI)
231 if (needOtherAI)
233 return IsInCombatWith(who);
234}
235
237{
238 return (_pveRefs.find(guid) != _pveRefs.end()) || (_pvpRefs.find(guid) != _pvpRefs.end());
239}
240
242{
243 return IsInCombatWith(who->GetGUID());
244}
245
247{
248 CombatManager const& mgr = who->GetCombatManager();
249 for (auto& ref : mgr._pveRefs)
250 {
251 if (!IsInCombatWith(ref.first))
252 {
253 Unit* target = ref.second->GetOther(who);
256 continue;
257 SetInCombatWith(target);
258 }
259 }
260 for (auto& ref : mgr._pvpRefs)
261 {
262 Unit* target = ref.second->GetOther(who);
265 continue;
266 SetInCombatWith(target);
267 }
268}
269
270void CombatManager::EndCombatBeyondRange(float range, bool includingPvP)
271{
272 auto it = _pveRefs.begin(), end = _pveRefs.end();
273 while (it != end)
274 {
275 CombatReference* const ref = it->second;
276 if (!ref->first->IsWithinDistInMap(ref->second, range))
277 {
278 it = _pveRefs.erase(it), end = _pveRefs.end(); // erase manually here to avoid iterator invalidation
279 ref->EndCombat();
280 }
281 else
282 ++it;
283 }
284
285 if (!includingPvP)
286 return;
287
288 auto it2 = _pvpRefs.begin(), end2 = _pvpRefs.end();
289 while (it2 != end2)
290 {
291 CombatReference* const ref = it2->second;
292 if (!ref->first->IsWithinDistInMap(ref->second, range))
293 {
294 it2 = _pvpRefs.erase(it2), end2 = _pvpRefs.end(); // erase manually here to avoid iterator invalidation
295 ref->EndCombat();
296 }
297 else
298 ++it2;
299 }
300}
301
302void CombatManager::SuppressPvPCombat(UnitFilter* unitFilter /*= nullptr*/)
303{
304 for (auto const& [guid, combatRef] : _pvpRefs)
305 if (!unitFilter || unitFilter(combatRef->GetOther(_owner)))
306 combatRef->Suppress(_owner);
307
309 if (UnitAI* ownerAI = _owner->GetAI())
310 ownerAI->JustExitedCombat();
311}
312
313void CombatManager::EndAllPvECombat(UnitFilter* unitFilter /*= nullptr*/)
314{
315 // cannot have threat without combat
318
319 std::vector<CombatReference*> combatReferencesToRemove;
320 combatReferencesToRemove.reserve(_pveRefs.size());
321 for (auto const& [guid, combatRef] : _pveRefs)
322 if (!unitFilter || unitFilter(combatRef->GetOther(_owner)))
323 combatReferencesToRemove.push_back(combatRef);
324
325 for (CombatReference* combatRef : combatReferencesToRemove)
326 combatRef->EndCombat();
327}
328
330{
331 auto it = _pveRefs.begin(), end = _pveRefs.end();
332 while (it != end)
333 {
334 CombatReference* const ref = it->second;
336 {
337 it = _pveRefs.erase(it), end = _pveRefs.end(); // erase manually here to avoid iterator invalidation
338 ref->EndCombat();
339 }
340 else
341 ++it;
342 }
343
344 auto it2 = _pvpRefs.begin(), end2 = _pvpRefs.end();
345 while (it2 != end2)
346 {
347 CombatReference* const ref = it2->second;
349 {
350 it2 = _pvpRefs.erase(it2), end2 = _pvpRefs.end(); // erase manually here to avoid iterator invalidation
351 ref->EndCombat();
352 }
353 else
354 ++it2;
355 }
356}
357
358void CombatManager::EndAllPvPCombat(UnitFilter* unitFilter /*= nullptr*/)
359{
360 std::vector<CombatReference*> combatReferencesToRemove;
361 combatReferencesToRemove.reserve(_pvpRefs.size());
362 for (auto const& [guid, combatRef] : _pvpRefs)
363 if (!unitFilter || unitFilter(combatRef->GetOther(_owner)))
364 combatReferencesToRemove.push_back(combatRef);
365
366 for (CombatReference* combatRef : combatReferencesToRemove)
367 combatRef->EndCombat();
368}
369
370/*static*/ void CombatManager::NotifyAICombat(Unit* me, Unit* other)
371{
372 if (UnitAI* ai = me->GetAI())
373 ai->JustEnteredCombat(other);
374}
375
377{
378 if (ref->_isPvP)
379 {
380 auto& inMap = _pvpRefs[guid];
381 ASSERT(!inMap, "Duplicate combat state at %p being inserted for %s vs %s - memory leak!", ref, _owner->GetGUID().ToString().c_str(), guid.ToString().c_str());
382 inMap = static_cast<PvPCombatReference*>(ref);
383 }
384 else
385 {
386 auto& inMap = _pveRefs[guid];
387 ASSERT(!inMap, "Duplicate combat state at %p being inserted for %s vs %s - memory leak!", ref, _owner->GetGUID().ToString().c_str(), guid.ToString().c_str());
388 inMap = ref;
389 }
390}
391
392void CombatManager::PurgeReference(ObjectGuid const& guid, bool pvp)
393{
394 if (pvp)
395 _pvpRefs.erase(guid);
396 else
397 _pveRefs.erase(guid);
398}
399
401{
402 bool const combatState = HasCombat();
403 if (combatState == _owner->IsInCombat())
404 return false;
405
406 if (combatState)
407 {
410 if (_owner->GetTypeId() != TYPEID_UNIT)
412 }
413 else
414 {
417 if (_owner->GetTypeId() != TYPEID_UNIT)
419 }
420
421 if (Unit* master = _owner->GetCharmerOrOwner())
422 master->UpdatePetCombatState();
423
424 return true;
425}
uint32_t uint32
Definition Define.h:154
#define ASSERT
Definition Errors.h:80
@ TYPEID_UNIT
Definition ObjectGuid.h:43
@ TYPEID_PLAYER
Definition ObjectGuid.h:44
@ UNIT_FLAG_IN_COMBAT
@ UNIT_FLAG_PLAYER_CONTROLLED
@ UNIT_STATE_EVADE
Definition Unit.h:283
@ UNIT_STATE_IN_FLIGHT
Definition Unit.h:269
ObjectGuid const & GetGUID() const
Definition BaseEntity.h:163
bool IsInWorld() const
Definition BaseEntity.h:158
TypeID GetTypeId() const
Definition BaseEntity.h:166
void SuppressPvPCombat(UnitFilter *unitFilter=nullptr)
void EndAllPvECombat(UnitFilter *unitFilter=nullptr)
std::unordered_map< ObjectGuid, PvPCombatReference * > _pvpRefs
bool HasCombat() const
void PutReference(ObjectGuid const &guid, CombatReference *ref)
static bool CanBeginCombat(Unit const *a, Unit const *b)
Unit * GetAnyTarget() const
Unit *const _owner
void InheritCombatStatesFrom(Unit const *who)
friend struct CombatReference
std::unordered_map< ObjectGuid, CombatReference * > _pveRefs
void Update(uint32 tdiff)
void EndCombatBeyondRange(float range, bool includingPvP=false)
bool HasPvECombat() const
CombatManager(Unit *owner)
static void NotifyAICombat(Unit *me, Unit *other)
bool HasPvECombatWithPlayers() const
bool UpdateOwnerCombatState() const
bool HasPvPCombat() const
bool(Unit const *otherUnit) UnitFilter
friend struct PvPCombatReference
bool SetInCombatWith(Unit *who, bool addSecondUnitSuppressed=false)
bool IsInCombatWith(ObjectGuid const &who) const
void EndAllPvPCombat(UnitFilter *unitFilter=nullptr)
void PurgeReference(ObjectGuid const &guid, bool pvp)
std::string ToString() const
bool IsGameMaster() const
Definition Player.h:1309
void RemoveMeFromThreatLists(bool(*unitFilter)(Unit const *otherUnit))
void ClearThreat(Unit *target)
Definition Unit.h:635
ThreatManager & GetThreatManager()
Definition Unit.h:1078
virtual void AtExitCombat()
Definition Unit.cpp:9240
virtual void AtEngage(Unit *)
Definition Unit.h:1980
bool HasUnitFlag(UnitFlags flags) const
Definition Unit.h:845
virtual void AtEnterCombat()
Definition Unit.cpp:9216
virtual void AtDisengage()
Definition Unit.h:1981
bool IsAlive() const
Definition Unit.h:1185
UnitAI * GetAI() const
Definition Unit.h:668
bool IsImmuneToNPC() const
Definition Unit.h:1048
Unit * GetCharmerOrOwner() const
Definition Unit.h:1221
bool HasUnitState(const uint32 f) const
Definition Unit.h:743
bool IsControlledByPlayer() const
Definition Unit.h:1214
bool IsCombatDisallowed() const
Definition Unit.h:1868
CombatManager & GetCombatManager()
Definition Unit.h:1038
void SetUnitFlag(UnitFlags flags)
Definition Unit.h:846
bool IsImmuneToPC() const
Definition Unit.h:1045
bool IsInCombat() const
Definition Unit.h:1058
void RemoveUnitFlag(UnitFlags flags)
Definition Unit.h:847
bool InSamePhase(PhaseShift const &phaseShift) const
Definition Object.h:314
Map * GetMap() const
Definition Object.h:411
Player * GetCharmerOrOwnerPlayerOrPlayerItself() const
Definition Object.cpp:1621
bool IsWithinDistInMap(WorldObject const *obj, float dist2compare, bool is3D=true, bool incOwnRadius=true, bool incTargetRadius=true) const
Definition Object.cpp:501
bool IsFriendlyTo(WorldObject const *target) const
Definition Object.cpp:2186
auto MapGetValuePtr(M &map, typename M::key_type const &key)
Definition MapUtils.h:37
bool const _isPvP
Unit *const second
Unit *const first
void SuppressFor(Unit *who)
Unit * GetOther(Unit const *me) const
void Suppress(Unit *who)
bool Update(uint32 tdiff)
static const uint32 PVP_COMBAT_TIMEOUT