/*
* 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 "LFGMgr.h"
#include "Common.h"
#include "DatabaseEnv.h"
#include "DBCStores.h"
#include "DisableMgr.h"
#include "GameEventMgr.h"
#include "GameTime.h"
#include "Group.h"
#include "GroupMgr.h"
#include "InstanceSaveMgr.h"
#include "LFGGroupData.h"
#include "LFGPlayerData.h"
#include "LFGScripts.h"
#include "LFGQueue.h"
#include "Log.h"
#include "Map.h"
#include "MapUtils.h"
#include "ObjectAccessor.h"
#include "ObjectMgr.h"
#include "Player.h"
#include "RBAC.h"
#include "SharedDefines.h"
#include "SocialMgr.h"
#include "World.h"
#include "WorldSession.h"
namespace lfg
{
LFGDungeonData::LFGDungeonData() : id(0), name(), map(0), type(0), expansion(0), group(0), minlevel(),
maxlevel(), difficulty(REGULAR_DIFFICULTY), seasonal(false), x(0.0f), y(0.0f), z(0.0f), o(0.0f)
{
}
LFGDungeonData::LFGDungeonData(LFGDungeonEntry const* dbc) : id(dbc->ID), name(dbc->Name[0]), map(dbc->MapID),
type(dbc->TypeID), expansion(uint8(dbc->ExpansionLevel)), group(uint8(dbc->GroupID)),
minlevel({ uint8(dbc->MinLevel), uint8(dbc->MinLevel), uint8(dbc->MinLevel) }),
maxlevel({ uint8(dbc->MaxLevel), uint8(dbc->MaxLevel), uint8(dbc->MaxLevel) }),
difficulty(Difficulty(dbc->Difficulty)), seasonal((dbc->Flags & LFG_FLAG_SEASONAL) != 0), x(0.0f), y(0.0f), z(0.0f), o(0.0f)
{
}
LFGMgr::LFGMgr(): m_QueueTimer(0), m_lfgProposalId(1),
m_options(sWorld->getIntConfig(CONFIG_LFG_OPTIONSMASK))
{
}
LFGMgr::~LFGMgr()
{
for (LfgRewardContainer::iterator itr = RewardMapStore.begin(); itr != RewardMapStore.end(); ++itr)
delete itr->second;
}
void LFGMgr::_LoadFromDB(Field* fields, ObjectGuid guid)
{
if (!fields)
return;
if (!guid.IsGroup())
return;
SetLeader(guid, ObjectGuid(HighGuid::Player, fields[0].GetUInt32()));
uint32 dungeon = fields[17].GetUInt32();
uint8 state = fields[18].GetUInt8();
if (!dungeon || !state)
return;
SetDungeon(guid, dungeon);
switch (state)
{
case LFG_STATE_DUNGEON:
case LFG_STATE_FINISHED_DUNGEON:
SetState(guid, (LfgState)state);
break;
default:
break;
}
}
void LFGMgr::_SaveToDB(ObjectGuid guid, uint32 db_guid)
{
if (!guid.IsGroup())
return;
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_LFG_DATA);
stmt->setUInt32(0, db_guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_LFG_DATA);
stmt->setUInt32(0, db_guid);
stmt->setUInt32(1, GetDungeon(guid));
stmt->setUInt32(2, GetState(guid));
trans->Append(stmt);
CharacterDatabase.CommitTransaction(trans);
}
/// Load rewards for completing dungeons
void LFGMgr::LoadRewards()
{
uint32 oldMSTime = getMSTime();
for (LfgRewardContainer::iterator itr = RewardMapStore.begin(); itr != RewardMapStore.end(); ++itr)
delete itr->second;
RewardMapStore.clear();
// ORDER BY is very important for GetRandomDungeonReward!
QueryResult result = WorldDatabase.Query("SELECT dungeonId, maxLevel, firstQuestId, otherQuestId FROM lfg_dungeon_rewards ORDER BY dungeonId, maxLevel ASC");
if (!result)
{
TC_LOG_INFO("server.loading", ">> Loaded 0 lfg dungeon rewards. DB table `lfg_dungeon_rewards` is empty!");
return;
}
uint32 count = 0;
Field* fields = nullptr;
do
{
fields = result->Fetch();
uint32 dungeonId = fields[0].GetUInt32();
uint32 maxLevel = fields[1].GetUInt8();
uint32 firstQuestId = fields[2].GetUInt32();
uint32 otherQuestId = fields[3].GetUInt32();
if (!GetLFGDungeonEntry(dungeonId))
{
TC_LOG_ERROR("sql.sql", "Dungeon {} specified in table `lfg_dungeon_rewards` does not exist!", dungeonId);
continue;
}
if (!maxLevel || maxLevel > sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL))
{
TC_LOG_ERROR("sql.sql", "Level {} specified for dungeon {} in table `lfg_dungeon_rewards` can never be reached!", maxLevel, dungeonId);
maxLevel = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL);
}
if (!firstQuestId || !sObjectMgr->GetQuestTemplate(firstQuestId))
{
TC_LOG_ERROR("sql.sql", "First quest {} specified for dungeon {} in table `lfg_dungeon_rewards` does not exist!", firstQuestId, dungeonId);
continue;
}
if (otherQuestId && !sObjectMgr->GetQuestTemplate(otherQuestId))
{
TC_LOG_ERROR("sql.sql", "Other quest {} specified for dungeon {} in table `lfg_dungeon_rewards` does not exist!", otherQuestId, dungeonId);
otherQuestId = 0;
}
RewardMapStore.insert(LfgRewardContainer::value_type(dungeonId, new LfgReward(maxLevel, firstQuestId, otherQuestId)));
++count;
}
while (result->NextRow());
TC_LOG_INFO("server.loading", ">> Loaded {} lfg dungeon rewards in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
}
LFGDungeonData const* LFGMgr::GetLFGDungeon(uint32 id)
{
LFGDungeonContainer::const_iterator itr = LfgDungeonStore.find(id);
if (itr != LfgDungeonStore.end())
return &(itr->second);
return nullptr;
}
void LFGMgr::LoadLFGDungeons()
{
uint32 oldMSTime = getMSTime();
LfgDungeonStore.clear();
// Initialize Dungeon map with data from dbcs
for (uint32 i = 0; i < sLFGDungeonStore.GetNumRows(); ++i)
{
LFGDungeonEntry const* dungeon = sLFGDungeonStore.LookupEntry(i);
if (!dungeon)
continue;
switch (dungeon->TypeID)
{
case LFG_TYPE_DUNGEON:
case LFG_TYPE_HEROIC:
case LFG_TYPE_RAID:
case LFG_TYPE_RANDOM:
LfgDungeonStore[dungeon->ID] = LFGDungeonData(dungeon);
break;
}
}
for (LFGDungeonExpansionEntry const* dungeonExpansion : sLFGDungeonExpansionStore)
{
if (LFGDungeonData* dungeon = Trinity::Containers::MapGetValuePtr(LfgDungeonStore, dungeonExpansion->LfgID))
{
dungeon->minlevel[dungeonExpansion->ExpansionLevel] = dungeonExpansion->HardLevelMin;
dungeon->maxlevel[dungeonExpansion->ExpansionLevel] = dungeonExpansion->HardLevelMax;
}
}
// Fill teleport locations from DB
// 0 1 2 3 4
QueryResult result = WorldDatabase.Query("SELECT dungeonId, position_x, position_y, position_z, orientation FROM lfg_dungeon_template");
if (!result)
{
TC_LOG_INFO("server.loading", ">> Loaded 0 lfg dungeon templates. DB table `lfg_dungeon_template` is empty!");
return;
}
uint32 count = 0;
do
{
Field* fields = result->Fetch();
uint32 dungeonId = fields[0].GetUInt32();
LFGDungeonContainer::iterator dungeonItr = LfgDungeonStore.find(dungeonId);
if (dungeonItr == LfgDungeonStore.end())
{
TC_LOG_ERROR("sql.sql", "table `lfg_dungeon_template` contains coordinates for wrong dungeon {}", dungeonId);
continue;
}
LFGDungeonData& data = dungeonItr->second;
data.x = fields[1].GetFloat();
data.y = fields[2].GetFloat();
data.z = fields[3].GetFloat();
data.o = fields[4].GetFloat();
++count;
}
while (result->NextRow());
TC_LOG_INFO("server.loading", ">> Loaded {} lfg dungeon templates in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
CachedDungeonMapStore.clear();
// Fill all other teleport coords from areatriggers
for (LFGDungeonContainer::iterator itr = LfgDungeonStore.begin(); itr != LfgDungeonStore.end(); ++itr)
{
LFGDungeonData& dungeon = itr->second;
// No teleport coords in database, load from areatriggers
if (dungeon.type != LFG_TYPE_RANDOM && dungeon.x == 0.0f && dungeon.y == 0.0f && dungeon.z == 0.0f)
{
AreaTriggerTeleport const* at = sObjectMgr->GetMapEntranceTrigger(dungeon.map);
if (!at)
{
TC_LOG_ERROR("sql.sql", "Failed to load dungeon {} (Id: {}), cant find areatrigger for map {}", dungeon.name, dungeon.id, dungeon.map);
continue;
}
dungeon.map = at->target_mapId;
dungeon.x = at->target_X;
dungeon.y = at->target_Y;
dungeon.z = at->target_Z;
dungeon.o = at->target_Orientation;
}
if (dungeon.type != LFG_TYPE_RANDOM)
CachedDungeonMapStore[dungeon.group].insert(dungeon.id);
CachedDungeonMapStore[0].insert(dungeon.id);
}
}
LFGMgr* LFGMgr::instance()
{
static LFGMgr instance;
return &instance;
}
void LFGMgr::Update(uint32 diff)
{
if (!isOptionEnabled(LFG_OPTION_ENABLE_DUNGEON_FINDER | LFG_OPTION_ENABLE_RAID_BROWSER))
return;
time_t currTime = GameTime::GetGameTime();
// Remove obsolete role checks
for (LfgRoleCheckContainer::iterator it = RoleChecksStore.begin(); it != RoleChecksStore.end();)
{
LfgRoleCheckContainer::iterator itRoleCheck = it++;
LfgRoleCheck& roleCheck = itRoleCheck->second;
if (currTime < roleCheck.cancelTime)
continue;
roleCheck.state = LFG_ROLECHECK_MISSING_ROLE;
for (LfgRolesMap::const_iterator itRoles = roleCheck.roles.begin(); itRoles != roleCheck.roles.end(); ++itRoles)
{
ObjectGuid guid = itRoles->first;
RestoreState(guid, "Remove Obsolete RoleCheck");
SendLfgRoleCheckUpdate(guid, roleCheck);
if (guid == roleCheck.leader)
SendLfgJoinResult(guid, LfgJoinResultData(LFG_JOIN_FAILED, LFG_ROLECHECK_MISSING_ROLE));
}
RestoreState(itRoleCheck->first, "Remove Obsolete RoleCheck");
RoleChecksStore.erase(itRoleCheck);
}
// Remove obsolete proposals
for (LfgProposalContainer::iterator it = ProposalsStore.begin(); it != ProposalsStore.end();)
{
LfgProposalContainer::iterator itRemove = it++;
if (itRemove->second.cancelTime < currTime)
RemoveProposal(itRemove, LFG_UPDATETYPE_PROPOSAL_FAILED);
}
// Remove obsolete kicks
for (LfgPlayerBootContainer::iterator it = BootsStore.begin(); it != BootsStore.end();)
{
LfgPlayerBootContainer::iterator itBoot = it++;
LfgPlayerBoot& boot = itBoot->second;
if (boot.cancelTime < currTime)
{
boot.inProgress = false;
for (LfgAnswerContainer::const_iterator itVotes = boot.votes.begin(); itVotes != boot.votes.end(); ++itVotes)
{
ObjectGuid pguid = itVotes->first;
if (pguid != boot.victim)
SendLfgBootProposalUpdate(pguid, boot);
}
SetVoteKick(itBoot->first, false);
BootsStore.erase(itBoot);
}
}
uint32 lastProposalId = m_lfgProposalId;
// Check if a proposal can be formed with the new groups being added
for (LfgQueueContainer::iterator it = QueuesStore.begin(); it != QueuesStore.end(); ++it)
if (uint8 newProposals = it->second.FindGroups())
TC_LOG_DEBUG("lfg.update", "Found {} new groups in queue {}", newProposals, it->first);
if (lastProposalId != m_lfgProposalId)
{
// FIXME lastProposalId ? lastProposalId +1 ?
for (LfgProposalContainer::const_iterator itProposal = ProposalsStore.find(m_lfgProposalId); itProposal != ProposalsStore.end(); ++itProposal)
{
uint32 proposalId = itProposal->first;
LfgProposal& proposal = ProposalsStore[proposalId];
ObjectGuid guid;
for (LfgProposalPlayerContainer::const_iterator itPlayers = proposal.players.begin(); itPlayers != proposal.players.end(); ++itPlayers)
{
guid = itPlayers->first;
SetState(guid, LFG_STATE_PROPOSAL);
if (ObjectGuid gguid = GetGroup(guid))
{
SetState(gguid, LFG_STATE_PROPOSAL);
SendLfgUpdateParty(guid, LfgUpdateData(LFG_UPDATETYPE_PROPOSAL_BEGIN, GetSelectedDungeons(guid), GetComment(guid)));
}
else
SendLfgUpdatePlayer(guid, LfgUpdateData(LFG_UPDATETYPE_PROPOSAL_BEGIN, GetSelectedDungeons(guid), GetComment(guid)));
SendLfgUpdateProposal(guid, proposal);
}
if (proposal.state == LFG_PROPOSAL_SUCCESS)
UpdateProposal(proposalId, guid, true);
}
}
// Update all players status queue info
if (m_QueueTimer > LFG_QUEUEUPDATE_INTERVAL)
{
m_QueueTimer = 0;
for (LfgQueueContainer::iterator it = QueuesStore.begin(); it != QueuesStore.end(); ++it)
it->second.UpdateQueueTimers(currTime);
}
else
m_QueueTimer += diff;
}
/**
Adds the player/group to lfg queue. If player is in a group then it is the leader
of the group tying to join the group. Join conditions are checked before adding
to the new queue.
@param[in] player Player trying to join (or leader of group trying to join)
@param[in] roles Player selected roles
@param[in] dungeons Dungeons the player/group is applying for
@param[in] comment Player selected comment
*/
void LFGMgr::JoinLfg(Player* player, uint8 roles, LfgDungeonSet& dungeons, const std::string& comment)
{
if (!player || !player->GetSession() || dungeons.empty())
return;
// Sanitize input roles
roles &= PLAYER_ROLE_ANY;
roles = FilterClassRoles(player, roles);
// At least 1 role must be selected
if (!(roles & (PLAYER_ROLE_TANK | PLAYER_ROLE_HEALER | PLAYER_ROLE_DAMAGE)))
return;
Group* grp = player->GetGroup();
ObjectGuid guid = player->GetGUID();
ObjectGuid gguid = grp ? grp->GetGUID() : guid;
LfgJoinResultData joinData;
GuidSet players;
uint32 rDungeonId = 0;
bool isContinue = grp && grp->isLFGGroup() && GetState(gguid) != LFG_STATE_FINISHED_DUNGEON;
// Do not allow to change dungeon in the middle of a current dungeon
if (isContinue)
{
dungeons.clear();
dungeons.insert(GetDungeon(gguid));
}
// Already in queue?
LfgState state = GetState(gguid);
if (state == LFG_STATE_QUEUED)
{
LFGQueue& queue = GetQueue(gguid);
queue.RemoveFromQueue(gguid);
}
// Check player or group member restrictions
if (!player->GetSession()->HasPermission(rbac::RBAC_PERM_JOIN_DUNGEON_FINDER))
joinData.result = LFG_JOIN_NOT_MEET_REQS;
else if (player->InBattleground() || player->InArena() || player->InBattlegroundQueue())
joinData.result = LFG_JOIN_USING_BG_SYSTEM;
else if (player->HasAura(LFG_SPELL_DUNGEON_DESERTER))
joinData.result = LFG_JOIN_DESERTER;
else if (!isContinue && player->HasAura(LFG_SPELL_DUNGEON_COOLDOWN))
joinData.result = LFG_JOIN_RANDOM_COOLDOWN;
else if (dungeons.empty())
joinData.result = LFG_JOIN_NOT_MEET_REQS;
else if (player->HasAura(9454)) // check Freeze debuff
joinData.result = LFG_JOIN_NOT_MEET_REQS;
else if (grp)
{
if (grp->GetMembersCount() > MAX_GROUP_SIZE)
joinData.result = LFG_JOIN_TOO_MUCH_MEMBERS;
else
{
uint8 memberCount = 0;
for (GroupReference* itr = grp->GetFirstMember(); itr != nullptr && joinData.result == LFG_JOIN_OK; itr = itr->next())
{
if (Player* plrg = itr->GetSource())
{
if (!plrg->GetSession()->HasPermission(rbac::RBAC_PERM_JOIN_DUNGEON_FINDER))
joinData.result = LFG_JOIN_PARTY_NOT_MEET_REQS;
if (plrg->HasAura(LFG_SPELL_DUNGEON_DESERTER))
joinData.result = LFG_JOIN_PARTY_DESERTER;
else if (!isContinue && plrg->HasAura(LFG_SPELL_DUNGEON_COOLDOWN))
joinData.result = LFG_JOIN_PARTY_RANDOM_COOLDOWN;
else if (plrg->InBattleground() || plrg->InArena() || plrg->InBattlegroundQueue())
joinData.result = LFG_JOIN_USING_BG_SYSTEM;
else if (plrg->HasAura(9454)) // check Freeze debuff
joinData.result = LFG_JOIN_PARTY_NOT_MEET_REQS;
++memberCount;
players.insert(plrg->GetGUID());
}
}
if (joinData.result == LFG_JOIN_OK && memberCount != grp->GetMembersCount())
joinData.result = LFG_JOIN_DISCONNECTED;
}
}
else
players.insert(player->GetGUID());
// Check if all dungeons are valid
bool isRaid = false;
if (joinData.result == LFG_JOIN_OK)
{
bool isDungeon = false;
for (LfgDungeonSet::const_iterator it = dungeons.begin(); it != dungeons.end() && joinData.result == LFG_JOIN_OK; ++it)
{
LfgType type = GetDungeonType(*it);
switch (type)
{
case LFG_TYPE_RANDOM:
if (dungeons.size() > 1) // Only allow 1 random dungeon
joinData.result = LFG_JOIN_DUNGEON_INVALID;
else
rDungeonId = (*dungeons.begin());
[[fallthrough]]; // Random can only be dungeon or heroic dungeon
case LFG_TYPE_HEROIC:
case LFG_TYPE_DUNGEON:
if (isRaid)
joinData.result = LFG_JOIN_MIXED_RAID_DUNGEON;
isDungeon = true;
break;
case LFG_TYPE_RAID:
if (isDungeon)
joinData.result = LFG_JOIN_MIXED_RAID_DUNGEON;
isRaid = true;
break;
default:
joinData.result = LFG_JOIN_DUNGEON_INVALID;
break;
}
}
// it could be changed
if (joinData.result == LFG_JOIN_OK)
{
// Expand random dungeons and check restrictions
if (rDungeonId)
dungeons = GetDungeonsByRandom(rDungeonId);
// if we have lockmap then there are no compatible dungeons
GetCompatibleDungeons(dungeons, players, joinData.lockmap, isContinue);
if (dungeons.empty())
joinData.result = grp ? LFG_JOIN_PARTY_NOT_MEET_REQS : LFG_JOIN_NOT_MEET_REQS;
}
}
// Can't join. Send result
if (joinData.result != LFG_JOIN_OK)
{
TC_LOG_DEBUG("lfg.join", "{} joining with {} members. Result: {}, Dungeons: {}",
guid.ToString(), grp ? grp->GetMembersCount() : 1, joinData.result, ConcatenateDungeons(dungeons));
if (!dungeons.empty()) // Only should show lockmap when have no dungeons available
joinData.lockmap.clear();
player->GetSession()->SendLfgJoinResult(joinData);
return;
}
SetComment(guid, comment);
if (isRaid)
{
TC_LOG_DEBUG("lfg.join", "{} trying to join raid browser and it's disabled.", guid.ToString());
return;
}
std::string debugNames = "";
if (grp) // Begin rolecheck
{
// Create new rolecheck
LfgRoleCheck& roleCheck = RoleChecksStore[gguid];
roleCheck.cancelTime = GameTime::GetGameTime() + LFG_TIME_ROLECHECK;
roleCheck.state = LFG_ROLECHECK_INITIALITING;
roleCheck.leader = guid;
roleCheck.dungeons = dungeons;
roleCheck.rDungeonId = rDungeonId;
if (rDungeonId)
{
dungeons.clear();
dungeons.insert(rDungeonId);
}
SetState(gguid, LFG_STATE_ROLECHECK);
// Send update to player
LfgUpdateData updateData = LfgUpdateData(LFG_UPDATETYPE_JOIN_QUEUE, dungeons, comment);
for (GroupReference* itr = grp->GetFirstMember(); itr != nullptr; itr = itr->next())
{
if (Player* plrg = itr->GetSource())
{
ObjectGuid pguid = plrg->GetGUID();
plrg->GetSession()->SendLfgUpdateParty(updateData);
SetState(pguid, LFG_STATE_ROLECHECK);
if (!isContinue)
SetSelectedDungeons(pguid, dungeons);
roleCheck.roles[pguid] = 0;
if (!debugNames.empty())
debugNames.append(", ");
debugNames.append(plrg->GetName());
}
}
// Update leader role
UpdateRoleCheck(gguid, guid, roles);
}
else // Add player to queue
{
LfgRolesMap rolesMap;
rolesMap[guid] = roles;
LFGQueue& queue = GetQueue(guid);
queue.AddQueueData(guid, GameTime::GetGameTime(), dungeons, rolesMap);
if (!isContinue)
{
if (rDungeonId)
{
dungeons.clear();
dungeons.insert(rDungeonId);
}
SetSelectedDungeons(guid, dungeons);
}
// Send update to player
player->GetSession()->SendLfgJoinResult(joinData);
player->GetSession()->SendLfgUpdatePlayer(LfgUpdateData(LFG_UPDATETYPE_JOIN_QUEUE, dungeons, comment));
SetState(gguid, LFG_STATE_QUEUED);
SetRoles(guid, roles);
debugNames.append(player->GetName());
}
TC_LOG_DEBUG("lfg.join", "{} joined ({}), Members: {}. Dungeons ({}): {}", guid.ToString(),
grp ? "group" : "player", debugNames, uint32(dungeons.size()), ConcatenateDungeons(dungeons));
}
/**
Leaves Dungeon System. Player/Group is removed from queue, rolechecks, proposals
or votekicks. Player or group needs to be not NULL and using Dungeon System
@param[in] guid Player or group guid
*/
void LFGMgr::LeaveLfg(ObjectGuid guid, bool disconnected)
{
ObjectGuid gguid = guid.IsGroup() ? guid : GetGroup(guid);
TC_LOG_DEBUG("lfg.leave", "{} left ({})", guid.ToString(), guid == gguid ? "group" : "player");
LfgState state = GetState(guid);
switch (state)
{
case LFG_STATE_QUEUED:
if (gguid)
{
LfgState newState = LFG_STATE_NONE;
LfgState oldState = GetOldState(gguid);
// Set the new state to LFG_STATE_DUNGEON/LFG_STATE_FINISHED_DUNGEON if the group is already in a dungeon
// This is required in case a LFG group vote-kicks a player in a dungeon, queues, then leaves the queue (maybe to queue later again)
if (Group* group = sGroupMgr->GetGroupByGUID(gguid.GetCounter()))
if (group->isLFGGroup() && GetDungeon(gguid) && (oldState == LFG_STATE_DUNGEON || oldState == LFG_STATE_FINISHED_DUNGEON))
newState = oldState;
LFGQueue& queue = GetQueue(gguid);
queue.RemoveFromQueue(gguid);
SetState(gguid, newState);
GuidSet const& players = GetPlayers(gguid);
for (GuidSet::const_iterator it = players.begin(); it != players.end(); ++it)
{
SetState(*it, newState);
SendLfgUpdateParty(*it, LfgUpdateData(LFG_UPDATETYPE_REMOVED_FROM_QUEUE));
}
}
else
{
LFGQueue& queue = GetQueue(guid);
queue.RemoveFromQueue(guid);
SendLfgUpdatePlayer(guid, LfgUpdateData(LFG_UPDATETYPE_REMOVED_FROM_QUEUE));
SetState(guid, LFG_STATE_NONE);
}
break;
case LFG_STATE_ROLECHECK:
if (gguid)
UpdateRoleCheck(gguid); // No player to update role = LFG_ROLECHECK_ABORTED
break;
case LFG_STATE_PROPOSAL:
{
// Remove from Proposals
LfgProposalContainer::iterator it = ProposalsStore.begin();
ObjectGuid pguid = gguid == guid ? GetLeader(gguid) : guid;
while (it != ProposalsStore.end())
{
LfgProposalPlayerContainer::iterator itPlayer = it->second.players.find(pguid);
if (itPlayer != it->second.players.end())
{
// Mark the player/leader of group who left as didn't accept the proposal
itPlayer->second.accept = LFG_ANSWER_DENY;
break;
}
++it;
}
// Remove from queue - if proposal is found, RemoveProposal will call RemoveFromQueue
if (it != ProposalsStore.end())
RemoveProposal(it, LFG_UPDATETYPE_PROPOSAL_DECLINED);
break;
}
case LFG_STATE_NONE:
case LFG_STATE_RAIDBROWSER:
break;
case LFG_STATE_DUNGEON:
case LFG_STATE_FINISHED_DUNGEON:
if (guid != gguid && !disconnected) // Player
SetState(guid, LFG_STATE_NONE);
break;
}
}
/**
Update the Role check info with the player selected role.
@param[in] grp Group guid to update rolecheck
@param[in] guid Player guid (0 = rolecheck failed)
@param[in] roles Player selected roles
*/
void LFGMgr::UpdateRoleCheck(ObjectGuid gguid, ObjectGuid guid /* = ObjectGuid::Empty */, uint8 roles /* = PLAYER_ROLE_NONE */)
{
if (!gguid)
return;
LfgRolesMap check_roles;
LfgRoleCheckContainer::iterator itRoleCheck = RoleChecksStore.find(gguid);
if (itRoleCheck == RoleChecksStore.end())
return;
// Sanitize input roles
roles &= PLAYER_ROLE_ANY;
if (guid)
{
if (Player* player = ObjectAccessor::FindPlayer(guid))
roles = FilterClassRoles(player, roles);
else
return;
}
LfgRoleCheck& roleCheck = itRoleCheck->second;
bool sendRoleChosen = roleCheck.state != LFG_ROLECHECK_DEFAULT && guid;
if (!guid)
roleCheck.state = LFG_ROLECHECK_ABORTED;
else if (roles < PLAYER_ROLE_TANK) // Player selected no role.
roleCheck.state = LFG_ROLECHECK_NO_ROLE;
else
{
roleCheck.roles[guid] = roles;
// Check if all players have selected a role
LfgRolesMap::const_iterator itRoles = roleCheck.roles.begin();
while (itRoles != roleCheck.roles.end() && itRoles->second != PLAYER_ROLE_NONE)
++itRoles;
if (itRoles == roleCheck.roles.end())
{
// use temporal var to check roles, CheckGroupRoles modifies the roles
check_roles = roleCheck.roles;
roleCheck.state = CheckGroupRoles(check_roles) ? LFG_ROLECHECK_FINISHED : LFG_ROLECHECK_WRONG_ROLES;
}
}
LfgDungeonSet dungeons;
if (roleCheck.rDungeonId)
dungeons.insert(roleCheck.rDungeonId);
else
dungeons = roleCheck.dungeons;
LfgJoinResultData joinData = LfgJoinResultData(LFG_JOIN_FAILED, roleCheck.state);
for (LfgRolesMap::const_iterator it = roleCheck.roles.begin(); it != roleCheck.roles.end(); ++it)
{
ObjectGuid pguid = it->first;
if (sendRoleChosen)
SendLfgRoleChosen(pguid, guid, roles);
SendLfgRoleCheckUpdate(pguid, roleCheck);
switch (roleCheck.state)
{
case LFG_ROLECHECK_INITIALITING:
continue;
case LFG_ROLECHECK_FINISHED:
SetState(pguid, LFG_STATE_QUEUED);
SetRoles(pguid, it->second);
SendLfgUpdateParty(pguid, LfgUpdateData(LFG_UPDATETYPE_ADDED_TO_QUEUE, dungeons, GetComment(pguid)));
break;
default:
if (roleCheck.leader == pguid)
SendLfgJoinResult(pguid, joinData);
SendLfgUpdateParty(pguid, LfgUpdateData(LFG_UPDATETYPE_ROLECHECK_FAILED));
RestoreState(pguid, "Rolecheck Failed");
break;
}
}
if (roleCheck.state == LFG_ROLECHECK_FINISHED)
{
SetState(gguid, LFG_STATE_QUEUED);
LFGQueue& queue = GetQueue(gguid);
queue.AddQueueData(gguid, GameTime::GetGameTime(), roleCheck.dungeons, roleCheck.roles);
RoleChecksStore.erase(itRoleCheck);
}
else if (roleCheck.state != LFG_ROLECHECK_INITIALITING)
{
RestoreState(gguid, "Rolecheck Failed");
RoleChecksStore.erase(itRoleCheck);
}
}
/**
Given a list of dungeons remove the dungeons players have restrictions.
@param[in, out] dungeons Dungeons to check restrictions
@param[in] players Set of players to check their dungeon restrictions
@param[out] lockMap Map of players Lock status info of given dungeons (Empty if dungeons is not empty)
*/
void LFGMgr::GetCompatibleDungeons(LfgDungeonSet& dungeons, GuidSet const& players, LfgLockPartyMap& lockMap, bool isContinue)
{
lockMap.clear();
std::map lockedDungeons;
for (GuidSet::const_iterator it = players.begin(); it != players.end() && !dungeons.empty(); ++it)
{
ObjectGuid guid = (*it);
LfgLockMap const& cachedLockMap = GetLockedDungeons(guid);
Player* player = ObjectAccessor::FindConnectedPlayer(guid);
for (LfgLockMap::const_iterator it2 = cachedLockMap.begin(); it2 != cachedLockMap.end() && !dungeons.empty(); ++it2)
{
uint32 dungeonId = (it2->first & 0x00FFFFFF); // Compare dungeon ids
LfgDungeonSet::iterator itDungeon = dungeons.find(dungeonId);
if (itDungeon != dungeons.end())
{
bool eraseDungeon = true;
// Don't remove the dungeon if team members are trying to continue a locked instance
if (it2->second == LFG_LOCKSTATUS_RAID_LOCKED && isContinue)
{
LFGDungeonData const* dungeon = GetLFGDungeon(dungeonId);
ASSERT(dungeon);
ASSERT(player);
if (InstancePlayerBind* playerBind = player->GetBoundInstance(dungeon->map, Difficulty(dungeon->difficulty)))
{
if (InstanceSave* playerSave = playerBind->save)
{
uint32 dungeonInstanceId = playerSave->GetInstanceId();
auto itLockedDungeon = lockedDungeons.find(dungeonId);
if (itLockedDungeon == lockedDungeons.end() || itLockedDungeon->second == dungeonInstanceId)
eraseDungeon = false;
lockedDungeons[dungeonId] = dungeonInstanceId;
}
}
}
if (eraseDungeon)
dungeons.erase(itDungeon);
lockMap[guid][dungeonId] = it2->second;
}
}
}
if (!dungeons.empty())
lockMap.clear();
}
/**
Check if a group can be formed with the given group roles
@param[in] groles Map of roles to check
@return True if roles are compatible
*/
bool LFGMgr::CheckGroupRoles(LfgRolesMap& groles)
{
if (groles.empty())
return false;
uint8 damage = 0;
uint8 tank = 0;
uint8 healer = 0;
for (LfgRolesMap::iterator it = groles.begin(); it != groles.end(); ++it)
{
uint8 role = it->second & ~PLAYER_ROLE_LEADER;
if (role == PLAYER_ROLE_NONE)
return false;
if (role & PLAYER_ROLE_DAMAGE)
{
if (role != PLAYER_ROLE_DAMAGE)
{
it->second -= PLAYER_ROLE_DAMAGE;
if (CheckGroupRoles(groles))
return true;
it->second += PLAYER_ROLE_DAMAGE;
}
else if (damage == LFG_DPS_NEEDED)
return false;
else
damage++;
}
if (role & PLAYER_ROLE_HEALER)
{
if (role != PLAYER_ROLE_HEALER)
{
it->second -= PLAYER_ROLE_HEALER;
if (CheckGroupRoles(groles))
return true;
it->second += PLAYER_ROLE_HEALER;
}
else if (healer == LFG_HEALERS_NEEDED)
return false;
else
healer++;
}
if (role & PLAYER_ROLE_TANK)
{
if (role != PLAYER_ROLE_TANK)
{
it->second -= PLAYER_ROLE_TANK;
if (CheckGroupRoles(groles))
return true;
it->second += PLAYER_ROLE_TANK;
}
else if (tank == LFG_TANKS_NEEDED)
return false;
else
tank++;
}
}
return (tank + healer + damage) == uint8(groles.size());
}
/**
Makes a new group given a proposal
@param[in] proposal Proposal to get info from
*/
void LFGMgr::MakeNewGroup(LfgProposal const& proposal)
{
GuidList players, tankPlayers, healPlayers, dpsPlayers;
GuidList playersToTeleport;
for (LfgProposalPlayerContainer::const_iterator it = proposal.players.begin(); it != proposal.players.end(); ++it)
{
ObjectGuid guid = it->first;
if (guid == proposal.leader)
players.push_back(guid);
else
switch (it->second.role & ~PLAYER_ROLE_LEADER)
{
case PLAYER_ROLE_TANK:
tankPlayers.push_back(guid);
break;
case PLAYER_ROLE_HEALER:
healPlayers.push_back(guid);
break;
case PLAYER_ROLE_DAMAGE:
dpsPlayers.push_back(guid);
break;
default:
ABORT_MSG("Invalid LFG role %u", it->second.role);
break;
}
if (proposal.isNew || GetGroup(guid) != proposal.group)
playersToTeleport.push_back(guid);
}
players.splice(players.end(), tankPlayers);
players.splice(players.end(), healPlayers);
players.splice(players.end(), dpsPlayers);
// Set the dungeon difficulty
LFGDungeonData const* dungeon = GetLFGDungeon(proposal.dungeonId);
ASSERT(dungeon);
Group* grp = proposal.group ? sGroupMgr->GetGroupByGUID(proposal.group.GetCounter()) : nullptr;
for (GuidList::const_iterator it = players.begin(); it != players.end(); ++it)
{
ObjectGuid pguid = (*it);
Player* player = ObjectAccessor::FindConnectedPlayer(pguid);
if (!player)
continue;
Group* group = player->GetGroup();
if (group && group != grp)
group->RemoveMember(player->GetGUID());
if (!grp)
{
grp = new Group();
grp->ConvertToLFG();
grp->Create(player);
ObjectGuid gguid = grp->GetGUID();
SetState(gguid, LFG_STATE_PROPOSAL);
sGroupMgr->AddGroup(grp);
}
else if (group != grp)
grp->AddMember(player);
grp->SetLfgRoles(pguid, proposal.players.find(pguid)->second.role);
// Add the cooldown spell if queued for a random dungeon
const LfgDungeonSet& dungeons = GetSelectedDungeons(player->GetGUID());
if (!dungeons.empty())
{
uint32 rDungeonId = (*dungeons.begin());
LFGDungeonEntry const* dungeonEntry = sLFGDungeonStore.LookupEntry(rDungeonId);
if (dungeonEntry && dungeonEntry->TypeID == LFG_TYPE_RANDOM)
player->CastSpell(player, LFG_SPELL_DUNGEON_COOLDOWN, false);
}
}
ASSERT(grp);
grp->SetDungeonDifficulty(Difficulty(dungeon->difficulty));
ObjectGuid gguid = grp->GetGUID();
SetDungeon(gguid, dungeon->Entry());
SetState(gguid, LFG_STATE_DUNGEON);
_SaveToDB(gguid, grp->GetDbStoreId());
// Teleport Player
for (GuidList::const_iterator it = playersToTeleport.begin(); it != playersToTeleport.end(); ++it)
if (Player* player = ObjectAccessor::FindPlayer(*it))
TeleportPlayer(player, false);
// Update group info
grp->SendUpdate();
}
uint32 LFGMgr::AddProposal(LfgProposal& proposal)
{
proposal.id = ++m_lfgProposalId;
ProposalsStore[m_lfgProposalId] = proposal;
return m_lfgProposalId;
}
/**
Update Proposal info with player answer
@param[in] proposalId Proposal id to be updated
@param[in] guid Player guid to update answer
@param[in] accept Player answer
*/
void LFGMgr::UpdateProposal(uint32 proposalId, ObjectGuid guid, bool accept)
{
// Check if the proposal exists
LfgProposalContainer::iterator itProposal = ProposalsStore.find(proposalId);
if (itProposal == ProposalsStore.end())
return;
LfgProposal& proposal = itProposal->second;
// Check if proposal have the current player
LfgProposalPlayerContainer::iterator itProposalPlayer = proposal.players.find(guid);
if (itProposalPlayer == proposal.players.end())
return;
LfgProposalPlayer& player = itProposalPlayer->second;
player.accept = LfgAnswer(accept);
TC_LOG_DEBUG("lfg.proposal.update", "{}, Proposal {}, Selection: {}", guid.ToString(), proposalId, accept);
if (!accept)
{
RemoveProposal(itProposal, LFG_UPDATETYPE_PROPOSAL_DECLINED);
return;
}
// check if all have answered and reorder players (leader first)
bool allAnswered = true;
for (LfgProposalPlayerContainer::const_iterator itPlayers = proposal.players.begin(); itPlayers != proposal.players.end(); ++itPlayers)
if (itPlayers->second.accept != LFG_ANSWER_AGREE) // No answer (-1) or not accepted (0)
allAnswered = false;
if (!allAnswered)
{
for (LfgProposalPlayerContainer::const_iterator it = proposal.players.begin(); it != proposal.players.end(); ++it)
SendLfgUpdateProposal(it->first, proposal);
return;
}
bool sendUpdate = proposal.state != LFG_PROPOSAL_SUCCESS;
proposal.state = LFG_PROPOSAL_SUCCESS;
time_t joinTime = GameTime::GetGameTime();
LFGQueue& queue = GetQueue(guid);
LfgUpdateData updateData = LfgUpdateData(LFG_UPDATETYPE_GROUP_FOUND);
for (LfgProposalPlayerContainer::const_iterator it = proposal.players.begin(); it != proposal.players.end(); ++it)
{
ObjectGuid pguid = it->first;
ObjectGuid gguid = it->second.group;
uint32 dungeonId = (*GetSelectedDungeons(pguid).begin());
int32 waitTime = -1;
if (sendUpdate)
SendLfgUpdateProposal(pguid, proposal);
if (gguid)
{
waitTime = int32((joinTime - queue.GetJoinTime(gguid)) / IN_MILLISECONDS);
SendLfgUpdateParty(pguid, updateData);
}
else
{
waitTime = int32((joinTime - queue.GetJoinTime(pguid)) / IN_MILLISECONDS);
SendLfgUpdatePlayer(pguid, updateData);
}
updateData.updateType = LFG_UPDATETYPE_REMOVED_FROM_QUEUE;
SendLfgUpdatePlayer(pguid, updateData);
SendLfgUpdateParty(pguid, updateData);
// Update timers
uint8 role = GetRoles(pguid);
role &= ~PLAYER_ROLE_LEADER;
switch (role)
{
case PLAYER_ROLE_DAMAGE:
queue.UpdateWaitTimeDps(waitTime, dungeonId);
break;
case PLAYER_ROLE_HEALER:
queue.UpdateWaitTimeHealer(waitTime, dungeonId);
break;
case PLAYER_ROLE_TANK:
queue.UpdateWaitTimeTank(waitTime, dungeonId);
break;
default:
queue.UpdateWaitTimeAvg(waitTime, dungeonId);
break;
}
// Store the number of players that were present in group when joining RFD, used for achievement purposes
if (Player* player = ObjectAccessor::FindConnectedPlayer(pguid))
if (Group* group = player->GetGroup())
PlayersStore[pguid].SetNumberOfPartyMembersAtJoin(group->GetMembersCount());
SetState(pguid, LFG_STATE_DUNGEON);
}
// Remove players/groups from Queue
for (GuidList::const_iterator it = proposal.queues.begin(); it != proposal.queues.end(); ++it)
queue.RemoveFromQueue(*it);
MakeNewGroup(proposal);
ProposalsStore.erase(itProposal);
}
/**
Remove a proposal from the pool, remove the group that didn't accept (if needed) and readd the other members to the queue
@param[in] itProposal Iterator to the proposal to remove
@param[in] type Type of removal (LFG_UPDATETYPE_PROPOSAL_FAILED, LFG_UPDATETYPE_PROPOSAL_DECLINED)
*/
void LFGMgr::RemoveProposal(LfgProposalContainer::iterator itProposal, LfgUpdateType type)
{
LfgProposal& proposal = itProposal->second;
proposal.state = LFG_PROPOSAL_FAILED;
TC_LOG_DEBUG("lfg.proposal.remove", "Proposal {}, state FAILED, UpdateType {}", itProposal->first, type);
// Mark all people that didn't answered as no accept
if (type == LFG_UPDATETYPE_PROPOSAL_FAILED)
for (LfgProposalPlayerContainer::iterator it = proposal.players.begin(); it != proposal.players.end(); ++it)
if (it->second.accept == LFG_ANSWER_PENDING)
it->second.accept = LFG_ANSWER_DENY;
// Mark players/groups to be removed
GuidSet toRemove;
for (LfgProposalPlayerContainer::iterator it = proposal.players.begin(); it != proposal.players.end(); ++it)
{
if (it->second.accept == LFG_ANSWER_AGREE)
continue;
ObjectGuid guid = it->second.group ? it->second.group : it->first;
// Player didn't accept or still pending when no secs left
if (it->second.accept == LFG_ANSWER_DENY || type == LFG_UPDATETYPE_PROPOSAL_FAILED)
{
it->second.accept = LFG_ANSWER_DENY;
toRemove.insert(guid);
}
}
// Notify players
for (LfgProposalPlayerContainer::const_iterator it = proposal.players.begin(); it != proposal.players.end(); ++it)
{
ObjectGuid guid = it->first;
ObjectGuid gguid = it->second.group ? it->second.group : guid;
SendLfgUpdateProposal(guid, proposal);
if (toRemove.find(gguid) != toRemove.end()) // Didn't accept or in same group that someone that didn't accept
{
LfgUpdateData updateData;
if (it->second.accept == LFG_ANSWER_DENY)
{
updateData.updateType = type;
TC_LOG_DEBUG("lfg.proposal.remove", "{} didn't accept. Removing from queue and compatible cache", guid.ToString());
}
else
{
updateData.updateType = LFG_UPDATETYPE_REMOVED_FROM_QUEUE;
TC_LOG_DEBUG("lfg.proposal.remove", "{} in same group that someone that didn't accept. Removing from queue and compatible cache", guid.ToString());
}
RestoreState(guid, "Proposal Fail (didn't accepted or in group with someone that didn't accept");
if (gguid != guid)
{
RestoreState(it->second.group, "Proposal Fail (someone in group didn't accepted)");
SendLfgUpdateParty(guid, updateData);
}
else
SendLfgUpdatePlayer(guid, updateData);
}
else
{
TC_LOG_DEBUG("lfg.proposal.remove", "Readding {} to queue.", guid.ToString());
SetState(guid, LFG_STATE_QUEUED);
if (gguid != guid)
{
SetState(gguid, LFG_STATE_QUEUED);
SendLfgUpdateParty(guid, LfgUpdateData(LFG_UPDATETYPE_ADDED_TO_QUEUE, GetSelectedDungeons(guid), GetComment(guid)));
}
else
SendLfgUpdatePlayer(guid, LfgUpdateData(LFG_UPDATETYPE_ADDED_TO_QUEUE, GetSelectedDungeons(guid), GetComment(guid)));
}
}
LFGQueue& queue = GetQueue(proposal.players.begin()->first);
// Remove players/groups from queue
for (GuidSet::const_iterator it = toRemove.begin(); it != toRemove.end(); ++it)
{
ObjectGuid guid = *it;
queue.RemoveFromQueue(guid);
proposal.queues.remove(guid);
}
// Readd to queue
for (GuidList::const_iterator it = proposal.queues.begin(); it != proposal.queues.end(); ++it)
{
ObjectGuid guid = *it;
queue.AddToQueue(guid, true);
}
ProposalsStore.erase(itProposal);
}
/**
Initialize a boot kick vote
@param[in] gguid Group the vote kicks belongs to
@param[in] kicker Kicker guid
@param[in] victim Victim guid
@param[in] reason Kick reason
*/
void LFGMgr::InitBoot(ObjectGuid gguid, ObjectGuid kicker, ObjectGuid victim, std::string const& reason)
{
SetVoteKick(gguid, true);
LfgPlayerBoot& boot = BootsStore[gguid];
boot.inProgress = true;
boot.cancelTime = GameTime::GetGameTime() + LFG_TIME_BOOT;
boot.reason = reason;
boot.victim = victim;
GuidSet const& players = GetPlayers(gguid);
// Set votes
for (GuidSet::const_iterator itr = players.begin(); itr != players.end(); ++itr)
{
ObjectGuid guid = (*itr);
boot.votes[guid] = LFG_ANSWER_PENDING;
}
boot.votes[victim] = LFG_ANSWER_DENY; // Victim auto vote NO
boot.votes[kicker] = LFG_ANSWER_AGREE; // Kicker auto vote YES
// Notify players
for (GuidSet::const_iterator it = players.begin(); it != players.end(); ++it)
SendLfgBootProposalUpdate(*it, boot);
}
/**
Update Boot info with player answer
@param[in] guid Player who has answered
@param[in] player answer
*/
void LFGMgr::UpdateBoot(ObjectGuid guid, bool accept)
{
ObjectGuid gguid = GetGroup(guid);
if (!gguid)
return;
LfgPlayerBootContainer::iterator itBoot = BootsStore.find(gguid);
if (itBoot == BootsStore.end())
return;
LfgPlayerBoot& boot = itBoot->second;
if (boot.votes[guid] != LFG_ANSWER_PENDING) // Cheat check: Player can't vote twice
return;
boot.votes[guid] = LfgAnswer(accept);
uint8 agreeNum = 0;
uint8 denyNum = 0;
for (LfgAnswerContainer::const_iterator itVotes = boot.votes.begin(); itVotes != boot.votes.end(); ++itVotes)
{
switch (itVotes->second)
{
case LFG_ANSWER_PENDING:
break;
case LFG_ANSWER_AGREE:
++agreeNum;
break;
case LFG_ANSWER_DENY:
++denyNum;
break;
}
}
// if we don't have enough votes (agree or deny) do nothing
if (agreeNum < LFG_GROUP_KICK_VOTES_NEEDED && (boot.votes.size() - denyNum) >= LFG_GROUP_KICK_VOTES_NEEDED)
return;
// Send update info to all players
boot.inProgress = false;
for (LfgAnswerContainer::const_iterator itVotes = boot.votes.begin(); itVotes != boot.votes.end(); ++itVotes)
{
ObjectGuid pguid = itVotes->first;
if (pguid != boot.victim)
SendLfgBootProposalUpdate(pguid, boot);
}
SetVoteKick(gguid, false);
if (agreeNum == LFG_GROUP_KICK_VOTES_NEEDED) // Vote passed - Kick player
{
if (Group* group = sGroupMgr->GetGroupByGUID(gguid.GetCounter()))
Player::RemoveFromGroup(group, boot.victim, GROUP_REMOVEMETHOD_KICK_LFG);
DecreaseKicksLeft(gguid);
}
BootsStore.erase(itBoot);
}
/**
Teleports the player in or out the dungeon
@param[in] player Player to teleport
@param[in] out Teleport out (true) or in (false)
@param[in] fromOpcode Function called from opcode handlers? (Default false)
*/
void LFGMgr::TeleportPlayer(Player* player, bool out, bool fromOpcode /*= false*/)
{
LFGDungeonData const* dungeon = nullptr;
Group* group = player->GetGroup();
if (group && group->isLFGGroup())
dungeon = GetLFGDungeon(GetDungeon(group->GetGUID()));
if (!dungeon)
{
TC_LOG_DEBUG("lfg.teleport", "Player {} not in group/lfggroup or dungeon not found!",
player->GetName());
player->GetSession()->SendLfgTeleportError(uint8(LFG_TELEPORTERROR_INVALID_LOCATION));
return;
}
if (out)
{
TC_LOG_DEBUG("lfg.teleport", "Player {} is being teleported out. Current Map {} - Expected Map {}",
player->GetName(), player->GetMapId(), uint32(dungeon->map));
if (player->GetMapId() == uint32(dungeon->map))
player->TeleportToBGEntryPoint();
return;
}
LfgTeleportError error = LFG_TELEPORTERROR_OK;
if (!player->IsAlive())
error = LFG_TELEPORTERROR_PLAYER_DEAD;
else if (player->IsFalling() || player->HasUnitState(UNIT_STATE_JUMPING))
error = LFG_TELEPORTERROR_FALLING;
else if (player->IsMirrorTimerActive(FATIGUE_TIMER))
error = LFG_TELEPORTERROR_FATIGUE;
else if (player->GetVehicle())
error = LFG_TELEPORTERROR_IN_VEHICLE;
else if (player->GetCharmedGUID())
error = LFG_TELEPORTERROR_CHARMING;
else if (player->HasAura(9454)) // check Freeze debuff
error = LFG_TELEPORTERROR_INVALID_LOCATION;
else if (player->GetMapId() != uint32(dungeon->map)) // Do not teleport players in dungeon to the entrance
{
uint32 mapid = dungeon->map;
float x = dungeon->x;
float y = dungeon->y;
float z = dungeon->z;
float orientation = dungeon->o;
if (!fromOpcode)
{
// Select a player inside to be teleported to
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
{
Player* plrg = itr->GetSource();
if (plrg && plrg != player && plrg->GetMapId() == uint32(dungeon->map))
{
mapid = plrg->GetMapId();
x = plrg->GetPositionX();
y = plrg->GetPositionY();
z = plrg->GetPositionZ();
orientation = plrg->GetOrientation();
break;
}
}
}
if (!player->GetMap()->IsDungeon())
player->SetBattlegroundEntryPoint();
player->FinishTaxiFlight();
if (!player->TeleportTo(mapid, x, y, z, orientation))
error = LFG_TELEPORTERROR_INVALID_LOCATION;
}
else
error = LFG_TELEPORTERROR_INVALID_LOCATION;
if (error != LFG_TELEPORTERROR_OK)
player->GetSession()->SendLfgTeleportError(uint8(error));
TC_LOG_DEBUG("lfg.teleport", "Player {} is being teleported in to map {} "
"(x: {}, y: {}, z: {}) Result: {}", player->GetName(), dungeon->map,
dungeon->x, dungeon->y, dungeon->z, error);
}
/**
Finish a dungeon and give reward, if any.
@param[in] guid Group guid
@param[in] dungeonId Dungeonid
*/
void LFGMgr::FinishDungeon(ObjectGuid gguid, const uint32 dungeonId, Map const* currMap)
{
uint32 gDungeonId = GetDungeon(gguid);
if (gDungeonId != dungeonId)
{
TC_LOG_DEBUG("lfg.dungeon.finish", "Group {} finished dungeon {} but queued for {}", gguid.ToString(), dungeonId, gDungeonId);
return;
}
if (GetState(gguid) == LFG_STATE_FINISHED_DUNGEON) // Shouldn't happen. Do not reward multiple times
{
TC_LOG_DEBUG("lfg.dungeon.finish", "Group: {} already rewarded", gguid.ToString());
return;
}
SetState(gguid, LFG_STATE_FINISHED_DUNGEON);
GuidSet const& players = GetPlayers(gguid);
for (GuidSet::const_iterator it = players.begin(); it != players.end(); ++it)
{
ObjectGuid guid = (*it);
if (GetState(guid) == LFG_STATE_FINISHED_DUNGEON)
{
TC_LOG_DEBUG("lfg.dungeon.finish", "Group: {}, Player: {} already rewarded", gguid.ToString(), guid.ToString());
continue;
}
uint32 rDungeonId = 0;
LfgDungeonSet const& dungeons = GetSelectedDungeons(guid);
if (!dungeons.empty())
rDungeonId = (*dungeons.begin());
SetState(guid, LFG_STATE_FINISHED_DUNGEON);
// Give rewards only if its a random dungeon
LFGDungeonData const* dungeon = GetLFGDungeon(rDungeonId);
if (!dungeon || (dungeon->type != LFG_TYPE_RANDOM && !dungeon->seasonal))
{
TC_LOG_DEBUG("lfg.dungeon.finish", "Group: {}, Player: {} dungeon {} is not random or seasonal", gguid.ToString(), guid.ToString(), rDungeonId);
continue;
}
Player* player = ObjectAccessor::FindPlayer(guid);
if (!player)
{
TC_LOG_DEBUG("lfg.dungeon.finish", "Group: {}, Player: {} not found in world", gguid.ToString(), guid.ToString());
continue;
}
if (player->FindMap() != currMap)
{
TC_LOG_DEBUG("lfg.dungeon.finish", "Group: {}, Player: {} is in a different map", gguid.ToString(), guid.ToString());
continue;
}
player->RemoveAurasDueToSpell(LFG_SPELL_DUNGEON_COOLDOWN);
LFGDungeonData const* dungeonDone = GetLFGDungeon(dungeonId);
uint32 mapId = dungeonDone ? uint32(dungeonDone->map) : 0;
if (player->GetMapId() != mapId)
{
TC_LOG_DEBUG("lfg.dungeon.finish", "Group: {}, Player: {} is in map {} and should be in {} to get reward", gguid.ToString(), guid.ToString(), player->GetMapId(), mapId);
continue;
}
// Update achievements
if (dungeon->difficulty == DUNGEON_DIFFICULTY_HEROIC)
{
uint8 lfdRandomPlayers = 0;
if (uint8 numParty = PlayersStore[guid].GetNumberOfPartyMembersAtJoin())
lfdRandomPlayers = 5 - numParty;
else
lfdRandomPlayers = 4;
player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_USE_LFD_TO_GROUP_WITH_PLAYERS, lfdRandomPlayers);
}
LfgReward const* reward = GetRandomDungeonReward(rDungeonId, player->GetLevel());
if (!reward)
continue;
bool done = false;
Quest const* quest = sObjectMgr->GetQuestTemplate(reward->firstQuest);
if (!quest)
continue;
// if we can take the quest, means that we haven't done this kind of "run", IE: First Heroic Random of Day.
if (player->CanRewardQuest(quest, false))
player->RewardQuest(quest, 0, nullptr, false);
else
{
done = true;
quest = sObjectMgr->GetQuestTemplate(reward->otherQuest);
if (!quest)
continue;
// we give reward without informing client (retail does this)
player->RewardQuest(quest, 0, nullptr, false);
}
// Give rewards
TC_LOG_DEBUG("lfg.dungeon.finish", "Group: {}, Player: {} done dungeon {}, {} previously done.", gguid.ToString(), guid.ToString(), GetDungeon(gguid), done ? " " : " not");
LfgPlayerRewardData data = LfgPlayerRewardData(dungeon->Entry(), GetDungeon(gguid, false), done, quest);
player->GetSession()->SendLfgPlayerReward(data);
}
}
// --------------------------------------------------------------------------//
// Auxiliar Functions
// --------------------------------------------------------------------------//
/**
Get the dungeon list that can be done given a random dungeon entry.
@param[in] randomdungeon Random dungeon id (if value = 0 will return all dungeons)
@returns Set of dungeons that can be done.
*/
LfgDungeonSet const& LFGMgr::GetDungeonsByRandom(uint32 randomdungeon)
{
LFGDungeonData const* dungeon = GetLFGDungeon(randomdungeon);
uint32 group = dungeon ? dungeon->group : 0;
return CachedDungeonMapStore[group];
}
/**
Get the reward of a given random dungeon at a certain level
@param[in] dungeon dungeon id
@param[in] level Player level
@returns Reward
*/
LfgReward const* LFGMgr::GetRandomDungeonReward(uint32 dungeon, uint8 level)
{
LfgReward const* rew = nullptr;
LfgRewardContainerBounds bounds = RewardMapStore.equal_range(dungeon & 0x00FFFFFF);
for (LfgRewardContainer::const_iterator itr = bounds.first; itr != bounds.second; ++itr)
{
rew = itr->second;
// ordered properly at loading
if (itr->second->maxLevel >= level)
break;
}
return rew;
}
/**
Given a Dungeon id returns the dungeon Type
@param[in] dungeon dungeon id
@returns Dungeon type
*/
LfgType LFGMgr::GetDungeonType(uint32 dungeonId)
{
LFGDungeonData const* dungeon = GetLFGDungeon(dungeonId);
if (!dungeon)
return LFG_TYPE_NONE;
return LfgType(dungeon->type);
}
LfgState LFGMgr::GetState(ObjectGuid guid)
{
LfgState state;
if (guid.IsGroup())
{
state = GroupsStore[guid].GetState();
TC_LOG_TRACE("lfg.data.group.state.get", "Group: {}, State: {}", guid.ToString(), GetStateString(state));
}
else
{
state = PlayersStore[guid].GetState();
TC_LOG_TRACE("lfg.data.player.state.get", "Player: {}, State: {}", guid.ToString(), GetStateString(state));
}
return state;
}
LfgState LFGMgr::GetOldState(ObjectGuid guid)
{
LfgState state;
if (guid.IsGroup())
{
state = GroupsStore[guid].GetOldState();
TC_LOG_TRACE("lfg.data.group.oldstate.get", "Group: {}, Old state: {}", guid.ToString(), state);
}
else
{
state = PlayersStore[guid].GetOldState();
TC_LOG_TRACE("lfg.data.player.oldstate.get", "Player: {}, Old state: {}", guid.ToString(), state);
}
return state;
}
bool LFGMgr::IsVoteKickActive(ObjectGuid gguid)
{
ASSERT(gguid.IsGroup());
bool active = GroupsStore[gguid].IsVoteKickActive();
TC_LOG_TRACE("lfg.data.group.votekick.get", "Group: {}, Active: {}", gguid.ToString(), active);
return active;
}
uint32 LFGMgr::GetDungeon(ObjectGuid guid, bool asId /*= true */)
{
uint32 dungeon = GroupsStore[guid].GetDungeon(asId);
TC_LOG_TRACE("lfg.data.group.dungeon.get", "Group: {}, asId: {}, Dungeon: {}", guid.ToString(), asId, dungeon);
return dungeon;
}
uint32 LFGMgr::GetDungeonMapId(ObjectGuid guid)
{
uint32 dungeonId = GroupsStore[guid].GetDungeon(true);
uint32 mapId = 0;
if (dungeonId)
if (LFGDungeonData const* dungeon = GetLFGDungeon(dungeonId))
mapId = dungeon->map;
TC_LOG_TRACE("lfg.data.group.dungeon.map", "Group: {}, MapId: {} (DungeonId: {})", guid.ToString(), mapId, dungeonId);
return mapId;
}
uint8 LFGMgr::GetRoles(ObjectGuid guid)
{
uint8 roles = PlayersStore[guid].GetRoles();
TC_LOG_TRACE("lfg.data.player.role.get", "Player: {}, Role: {}", guid.ToString(), roles);
return roles;
}
const std::string& LFGMgr::GetComment(ObjectGuid guid)
{
TC_LOG_TRACE("lfg.data.player.comment.get", "Player: {}, Comment: {}", guid.ToString(), PlayersStore[guid].GetComment());
return PlayersStore[guid].GetComment();
}
LfgDungeonSet const& LFGMgr::GetSelectedDungeons(ObjectGuid guid)
{
TC_LOG_TRACE("lfg.data.player.dungeons.selected.get", "Player: {}, Selected Dungeons: {}", guid.ToString(), ConcatenateDungeons(PlayersStore[guid].GetSelectedDungeons()));
return PlayersStore[guid].GetSelectedDungeons();
}
LfgLockMap const LFGMgr::GetLockedDungeons(ObjectGuid guid)
{
TC_LOG_TRACE("lfg.data.player.dungeons.locked.get", "Player: {}, LockedDungeons.", guid.ToString());
LfgLockMap lock;
Player* player = ObjectAccessor::FindConnectedPlayer(guid);
if (!player)
{
TC_LOG_WARN("lfg.data.player.dungeons.locked.get", "Player: {} not ingame while retrieving his LockedDungeons.", guid.ToString());
return lock;
}
uint8 level = player->GetLevel();
uint8 expansion = player->GetSession()->Expansion();
LfgDungeonSet const& dungeons = GetDungeonsByRandom(0);
bool denyJoin = !player->GetSession()->HasPermission(rbac::RBAC_PERM_JOIN_DUNGEON_FINDER);
for (LfgDungeonSet::const_iterator it = dungeons.begin(); it != dungeons.end(); ++it)
{
LFGDungeonData const* dungeon = GetLFGDungeon(*it);
if (!dungeon) // should never happen - We provide a list from sLFGDungeonStore
continue;
uint32 lockStatus = 0;
if (denyJoin)
lockStatus = LFG_LOCKSTATUS_RAID_LOCKED;
else if (dungeon->expansion > expansion)
lockStatus = LFG_LOCKSTATUS_INSUFFICIENT_EXPANSION;
else if (DisableMgr::IsDisabledFor(DISABLE_TYPE_MAP, dungeon->map, player))
lockStatus = LFG_LOCKSTATUS_RAID_LOCKED;
else if (DisableMgr::IsDisabledFor(DISABLE_TYPE_LFG_MAP, dungeon->map, player))
lockStatus = LFG_LOCKSTATUS_RAID_LOCKED;
else if (dungeon->difficulty > DUNGEON_DIFFICULTY_NORMAL && player->GetBoundInstance(dungeon->map, Difficulty(dungeon->difficulty)))
lockStatus = LFG_LOCKSTATUS_RAID_LOCKED;
else if (dungeon->minlevel[expansion] > level)
lockStatus = LFG_LOCKSTATUS_TOO_LOW_LEVEL;
else if (dungeon->maxlevel[expansion] < level)
lockStatus = LFG_LOCKSTATUS_TOO_HIGH_LEVEL;
else if (dungeon->seasonal && !IsSeasonActive(dungeon->id))
lockStatus = LFG_LOCKSTATUS_NOT_IN_SEASON;
else if (AccessRequirement const* ar = sObjectMgr->GetAccessRequirement(dungeon->map, Difficulty(dungeon->difficulty)))
{
if (ar->item_level && player->GetAverageItemLevel() < ar->item_level)
lockStatus = LFG_LOCKSTATUS_TOO_LOW_GEAR_SCORE;
else if (ar->achievement && !player->HasAchieved(ar->achievement))
lockStatus = LFG_LOCKSTATUS_MISSING_ACHIEVEMENT;
else if (player->GetTeam() == ALLIANCE && ar->quest_A && !player->GetQuestRewardStatus(ar->quest_A))
lockStatus = LFG_LOCKSTATUS_QUEST_NOT_COMPLETED;
else if (player->GetTeam() == HORDE && ar->quest_H && !player->GetQuestRewardStatus(ar->quest_H))
lockStatus = LFG_LOCKSTATUS_QUEST_NOT_COMPLETED;
else
if (ar->item)
{
if (!player->HasItemCount(ar->item) && (!ar->item2 || !player->HasItemCount(ar->item2)))
lockStatus = LFG_LOCKSTATUS_MISSING_ITEM;
}
else if (ar->item2 && !player->HasItemCount(ar->item2))
lockStatus = LFG_LOCKSTATUS_MISSING_ITEM;
}
/* @todo VoA closed if WG is not under team control (LFG_LOCKSTATUS_RAID_LOCKED)
lockData = LFG_LOCKSTATUS_TOO_HIGH_GEAR_SCORE;
lockData = LFG_LOCKSTATUS_ATTUNEMENT_TOO_LOW_LEVEL;
lockData = LFG_LOCKSTATUS_ATTUNEMENT_TOO_HIGH_LEVEL;
*/
if (lockStatus)
lock[dungeon->Entry()] = lockStatus;
}
return lock;
}
uint8 LFGMgr::GetKicksLeft(ObjectGuid guid)
{
uint8 kicks = GroupsStore[guid].GetKicksLeft();
TC_LOG_TRACE("lfg.data.group.kickleft.get", "Group: {}, Kicks left: {}", guid.ToString(), kicks);
return kicks;
}
void LFGMgr::RestoreState(ObjectGuid guid, char const* debugMsg)
{
if (guid.IsGroup())
{
LfgGroupData& data = GroupsStore[guid];
TC_LOG_TRACE("lfg.data.group.state.restore", "Group: {} ({}), State: {}, Old state: {}",
guid.ToString(), debugMsg, GetStateString(data.GetState()),
GetStateString(data.GetOldState()));
data.RestoreState();
}
else
{
LfgPlayerData& data = PlayersStore[guid];
TC_LOG_TRACE("lfg.data.player.state.restore", "Player: {} ({}), State: {}, Old state: {}",
guid.ToString(), debugMsg, GetStateString(data.GetState()),
GetStateString(data.GetOldState()));
data.RestoreState();
}
}
void LFGMgr::SetState(ObjectGuid guid, LfgState state)
{
if (guid.IsGroup())
{
LfgGroupData& data = GroupsStore[guid];
TC_LOG_TRACE("lfg.data.group.state.set", "Group: {}, New state: {}, Previous: {}, Old state: {}",
guid.ToString(), GetStateString(state), GetStateString(data.GetState()),
GetStateString(data.GetOldState()));
data.SetState(state);
}
else
{
LfgPlayerData& data = PlayersStore[guid];
TC_LOG_TRACE("lfg.data.player.state.set", "Player: {}, New state: {}, Previous: {}, OldState: {}",
guid.ToString(), GetStateString(state), GetStateString(data.GetState()),
GetStateString(data.GetOldState()));
data.SetState(state);
}
}
void LFGMgr::SetVoteKick(ObjectGuid gguid, bool active)
{
ASSERT(gguid.IsGroup());
LfgGroupData& data = GroupsStore[gguid];
TC_LOG_TRACE("lfg.data.group.votekick.set", "Group: {}, New state: {}, Previous: {}",
gguid.ToString(), active, data.IsVoteKickActive());
data.SetVoteKick(active);
}
void LFGMgr::SetDungeon(ObjectGuid guid, uint32 dungeon)
{
TC_LOG_TRACE("lfg.data.group.dungeon.set", "Group: {}, Dungeon: {}", guid.ToString(), dungeon);
GroupsStore[guid].SetDungeon(dungeon);
}
void LFGMgr::SetRoles(ObjectGuid guid, uint8 roles)
{
TC_LOG_TRACE("lfg.data.player.role.set", "Player: {}, Roles: {}", guid.ToString(), roles);
PlayersStore[guid].SetRoles(roles);
}
void LFGMgr::SetComment(ObjectGuid guid, std::string const& comment)
{
TC_LOG_TRACE("lfg.data.player.comment.set", "Player: {}, Comment: {}", guid.ToString(), comment);
PlayersStore[guid].SetComment(comment);
}
void LFGMgr::SetSelectedDungeons(ObjectGuid guid, LfgDungeonSet const& dungeons)
{
TC_LOG_TRACE("lfg.data.player.dungeon.selected.set", "Player: {}, Dungeons: {}", guid.ToString(), ConcatenateDungeons(dungeons));
PlayersStore[guid].SetSelectedDungeons(dungeons);
}
void LFGMgr::DecreaseKicksLeft(ObjectGuid guid)
{
GroupsStore[guid].DecreaseKicksLeft();
TC_LOG_TRACE("lfg.data.group.kicksleft.decrease", "Group: {}, Kicks: {}", guid.ToString(), GroupsStore[guid].GetKicksLeft());
}
void LFGMgr::RemovePlayerData(ObjectGuid guid)
{
TC_LOG_TRACE("lfg.data.player.remove", "Player: {}", guid.ToString());
LfgPlayerDataContainer::iterator it = PlayersStore.find(guid);
if (it != PlayersStore.end())
PlayersStore.erase(it);
}
void LFGMgr::RemoveGroupData(ObjectGuid guid)
{
TC_LOG_TRACE("lfg.data.group.remove", "Group: {}", guid.ToString());
LfgGroupDataContainer::iterator it = GroupsStore.find(guid);
if (it == GroupsStore.end())
return;
LfgState state = GetState(guid);
// If group is being formed after proposal success do nothing more
GuidSet const& players = it->second.GetPlayers();
for (ObjectGuid playerGUID : players)
{
SetGroup(playerGUID, ObjectGuid::Empty);
if (state != LFG_STATE_PROPOSAL)
{
SetState(playerGUID, LFG_STATE_NONE);
SendLfgUpdateParty(playerGUID, LfgUpdateData(LFG_UPDATETYPE_REMOVED_FROM_QUEUE));
}
}
GroupsStore.erase(it);
}
uint8 LFGMgr::GetTeam(ObjectGuid guid)
{
uint8 team = PlayersStore[guid].GetTeam();
TC_LOG_TRACE("lfg.data.player.team.get", "Player: {}, Team: {}", guid.ToString(), team);
return team;
}
uint8 LFGMgr::FilterClassRoles(Player* player, uint8 roles)
{
roles &= PLAYER_ROLE_ANY;
if (!(LfgRoleClasses::TANK & player->GetClassMask()))
roles &= ~PLAYER_ROLE_TANK;
if (!(LfgRoleClasses::HEALER & player->GetClassMask()))
roles &= ~PLAYER_ROLE_HEALER;
return roles;
}
uint8 LFGMgr::RemovePlayerFromGroup(ObjectGuid gguid, ObjectGuid guid)
{
return GroupsStore[gguid].RemovePlayer(guid);
}
void LFGMgr::AddPlayerToGroup(ObjectGuid gguid, ObjectGuid guid)
{
GroupsStore[gguid].AddPlayer(guid);
}
void LFGMgr::SetLeader(ObjectGuid gguid, ObjectGuid leader)
{
GroupsStore[gguid].SetLeader(leader);
}
void LFGMgr::SetTeam(ObjectGuid guid, uint8 team)
{
if (sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_GROUP))
team = 0;
PlayersStore[guid].SetTeam(team);
}
ObjectGuid LFGMgr::GetGroup(ObjectGuid guid)
{
return PlayersStore[guid].GetGroup();
}
void LFGMgr::SetGroup(ObjectGuid guid, ObjectGuid group)
{
PlayersStore[guid].SetGroup(group);
}
GuidSet const& LFGMgr::GetPlayers(ObjectGuid guid)
{
return GroupsStore[guid].GetPlayers();
}
uint8 LFGMgr::GetPlayerCount(ObjectGuid guid)
{
return GroupsStore[guid].GetPlayerCount();
}
ObjectGuid LFGMgr::GetLeader(ObjectGuid guid)
{
return GroupsStore[guid].GetLeader();
}
bool LFGMgr::HasIgnore(ObjectGuid guid1, ObjectGuid guid2)
{
Player* plr1 = ObjectAccessor::FindConnectedPlayer(guid1);
Player* plr2 = ObjectAccessor::FindConnectedPlayer(guid2);
return plr1 && plr2 && (plr1->GetSocial()->HasIgnore(guid2) || plr2->GetSocial()->HasIgnore(guid1));
}
void LFGMgr::SendLfgRoleChosen(ObjectGuid guid, ObjectGuid pguid, uint8 roles)
{
if (Player* player = ObjectAccessor::FindConnectedPlayer(guid))
player->GetSession()->SendLfgRoleChosen(pguid, roles);
}
void LFGMgr::SendLfgRoleCheckUpdate(ObjectGuid guid, LfgRoleCheck const& roleCheck)
{
if (Player* player = ObjectAccessor::FindConnectedPlayer(guid))
player->GetSession()->SendLfgRoleCheckUpdate(roleCheck);
}
void LFGMgr::SendLfgUpdatePlayer(ObjectGuid guid, LfgUpdateData const& data)
{
if (Player* player = ObjectAccessor::FindConnectedPlayer(guid))
player->GetSession()->SendLfgUpdatePlayer(data);
}
void LFGMgr::SendLfgUpdateParty(ObjectGuid guid, LfgUpdateData const& data)
{
if (Player* player = ObjectAccessor::FindConnectedPlayer(guid))
player->GetSession()->SendLfgUpdateParty(data);
}
void LFGMgr::SendLfgJoinResult(ObjectGuid guid, LfgJoinResultData const& data)
{
if (Player* player = ObjectAccessor::FindConnectedPlayer(guid))
player->GetSession()->SendLfgJoinResult(data);
}
void LFGMgr::SendLfgBootProposalUpdate(ObjectGuid guid, LfgPlayerBoot const& boot)
{
if (Player* player = ObjectAccessor::FindConnectedPlayer(guid))
player->GetSession()->SendLfgBootProposalUpdate(boot);
}
void LFGMgr::SendLfgUpdateProposal(ObjectGuid guid, LfgProposal const& proposal)
{
if (Player* player = ObjectAccessor::FindConnectedPlayer(guid))
player->GetSession()->SendLfgUpdateProposal(proposal);
}
void LFGMgr::SendLfgQueueStatus(ObjectGuid guid, LfgQueueStatusData const& data)
{
if (Player* player = ObjectAccessor::FindConnectedPlayer(guid))
player->GetSession()->SendLfgQueueStatus(data);
}
bool LFGMgr::IsLfgGroup(ObjectGuid guid)
{
return guid && guid.IsGroup() && GroupsStore[guid].IsLfgGroup();
}
uint8 LFGMgr::GetQueueId(ObjectGuid guid)
{
if (guid.IsGroup())
{
GuidSet const& players = GetPlayers(guid);
ObjectGuid pguid = players.empty() ? ObjectGuid::Empty : (*players.begin());
if (!pguid.IsEmpty())
return GetTeam(pguid);
}
return GetTeam(guid);
}
LFGQueue& LFGMgr::GetQueue(ObjectGuid guid)
{
uint8 queueId = GetQueueId(guid);
return QueuesStore[queueId];
}
bool LFGMgr::AllQueued(GuidList const& check)
{
if (check.empty())
return false;
for (GuidList::const_iterator it = check.begin(); it != check.end(); ++it)
{
LfgState state = GetState(*it);
if (state != LFG_STATE_QUEUED)
{
if (state != LFG_STATE_PROPOSAL)
TC_LOG_DEBUG("lfg.allqueued", "Unexpected state found while trying to form new group. Guid: {}, State: {}", (*it).ToString(), GetStateString(state));
return false;
}
}
return true;
}
time_t LFGMgr::GetQueueJoinTime(ObjectGuid guid)
{
uint8 queueId = GetQueueId(guid);
LfgQueueContainer::const_iterator itr = QueuesStore.find(queueId);
if (itr != QueuesStore.end())
return itr->second.GetJoinTime(guid);
return 0;
}
// Only for debugging purposes
void LFGMgr::Clean()
{
QueuesStore.clear();
}
bool LFGMgr::isOptionEnabled(uint32 option)
{
return (m_options & option) != 0;
}
uint32 LFGMgr::GetOptions()
{
return m_options;
}
void LFGMgr::SetOptions(uint32 options)
{
m_options = options;
}
LfgUpdateData LFGMgr::GetLfgStatus(ObjectGuid guid)
{
LfgPlayerData& playerData = PlayersStore[guid];
return LfgUpdateData(LFG_UPDATETYPE_UPDATE_STATUS, playerData.GetState(), playerData.GetSelectedDungeons());
}
bool LFGMgr::IsSeasonActive(uint32 dungeonId)
{
switch (dungeonId)
{
case 285: // The Headless Horseman
return IsHolidayActive(HOLIDAY_HALLOWS_END);
case 286: // The Frost Lord Ahune
return IsHolidayActive(HOLIDAY_FIRE_FESTIVAL);
case 287: // Coren Direbrew
return IsHolidayActive(HOLIDAY_BREWFEST);
case 288: // The Crown Chemical Co.
return IsHolidayActive(HOLIDAY_LOVE_IS_IN_THE_AIR);
}
return false;
}
std::string LFGMgr::DumpQueueInfo(bool full)
{
uint32 size = uint32(QueuesStore.size());
std::ostringstream o;
o << "Number of Queues: " << size << "\n";
for (LfgQueueContainer::const_iterator itr = QueuesStore.begin(); itr != QueuesStore.end(); ++itr)
{
std::string const& queued = itr->second.DumpQueueInfo();
std::string const& compatibles = itr->second.DumpCompatibleInfo(full);
o << queued << compatibles;
}
return o.str();
}
void LFGMgr::SetupGroupMember(ObjectGuid guid, ObjectGuid gguid)
{
LfgDungeonSet dungeons;
dungeons.insert(GetDungeon(gguid));
SetSelectedDungeons(guid, dungeons);
SetState(guid, GetState(gguid));
SetGroup(guid, gguid);
AddPlayerToGroup(gguid, guid);
}
bool LFGMgr::selectedRandomLfgDungeon(ObjectGuid guid)
{
if (GetState(guid) != LFG_STATE_NONE)
{
LfgDungeonSet const& dungeons = GetSelectedDungeons(guid);
if (!dungeons.empty())
{
LFGDungeonData const* dungeon = GetLFGDungeon(*dungeons.begin());
if (dungeon && (dungeon->type == LFG_TYPE_RANDOM || dungeon->seasonal))
return true;
}
}
return false;
}
bool LFGMgr::inLfgDungeonMap(ObjectGuid guid, uint32 map, Difficulty difficulty)
{
if (!guid.IsGroup())
guid = GetGroup(guid);
if (uint32 dungeonId = GetDungeon(guid, true))
if (LFGDungeonData const* dungeon = GetLFGDungeon(dungeonId))
if (uint32(dungeon->map) == map && dungeon->difficulty == difficulty)
return true;
return false;
}
uint32 LFGMgr::GetLFGDungeonEntry(uint32 id)
{
if (id)
if (LFGDungeonData const* dungeon = GetLFGDungeon(id))
return dungeon->Entry();
return 0;
}
LfgDungeonSet LFGMgr::GetRandomAndSeasonalDungeons(uint8 level, uint8 expansion)
{
LfgDungeonSet randomDungeons;
for (lfg::LFGDungeonContainer::const_iterator itr = LfgDungeonStore.begin(); itr != LfgDungeonStore.end(); ++itr)
{
lfg::LFGDungeonData const& dungeon = itr->second;
if ((dungeon.type == lfg::LFG_TYPE_RANDOM || (dungeon.seasonal && sLFGMgr->IsSeasonActive(dungeon.id)))
&& dungeon.expansion <= expansion && dungeon.minlevel[expansion] <= level && level <= dungeon.maxlevel[expansion])
randomDungeons.insert(dungeon.Entry());
}
return randomDungeons;
}
} // namespace lfg