/*
* 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 .
*/
#include "ObjectDefines.h"
#include "Containers.h"
#include "DBCStructure.h"
#include "DBCStores.h"
#include "GameTime.h"
#include "Group.h"
#include "LFGQueue.h"
#include "LFGMgr.h"
#include "ClientlessBrowserPolicy.h"
#include "World.h"
#include "Log.h"
namespace lfg
{
/**
Given a list of guids returns the concatenation using | as delimiter
@param[in] check list of guids
@returns Concatenated string
*/
std::string ConcatenateGuids(GuidList const& check)
{
if (check.empty())
return "";
// need the guids in order to avoid duplicates
GuidSet guids(check.begin(), check.end());
std::ostringstream o;
GuidSet::const_iterator it = guids.begin();
o << it->GetRawValue();
for (++it; it != guids.end(); ++it)
o << '|' << it->GetRawValue();
return o.str();
}
char const* GetCompatibleString(LfgCompatibility compatibles)
{
switch (compatibles)
{
case LFG_COMPATIBILITY_PENDING:
return "Pending";
case LFG_COMPATIBLES_BAD_STATES:
return "Compatibles (Bad States)";
case LFG_COMPATIBLES_MATCH:
return "Match";
case LFG_COMPATIBLES_WITH_LESS_PLAYERS:
return "Compatibles (Not enough players)";
case LFG_INCOMPATIBLES_HAS_IGNORES:
return "Has ignores";
case LFG_INCOMPATIBLES_MULTIPLE_LFG_GROUPS:
return "Multiple Lfg Groups";
case LFG_INCOMPATIBLES_NO_DUNGEONS:
return "Incompatible dungeons";
case LFG_INCOMPATIBLES_NO_ROLES:
return "Incompatible roles";
case LFG_INCOMPATIBLES_TOO_MUCH_PLAYERS:
return "Too many players";
case LFG_INCOMPATIBLES_WRONG_GROUP_SIZE:
return "Wrong group size";
default:
return "Unknown";
}
}
LfgQueueData::LfgQueueData(): joinTime(GameTime::GetGameTime())
{
InitializeGroupSetup();
}
void LfgQueueData::InitializeGroupSetup()
{
tanks = 0;
healers = 0;
dps = 0;
if (!dungeons.empty())
{
if (LFGDungeonData const* dungeon = sLFGMgr->GetLFGDungeon(*dungeons.begin()))
{
tanks = dungeon->requiredTanks;
healers = dungeon->requiredHealers;
dps = dungeon->requiredDamageDealers;
}
}
}
std::string LFGQueue::GetDetailedMatchRoles(GuidList const& check) const
{
if (check.empty())
return "";
// need the guids in order to avoid duplicates
GuidSet guids(check.begin(), check.end());
std::ostringstream o;
GuidSet::const_iterator it = guids.begin();
o << it->GetRawValue();
LfgQueueDataContainer::const_iterator itQueue = QueueDataStore.find(*it);
if (itQueue != QueueDataStore.end())
{
// skip leader flag, log only dps/tank/healer
auto role = itQueue->second.roles.find(*it);
if (role != itQueue->second.roles.end())
o << ' ' << GetRolesString(itQueue->second.roles.at(*it) & uint8(~PLAYER_ROLE_LEADER));
}
for (++it; it != guids.end(); ++it)
{
o << '|' << it->GetRawValue();
itQueue = QueueDataStore.find(*it);
if (itQueue != QueueDataStore.end())
{
// skip leader flag, log only dps/tank/healer
auto role = itQueue->second.roles.find(*it);
if (role != itQueue->second.roles.end())
o << ' ' << GetRolesString(itQueue->second.roles.at(*it) & uint8(~PLAYER_ROLE_LEADER));
}
}
return o.str();
}
void LFGQueue::AddToQueue(ObjectGuid guid, bool reAdd)
{
LfgQueueDataContainer::iterator itQueue = QueueDataStore.find(guid);
if (itQueue == QueueDataStore.end())
{
TC_LOG_ERROR("lfg.queue.add", "Queue data not found for [%s]", guid.ToString().c_str());
return;
}
if (reAdd)
AddToFrontCurrentQueue(guid);
else
AddToNewQueue(guid);
}
void LFGQueue::RemoveFromQueue(ObjectGuid guid)
{
RemoveFromNewQueue(guid);
RemoveFromCurrentQueue(guid);
RemoveFromCompatibles(guid);
std::ostringstream o;
o << guid.GetRawValue();
std::string sguid = o.str();
LfgQueueDataContainer::iterator itDelete = QueueDataStore.end();
for (LfgQueueDataContainer::iterator itr = QueueDataStore.begin(); itr != QueueDataStore.end(); ++itr)
if (itr->first != guid)
{
if (std::string::npos != itr->second.bestCompatible.find(sguid))
{
itr->second.bestCompatible.clear();
FindBestCompatibleInQueue(itr);
}
}
else
itDelete = itr;
if (itDelete != QueueDataStore.end())
QueueDataStore.erase(itDelete);
}
void LFGQueue::AddToNewQueue(ObjectGuid guid)
{
newToQueueStore.push_back(guid);
}
void LFGQueue::RemoveFromNewQueue(ObjectGuid guid)
{
newToQueueStore.remove(guid);
}
void LFGQueue::AddToCurrentQueue(ObjectGuid guid)
{
currentQueueStore.push_back(guid);
}
void LFGQueue::AddToFrontCurrentQueue(ObjectGuid guid)
{
currentQueueStore.push_front(guid);
}
void LFGQueue::RemoveFromCurrentQueue(ObjectGuid guid)
{
currentQueueStore.remove(guid);
}
void LFGQueue::AddQueueData(ObjectGuid guid, time_t joinTime, LfgDungeonSet const& dungeons, LfgRolesMap const& rolesMap)
{
QueueDataStore[guid] = LfgQueueData(joinTime, dungeons, rolesMap);
AddToQueue(guid);
}
void LFGQueue::RemoveQueueData(ObjectGuid guid)
{
LfgQueueDataContainer::iterator it = QueueDataStore.find(guid);
if (it != QueueDataStore.end())
QueueDataStore.erase(it);
}
void LFGQueue::UpdateWaitTimeAvg(int32 waitTime, uint32 dungeonId)
{
LfgWaitTime &wt = waitTimesAvgStore[dungeonId];
uint32 old_number = wt.number++;
wt.time = int32((wt.time * old_number + waitTime) / wt.number);
}
void LFGQueue::UpdateWaitTimeTank(int32 waitTime, uint32 dungeonId)
{
LfgWaitTime &wt = waitTimesTankStore[dungeonId];
uint32 old_number = wt.number++;
wt.time = int32((wt.time * old_number + waitTime) / wt.number);
}
void LFGQueue::UpdateWaitTimeHealer(int32 waitTime, uint32 dungeonId)
{
LfgWaitTime &wt = waitTimesHealerStore[dungeonId];
uint32 old_number = wt.number++;
wt.time = int32((wt.time * old_number + waitTime) / wt.number);
}
void LFGQueue::UpdateWaitTimeDps(int32 waitTime, uint32 dungeonId)
{
LfgWaitTime &wt = waitTimesDpsStore[dungeonId];
uint32 old_number = wt.number++;
wt.time = int32((wt.time * old_number + waitTime) / wt.number);
}
/**
Remove from cached compatible dungeons any entry that contains the given guid
@param[in] guid Guid to remove from compatible cache
*/
void LFGQueue::RemoveFromCompatibles(ObjectGuid guid)
{
std::stringstream out;
out << guid.GetRawValue();
std::string strGuid = out.str();
TC_LOG_DEBUG("lfg.queue.data.compatibles.remove", "Removing %s", guid.ToString().c_str());
for (LfgCompatibleContainer::iterator itNext = CompatibleMapStore.begin(); itNext != CompatibleMapStore.end();)
{
LfgCompatibleContainer::iterator it = itNext++;
if (std::string::npos != it->first.find(strGuid))
CompatibleMapStore.erase(it);
}
}
/**
Stores the compatibility of a list of guids
@param[in] key String concatenation of guids (| used as separator)
@param[in] compatibles type of compatibility
*/
void LFGQueue::SetCompatibles(std::string const& key, LfgCompatibility compatibles)
{
LfgCompatibilityData& data = CompatibleMapStore[key];
data.compatibility = compatibles;
}
void LFGQueue::SetCompatibilityData(std::string const& key, LfgCompatibilityData const& data)
{
CompatibleMapStore[key] = data;
}
/**
Get the compatibility of a group of guids
@param[in] key String concatenation of guids (| used as separator)
@return LfgCompatibility type of compatibility
*/
LfgCompatibility LFGQueue::GetCompatibles(std::string const& key)
{
LfgCompatibleContainer::iterator itr = CompatibleMapStore.find(key);
if (itr != CompatibleMapStore.end())
return itr->second.compatibility;
return LFG_COMPATIBILITY_PENDING;
}
LfgCompatibilityData* LFGQueue::GetCompatibilityData(std::string const& key)
{
LfgCompatibleContainer::iterator itr = CompatibleMapStore.find(key);
if (itr != CompatibleMapStore.end())
return &(itr->second);
return nullptr;
}
uint8 LFGQueue::FindGroups()
{
// Bot-only waiting populations must not trigger combinatorial searches on
// every world tick. Keep their queue records intact until a human arrives.
bool hasHuman=false,hasBot=false;
for(auto const& entry:QueueDataStore)for(auto const& member:entry.second.roles)
if(sLFGMgr->GetState(member.first)==LFG_STATE_QUEUED)
if(auto player=ObjectAccessor::FindConnectedPlayer(member.first))
{if(ClientlessBrowserPolicy::Bot(player))hasBot=true;else if(ClientlessBrowserPolicy::Human(player))hasHuman=true;}
if(hasBot && !hasHuman && !ClientlessBrowserPolicy::BotOnlyTests())return 0;
uint8 proposals = 0;
GuidList firstNew;
while (!newToQueueStore.empty())
{
ObjectGuid frontguid = newToQueueStore.front();
TC_LOG_DEBUG("lfg.queue.match.check.new", "Checking [%s] newToQueue(%u), currentQueue(%u)", frontguid.ToString().c_str(),
uint32(newToQueueStore.size()), uint32(currentQueueStore.size()));
firstNew.clear();
firstNew.push_back(frontguid);
RemoveFromNewQueue(frontguid);
GuidList temporalList = currentQueueStore;
LfgCompatibility compatibles = FindNewGroups(firstNew, temporalList);
if (compatibles == LFG_COMPATIBLES_MATCH)
++proposals;
else
AddToCurrentQueue(frontguid); // Lfg group not found, add this group to the queue.
}
// Keep normal TrinityCore matchmaking authoritative. Only after normal
// matching has failed do we permit an underfilled low-population group.
if (sWorld->getBoolConfig(CONFIG_DUNGEON_FINDER_LOW_POPULATION_ENABLE))
proposals += FindLowPopulationGroups();
return proposals;
}
GuidList LFGQueue::GetQueueGuidsFromKey(std::string const& key) const
{
GuidList result;
for (GuidList::const_iterator itr = currentQueueStore.begin(); itr != currentQueueStore.end(); ++itr)
{
std::ostringstream o;
o << itr->GetRawValue();
std::string token = o.str();
size_t pos = key.find(token);
while (pos != std::string::npos)
{
size_t end = pos + token.length();
bool leftBoundary = pos == 0 || key[pos - 1] == '|';
bool rightBoundary = end == key.length() || key[end] == '|';
if (leftBoundary && rightBoundary)
{
result.push_back(*itr);
break;
}
pos = key.find(token, pos + 1);
}
}
return result;
}
uint8 LFGQueue::FindLowPopulationGroups()
{
uint8 proposals = 0;
uint32 const fallbackDelay =
sWorld->getIntConfig(CONFIG_DUNGEON_FINDER_LOW_POPULATION_FALLBACK_DELAY);
uint32 const minPlayers =
sWorld->getIntConfig(CONFIG_DUNGEON_FINDER_LOW_POPULATION_MIN_PLAYERS);
time_t const now = GameTime::GetGameTime();
bool createdProposal = true;
while (createdProposal)
{
createdProposal = false;
GuidList queueSnapshot = currentQueueStore;
for (GuidList::const_iterator itrGuid = queueSnapshot.begin(); itrGuid != queueSnapshot.end(); ++itrGuid)
{
LfgQueueDataContainer::iterator itrQueue = QueueDataStore.find(*itrGuid);
if (itrQueue == QueueDataStore.end())
continue;
LfgQueueData& queueData = itrQueue->second;
if (now < queueData.joinTime || uint32(now - queueData.joinTime) < fallbackDelay)
continue;
if (queueData.bestCompatible.empty())
FindBestCompatibleInQueue(itrQueue);
if (queueData.bestCompatible.empty())
continue;
GuidList check = GetQueueGuidsFromKey(queueData.bestCompatible);
if (check.empty())
continue;
uint32 playerCount = 0;
for (GuidList::const_iterator itrCheck = check.begin(); itrCheck != check.end(); ++itrCheck)
{
LfgQueueDataContainer::const_iterator itrData = QueueDataStore.find(*itrCheck);
if (itrData != QueueDataStore.end())
playerCount += uint32(itrData->second.roles.size());
}
// Every participating queue entry must have received the full
// fallback window, including a human joining long-waiting bots.
bool waited = std::all_of(check.begin(), check.end(), [&](ObjectGuid guid)
{
auto entry = QueueDataStore.find(guid);
return entry != QueueDataStore.end() && now >= entry->second.joinTime &&
uint64(now - entry->second.joinTime) >= fallbackDelay;
});
if (playerCount < minPlayers || !waited)
continue;
TC_LOG_DEBUG("lfg.queue.lowpopulation",
"Low population fallback after %u seconds: trying partial group (%s) with %u player(s)",
fallbackDelay, ConcatenateGuids(check).c_str(), playerCount);
if (CheckCompatibility(check, true) == LFG_COMPATIBLES_MATCH)
{
++proposals;
createdProposal = true;
break;
}
}
}
return proposals;
}
/**
Checks que main queue to try to form a Lfg group. Returns first match found (if any)
@param[in] check List of guids trying to match with other groups
@param[in] all List of all other guids in main queue to match against
@return LfgCompatibility type of compatibility between groups
*/
LfgCompatibility LFGQueue::FindNewGroups(GuidList& check, GuidList& all)
{
// Mutable browser switches and human presence are not compatibility-cache
// facts. Evaluate them before using cached native combinations.
bool bot=false,human=false;
unsigned playerCount=0;
LFGDungeonData const* selectedDungeon=nullptr;
auto hasHuman=[&](ObjectGuid guid)
{
auto found=QueueDataStore.find(guid);if(found==QueueDataStore.end())return false;
for(auto const& member:found->second.roles)
if(auto player=ObjectAccessor::FindConnectedPlayer(member.first))
if(ClientlessBrowserPolicy::Human(player))return true;
return false;
};
for(auto guid:check)
{
auto found=QueueDataStore.find(guid);if(found==QueueDataStore.end())return LFG_COMPATIBILITY_PENDING;
playerCount+=found->second.roles.size();
if(!selectedDungeon && !found->second.dungeons.empty())selectedDungeon=sLFGMgr->GetLFGDungeon(*found->second.dungeons.begin());
bool entryBot=false;
for(auto const& member:found->second.roles)
if(ClientlessBrowserPolicy::Bot(ObjectAccessor::FindConnectedPlayer(member.first)))entryBot=true;
bot|=entryBot;human|=hasHuman(guid);
if(entryBot && !found->second.dungeons.empty())
if(auto dungeon=sLFGMgr->GetLFGDungeon(*found->second.dungeons.begin()))
{
auto kind=dungeon->IsRaid()?ClientlessBrowserPolicy::Kind::Raid:ClientlessBrowserPolicy::Kind::Dungeon;
if(!ClientlessBrowserPolicy::QueueEnabled(kind) || !ClientlessBrowserPolicy::JoinEnabled(kind))return LFG_COMPATIBILITY_PENDING;
}
}
if(bot && !human && !ClientlessBrowserPolicy::BotOnlyTests())
{
// Prune a full all-bot branch immediately. Recursing into a sixth queue
// entry would consume the human candidate instead of trying that human
// in the fifth slot on the parent branch. Never cache this decision.
if((selectedDungeon && playerCount>=selectedDungeon->GetMaxGroupSize()) ||
std::none_of(all.begin(),all.end(),hasHuman))return LFG_COMPATIBILITY_PENDING;
}
std::string strGuids = ConcatenateGuids(check);
LfgCompatibility compatibles = GetCompatibles(strGuids);
TC_LOG_DEBUG("lfg.queue.match.check", "Guids: (%s): %s - all(%s)", GetDetailedMatchRoles(check).c_str(), GetCompatibleString(compatibles), GetDetailedMatchRoles(all).c_str());
if (compatibles == LFG_COMPATIBILITY_PENDING) // Not previously cached, calculate
compatibles = CheckCompatibility(check);
if (compatibles == LFG_COMPATIBLES_BAD_STATES && sLFGMgr->AllQueued(check))
{
TC_LOG_DEBUG("lfg.queue.match.check", "Guids: (%s) compatibles (cached) changed from bad states to match", GetDetailedMatchRoles(check).c_str());
SetCompatibles(strGuids, LFG_COMPATIBLES_MATCH);
return LFG_COMPATIBLES_MATCH;
}
if (compatibles != LFG_COMPATIBLES_WITH_LESS_PLAYERS)
return compatibles;
// Try to match with queued groups
while (!all.empty())
{
check.push_back(all.front());
all.pop_front();
LfgCompatibility subcompatibility = FindNewGroups(check, all);
if (subcompatibility == LFG_COMPATIBLES_MATCH)
return LFG_COMPATIBLES_MATCH;
check.pop_back();
}
return compatibles;
}
/**
Check compatibilities between groups. If group is Matched proposal will be created
@param[in] check List of guids to check compatibilities
@return LfgCompatibility type of compatibility
*/
LfgCompatibility LFGQueue::CheckCompatibility(GuidList check, bool allowIncomplete)
{
std::string strGuids = ConcatenateGuids(check);
LfgProposal proposal;
LfgDungeonSet proposalDungeons;
LfgGroupsMap proposalGroups;
LfgRolesMap proposalRoles;
// Check for correct size
if (check.empty())
{
TC_LOG_DEBUG("lfg.queue.match.compatibility.check", "Guids: (%s): Size wrong - Not compatibles", GetDetailedMatchRoles(check).c_str());
return LFG_INCOMPATIBLES_WRONG_GROUP_SIZE;
}
// Check all-but-new compatiblitity
if (check.size() > 2)
{
ObjectGuid frontGuid = check.front();
check.pop_front();
// Check all-but-new compatibilities (New, A, B, C, D) --> check(A, B, C, D)
LfgCompatibility child_compatibles = CheckCompatibility(check);
if (child_compatibles < LFG_COMPATIBLES_WITH_LESS_PLAYERS) // Group not compatible
{
TC_LOG_DEBUG("lfg.queue.match.compatibility.check", "Guids: (%s) child %s not compatibles", strGuids.c_str(), GetDetailedMatchRoles(check).c_str());
SetCompatibles(strGuids, child_compatibles);
return child_compatibles;
}
check.push_front(frontGuid);
}
std::ostringstream o;
// Find compatible dungeons
if (check.size() > 1)
{
GuidList::iterator itguid = check.begin();
proposalDungeons = QueueDataStore[*itguid].dungeons;
for (++itguid; itguid != check.end(); ++itguid)
{
LfgDungeonSet temporal;
LfgDungeonSet& dungeons = QueueDataStore[*itguid].dungeons;
std::set_intersection(proposalDungeons.begin(), proposalDungeons.end(), dungeons.begin(), dungeons.end(), std::inserter(temporal, temporal.begin()));
proposalDungeons = temporal;
}
}
else
{
proposalDungeons = QueueDataStore[check.front()].dungeons;
}
LFGDungeonData const* dungeon = nullptr;
do
{
if (proposalDungeons.empty())
{
TC_LOG_DEBUG("lfg.queue.match.compatibility.check", "LFGQueue::CheckCompatibility: (%s) No compatible dungeons%s", strGuids.c_str(), o.str().c_str());
SetCompatibles(strGuids, LFG_INCOMPATIBLES_NO_DUNGEONS);
return LFG_INCOMPATIBLES_NO_DUNGEONS;
}
proposal.dungeonId = Trinity::Containers::SelectRandomContainerElement(proposalDungeons);
proposalDungeons.erase(proposal.dungeonId);
dungeon = sLFGMgr->GetLFGDungeon(proposal.dungeonId);
} while (!dungeon);
// Check for correct size
if (check.size() > dungeon->GetMaxGroupSize())
{
TC_LOG_DEBUG("lfg.queue.match.compatibility.check", "LFGQueue::CheckCompatibility: (%s): Size wrong - Not compatibles", strGuids.c_str());
return LFG_INCOMPATIBLES_WRONG_GROUP_SIZE;
}
// Check if more than one LFG group and number of players joining
uint8 numPlayers = 0;
uint8 numLfgGroups = 0;
for (GuidList::const_iterator it = check.begin(); it != check.end() && numLfgGroups < 1 && numPlayers <= dungeon->GetMaxGroupSize(); ++it)
{
ObjectGuid guid = *it;
LfgQueueDataContainer::iterator itQueue = QueueDataStore.find(guid);
if (itQueue == QueueDataStore.end())
{
TC_LOG_ERROR("lfg.queue.match.compatibility.check", "Guid: [%s] is not queued but listed as queued!", guid.ToString().c_str());
RemoveFromQueue(guid);
return LFG_COMPATIBILITY_PENDING;
}
// 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)
for (LfgRolesMap::const_iterator it2 = itQueue->second.roles.begin(); it2 != itQueue->second.roles.end(); ++it2)
proposalGroups[it2->first] = itQueue->first.IsGroup() ? itQueue->first : ObjectGuid::Empty;
numPlayers += itQueue->second.roles.size();
if (sLFGMgr->IsLfgGroup(guid))
{
if (!numLfgGroups)
proposal.group = guid;
++numLfgGroups;
}
}
if (numLfgGroups > 1)
{
TC_LOG_DEBUG("lfg.queue.match.compatibility.check", "Guids: (%s) More than one Lfggroup (%u)", GetDetailedMatchRoles(check).c_str(), numLfgGroups);
SetCompatibles(strGuids, LFG_INCOMPATIBLES_MULTIPLE_LFG_GROUPS);
return LFG_INCOMPATIBLES_MULTIPLE_LFG_GROUPS;
}
if (numPlayers > dungeon->GetMaxGroupSize())
{
TC_LOG_DEBUG("lfg.queue.match.compatibility.check", "Guids: (%s) Too many players (%u)", GetDetailedMatchRoles(check).c_str(), numPlayers);
SetCompatibles(strGuids, LFG_INCOMPATIBLES_TOO_MUCH_PLAYERS);
return LFG_INCOMPATIBLES_TOO_MUCH_PLAYERS;
}
// 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
if (check.size() > 1)
{
for (GuidList::const_iterator it = check.begin(); it != check.end(); ++it)
{
LfgRolesMap const& roles = QueueDataStore[(*it)].roles;
for (LfgRolesMap::const_iterator itRoles = roles.begin(); itRoles != roles.end(); ++itRoles)
{
LfgRolesMap::const_iterator itPlayer;
for (itPlayer = proposalRoles.begin(); itPlayer != proposalRoles.end(); ++itPlayer)
{
if (itRoles->first == itPlayer->first)
TC_LOG_ERROR("lfg.queue.match.compatibility.check", "Guids: ERROR! Player multiple times in queue! [%s]", itRoles->first.ToString().c_str());
else if (sLFGMgr->HasIgnore(itRoles->first, itPlayer->first))
break;
}
if (itPlayer == proposalRoles.end())
proposalRoles[itRoles->first] = itRoles->second;
}
}
if (uint8 playersize = numPlayers - proposalRoles.size())
{
TC_LOG_DEBUG("lfg.queue.match.compatibility.check", "Guids: (%s) not compatible, %u players are ignoring each other", GetDetailedMatchRoles(check).c_str(), playersize);
SetCompatibles(strGuids, LFG_INCOMPATIBLES_HAS_IGNORES);
return LFG_INCOMPATIBLES_HAS_IGNORES;
}
LfgRolesMap debugRoles = proposalRoles;
if (!LFGMgr::CheckGroupRoles(proposalRoles, dungeon))
{
std::ostringstream o;
for (LfgRolesMap::const_iterator it = debugRoles.begin(); it != debugRoles.end(); ++it)
o << ", " << it->first.GetRawValue() << ": " << GetRolesString(it->second);
TC_LOG_DEBUG("lfg.queue.match.compatibility.check", "Guids: (%s) Roles not compatible%s", GetDetailedMatchRoles(check).c_str(), o.str().c_str());
SetCompatibles(strGuids, LFG_INCOMPATIBLES_NO_ROLES);
return LFG_INCOMPATIBLES_NO_ROLES;
}
}
else
{
proposalRoles = QueueDataStore[check.front()].roles;
LFGMgr::CheckGroupRoles(proposalRoles, dungeon); // assign new roles
}
// Enough players?
if (numPlayers != dungeon->GetMaxGroupSize() && !allowIncomplete)
{
TC_LOG_DEBUG("lfg.queue.match.compatibility.check", "Guids: (%s) Compatibles but not enough players(%u)", GetDetailedMatchRoles(check).c_str(), numPlayers);
LfgCompatibilityData data(LFG_COMPATIBLES_WITH_LESS_PLAYERS);
data.roles = proposalRoles;
for (GuidList::const_iterator itr = check.begin(); itr != check.end(); ++itr)
UpdateBestCompatibleInQueue(QueueDataStore.find(*itr), strGuids, data.roles);
SetCompatibilityData(strGuids, data);
return LFG_COMPATIBLES_WITH_LESS_PLAYERS;
}
std::vector participants;
for(auto const& role:proposalRoles)participants.push_back(role.first);
if(!ClientlessBrowserPolicy::CanMatch(participants,dungeon->IsRaid()?ClientlessBrowserPolicy::Kind::Raid:ClientlessBrowserPolicy::Kind::Dungeon))
return LFG_COMPATIBILITY_PENDING; // Prune without consuming alternatives or caching mutable admission.
ObjectGuid gguid = check.front();
proposal.queues = check;
proposal.isNew = numLfgGroups != 1 || sLFGMgr->GetOldState(gguid) != LFG_STATE_DUNGEON;
if (!sLFGMgr->AllQueued(check))
{
TC_LOG_DEBUG("lfg.queue.match.compatibility.check", "Guids: (%s) Group MATCH but can't create proposal!", GetDetailedMatchRoles(check).c_str());
SetCompatibles(strGuids, LFG_COMPATIBLES_BAD_STATES);
return LFG_COMPATIBLES_BAD_STATES;
}
// Create a new proposal
proposal.cancelTime = GameTime::GetGameTime() + LFG_TIME_PROPOSAL;
proposal.state = LFG_PROPOSAL_INITIATING;
proposal.leader.Clear();
bool leader = false;
for (LfgRolesMap::const_iterator itRoles = proposalRoles.begin(); itRoles != proposalRoles.end(); ++itRoles)
{
// Assing new leader
if (itRoles->second & PLAYER_ROLE_LEADER)
{
if (!leader || !proposal.leader || urand(0, 1))
proposal.leader = itRoles->first;
leader = true;
}
else if (!leader && (!proposal.leader || urand(0, 1)))
proposal.leader = itRoles->first;
// Assing player data and roles
LfgProposalPlayer &data = proposal.players[itRoles->first];
data.role = itRoles->second;
data.group = proposalGroups.find(itRoles->first)->second;
if (!proposal.isNew && data.group && data.group == proposal.group) // Player from existing group, autoaccept
data.accept = LFG_ANSWER_AGREE;
}
// A browser group containing humans is always led by one of those humans.
std::vector humans;
bool hasBot=false;
for(auto guid:participants)
if(ClientlessBrowserPolicy::Bot(ObjectAccessor::FindConnectedPlayer(guid)))hasBot=true;
else if(ClientlessBrowserPolicy::Human(ObjectAccessor::FindConnectedPlayer(guid)))humans.push_back(guid);
if(hasBot && !humans.empty())proposal.leader=humans[urand(0,uint32(humans.size()-1))];
// Mark proposal members as not queued (but not remove queue data)
for (GuidList::const_iterator itQueue = proposal.queues.begin(); itQueue != proposal.queues.end(); ++itQueue)
{
ObjectGuid guid = (*itQueue);
RemoveFromNewQueue(guid);
RemoveFromCurrentQueue(guid);
}
sLFGMgr->AddProposal(proposal);
TC_LOG_DEBUG("lfg.queue.match.compatibility.check", "Guids: (%s) MATCH! Group formed", GetDetailedMatchRoles(check).c_str());
SetCompatibles(strGuids, LFG_COMPATIBLES_MATCH);
return LFG_COMPATIBLES_MATCH;
}
void LFGQueue::UpdateQueueTimers(uint8 queueId, time_t currTime, LfgQueueRoleContainer& rolesPerDungeonId)
{
TC_LOG_TRACE("lfg.queue.timers.update", "Updating queue timers...");
for (LfgQueueDataContainer::iterator itQueue = QueueDataStore.begin(); itQueue != QueueDataStore.end(); ++itQueue)
{
LfgQueueData& queueinfo = itQueue->second;
uint32 dungeonId = (*queueinfo.dungeons.begin());
uint32 queuedTime = uint32(currTime - queueinfo.joinTime);
uint8 role = PLAYER_ROLE_NONE;
int32 waitTime = -1;
int32 wtTank = waitTimesTankStore[dungeonId].time;
int32 wtHealer = waitTimesHealerStore[dungeonId].time;
int32 wtDps = waitTimesDpsStore[dungeonId].time;
int32 wtAvg = waitTimesAvgStore[dungeonId].time;
for (LfgRolesMap::const_iterator itPlayer = queueinfo.roles.begin(); itPlayer != queueinfo.roles.end(); ++itPlayer)
role |= itPlayer->second;
role &= ~PLAYER_ROLE_LEADER;
if (role & PLAYER_ROLE_TANK)
rolesPerDungeonId[dungeonId].currentTanks++;
if (role & PLAYER_ROLE_HEALER)
rolesPerDungeonId[dungeonId].currentHealers++;
if (role & PLAYER_ROLE_DAMAGE)
rolesPerDungeonId[dungeonId].currentDps++;
switch (role)
{
case PLAYER_ROLE_NONE: // Should not happen - just in case
waitTime = -1;
break;
case PLAYER_ROLE_TANK:
waitTime = wtTank;
break;
case PLAYER_ROLE_HEALER:
waitTime = wtHealer;
break;
case PLAYER_ROLE_DAMAGE:
waitTime = wtDps;
break;
default:
waitTime = wtAvg;
break;
}
if (queueinfo.bestCompatible.empty())
FindBestCompatibleInQueue(itQueue);
LfgQueueStatusData queueData(queueId, dungeonId, waitTime, wtAvg, wtTank, wtHealer, wtDps, queuedTime, queueinfo.tanks, queueinfo.healers, queueinfo.dps);
for (LfgRolesMap::const_iterator itPlayer = queueinfo.roles.begin(); itPlayer != queueinfo.roles.end(); ++itPlayer)
{
ObjectGuid pguid = itPlayer->first;
LFGMgr::SendLfgQueueStatus(pguid, queueData);
}
}
}
time_t LFGQueue::GetJoinTime(ObjectGuid guid) const
{
LfgQueueDataContainer::const_iterator itr = QueueDataStore.find(guid);
if (itr != QueueDataStore.end())
return itr->second.joinTime;
return 0;
}
std::string LFGQueue::DumpQueueInfo() const
{
uint32 players = 0;
uint32 groups = 0;
uint32 playersInGroup = 0;
for (uint8 i = 0; i < 2; ++i)
{
GuidList const& queue = i ? newToQueueStore : currentQueueStore;
for (GuidList::const_iterator it = queue.begin(); it != queue.end(); ++it)
{
ObjectGuid guid = *it;
if (guid.IsGroup())
{
groups++;
playersInGroup += sLFGMgr->GetPlayerCount(guid);
}
else
players++;
}
}
std::ostringstream o;
o << "Queued Players: " << players << " (in group: " << playersInGroup << ") Groups: " << groups << "\n";
return o.str();
}
std::string LFGQueue::DumpCompatibleInfo(bool full /* = false */) const
{
std::ostringstream o;
o << "Compatible Map size: " << CompatibleMapStore.size() << "\n";
if (full)
for (LfgCompatibleContainer::const_iterator itr = CompatibleMapStore.begin(); itr != CompatibleMapStore.end(); ++itr)
{
o << "(" << itr->first << "): " << GetCompatibleString(itr->second.compatibility);
if (!itr->second.roles.empty())
{
o << " (";
bool first = true;
for (const auto& role : itr->second.roles)
{
if (!first)
o << "|";
o << role.first.GetRawValue() << " " << GetRolesString(role.second & uint8(~PLAYER_ROLE_LEADER));
first = false;
}
o << ")";
}
o << "\n";
}
return o.str();
}
void LFGQueue::FindBestCompatibleInQueue(LfgQueueDataContainer::iterator itrQueue)
{
TC_LOG_DEBUG("lfg.queue.compatibles.find", "%s", itrQueue->first.ToString().c_str());
std::ostringstream o;
o << itrQueue->first.GetRawValue();
std::string sguid = o.str();
for (LfgCompatibleContainer::const_iterator itr = CompatibleMapStore.begin(); itr != CompatibleMapStore.end(); ++itr)
{
if (itr->second.compatibility == LFG_COMPATIBLES_WITH_LESS_PLAYERS && std::string::npos != itr->first.find(sguid))
{
UpdateBestCompatibleInQueue(itrQueue, itr->first, itr->second.roles);
}
}
}
void LFGQueue::UpdateBestCompatibleInQueue(LfgQueueDataContainer::iterator itrQueue, std::string const& key, LfgRolesMap const& roles)
{
LfgQueueData& queueData = itrQueue->second;
uint8 storedSize = queueData.bestCompatible.empty() ? 0 :
std::count(queueData.bestCompatible.begin(), queueData.bestCompatible.end(), '|') + 1;
uint8 size = std::count(key.begin(), key.end(), '|') + 1;
if (size <= storedSize)
return;
TC_LOG_DEBUG("lfg.queue.compatibles.update", "Changed (%s) to (%s) as best compatible group for %s",
queueData.bestCompatible.c_str(), key.c_str(), itrQueue->first.ToString().c_str());
queueData.bestCompatible = key;
queueData.InitializeGroupSetup();
for (LfgRolesMap::const_iterator it = roles.begin(); it != roles.end(); ++it)
{
uint8 role = it->second;
if (role & PLAYER_ROLE_TANK)
--queueData.tanks;
else if (role & PLAYER_ROLE_HEALER)
--queueData.healers;
else
--queueData.dps;
}
}
} // namespace lfg