Project SkyFire Core
SkyFire 5.4.8 server core API documentation
Loading...
Searching...
No Matches
LFGQueue.cpp
Go to the documentation of this file.
1/*
2* This file is part of Project SkyFire https://www.projectskyfire.org.
3* See LICENSE.md file for Copyright information
4*/
5
6#include "Containers.h"
7#include "DBCStores.h"
8#include "DBCStructure.h"
9#include "Group.h"
10#include "LFGMgr.h"
11#include "LFGQueue.h"
12#include "Log.h"
13#include "ObjectDefines.h"
14
15#include <algorithm>
16
17namespace lfg
18{
19 namespace
20 {
21 uint8 const LFG_COMBAT_ROLE_MASK = PLAYER_ROLE_TANK | PLAYER_ROLE_HEALER | PLAYER_ROLE_DAMAGE;
22 uint8 const LFG_FLEX_RAID_MIN_PLAYERS = 10;
23
24 bool QueueContainsGuid(LfgGuidList const& queue, uint64 guid)
25 {
26 return std::find(queue.begin(), queue.end(), guid) != queue.end();
27 }
28
29 bool CompatibleKeyContainsGuid(std::string const& key, uint64 guid)
30 {
31 std::ostringstream out;
32 out << guid;
33 std::string guidString = out.str();
34
35 std::string::size_type tokenStart = 0;
36 while (tokenStart <= key.length())
37 {
38 std::string::size_type tokenEnd = key.find('|', tokenStart);
39 std::string::size_type tokenLength = tokenEnd == std::string::npos ? std::string::npos : tokenEnd - tokenStart;
40 if (key.compare(tokenStart, tokenLength, guidString) == 0)
41 return true;
42
43 if (tokenEnd == std::string::npos)
44 break;
45
46 tokenStart = tokenEnd + 1;
47 }
48
49 return false;
50 }
51
52 void ConsumeRoleSlot(uint8 role, uint8& tanks, uint8& healers, uint8& dps)
53 {
54 role &= ~PLAYER_ROLE_LEADER;
55
56 if ((role & PLAYER_ROLE_TANK) && tanks)
57 --tanks;
58 else if ((role & PLAYER_ROLE_HEALER) && healers)
59 --healers;
60 else if ((role & PLAYER_ROLE_DAMAGE) && dps)
61 --dps;
62 }
63
64 bool IsScenarioDifficulty(uint32 difficulty)
65 {
66 return difficulty == DIFFICULTY_SCE_NORMAL || difficulty == DIFFICULTY_SCE_HEROIC;
67 }
68
69 bool IsScenarioDungeon(uint32 dungeonId)
70 {
71 LFGDungeonEntry const* dungeon = sLFGDungeonStore.LookupEntry(dungeonId);
72 if (!dungeon)
73 return false;
74
75 if (IsScenarioDifficulty(dungeon->m_DifficultyID))
76 return true;
77
78 MapEntry const* map = sMapStore.LookupEntry(dungeon->m_ContinentID);
79 return map && map->IsScenario();
80 }
81
82 bool IsFlexibleRaidDungeon(uint32 dungeonId)
83 {
84 LFGDungeonEntry const* dungeon = sLFGDungeonStore.LookupEntry(dungeonId);
85 if (!dungeon || dungeon->m_DifficultyID != DIFFICULTY_FLEX)
86 return false;
87
88 MapEntry const* map = sMapStore.LookupEntry(dungeon->m_ContinentID);
89 return map && map->IsRaid();
90 }
91
92 uint8 GetDungeonMaxGroupSize(uint32 dungeonId)
93 {
94 LFGDungeonEntry const* dungeon = sLFGDungeonStore.LookupEntry(dungeonId);
95 if (!dungeon)
96 return MAXGROUPSIZE;
97
98 if (IsScenarioDungeon(dungeonId))
99 {
100 if (MapDifficulty const* difficulty = GetMapDifficultyData(dungeon->m_ContinentID, DifficultyID(dungeon->m_DifficultyID)))
101 if (difficulty->maxPlayers)
102 return uint8(std::min<uint32>(difficulty->maxPlayers, MAXGROUPSIZE));
103
104 return 3;
105 }
106
107 if (IsFlexibleRaidDungeon(dungeonId))
108 {
109 if (MapDifficulty const* difficulty = GetMapDifficultyData(dungeon->m_ContinentID, DifficultyID(dungeon->m_DifficultyID)))
110 if (difficulty->maxPlayers)
111 return uint8(std::min<uint32>(difficulty->maxPlayers, MAXRAIDSIZE));
112
113 return MAXRAIDSIZE;
114 }
115
116 return MAXGROUPSIZE;
117 }
118
119 bool HasScenarioDungeon(LfgDungeonSet const& dungeons)
120 {
121 for (LfgDungeonSet::const_iterator it = dungeons.begin(); it != dungeons.end(); ++it)
122 if (IsScenarioDungeon(*it))
123 return true;
124
125 return false;
126 }
127
128 bool HasFlexibleRaidDungeon(LfgDungeonSet const& dungeons)
129 {
130 for (LfgDungeonSet::const_iterator it = dungeons.begin(); it != dungeons.end(); ++it)
131 if (IsFlexibleRaidDungeon(*it))
132 return true;
133
134 return false;
135 }
136
137 uint8 GetDungeonMinGroupSize(uint32 dungeonId)
138 {
139 if (IsFlexibleRaidDungeon(dungeonId))
140 return sLFGMgr->IsDebugFlexRaidMinimumOverrideEnabled() ? 1 : LFG_FLEX_RAID_MIN_PLAYERS;
141
142 return GetDungeonMaxGroupSize(dungeonId);
143 }
144
145 void GetQueueGroupSizeRange(LfgDungeonSet const& dungeons, uint8& minSize, uint8& maxSize)
146 {
147 minSize = 0;
148 maxSize = 0;
149 for (LfgDungeonSet::const_iterator it = dungeons.begin(); it != dungeons.end(); ++it)
150 {
151 uint8 dungeonMinSize = GetDungeonMinGroupSize(*it);
152 uint8 dungeonMaxSize = GetDungeonMaxGroupSize(*it);
153 if (!minSize || dungeonMinSize < minSize)
154 minSize = dungeonMinSize;
155 if (!maxSize || dungeonMaxSize < maxSize)
156 maxSize = dungeonMaxSize;
157 }
158
159 if (!minSize)
160 minSize = MAXGROUPSIZE;
161 if (!maxSize)
162 maxSize = MAXGROUPSIZE;
163 }
164
165 uint8 GetQueueMaxGroupSize(LfgDungeonSet const& dungeons)
166 {
167 uint8 minSize = 0;
168 uint8 maxSize = 0;
169 GetQueueGroupSizeRange(dungeons, minSize, maxSize);
170 return maxSize;
171 }
172
173 uint8 GetQueueMinGroupSize(LfgDungeonSet const& dungeons)
174 {
175 uint8 minSize = 0;
176 uint8 maxSize = 0;
177 GetQueueGroupSizeRange(dungeons, minSize, maxSize);
178 return minSize;
179 }
180
181 bool CheckQueueRoles(LfgRolesMap& roles, LfgDungeonSet const& dungeons)
182 {
183 if (HasScenarioDungeon(dungeons))
184 return LFGMgr::CheckDpsOnlyRoles(roles, GetQueueMaxGroupSize(dungeons));
185
186 if (HasFlexibleRaidDungeon(dungeons))
187 return LFGMgr::CheckFlexibleRaidRoles(roles, GetQueueMaxGroupSize(dungeons));
188
189 return LFGMgr::CheckGroupRoles(roles);
190 }
191
192 void CalculateRoleShortage(LfgRolesMap const& roles, LfgDungeonSet const& dungeons, uint8& tanks, uint8& healers, uint8& dps)
193 {
194 if (HasScenarioDungeon(dungeons))
195 {
196 uint8 groupSize = GetQueueMaxGroupSize(dungeons);
197 tanks = 0;
198 healers = 0;
199 dps = roles.size() >= groupSize ? 0 : uint8(groupSize - roles.size());
200 return;
201 }
202
203 if (HasFlexibleRaidDungeon(dungeons))
204 {
205 uint8 groupSize = GetQueueMaxGroupSize(dungeons);
206 tanks = 0;
207 healers = 0;
208 dps = roles.size() >= groupSize ? 0 : uint8(groupSize - roles.size());
209 return;
210 }
211
212 tanks = LFG_TANKS_NEEDED;
213 healers = LFG_HEALERS_NEEDED;
214 dps = LFG_DPS_NEEDED;
215
216 LfgRolesMap assignedRoles = roles;
217 if (LFGMgr::CheckGroupRoles(assignedRoles))
218 for (LfgRolesMap::const_iterator it = assignedRoles.begin(); it != assignedRoles.end(); ++it)
219 ConsumeRoleSlot(it->second, tanks, healers, dps);
220 else
221 for (LfgRolesMap::const_iterator it = roles.begin(); it != roles.end(); ++it)
222 ConsumeRoleSlot(it->second, tanks, healers, dps);
223 }
224
225 int32 SelectWaitTime(uint8 tanks, uint8 healers, uint8 dps, int32 wtTank, int32 wtHealer, int32 wtDps, int32 wtAvg)
226 {
227 if (tanks)
228 return wtTank;
229
230 if (healers)
231 return wtHealer;
232
233 if (dps)
234 return wtDps;
235
236 return wtAvg;
237 }
238 }
239
246 std::string ConcatenateGuids(LfgGuidList const& check)
247 {
248 if (check.empty())
249 return "";
250
251 // need the guids in order to avoid duplicates
252 LfgGuidSet guids(check.begin(), check.end());
253
254 std::ostringstream o;
255
256 LfgGuidSet::const_iterator it = guids.begin();
257 o << (*it);
258 for (++it; it != guids.end(); ++it)
259 o << '|' << (*it);
260
261 return o.str();
262 }
263
264 char const* GetCompatibleString(LfgCompatibility compatibles)
265 {
266 switch (compatibles)
267 {
269 return "Pending";
271 return "Compatibles (Bad States)";
273 return "Match";
275 return "Compatibles (Not enough players)";
277 return "Has ignores";
279 return "Multiple Lfg Groups";
281 return "Incompatible dungeons";
283 return "Incompatible roles";
285 return "Too much players";
287 return "Wrong group size";
288 default:
289 return "Unknown";
290 }
291 }
292
293 void LFGQueue::AddToQueue(uint64 guid, bool reQueue)
294 {
295 LfgQueueDataContainer::iterator itQueue = QueueDataStore.find(guid);
296 if (itQueue == QueueDataStore.end())
297 {
298 SF_LOG_ERROR("lfg.queue.add", "Queue data not found for [%u]", GUID_LOPART(guid));
299 return;
300 }
301
302 if (reQueue)
303 AddToCurrentQueue(guid);
304 else
305 AddToNewQueue(guid);
306 }
307
309 {
310 RemoveFromNewQueue(guid);
313
314 LfgQueueDataContainer::iterator itDelete = QueueDataStore.end();
315 for (LfgQueueDataContainer::iterator itr = QueueDataStore.begin(); itr != QueueDataStore.end(); ++itr)
316 if (itr->first != guid)
317 {
318 if (CompatibleKeyContainsGuid(itr->second.bestCompatible, guid))
319 {
320 itr->second.bestCompatible.clear();
322 }
323 }
324 else
325 itDelete = itr;
326
327 if (itDelete != QueueDataStore.end())
328 QueueDataStore.erase(itDelete);
329 }
330
332 {
333 if (!QueueContainsGuid(newToQueueStore, guid))
334 newToQueueStore.push_back(guid);
335 }
336
338 {
339 newToQueueStore.remove(guid);
340 }
341
343 {
344 if (!QueueContainsGuid(currentQueueStore, guid))
345 currentQueueStore.push_back(guid);
346 }
347
349 {
350 currentQueueStore.remove(guid);
351 }
352
353 void LFGQueue::AddQueueData(uint64 guid, time_t joinTime, LfgDungeonSet const& dungeons, LfgRolesMap const& rolesMap)
354 {
355 QueueDataStore[guid] = LfgQueueData(joinTime, dungeons, rolesMap);
356 AddToQueue(guid);
357 }
358
360 {
361 LfgQueueDataContainer::iterator it = QueueDataStore.find(guid);
362 if (it != QueueDataStore.end())
363 QueueDataStore.erase(it);
364 }
365
367 {
368 return QueueDataStore.find(guid) != QueueDataStore.end();
369 }
370
371 void LFGQueue::UpdateWaitTimeAvg(int32 waitTime, uint32 dungeonId)
372 {
373 LfgWaitTime& wt = waitTimesAvgStore[dungeonId];
374 uint32 old_number = wt.number++;
375 wt.time = int32((wt.time * old_number + waitTime) / wt.number);
376 }
377
378 void LFGQueue::UpdateWaitTimeTank(int32 waitTime, uint32 dungeonId)
379 {
380 LfgWaitTime& wt = waitTimesTankStore[dungeonId];
381 uint32 old_number = wt.number++;
382 wt.time = int32((wt.time * old_number + waitTime) / wt.number);
383 }
384
386 {
387 LfgWaitTime& wt = waitTimesHealerStore[dungeonId];
388 uint32 old_number = wt.number++;
389 wt.time = int32((wt.time * old_number + waitTime) / wt.number);
390 }
391
392 void LFGQueue::UpdateWaitTimeDps(int32 waitTime, uint32 dungeonId)
393 {
394 LfgWaitTime& wt = waitTimesDpsStore[dungeonId];
395 uint32 old_number = wt.number++;
396 wt.time = int32((wt.time * old_number + waitTime) / wt.number);
397 }
398
405 {
406 SF_LOG_DEBUG("lfg.queue.data.compatibles.remove", "Removing [%u]", GUID_LOPART(guid));
407 for (LfgCompatibleContainer::iterator itNext = CompatibleMapStore.begin(); itNext != CompatibleMapStore.end();)
408 {
409 LfgCompatibleContainer::iterator it = itNext++;
410 if (CompatibleKeyContainsGuid(it->first, guid))
411 CompatibleMapStore.erase(it);
412 }
413 }
414
421 void LFGQueue::SetCompatibles(std::string const& key, LfgCompatibility compatibles)
422 {
424 data.compatibility = compatibles;
425 }
426
427 void LFGQueue::SetCompatibilityData(std::string const& key, LfgCompatibilityData const& data)
428 {
429 CompatibleMapStore[key] = data;
430 }
431
439 {
440 LfgCompatibleContainer::iterator itr = CompatibleMapStore.find(key);
441 if (itr != CompatibleMapStore.end())
442 return itr->second.compatibility;
443
445 }
446
448 {
449 LfgCompatibleContainer::iterator itr = CompatibleMapStore.find(key);
450 if (itr != CompatibleMapStore.end())
451 return &(itr->second);
452
453 return NULL;
454 }
455
457 {
458 uint8 proposals = 0;
459 LfgGuidList firstNew;
460 while (!newToQueueStore.empty())
461 {
462 uint64 frontguid = newToQueueStore.front();
463 SF_LOG_DEBUG("lfg.queue.match.check.new", "Checking [%u] newToQueue(%u), currentQueue(%u)", GUID_LOPART(frontguid), uint32(newToQueueStore.size()), uint32(currentQueueStore.size()));
464 firstNew.clear();
465 firstNew.push_back(frontguid);
466 RemoveFromNewQueue(frontguid);
467
468 LfgGuidList temporalList = currentQueueStore;
469 LfgCompatibility compatibles = FindNewGroups(firstNew, temporalList);
470
471 if (compatibles == LFG_COMPATIBLES_MATCH)
472 ++proposals;
473 else
474 AddToCurrentQueue(frontguid); // Lfg group not found, add this group to the queue.
475 }
476 return proposals;
477 }
478
487 {
488 std::string strGuids = ConcatenateGuids(check);
489 LfgCompatibility compatibles = GetCompatibles(strGuids);
490
491 SF_LOG_DEBUG("lfg.queue.match.check", "Guids: (%s): %s - all(%s)", strGuids.c_str(), GetCompatibleString(compatibles), ConcatenateGuids(all).c_str());
492 if (compatibles == LFG_COMPATIBILITY_PENDING) // Not previously cached, calculate
493 compatibles = CheckCompatibility(check);
494
495 if (compatibles == LFG_COMPATIBLES_BAD_STATES && sLFGMgr->AllQueued(check))
496 {
497 SF_LOG_DEBUG("lfg.queue.match.check", "Guids: (%s) cached bad states are now valid, rebuilding compatibility", strGuids.c_str());
499 return CheckCompatibility(check);
500 }
501
502 if (compatibles != LFG_COMPATIBLES_WITH_LESS_PLAYERS)
503 return compatibles;
504
505 // Try to match with queued groups
506 while (!all.empty())
507 {
508 check.push_back(all.front());
509 all.pop_front();
510 LfgCompatibility subcompatibility = FindNewGroups(check, all);
511 if (subcompatibility == LFG_COMPATIBLES_MATCH)
513 check.pop_back();
514 }
515 return compatibles;
516 }
517
525 {
526 std::string strGuids = ConcatenateGuids(check);
527 LfgProposal proposal;
528 LfgDungeonSet proposalDungeons;
529 LfgGroupsMap proposalGroups;
530 LfgRolesMap proposalRoles;
531
532 // Check for correct size
533 if (check.empty())
534 {
535 SF_LOG_DEBUG("lfg.queue.match.compatibility.check", "Guids: (%s): Size wrong - Not compatibles", strGuids.c_str());
537 }
538
539 uint8 checkGroupLimit = MAXGROUPSIZE;
540 for (LfgGuidList::const_iterator it = check.begin(); it != check.end(); ++it)
541 {
542 LfgQueueDataContainer::const_iterator itQueue = QueueDataStore.find(*it);
543 if (itQueue != QueueDataStore.end() && HasFlexibleRaidDungeon(itQueue->second.dungeons))
544 {
545 checkGroupLimit = MAXRAIDSIZE;
546 break;
547 }
548 }
549
550 if (check.size() > checkGroupLimit)
551 {
552 SF_LOG_DEBUG("lfg.queue.match.compatibility.check", "Guids: (%s): Size wrong - Not compatibles", strGuids.c_str());
554 }
555
556 // Check all-but-new compatiblitity
557 if (check.size() > 2)
558 {
559 uint64 frontGuid = check.front();
560 check.pop_front();
561
562 // Check all-but-new compatibilities (New, A, B, C, D) --> check(A, B, C, D)
563 LfgCompatibility child_compatibles = CheckCompatibility(check);
564 if (child_compatibles < LFG_COMPATIBLES_WITH_LESS_PLAYERS) // Group not compatible
565 {
566 SF_LOG_DEBUG("lfg.queue.match.compatibility.check", "Guids: (%s) child %s not compatibles", strGuids.c_str(), ConcatenateGuids(check).c_str());
567 SetCompatibles(strGuids, child_compatibles);
568 return child_compatibles;
569 }
570 check.push_front(frontGuid);
571 }
572
573 // Check if more than one LFG group and number of players joining
574 uint8 numPlayers = 0;
575 uint8 numLfgGroups = 0;
576 for (LfgGuidList::const_iterator it = check.begin(); it != check.end() && numLfgGroups < 2 && numPlayers <= checkGroupLimit; ++it)
577 {
578 uint64 guid = (*it);
579 LfgQueueDataContainer::iterator itQueue = QueueDataStore.find(guid);
580 if (itQueue == QueueDataStore.end())
581 {
582 SF_LOG_ERROR("lfg.queue.match.compatibility.check", "Guid: [%u] is not queued but listed as queued!", GUID_LOPART(guid));
583 RemoveFromQueue(guid);
585 }
586
587 // Store group so we don't need to call Mgr to get it later (if it's player group will be 0 otherwise would have joined as group)
588 for (LfgRolesMap::const_iterator it2 = itQueue->second.roles.begin(); it2 != itQueue->second.roles.end(); ++it2)
589 proposalGroups[it2->first] = IS_GROUP_GUID(itQueue->first) ? itQueue->first : 0;
590
591 numPlayers += itQueue->second.roles.size();
592
593 if (sLFGMgr->IsLfgGroup(guid))
594 {
595 if (!numLfgGroups)
596 proposal.group = guid;
597 ++numLfgGroups;
598 }
599 }
600
601 // Group with less than the target dungeon size is always compatible
602 if (check.size() == 1)
603 {
604 LfgQueueDataContainer::iterator itQueue = QueueDataStore.find(check.front());
605 uint8 minGroupSize = GetQueueMinGroupSize(itQueue->second.dungeons);
606
607 if (numPlayers < minGroupSize)
608 {
609 SF_LOG_DEBUG("lfg.queue.match.compatibility.check", "Guids: (%s) single group. Compatibles", strGuids.c_str());
610
612 data.roles = itQueue->second.roles;
613 if (!CheckQueueRoles(data.roles, itQueue->second.dungeons))
614 {
615 SF_LOG_DEBUG("lfg.queue.match.compatibility.check", "Guids: (%s) single group has invalid roles", strGuids.c_str());
618 }
619
620 UpdateBestCompatibleInQueue(itQueue, strGuids, data.roles);
621 SetCompatibilityData(strGuids, data);
623 }
624 }
625
626 if (numLfgGroups > 1)
627 {
628 SF_LOG_DEBUG("lfg.queue.match.compatibility.check", "Guids: (%s) More than one Lfggroup (%u)", strGuids.c_str(), numLfgGroups);
631 }
632
633 // If it's single group no need to check for duplicate players, ignores, bad roles or bad dungeons as it's been checked before joining
634 if (check.size() > 1)
635 {
636 for (LfgGuidList::const_iterator it = check.begin(); it != check.end(); ++it)
637 {
638 const LfgRolesMap& roles = QueueDataStore[(*it)].roles;
639 for (LfgRolesMap::const_iterator itRoles = roles.begin(); itRoles != roles.end(); ++itRoles)
640 {
641 LfgRolesMap::const_iterator itPlayer;
642 for (itPlayer = proposalRoles.begin(); itPlayer != proposalRoles.end(); ++itPlayer)
643 {
644 if (itRoles->first == itPlayer->first)
645 {
646 SF_LOG_ERROR("lfg.queue.match.compatibility.check", "Guids: ERROR! Player multiple times in queue! [%u]", GUID_LOPART(itRoles->first));
647 }
648 else if (sLFGMgr->HasIgnore(itRoles->first, itPlayer->first))
649 break;
650 }
651 if (itPlayer == proposalRoles.end())
652 proposalRoles[itRoles->first] = itRoles->second;
653 }
654 }
655
656 if (uint8 playersize = numPlayers - proposalRoles.size())
657 {
658 SF_LOG_DEBUG("lfg.queue.match.compatibility.check", "Guids: (%s) not compatible, %u players are ignoring each other", strGuids.c_str(), playersize);
661 }
662
663 LfgGuidList::iterator itguid = check.begin();
664 proposalDungeons = QueueDataStore[*itguid].dungeons;
665 std::ostringstream o;
666 o << ", " << *itguid << ": (" << ConcatenateDungeons(proposalDungeons) << ")";
667 for (++itguid; itguid != check.end(); ++itguid)
668 {
669 LfgDungeonSet temporal;
670 LfgDungeonSet& dungeons = QueueDataStore[*itguid].dungeons;
671 o << ", " << *itguid << ": (" << ConcatenateDungeons(dungeons) << ")";
672 std::set_intersection(proposalDungeons.begin(), proposalDungeons.end(), dungeons.begin(), dungeons.end(), std::inserter(temporal, temporal.begin()));
673 proposalDungeons = temporal;
674 }
675
676 if (proposalDungeons.empty())
677 {
678 SF_LOG_DEBUG("lfg.queue.match.compatibility.check", "Guids: (%s) No compatible dungeons%s", strGuids.c_str(), o.str().c_str());
681 }
682
683 LfgRolesMap debugRoles = proposalRoles;
684 if (!CheckQueueRoles(proposalRoles, proposalDungeons))
685 {
686 std::ostringstream rolesDebug;
687 for (LfgRolesMap::const_iterator it = debugRoles.begin(); it != debugRoles.end(); ++it)
688 rolesDebug << ", " << it->first << ": " << GetRolesString(it->second);
689
690 SF_LOG_DEBUG("lfg.queue.match.compatibility.check", "Guids: (%s) Roles not compatible%s", strGuids.c_str(), rolesDebug.str().c_str());
693 }
694 }
695 else
696 {
697 uint64 gguid = *check.begin();
698 const LfgQueueData& queue = QueueDataStore[gguid];
699 proposalDungeons = queue.dungeons;
700 proposalRoles = queue.roles;
701 LfgRolesMap debugRoles = proposalRoles;
702 if (!CheckQueueRoles(proposalRoles, proposalDungeons)) // assign new roles
703 {
704 std::ostringstream o;
705 for (LfgRolesMap::const_iterator it = debugRoles.begin(); it != debugRoles.end(); ++it)
706 o << ", " << it->first << ": " << GetRolesString(it->second);
707
708 SF_LOG_DEBUG("lfg.queue.match.compatibility.check", "Guids: (%s) single group roles not compatible%s", strGuids.c_str(), o.str().c_str());
711 }
712 }
713
714 uint8 minGroupSize = GetQueueMinGroupSize(proposalDungeons);
715 uint8 maxGroupSize = GetQueueMaxGroupSize(proposalDungeons);
716 if (numPlayers > maxGroupSize)
717 {
718 SF_LOG_DEBUG("lfg.queue.match.compatibility.check", "Guids: (%s) Too much players (%u)", strGuids.c_str(), numPlayers);
721 }
722
723 // Enough players?
724 if (numPlayers < minGroupSize)
725 {
726 SF_LOG_DEBUG("lfg.queue.match.compatibility.check", "Guids: (%s) Compatibles but not enough players(%u)", strGuids.c_str(), numPlayers);
728 data.roles = proposalRoles;
729
730 for (LfgGuidList::const_iterator itr = check.begin(); itr != check.end(); ++itr)
731 UpdateBestCompatibleInQueue(QueueDataStore.find(*itr), strGuids, data.roles);
732
733 SetCompatibilityData(strGuids, data);
735 }
736
737 uint64 gguid = *check.begin();
738 proposal.queues = check;
739 proposal.isNew = numLfgGroups != 1 || sLFGMgr->GetOldState(gguid) != LFG_STATE_DUNGEON;
740
741 if (!sLFGMgr->AllQueued(check))
742 {
743 SF_LOG_DEBUG("lfg.queue.match.compatibility.check", "Guids: (%s) Group MATCH but can't create proposal!", strGuids.c_str());
746 }
747
748 // Create a new proposal
749 proposal.cancelTime = time(NULL) + LFG_TIME_PROPOSAL;
751 proposal.leader = 0;
753
754 bool leader = false;
755 for (LfgRolesMap::const_iterator itRoles = proposalRoles.begin(); itRoles != proposalRoles.end(); ++itRoles)
756 {
757 // Assing new leader
758 if (itRoles->second & PLAYER_ROLE_LEADER)
759 {
760 if (!leader || !proposal.leader || std::rand() % 2)
761 proposal.leader = itRoles->first;
762 leader = true;
763 }
764 else if (!leader && (!proposal.leader || std::rand() % 2))
765 proposal.leader = itRoles->first;
766
767 // Assing player data and roles
768 LfgProposalPlayer& data = proposal.players[itRoles->first];
769 data.role = itRoles->second;
770 data.group = proposalGroups.find(itRoles->first)->second;
771 if (!proposal.isNew && data.group && data.group == proposal.group) // Player from existing group, autoaccept
773 }
774
775 // Mark proposal members as not queued (but not remove queue data)
776 for (LfgGuidList::const_iterator itQueue = proposal.queues.begin(); itQueue != proposal.queues.end(); ++itQueue)
777 {
778 uint64 guid = (*itQueue);
779 RemoveFromNewQueue(guid);
781 }
782
783 sLFGMgr->AddProposal(proposal);
784
785 SF_LOG_DEBUG("lfg.queue.match.compatibility.check", "Guids: (%s) MATCH! Group formed", strGuids.c_str());
788 }
789
790 void LFGQueue::UpdateQueueTimers(uint8 queueId, time_t currTime)
791 {
792 SF_LOG_TRACE("lfg.queue.timers.update", "Updating queue timers...");
793 for (LfgQueueDataContainer::iterator itQueue = QueueDataStore.begin(); itQueue != QueueDataStore.end(); ++itQueue)
794 {
795 if (!QueueContainsGuid(newToQueueStore, itQueue->first) && !QueueContainsGuid(currentQueueStore, itQueue->first))
796 continue;
797
798 LfgQueueData& queueinfo = itQueue->second;
799 if (queueinfo.dungeons.empty())
800 {
801 SF_LOG_DEBUG("lfg.queue.timers.update", "Skipping queue data for [%u] with no selected dungeons", GUID_LOPART(itQueue->first));
802 continue;
803 }
804
805 uint32 dungeonId = (*queueinfo.dungeons.begin());
806 uint32 queuedTime = uint32(currTime - queueinfo.joinTime);
807 int32 waitTime = -1;
808 int32 wtTank = waitTimesTankStore[dungeonId].time;
809 int32 wtHealer = waitTimesHealerStore[dungeonId].time;
810 int32 wtDps = waitTimesDpsStore[dungeonId].time;
811 int32 wtAvg = waitTimesAvgStore[dungeonId].time;
812
813 if (queueinfo.bestCompatible.empty())
815
816 uint8 tanks = queueinfo.tanks;
817 uint8 healers = queueinfo.healers;
818 uint8 dps = queueinfo.dps;
819 if (!queueinfo.bestCompatible.empty())
820 waitTime = SelectWaitTime(tanks, healers, dps, wtTank, wtHealer, wtDps, wtAvg);
821 else
822 {
823 CalculateRoleShortage(queueinfo.roles, queueinfo.dungeons, tanks, healers, dps);
824 waitTime = SelectWaitTime(tanks, healers, dps, wtTank, wtHealer, wtDps, wtAvg);
825 }
826
827 LfgQueueStatusData queueData(queueId, dungeonId, queueinfo.joinTime, waitTime, wtAvg, wtTank, wtHealer, wtDps, queuedTime, tanks, healers, dps);
828 for (LfgRolesMap::const_iterator itPlayer = queueinfo.roles.begin(); itPlayer != queueinfo.roles.end(); ++itPlayer)
829 {
830 uint64 pguid = itPlayer->first;
831 LFGMgr::SendLfgQueueStatus(pguid, queueData);
832 }
833 }
834 }
835
836 time_t LFGQueue::GetJoinTime(uint64 guid) const
837 {
838 LfgQueueDataContainer::const_iterator itr = QueueDataStore.find(guid);
839 if (itr != QueueDataStore.end())
840 return itr->second.joinTime;
841
842 return 0;
843 }
844
845 std::string LFGQueue::DumpQueueInfo(bool full /* = false */) const
846 {
847 uint32 players = 0;
848 uint32 groups = 0;
849 uint32 playersInGroup = 0;
850 uint32 currentEntries = 0;
851 uint32 newEntries = 0;
852 uint32 storedOnlyEntries = 0;
853 uint32 tankRoles = 0;
854 uint32 healerRoles = 0;
855 uint32 damageRoles = 0;
856 uint32 leaderRoles = 0;
857 uint32 noRoles = 0;
858 time_t const currTime = time(NULL);
859
860 for (uint8 i = 0; i < 2; ++i)
861 {
862 LfgGuidList const& queue = i ? newToQueueStore : currentQueueStore;
863 if (i)
864 newEntries = uint32(queue.size());
865 else
866 currentEntries = uint32(queue.size());
867
868 for (LfgGuidList::const_iterator it = queue.begin(); it != queue.end(); ++it)
869 {
870 uint64 guid = *it;
871 if (IS_GROUP_GUID(guid))
872 {
873 groups++;
874 playersInGroup += sLFGMgr->GetPlayerCount(guid);
875 }
876 else
877 players++;
878 }
879 }
880
881 for (LfgQueueDataContainer::const_iterator itr = QueueDataStore.begin(); itr != QueueDataStore.end(); ++itr)
882 {
883 if (!QueueContainsGuid(currentQueueStore, itr->first) && !QueueContainsGuid(newToQueueStore, itr->first))
884 ++storedOnlyEntries;
885
886 for (LfgRolesMap::const_iterator itRoles = itr->second.roles.begin(); itRoles != itr->second.roles.end(); ++itRoles)
887 {
888 uint8 roles = itRoles->second;
889 if (roles & PLAYER_ROLE_TANK)
890 ++tankRoles;
891 if (roles & PLAYER_ROLE_HEALER)
892 ++healerRoles;
893 if (roles & PLAYER_ROLE_DAMAGE)
894 ++damageRoles;
895 if (roles & PLAYER_ROLE_LEADER)
896 ++leaderRoles;
897 if (!(roles & LFG_COMBAT_ROLE_MASK))
898 ++noRoles;
899 }
900 }
901
902 std::ostringstream o;
903 o << "Queued Players: " << players << " (in group: " << playersInGroup << ") Groups: " << groups << "\n";
904 o << "Entries current/new/stored-only: " << currentEntries << "/" << newEntries << "/" << storedOnlyEntries << "\n";
905 o << "Role buckets tank/healer/damage/leader/none: " << tankRoles << "/" << healerRoles << "/" << damageRoles
906 << "/" << leaderRoles << "/" << noRoles << "\n";
907 if (!full)
908 return o.str();
909
910 o << "Current Queue: " << ConcatenateGuids(currentQueueStore) << "\n";
911 o << "New Queue: " << ConcatenateGuids(newToQueueStore) << "\n";
912
913 for (LfgQueueDataContainer::const_iterator itr = QueueDataStore.begin(); itr != QueueDataStore.end(); ++itr)
914 {
915 LfgQueueData const& queueInfo = itr->second;
916 o << " " << (IS_GROUP_GUID(itr->first) ? "Group" : "Player")
917 << " " << itr->first
918 << " queued " << uint32(currTime > queueInfo.joinTime ? currTime - queueInfo.joinTime : 0) << "s"
919 << " roles: " << queueInfo.roles.size()
920 << " dungeons: " << ConcatenateDungeons(queueInfo.dungeons)
921 << " need T/H/D: " << uint32(queueInfo.tanks) << "/" << uint32(queueInfo.healers) << "/" << uint32(queueInfo.dps);
922
923 if (!queueInfo.bestCompatible.empty())
924 o << " best: " << queueInfo.bestCompatible;
925
926 if (QueueContainsGuid(currentQueueStore, itr->first))
927 o << " phase: current";
928 else if (QueueContainsGuid(newToQueueStore, itr->first))
929 o << " phase: new";
930 else
931 o << " phase: stored-only";
932
933 o << "\n";
934
935 for (LfgRolesMap::const_iterator itRoles = queueInfo.roles.begin(); itRoles != queueInfo.roles.end(); ++itRoles)
936 o << " role " << itRoles->first << ": " << GetRolesString(itRoles->second) << "\n";
937 }
938
939 return o.str();
940 }
941
942 std::string LFGQueue::DumpCompatibleInfo(bool full /* = false */) const
943 {
944 std::ostringstream o;
945 o << "Compatible Map size: " << CompatibleMapStore.size() << "\n";
946 if (full)
947 for (LfgCompatibleContainer::const_iterator itr = CompatibleMapStore.begin(); itr != CompatibleMapStore.end(); ++itr)
948 o << "(" << itr->first << "): " << GetCompatibleString(itr->second.compatibility) << "\n";
949
950 return o.str();
951 }
952
953 void LFGQueue::FindBestCompatibleInQueue(LfgQueueDataContainer::iterator itrQueue)
954 {
955 SF_LOG_DEBUG("lfg.queue.compatibles.find", "Guid: " UI64FMTD, itrQueue->first);
956
957 for (LfgCompatibleContainer::const_iterator itr = CompatibleMapStore.begin(); itr != CompatibleMapStore.end(); ++itr)
958 if (itr->second.compatibility == LFG_COMPATIBLES_WITH_LESS_PLAYERS &&
959 CompatibleKeyContainsGuid(itr->first, itrQueue->first))
960 {
961 UpdateBestCompatibleInQueue(itrQueue, itr->first, itr->second.roles);
962 }
963 }
964
965 void LFGQueue::UpdateBestCompatibleInQueue(LfgQueueDataContainer::iterator itrQueue, std::string const& key, LfgRolesMap const& roles)
966 {
967 LfgQueueData& queueData = itrQueue->second;
968
969 uint8 storedSize = queueData.bestCompatible.empty() ? 0 :
970 std::count(queueData.bestCompatible.begin(), queueData.bestCompatible.end(), '|') + 1;
971
972 uint8 size = std::count(key.begin(), key.end(), '|') + 1;
973
974 if (size <= storedSize)
975 return;
976
977 SF_LOG_DEBUG("lfg.queue.compatibles.update", "Changed (%s) to (%s) as best compatible group for " UI64FMTD,
978 queueData.bestCompatible.c_str(), key.c_str(), itrQueue->first);
979
980 queueData.bestCompatible = key;
981 CalculateRoleShortage(roles, queueData.dungeons, queueData.tanks, queueData.healers, queueData.dps);
982 }
983
984} // namespace lfg
DifficultyID
Definition DBCEnums.h:330
@ DIFFICULTY_SCE_HEROIC
Definition DBCEnums.h:341
@ DIFFICULTY_SCE_NORMAL
Definition DBCEnums.h:342
@ DIFFICULTY_FLEX
Definition DBCEnums.h:343
DBCStorage< LFGDungeonEntry > sLFGDungeonStore(LFGDungeonEntryfmt)
MapDifficulty const * GetMapDifficultyData(uint32 mapId, DifficultyID difficulty)
DBCStorage< MapEntry > sMapStore(MapEntryfmt)
std::int32_t int32
Definition Define.h:73
#define UI64FMTD
Definition Define.h:64
std::uint8_t uint8
Definition Define.h:79
std::uint32_t uint32
Definition Define.h:77
std::uint64_t uint64
Definition Define.h:76
#define MAXGROUPSIZE
Definition Group.h:29
#define MAXRAIDSIZE
Definition Group.h:30
#define sLFGMgr
Definition LFGMgr.h:518
#define SF_LOG_DEBUG(filterType__,...)
Definition Log.h:134
#define SF_LOG_ERROR(filterType__,...)
Definition Log.h:143
#define SF_LOG_TRACE(filterType__,...)
Definition Log.h:131
uint32 GUID_LOPART(uint64 x)
bool IS_GROUP_GUID(uint64 guid)
static bool CheckDpsOnlyRoles(LfgRolesMap &groles, uint8 neededDamage)
Assigns queued players to damage roles for role-neutral scenario queues.
Definition LFGMgr.cpp:1114
static void SendLfgQueueStatus(uint64 guid, LfgQueueStatusData const &data)
Sends queue status to player.
Definition LFGMgr.cpp:2573
static bool CheckGroupRoles(LfgRolesMap &groles)
Checks if given roles match, modifies given roles map with new roles.
Definition LFGMgr.cpp:1073
static bool CheckFlexibleRaidRoles(LfgRolesMap &groles, uint8 maxPlayers)
Assigns queued players to their preferred combat role for flexible raid queues.
Definition LFGMgr.cpp:1125
LfgCompatibility GetCompatibles(std::string const &key)
Definition LFGQueue.cpp:438
LfgQueueDataContainer QueueDataStore
Queued groups.
Definition LFGQueue.h:123
void RemoveFromQueue(uint64 guid)
Definition LFGQueue.cpp:308
LfgCompatibilityData * GetCompatibilityData(std::string const &key)
Definition LFGQueue.cpp:447
void UpdateBestCompatibleInQueue(LfgQueueDataContainer::iterator itrQueue, std::string const &key, LfgRolesMap const &roles)
Definition LFGQueue.cpp:965
void UpdateQueueTimers(uint8 queueId, time_t currTime)
Definition LFGQueue.cpp:790
LfgCompatibleContainer CompatibleMapStore
Compatible dungeons.
Definition LFGQueue.h:124
void AddToCurrentQueue(uint64 guid)
Definition LFGQueue.cpp:342
LfgGuidList currentQueueStore
Ordered list. Used to find groups.
Definition LFGQueue.h:130
void AddToQueue(uint64 guid, bool reQueue=false)
Definition LFGQueue.cpp:293
time_t GetJoinTime(uint64 guid) const
Definition LFGQueue.cpp:836
LfgWaitTimesContainer waitTimesTankStore
Average wait time to find a group queuing as tank.
Definition LFGQueue.h:127
std::string DumpQueueInfo(bool full=false) const
Definition LFGQueue.cpp:845
void UpdateWaitTimeAvg(int32 waitTime, uint32 dungeonId)
Definition LFGQueue.cpp:371
LfgWaitTimesContainer waitTimesAvgStore
Average wait time to find a group queuing as multiple roles.
Definition LFGQueue.h:126
void RemoveFromNewQueue(uint64 guid)
Definition LFGQueue.cpp:337
void FindBestCompatibleInQueue(LfgQueueDataContainer::iterator itrQueue)
Definition LFGQueue.cpp:953
std::string DumpCompatibleInfo(bool full=false) const
Definition LFGQueue.cpp:942
bool HasQueueData(uint64 guid) const
Definition LFGQueue.cpp:366
void SetCompatibles(std::string const &key, LfgCompatibility compatibles)
Definition LFGQueue.cpp:421
LfgGuidList newToQueueStore
New groups to add to queue.
Definition LFGQueue.h:131
uint8 FindGroups()
Definition LFGQueue.cpp:456
void RemoveFromCurrentQueue(uint64 guid)
Definition LFGQueue.cpp:348
void UpdateWaitTimeHealer(int32 waitTime, uint32 dungeonId)
Definition LFGQueue.cpp:385
LfgCompatibility CheckCompatibility(LfgGuidList check)
Definition LFGQueue.cpp:524
void SetCompatibilityData(std::string const &key, LfgCompatibilityData const &compatibles)
Definition LFGQueue.cpp:427
LfgWaitTimesContainer waitTimesDpsStore
Average wait time to find a group queuing as dps.
Definition LFGQueue.h:129
void RemoveQueueData(uint64 guid)
Definition LFGQueue.cpp:359
void UpdateWaitTimeTank(int32 waitTime, uint32 dungeonId)
Definition LFGQueue.cpp:378
LfgWaitTimesContainer waitTimesHealerStore
Average wait time to find a group queuing as healer.
Definition LFGQueue.h:128
void UpdateWaitTimeDps(int32 waitTime, uint32 dungeonId)
Definition LFGQueue.cpp:392
void AddQueueData(uint64 guid, time_t joinTime, LfgDungeonSet const &dungeons, LfgRolesMap const &rolesMap)
Definition LFGQueue.cpp:353
void AddToNewQueue(uint64 guid)
Definition LFGQueue.cpp:331
void RemoveFromCompatibles(uint64 guid)
Definition LFGQueue.cpp:404
LfgCompatibility FindNewGroups(LfgGuidList &check, LfgGuidList &all)
Definition LFGQueue.cpp:486
C::value_type const & SelectRandomContainerElement(C const &container)
Definition Containers.h:48
Definition LFG.cpp:11
@ LFG_HEALERS_NEEDED
Definition LFG.h:17
@ LFG_TANKS_NEEDED
Definition LFG.h:16
@ LFG_DPS_NEEDED
Definition LFG.h:18
char const * GetCompatibleString(LfgCompatibility compatibles)
Definition LFGQueue.cpp:264
@ LFG_STATE_DUNGEON
Definition LFG.h:67
std::map< uint64, uint64 > LfgGroupsMap
Definition LFG.h:113
@ LFG_TIME_PROPOSAL
Definition LFGMgr.h:34
@ LFG_ANSWER_AGREE
Definition LFG.h:94
LfgCompatibility
Definition LFGQueue.h:15
@ LFG_COMPATIBLES_WITH_LESS_PLAYERS
Definition LFGQueue.h:23
@ LFG_INCOMPATIBLES_HAS_IGNORES
Definition LFGQueue.h:20
@ LFG_INCOMPATIBLES_MULTIPLE_LFG_GROUPS
Definition LFGQueue.h:19
@ LFG_INCOMPATIBLES_NO_DUNGEONS
Definition LFGQueue.h:22
@ LFG_COMPATIBILITY_PENDING
Definition LFGQueue.h:16
@ LFG_COMPATIBLES_MATCH
Definition LFGQueue.h:25
@ LFG_INCOMPATIBLES_TOO_MUCH_PLAYERS
Definition LFGQueue.h:18
@ LFG_INCOMPATIBLES_WRONG_GROUP_SIZE
Definition LFGQueue.h:17
@ LFG_INCOMPATIBLES_NO_ROLES
Definition LFGQueue.h:21
@ LFG_COMPATIBLES_BAD_STATES
Definition LFGQueue.h:24
std::set< uint32 > LfgDungeonSet
Definition LFG.h:107
std::list< uint64 > LfgGuidList
Definition LFG.h:111
std::string ConcatenateGuids(LfgGuidList const &guids)
Definition LFGQueue.cpp:246
std::map< uint64, uint8 > LfgRolesMap
Definition LFG.h:112
@ PLAYER_ROLE_DAMAGE
Definition LFG.h:27
@ PLAYER_ROLE_TANK
Definition LFG.h:25
@ PLAYER_ROLE_LEADER
Definition LFG.h:24
@ PLAYER_ROLE_HEALER
Definition LFG.h:26
std::string GetRolesString(uint8 roles)
Definition LFG.cpp:36
std::set< uint64 > LfgGuidSet
Definition LFG.h:110
std::string ConcatenateDungeons(LfgDungeonSet const &dungeons)
Definition LFG.cpp:13
@ LFG_PROPOSAL_INITIATING
Definition LFGMgr.h:72
int32 m_ContinentID
uint32 m_DifficultyID
bool IsScenario() const
bool IsRaid() const
LfgCompatibility compatibility
Definition LFGQueue.h:35
Stores group data related to proposal to join.
Definition LFGMgr.h:227
uint32 dungeonId
Dungeon to join.
Definition LFGMgr.h:233
uint64 group
Proposal group (0 if new).
Definition LFGMgr.h:235
LfgGuidList queues
Queue Ids to remove/readd.
Definition LFGMgr.h:240
uint64 leader
Leader guid.
Definition LFGMgr.h:236
LfgProposalPlayerContainer players
Players data.
Definition LFGMgr.h:242
bool isNew
Determines if it's new group or not.
Definition LFGMgr.h:239
LfgProposalState state
State of the proposal.
Definition LFGMgr.h:234
time_t cancelTime
Time when we will cancel this proposal.
Definition LFGMgr.h:237
Stores player data related to proposal to join.
Definition LFGMgr.h:218
uint8 role
Proposed role.
Definition LFGMgr.h:220
LfgAnswer accept
Accept status (-1 not answer | 0 Not agree | 1 agree).
Definition LFGMgr.h:221
uint64 group
Original group guid. 0 if no original group.
Definition LFGMgr.h:222
Stores player or group queue info.
Definition LFGQueue.h:41
LfgRolesMap roles
Selected Player Role/s.
Definition LFGQueue.h:56
time_t joinTime
Player queue join time (to calculate wait times).
Definition LFGQueue.h:51
std::string bestCompatible
Best compatible combination of people queued.
Definition LFGQueue.h:57
uint8 tanks
Tanks needed.
Definition LFGQueue.h:52
LfgDungeonSet dungeons
Selected Player/Group Dungeon/s.
Definition LFGQueue.h:55
uint8 healers
Healers needed.
Definition LFGQueue.h:53
uint8 dps
Dps needed.
Definition LFGQueue.h:54
int32 time
Wait time.
Definition LFGQueue.h:63
uint32 number
Number of people used to get that wait time.
Definition LFGQueue.h:64