TrinityCore
Loading...
Searching...
No Matches
CreatureGroups.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 "CreatureGroups.h"
19#include "Creature.h"
20#include "CreatureAI.h"
21#include "DatabaseEnv.h"
22#include "Log.h"
23#include "Map.h"
24#include "MapUtils.h"
25#include "MotionMaster.h"
26#include "MovementGenerator.h"
27#include "ObjectMgr.h"
28#include "ZoneScript.h"
29
32
38
40{
41 Map* map = creature->GetMap();
42
43 auto [itr, isNew] = map->CreatureGroupHolder.try_emplace(leaderSpawnId, nullptr);
44 if (!isNew)
45 {
46 //Add member to an existing group
47 TC_LOG_DEBUG("entities.unit", "Group found: {}, inserting creature {}, Group InstanceID {}", leaderSpawnId, creature->GetGUID(), creature->GetInstanceId());
48
49 // With dynamic spawn the creature may have just respawned
50 // we need to find previous instance of creature and delete it from the formation, as it'll be invalidated
52 for (auto const& [spawnId, other] : bounds)
53 {
54 if (other == creature)
55 continue;
56
57 if (itr->second->HasMember(other))
58 itr->second->RemoveMember(other);
59 }
60 }
61 else
62 {
63 //Create new group
64 TC_LOG_DEBUG("entities.unit", "Group not found: {}. Creating new group.", leaderSpawnId);
65 itr->second = new CreatureGroup(leaderSpawnId);
66 }
67
68 itr->second->AddMember(creature);
69}
70
72{
73 ObjectGuid::LowType leaderSpawnId = group->GetLeaderSpawnId();
74
75 TC_LOG_DEBUG("entities.unit", "Deleting member pointer to GUID: {} from group {}", leaderSpawnId, member->GetSpawnId());
76 group->RemoveMember(member);
77
78 // If removed member was alive we need to check if we have any other alive members
79 // if not - fire OnCreatureGroupDepleted
80 if (ZoneScript* script = member->GetZoneScript())
81 if (member->IsAlive() && !group->HasAliveMembers())
82 script->OnCreatureGroupDepleted(group);
83
84 if (group->IsEmpty())
85 {
86 if (leaderSpawnId)
87 {
88 Map* map = member->GetMap();
89 TC_LOG_DEBUG("entities.unit", "Deleting group with InstanceID {}", map->GetInstanceId());
90 std::size_t erased = map->CreatureGroupHolder.erase(leaderSpawnId);
91 ASSERT(erased, "Not registered group " UI64FMTD " in map %u", leaderSpawnId, map->GetId());
92 }
93
94 delete group;
95 }
96}
97
99{
100 uint32 oldMSTime = getMSTime();
101
102 //Get group data
103 QueryResult result = WorldDatabase.Query("SELECT leaderGUID, memberGUID, dist, angle, groupAI, point_1, point_2 FROM creature_formations ORDER BY leaderGUID");
104 if (!result)
105 {
106 TC_LOG_INFO("server.loading", ">> Loaded 0 creatures in formations. DB table `creature_formations` is empty!");
107 return;
108 }
109
110 uint32 count = 0;
111 std::unordered_set<ObjectGuid::LowType> leaderSpawnIds;
112 do
113 {
114 Field* fields = result->Fetch();
115
116 //Load group member data
117 ObjectGuid::LowType leaderSpawnId = fields[0].GetUInt64();
118 ObjectGuid::LowType memberSpawnId = fields[1].GetUInt64();
119
120 // check data correctness
121 {
122 if (!sObjectMgr->GetCreatureData(leaderSpawnId))
123 {
124 TC_LOG_ERROR("sql.sql", "creature_formations table leader guid {} incorrect (not exist)", leaderSpawnId);
125 continue;
126 }
127
128 if (!sObjectMgr->GetCreatureData(memberSpawnId))
129 {
130 TC_LOG_ERROR("sql.sql", "creature_formations table member guid {} incorrect (not exist)", memberSpawnId);
131 continue;
132 }
133
134 leaderSpawnIds.insert(leaderSpawnId);
135 }
136
137 FormationInfo& member = _creatureGroupMap[memberSpawnId];
138 member.LeaderSpawnId = leaderSpawnId;
139 member.FollowDist = 0.f;
140 member.FollowAngle = 0.f;
141
142 //If creature is group leader we may skip loading of dist/angle
143 if (member.LeaderSpawnId != memberSpawnId)
144 {
145 member.FollowDist = fields[2].GetFloat();
146 member.FollowAngle = fields[3].GetFloat() * float(M_PI) / 180.0f;
147 }
148
149 member.GroupAI = fields[4].GetUInt32();
150 for (uint8 i = 0; i < 2; ++i)
151 member.LeaderWaypointIDs[i] = fields[5 + i].GetUInt16();
152
153 ++count;
154 } while (result->NextRow());
155
156 for (ObjectGuid::LowType leaderSpawnId : leaderSpawnIds)
157 {
158 if (!_creatureGroupMap.contains(leaderSpawnId))
159 {
160 TC_LOG_ERROR("sql.sql", "creature_formation contains leader spawn {} which is not included on its formation, removing", leaderSpawnId);
161 for (auto itr = _creatureGroupMap.begin(); itr != _creatureGroupMap.end();)
162 {
163 if (itr->second.LeaderSpawnId == leaderSpawnId)
164 {
165 itr = _creatureGroupMap.erase(itr);
166 continue;
167 }
168
169 ++itr;
170 }
171 }
172 }
173
174 TC_LOG_INFO("server.loading", ">> Loaded {} creatures in formations in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
175}
176
181
182void FormationMgr::AddFormationMember(ObjectGuid::LowType spawnId, float followAng, float followDist, ObjectGuid::LowType leaderSpawnId, uint32 groupAI)
183{
184 FormationInfo& member = _creatureGroupMap[spawnId];
185 member.LeaderSpawnId = leaderSpawnId;
186 member.FollowDist = followDist;
187 member.FollowAngle = followAng;
188 member.GroupAI = groupAI;
189 for (uint8 i = 0; i < 2; ++i)
190 member.LeaderWaypointIDs[i] = 0;
191}
192
193CreatureGroup::CreatureGroup(ObjectGuid::LowType leaderSpawnId) : _leader(nullptr), _members(), _leaderSpawnId(leaderSpawnId), _formed(false), _engaging(false)
194{
195}
196
198
200{
201 TC_LOG_DEBUG("entities.unit", "CreatureGroup::AddMember: Adding unit {}.", member->GetGUID());
202
203 //Check if it is a leader
204 if ((_leaderSpawnId && member->GetSpawnId() == _leaderSpawnId)
205 || (!_leaderSpawnId && !_leader)) // in formations made of tempsummons first member to be added is leader
206 {
207 TC_LOG_DEBUG("entities.unit", "Unit {} is formation leader. Adding group.", member->GetGUID());
208 _leader = member;
209 }
210
211 _members.emplace(member, sFormationMgr->GetFormationInfo(member->GetSpawnId()));
212 member->SetFormation(this);
213}
214
216{
217 if (_leader == member)
218 _leader = nullptr;
219
220 _members.erase(member);
221 member->SetFormation(nullptr);
222}
223
225{
226 // used to prevent recursive calls
227 if (_engaging)
228 return;
229
230 FormationInfo const* formationInfo = Trinity::Containers::MapGetValuePtr(_members, member);
231 if (!formationInfo)
232 return;
233
234 uint32 groupAI = formationInfo->GroupAI;
235 if (!groupAI)
236 return;
237
238 if (member == _leader)
239 {
240 if (!(groupAI & FLAG_MEMBERS_ASSIST_LEADER))
241 return;
242 }
243 else if (!(groupAI & FLAG_LEADER_ASSISTS_MEMBER))
244 return;
245
246 _engaging = true;
247
248 for (auto const& [other, _] : _members)
249 {
250 if (other == member)
251 continue;
252
253 if (!other->IsAlive())
254 continue;
255
256 if (((other != _leader && (groupAI & FLAG_MEMBERS_ASSIST_LEADER)) || (other == _leader && (groupAI & FLAG_LEADER_ASSISTS_MEMBER))) && other->IsValidAttackTarget(target))
257 other->EngageWithTarget(target);
258 }
259
260 _engaging = false;
261}
262
264{
265 for (auto const& [member, _] : _members)
266 {
267 if (member != _leader && member->IsAlive())
268 {
269 if (dismiss)
270 member->GetMotionMaster()->Remove(FORMATION_MOTION_TYPE, MOTION_SLOT_DEFAULT);
271 else
272 member->GetMotionMaster()->MoveIdle();
273
274 TC_LOG_DEBUG("entities.unit", "CreatureGroup::FormationReset: Set {} movement for member {}", dismiss ? "default" : "idle", member->GetGUID());
275 }
276 }
277
278 _formed = !dismiss;
279}
280
282{
283 if (!_leader)
284 return;
285
286 for (auto const& [member, formationInfo] : _members)
287 {
288 if (member == _leader || !member->IsAlive() || member->IsEngaged() || !formationInfo || !(formationInfo->GroupAI & FLAG_IDLE_IN_FORMATION))
289 continue;
290
291 float angle = formationInfo->FollowAngle + float(M_PI); // for some reason, someone thought it was a great idea to invert relativ angles...
292 float dist = formationInfo->FollowDist;
293
294 if (member->GetMotionMaster()->GetCurrentMovementGeneratorType(MOTION_SLOT_DEFAULT) != FORMATION_MOTION_TYPE)
295 member->GetMotionMaster()->MoveFormation(_leader, dist, angle, formationInfo->LeaderWaypointIDs[0], formationInfo->LeaderWaypointIDs[1]);
296 }
297}
298
300{
301 for (auto const& [member, _] : _members)
302 {
303 if (member != _leader && member->IsAlive())
304 {
305 if (member->IsEngaged() || member->IsReturningHome())
306 return false;
307 }
308 }
309
310 return true;
311}
312
314{
315 return std::ranges::any_of(_members, [](Creature const* member) { return member->IsAlive(); }, Trinity::Containers::MapKey);
316}
317
318bool CreatureGroup::LeaderHasStringId(std::string_view id) const
319{
320 if (_leader)
321 return _leader->HasStringId(id);
322
323 if (CreatureData const* leaderCreatureData = sObjectMgr->GetCreatureData(_leaderSpawnId))
324 {
325 if (leaderCreatureData->StringId == id)
326 return true;
327
328 if (ASSERT_NOTNULL(sObjectMgr->GetCreatureTemplate(leaderCreatureData->id))->StringId == id)
329 return true;
330 }
331
332 return false;
333}
#define M_PI
Definition Common.h:118
@ FLAG_LEADER_ASSISTS_MEMBER
@ FLAG_IDLE_IN_FORMATION
@ FLAG_MEMBERS_ASSIST_LEADER
#define sFormationMgr
std::shared_ptr< ResultSet > QueryResult
DatabaseWorkerPool< WorldDatabaseConnection > WorldDatabase
Accessor to the world database.
#define UI64FMTD
Definition Define.h:138
uint8_t uint8
Definition Define.h:156
uint32_t uint32
Definition Define.h:154
#define ASSERT_NOTNULL(pointer)
Definition Errors.h:82
#define ASSERT
Definition Errors.h:80
#define TC_LOG_DEBUG(filterType__, message__,...)
Definition Log.h:181
#define TC_LOG_ERROR(filterType__, message__,...)
Definition Log.h:190
#define TC_LOG_INFO(filterType__, message__,...)
Definition Log.h:184
@ MOTION_SLOT_DEFAULT
@ FORMATION_MOTION_TYPE
#define sObjectMgr
Definition ObjectMgr.h:1885
uint32 GetMSTimeDiffToNow(uint32 oldMSTime)
Definition Timer.h:57
uint32 getMSTime()
Definition Timer.h:33
ObjectGuid const & GetGUID() const
Definition BaseEntity.h:163
CreatureGroup(ObjectGuid::LowType leaderSpawnId)
void FormationReset(bool dismiss)
ObjectGuid::LowType GetLeaderSpawnId() const
bool HasAliveMembers() const
MembersMap _members
void RemoveMember(Creature *member)
void MemberEngagingTarget(Creature *member, Unit *target)
bool CanLeaderStartMoving() const
bool IsEmpty() const
ObjectGuid::LowType _leaderSpawnId
bool LeaderHasStringId(std::string_view id) const
Creature * _leader
void AddMember(Creature *member)
bool HasStringId(std::string_view id) const
ObjectGuid::LowType GetSpawnId() const
Definition Creature.h:110
void SetFormation(CreatureGroup *formation)
Definition Creature.h:407
Class used to access individual fields of database query result.
Definition Field.h:94
float GetFloat() const noexcept
Definition Field.cpp:85
uint64 GetUInt64() const noexcept
Definition Field.cpp:71
uint32 GetUInt32() const noexcept
Definition Field.cpp:57
uint16 GetUInt16() const noexcept
Definition Field.cpp:43
static void RemoveCreatureFromGroup(CreatureGroup *group, Creature *member)
static void AddCreatureToGroup(ObjectGuid::LowType leaderSpawnId, Creature *creature)
static FormationMgr * instance()
FormationInfo * GetFormationInfo(ObjectGuid::LowType spawnId)
void AddFormationMember(ObjectGuid::LowType spawnId, float followAng, float followDist, ObjectGuid::LowType leaderSpawnId, uint32 groupAI)
std::unordered_map< ObjectGuid::LowType, FormationInfo > _creatureGroupMap
void LoadCreatureFormations()
Definition Map.h:225
std::unordered_map< ObjectGuid::LowType, CreatureGroup * > CreatureGroupHolder
Definition Map.h:424
uint32 GetId() const
Definition Map.cpp:3257
CreatureBySpawnIdContainer & GetCreatureBySpawnIdStore()
Definition Map.h:461
uint32 GetInstanceId() const
Definition Map.h:350
uint64 LowType
Definition ObjectGuid.h:321
Definition Unit.h:635
bool IsAlive() const
Definition Unit.h:1185
Map * GetMap() const
Definition Object.h:411
ZoneScript * GetZoneScript() const
Definition Object.h:417
uint32 GetInstanceId() const
Definition Object.h:308
constexpr auto MapKey
Definition MapUtils.h:72
auto MapEqualRange(M &map, typename M::key_type const &key)
auto MapGetValuePtr(M &map, typename M::key_type const &key)
Definition MapUtils.h:37
ObjectGuid::LowType LeaderSpawnId
uint32 LeaderWaypointIDs[2]