/* * This file is part of the AzerothCore 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 Affero General Public License as published by the * Free Software Foundation; either version 3 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 Affero 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 "BattlegroundQueue.h" #include "ArenaTeam.h" #include "ArenaTeamMgr.h" #include "BattlegroundMgr.h" #include "BattlegroundSpamProtect.h" #include "Channel.h" #include "Chat.h" #include "GameTime.h" #include "Group.h" #include "Language.h" #include "Log.h" #include "ObjectMgr.h" #include "Player.h" #include "ScriptMgr.h" #include #include "BattlegroundUtils.h" /*********************************************************/ /*** BATTLEGROUND QUEUE SYSTEM ***/ /*********************************************************/ BattlegroundQueue::BattlegroundQueue() { for (uint32 i = 0; i < PVP_TEAMS_COUNT; ++i) { for (uint32 j = 0; j < MAX_BATTLEGROUND_BRACKETS; ++j) { m_WaitTimeLastIndex[i][j] = 0; for (uint32 k = 0; k < COUNT_OF_PLAYERS_TO_AVERAGE_WAIT_TIME; ++k) m_WaitTimes[i][j][k] = 0; } } _queueAnnouncementTimer.fill(-1); _queueAnnouncementCrossfactioned = false; } BattlegroundQueue::~BattlegroundQueue() { m_events.KillAllEvents(false); m_QueuedPlayers.clear(); for (auto& m_QueuedGroup : m_QueuedGroups) { for (auto& j : m_QueuedGroup) { for (auto& itr : j) delete itr; j.clear(); } } } /*********************************************************/ /*** BATTLEGROUND QUEUE SELECTION POOLS ***/ /*********************************************************/ // selection pool initialization, used to clean up from prev selection void BattlegroundQueue::SelectionPool::Init() { SelectedGroups.clear(); PlayerCount = 0; } // returns true if we kicked more than requested bool BattlegroundQueue::SelectionPool::KickGroup(const uint32 size) { if (SelectedGroups.empty()) return false; // find last group with proper size or largest bool foundProper = false; GroupQueueInfo* groupToKick{ SelectedGroups.front() }; for (auto const& gInfo : SelectedGroups) { // if proper size - overwrite to kick last one if (std::abs(int32(gInfo->Players.size()) - (int32)size) <= 1) { groupToKick = gInfo; foundProper = true; } else if (!foundProper && gInfo->Players.size() >= groupToKick->Players.size()) groupToKick = gInfo; } // remove selected from pool auto playersCountInGroup{ groupToKick->Players.size() }; PlayerCount -= playersCountInGroup; std::erase(SelectedGroups, groupToKick); if (foundProper) return false; return playersCountInGroup > size; } // returns true if added or desired count not yet reached bool BattlegroundQueue::SelectionPool::AddGroup(GroupQueueInfo* ginfo, uint32 desiredCount) { // add if we don't exceed desiredCount if (!ginfo->IsInvitedToBGInstanceGUID && desiredCount >= PlayerCount + ginfo->Players.size()) { SelectedGroups.push_back(ginfo); PlayerCount += ginfo->Players.size(); return true; } return PlayerCount < desiredCount; } /*********************************************************/ /*** BATTLEGROUND QUEUES ***/ /*********************************************************/ // add group or player (grp == nullptr) to bg queue with the given leader and bg specifications GroupQueueInfo* BattlegroundQueue::AddGroup(Player* leader, Group* group, BattlegroundTypeId bgTypeId, PvPDifficultyEntry const* bracketEntry, uint8 arenaType, bool isRated, bool isPremade, uint32 arenaRating, uint32 matchmakerRating, uint32 arenaTeamId /*= 0*/, uint32 opponentsArenaTeamId /*= 0*/) { BattlegroundBracketId bracketId = bracketEntry->GetBracketId(); // create new ginfo auto* ginfo = new GroupQueueInfo; ginfo->BgTypeId = bgTypeId; ginfo->ArenaType = arenaType; ginfo->ArenaTeamId = arenaTeamId; ginfo->IsRated = isRated; ginfo->IsInvitedToBGInstanceGUID = 0; ginfo->JoinTime = GameTime::GetGameTimeMS().count(); ginfo->RemoveInviteTime = 0; ginfo->teamId = leader->GetTeamId(); ginfo->RealTeamID = leader->GetTeamId(true); ginfo->ArenaTeamRating = arenaRating; ginfo->ArenaMatchmakerRating = matchmakerRating; ginfo->PreviousOpponentsTeamId = opponentsArenaTeamId; ginfo->OpponentsTeamRating = 0; ginfo->OpponentsMatchmakerRating = 0; ginfo->Players.clear(); //compute index (if group is premade or joined a rated match) to queues uint32 index = 0; if (!isRated && !isPremade) index += PVP_TEAMS_COUNT; if (ginfo->teamId == TEAM_HORDE) index++; sScriptMgr->OnAddGroup(this, ginfo, index, leader, group, bgTypeId, bracketEntry, arenaType, isRated, isPremade, arenaRating, matchmakerRating, arenaTeamId, opponentsArenaTeamId); LOG_DEBUG("bg.battleground", "Adding Group to BattlegroundQueue bgTypeId: {}, bracket_id: {}, index: {}", bgTypeId, bracketId, index); // pussywizard: store indices at which GroupQueueInfo is in m_QueuedGroups ginfo->BracketId = bracketId; ginfo->GroupType = index; //add players from group to ginfo if (group) { group->DoForAllMembers([this, ginfo](Player* member) { ASSERT(m_QueuedPlayers.count(member->GetGUID()) == 0); m_QueuedPlayers[member->GetGUID()] = ginfo; ginfo->Players.emplace(member->GetGUID()); }); } else { ASSERT(m_QueuedPlayers.count(leader->GetGUID()) == 0); m_QueuedPlayers[leader->GetGUID()] = ginfo; ginfo->Players.emplace(leader->GetGUID()); } //add GroupInfo to m_QueuedGroups m_QueuedGroups[bracketId][index].push_back(ginfo); // announce world (this doesn't need mutex) SendJoinMessageArenaQueue(leader, ginfo, bracketEntry, isRated); Battleground* bg = sBattlegroundMgr->GetBattlegroundTemplate(ginfo->BgTypeId); if (!bg) return ginfo; if (!isRated && !isPremade && sWorld->getBoolConfig(CONFIG_BATTLEGROUND_QUEUE_ANNOUNCER_ENABLE)) SendMessageBGQueue(leader, bg, bracketEntry); return ginfo; } void BattlegroundQueue::PlayerInvitedToBGUpdateAverageWaitTime(GroupQueueInfo* ginfo) { uint32 timeInQueue = std::max(1, getMSTimeDiff(ginfo->JoinTime, GameTime::GetGameTimeMS().count())); // team_index: bg alliance - TEAM_ALLIANCE, bg horde - TEAM_HORDE, arena skirmish - TEAM_ALLIANCE, arena rated - TEAM_HORDE uint8 team_index; if (!ginfo->ArenaType) team_index = ginfo->teamId; else team_index = (ginfo->IsRated ? TEAM_HORDE : TEAM_ALLIANCE); // just to be sure if (team_index >= TEAM_NEUTRAL) return; // pointer to last index uint32* lastIndex = &m_WaitTimeLastIndex[team_index][ginfo->BracketId]; // set time at index to new value m_WaitTimes[team_index][ginfo->BracketId][*lastIndex] = timeInQueue; // set last index to next one (*lastIndex)++; (*lastIndex) %= COUNT_OF_PLAYERS_TO_AVERAGE_WAIT_TIME; } uint32 BattlegroundQueue::GetAverageQueueWaitTime(GroupQueueInfo* ginfo) const { // team_index: bg alliance - TEAM_ALLIANCE, bg horde - TEAM_HORDE, arena skirmish - TEAM_ALLIANCE, arena rated - TEAM_HORDE uint8 team_index; if (!ginfo->ArenaType) team_index = ginfo->teamId; else team_index = (ginfo->IsRated ? TEAM_HORDE : TEAM_ALLIANCE); // just to be sure if (team_index >= TEAM_NEUTRAL) return 0; // if there are enough values: if (m_WaitTimes[team_index][ginfo->BracketId][COUNT_OF_PLAYERS_TO_AVERAGE_WAIT_TIME - 1]) { uint32 sum = 0; for (uint32 i = 0; i < COUNT_OF_PLAYERS_TO_AVERAGE_WAIT_TIME; ++i) sum += m_WaitTimes[team_index][ginfo->BracketId][i]; return sum / COUNT_OF_PLAYERS_TO_AVERAGE_WAIT_TIME; } else return 0; } //remove player from queue and from group info, if group info is empty then remove it too void BattlegroundQueue::RemovePlayer(ObjectGuid guid, bool decreaseInvitedCount) { //remove player from map, if he's there auto const& itr = m_QueuedPlayers.find(guid); if (itr == m_QueuedPlayers.end()) { //This happens if a player logs out while in a bg because WorldSession::LogoutPlayer() notifies the bg twice std::string playerName = "Unknown"; if (Player* player = ObjectAccessor::FindPlayer(guid)) { playerName = player->GetName(); } LOG_ERROR("bg.battleground", "BattlegroundQueue: couldn't find player {} ({})", playerName, guid.ToString()); //ABORT("BattlegroundQueue: couldn't find player {} ({})", playerName, guid.ToString()); return; } GroupQueueInfo* groupInfo = itr->second; uint32 _bracketId = groupInfo->BracketId; uint32 _groupType = groupInfo->GroupType; // find iterator auto group_itr = m_QueuedGroups[_bracketId][_groupType].end(); for (auto k = m_QueuedGroups[_bracketId][_groupType].begin(); k != m_QueuedGroups[_bracketId][_groupType].end(); k++) if ((*k) == groupInfo) { group_itr = k; break; } // player can't be in queue without group, but just in case if (group_itr == m_QueuedGroups[_bracketId][_groupType].end()) { LOG_ERROR("bg.battleground", "BattlegroundQueue: ERROR Cannot find groupinfo for {}", guid.ToString()); //ABORT("BattlegroundQueue: ERROR Cannot find groupinfo for {}", guid.ToString()); return; } LOG_DEBUG("bg.battleground", "BattlegroundQueue: Removing {}, from bracket_id {}", guid.ToString(), _bracketId); // remove player from group queue info auto const& pitr = groupInfo->Players.find(guid); ASSERT(pitr != groupInfo->Players.end()); if (pitr != groupInfo->Players.end()) groupInfo->Players.erase(pitr); // if invited to bg, and should decrease invited count, then do it if (decreaseInvitedCount && groupInfo->IsInvitedToBGInstanceGUID) if (Battleground* bg = sBattlegroundMgr->GetBattleground(groupInfo->IsInvitedToBGInstanceGUID, groupInfo->BgTypeId)) bg->DecreaseInvitedCount(groupInfo->teamId); // remove player queue info m_QueuedPlayers.erase(itr); // announce to world if arena team left queue for rated match, show only once SendExitMessageArenaQueue(groupInfo); // if player leaves queue and he is invited to a rated arena match, then count it as he lost if (groupInfo->IsInvitedToBGInstanceGUID && groupInfo->IsRated && decreaseInvitedCount) { if (ArenaTeam* at = sArenaTeamMgr->GetArenaTeamById(groupInfo->ArenaTeamId)) { LOG_DEBUG("bg.battleground", "UPDATING memberLost's personal arena rating for {} by opponents rating: {}", guid.ToString(), groupInfo->OpponentsTeamRating); if (Player* player = ObjectAccessor::FindConnectedPlayer(guid)) { at->MemberLost(player, groupInfo->OpponentsMatchmakerRating); } at->SaveToDB(); } } // remove group queue info no players left if (groupInfo->Players.empty()) { m_QueuedGroups[_bracketId][_groupType].erase(group_itr); delete groupInfo; return; } // group isn't yet empty, so not deleted yet // if it's a rated arena and any member leaves when group not yet invited - everyone from group leaves too! if (groupInfo->IsRated && !groupInfo->IsInvitedToBGInstanceGUID) { if (Player* plr = ObjectAccessor::FindConnectedPlayer(*(groupInfo->Players.begin()))) { Battleground* bg = sBattlegroundMgr->GetBattlegroundTemplate(groupInfo->BgTypeId); BattlegroundQueueTypeId bgQueueTypeId = BattlegroundMgr::BGQueueTypeId(groupInfo->BgTypeId, groupInfo->ArenaType); uint32 queueSlot = plr->GetBattlegroundQueueIndex(bgQueueTypeId); plr->RemoveBattlegroundQueueId(bgQueueTypeId); WorldPacket data; sBattlegroundMgr->BuildBattlegroundStatusPacket(&data, bg, queueSlot, STATUS_NONE, 0, 0, 0, TEAM_NEUTRAL); plr->SendDirectMessage(&data); } // recursive call RemovePlayer(*groupInfo->Players.begin(), decreaseInvitedCount); } } void BattlegroundQueue::AddEvent(BasicEvent* Event, uint64 e_time) { m_events.AddEventAtOffset(Event, Milliseconds(e_time)); } bool BattlegroundQueue::IsPlayerInvitedToRatedArena(ObjectGuid pl_guid) { auto qItr = m_QueuedPlayers.find(pl_guid); return qItr != m_QueuedPlayers.end() && qItr->second->IsRated && qItr->second->IsInvitedToBGInstanceGUID; } //returns true when player pl_guid is in queue and is invited to bgInstanceGuid bool BattlegroundQueue::IsPlayerInvited(ObjectGuid pl_guid, const uint32 bgInstanceGuid, const uint32 removeTime) { auto qItr = m_QueuedPlayers.find(pl_guid); return qItr != m_QueuedPlayers.end() && qItr->second->IsInvitedToBGInstanceGUID == bgInstanceGuid && qItr->second->RemoveInviteTime == removeTime; } bool BattlegroundQueue::GetPlayerGroupInfoData(ObjectGuid guid, GroupQueueInfo* ginfo) { auto qItr = m_QueuedPlayers.find(guid); if (qItr == m_QueuedPlayers.end()) return false; *ginfo = *(qItr->second); return true; } // this function is filling pools given free slots on both sides, result is ballanced void BattlegroundQueue::FillPlayersToBG(Battleground* bg, BattlegroundBracketId bracket_id) { if (!sScriptMgr->CanFillPlayersToBG(this, bg, bracket_id)) { return; } int32 hordeFree = bg->GetFreeSlotsForTeam(TEAM_HORDE); int32 aliFree = bg->GetFreeSlotsForTeam(TEAM_ALLIANCE); uint32 aliCount = m_QueuedGroups[bracket_id][BG_QUEUE_NORMAL_ALLIANCE].size(); uint32 hordeCount = m_QueuedGroups[bracket_id][BG_QUEUE_NORMAL_HORDE].size(); // try to get even teams if (sWorld->getIntConfig(CONFIG_BATTLEGROUND_INVITATION_TYPE) == BG_QUEUE_INVITATION_TYPE_EVEN) { // check if the teams are even if (hordeFree == 1 && aliFree == 1) { // if we are here, the teams have the same amount of players // then we have to allow to join the same amount of players int32 hordeExtra = hordeCount - aliCount; int32 aliExtra = aliCount - hordeCount; hordeExtra = std::max(hordeExtra, 0); aliExtra = std::max(aliExtra, 0); if (aliCount != hordeCount) { aliFree -= aliExtra; hordeFree -= hordeExtra; aliFree = std::max(aliFree, 0); hordeFree = std::max(hordeFree, 0); } } } GroupsQueueType::const_iterator Ali_itr = m_QueuedGroups[bracket_id][BG_QUEUE_NORMAL_ALLIANCE].begin(); //count of groups in queue - used to stop cycles //index to queue which group is current uint32 aliIndex = 0; for (; aliIndex < aliCount && m_SelectionPools[TEAM_ALLIANCE].AddGroup((*Ali_itr), aliFree); aliIndex++) ++Ali_itr; //the same thing for horde GroupsQueueType::const_iterator Horde_itr = m_QueuedGroups[bracket_id][BG_QUEUE_NORMAL_HORDE].begin(); uint32 hordeIndex = 0; for (; hordeIndex < hordeCount && m_SelectionPools[TEAM_HORDE].AddGroup((*Horde_itr), hordeFree); hordeIndex++) ++Horde_itr; //if ofc like BG queue invitation is set in config, then we are happy if (sWorld->getIntConfig(CONFIG_BATTLEGROUND_INVITATION_TYPE) == BG_QUEUE_INVITATION_TYPE_NO_BALANCE) return; /* if we reached this code, then we have to solve NP - complete problem called Subset sum problem So one solution is to check all possible invitation subgroups, or we can use these conditions: 1. Last time when BattlegroundQueue::Update was executed we invited all possible players - so there is only small possibility that we will invite now whole queue, because only 1 change has been made to queues from the last BattlegroundQueue::Update call 2. Other thing we should consider is group order in queue */ // At first we need to compare free space in bg and our selection pool int32 diffAli = aliFree - int32(m_SelectionPools[TEAM_ALLIANCE].GetPlayerCount()); int32 diffHorde = hordeFree - int32(m_SelectionPools[TEAM_HORDE].GetPlayerCount()); while (std::abs(diffAli - diffHorde) > 1 && (m_SelectionPools[TEAM_HORDE].GetPlayerCount() > 0 || m_SelectionPools[TEAM_ALLIANCE].GetPlayerCount() > 0)) { //each cycle execution we need to kick at least 1 group if (diffAli < diffHorde) { //kick alliance group, add to pool new group if needed if (m_SelectionPools[TEAM_ALLIANCE].KickGroup(diffHorde - diffAli)) { for (; aliIndex < aliCount && m_SelectionPools[TEAM_ALLIANCE].AddGroup((*Ali_itr), (aliFree >= diffHorde) ? aliFree - diffHorde : 0); aliIndex++) ++Ali_itr; } //if ali selection is already empty, then kick horde group, but if there are less horde than ali in bg - break; if (!m_SelectionPools[TEAM_ALLIANCE].GetPlayerCount()) { if (aliFree <= diffHorde + 1) break; m_SelectionPools[TEAM_HORDE].KickGroup(diffHorde - diffAli); } } else { //kick horde group, add to pool new group if needed if (m_SelectionPools[TEAM_HORDE].KickGroup(diffAli - diffHorde)) { for (; hordeIndex < hordeCount && m_SelectionPools[TEAM_HORDE].AddGroup((*Horde_itr), (hordeFree >= diffAli) ? hordeFree - diffAli : 0); hordeIndex++) ++Horde_itr; } if (!m_SelectionPools[TEAM_HORDE].GetPlayerCount()) { if (hordeFree <= diffAli + 1) break; m_SelectionPools[TEAM_ALLIANCE].KickGroup(diffAli - diffHorde); } } //count diffs after small update diffAli = aliFree - int32(m_SelectionPools[TEAM_ALLIANCE].GetPlayerCount()); diffHorde = hordeFree - int32(m_SelectionPools[TEAM_HORDE].GetPlayerCount()); } } // this method checks if premade versus premade battleground is possible // then after 30 mins (default) in queue it moves premade group to normal queue bool BattlegroundQueue::CheckPremadeMatch(BattlegroundBracketId bracket_id, uint32 MinPlayersPerTeam, uint32 MaxPlayersPerTeam) { if (!m_QueuedGroups[bracket_id][BG_QUEUE_PREMADE_ALLIANCE].empty() && !m_QueuedGroups[bracket_id][BG_QUEUE_PREMADE_HORDE].empty()) { //start premade match //if groups aren't invited GroupsQueueType::const_iterator ali_group, horde_group; for (ali_group = m_QueuedGroups[bracket_id][BG_QUEUE_PREMADE_ALLIANCE].begin(); ali_group != m_QueuedGroups[bracket_id][BG_QUEUE_PREMADE_ALLIANCE].end(); ++ali_group) if (!(*ali_group)->IsInvitedToBGInstanceGUID) break; for (horde_group = m_QueuedGroups[bracket_id][BG_QUEUE_PREMADE_HORDE].begin(); horde_group != m_QueuedGroups[bracket_id][BG_QUEUE_PREMADE_HORDE].end(); ++horde_group) if (!(*horde_group)->IsInvitedToBGInstanceGUID) break; // if found both groups if (ali_group != m_QueuedGroups[bracket_id][BG_QUEUE_PREMADE_ALLIANCE].end() && horde_group != m_QueuedGroups[bracket_id][BG_QUEUE_PREMADE_HORDE].end()) { m_SelectionPools[TEAM_ALLIANCE].AddGroup((*ali_group), MaxPlayersPerTeam); m_SelectionPools[TEAM_HORDE].AddGroup((*horde_group), MaxPlayersPerTeam); //add groups/players from normal queue to size of bigger group uint32 maxPlayers = std::min(m_SelectionPools[TEAM_ALLIANCE].GetPlayerCount(), m_SelectionPools[TEAM_HORDE].GetPlayerCount()); GroupsQueueType::const_iterator itr; for (uint32 i = 0; i < PVP_TEAMS_COUNT; i++) { for (itr = m_QueuedGroups[bracket_id][BG_QUEUE_NORMAL_ALLIANCE + i].begin(); itr != m_QueuedGroups[bracket_id][BG_QUEUE_NORMAL_ALLIANCE + i].end(); ++itr) { //if itr can join BG and player count is less that maxPlayers, then add group to selectionpool if (!(*itr)->IsInvitedToBGInstanceGUID && !m_SelectionPools[i].AddGroup((*itr), maxPlayers)) break; } } //premade selection pools are set return true; } } // now check if we can move group from Premade queue to normal queue (timer has expired) or group size lowered!! // this could be 2 cycles but i'm checking only first team in queue - it can cause problem - // if first is invited to BG and seconds timer expired, but we can ignore it, because players have only 80 seconds to click to enter bg // and when they click or after 80 seconds the queue info is removed from queue uint32 time_before = GameTime::GetGameTimeMS().count() - sWorld->getIntConfig(CONFIG_BATTLEGROUND_PREMADE_GROUP_WAIT_FOR_MATCH); for (uint32 i = 0; i < PVP_TEAMS_COUNT; i++) { if (!m_QueuedGroups[bracket_id][BG_QUEUE_PREMADE_ALLIANCE + i].empty()) { GroupsQueueType::iterator itr = m_QueuedGroups[bracket_id][BG_QUEUE_PREMADE_ALLIANCE + i].begin(); if (!(*itr)->IsInvitedToBGInstanceGUID && ((*itr)->JoinTime < time_before || (*itr)->Players.size() < MinPlayersPerTeam)) { //we must insert group to normal queue and erase pointer from premade queue (*itr)->GroupType = BG_QUEUE_NORMAL_ALLIANCE + i; // pussywizard: update GroupQueueInfo internal variable m_QueuedGroups[bracket_id][BG_QUEUE_NORMAL_ALLIANCE + i].push_front((*itr)); m_QueuedGroups[bracket_id][BG_QUEUE_PREMADE_ALLIANCE + i].erase(itr); } } } //selection pools are not set return false; } // this method tries to create battleground or arena with MinPlayersPerTeam against MinPlayersPerTeam bool BattlegroundQueue::CheckNormalMatch(Battleground* bgTemplate, BattlegroundBracketId bracket_id, uint32 minPlayers, uint32 maxPlayers) { auto CanStartMatch = [this, bgTemplate, minPlayers]() { //allow 1v0 if debug bg if (sBattlegroundMgr->isTesting() && bgTemplate->isBattleground() && (m_SelectionPools[TEAM_ALLIANCE].GetPlayerCount() || m_SelectionPools[TEAM_HORDE].GetPlayerCount())) return true; //return true if there are enough players in selection pools - enable to work .debug bg command correctly return m_SelectionPools[TEAM_ALLIANCE].GetPlayerCount() >= minPlayers && m_SelectionPools[TEAM_HORDE].GetPlayerCount() >= minPlayers; }; if (sScriptMgr->IsCheckNormalMatch(this, bgTemplate, bracket_id, minPlayers, maxPlayers)) return CanStartMatch(); GroupsQueueType::const_iterator itr_team[PVP_TEAMS_COUNT]; for (uint32 i = 0; i < PVP_TEAMS_COUNT; i++) { itr_team[i] = m_QueuedGroups[bracket_id][BG_QUEUE_NORMAL_ALLIANCE + i].begin(); for (; itr_team[i] != m_QueuedGroups[bracket_id][BG_QUEUE_NORMAL_ALLIANCE + i].end(); ++(itr_team[i])) { if (!(*(itr_team[i]))->IsInvitedToBGInstanceGUID) { m_SelectionPools[i].AddGroup(*(itr_team[i]), maxPlayers); if (m_SelectionPools[i].GetPlayerCount() >= minPlayers) break; } } } //try to invite same number of players - this cycle may cause longer wait time even if there are enough players in queue, but we want ballanced bg uint32 j = TEAM_ALLIANCE; if (m_SelectionPools[TEAM_HORDE].GetPlayerCount() < m_SelectionPools[TEAM_ALLIANCE].GetPlayerCount()) j = TEAM_HORDE; if (sWorld->getIntConfig(CONFIG_BATTLEGROUND_INVITATION_TYPE) != BG_QUEUE_INVITATION_TYPE_NO_BALANCE && m_SelectionPools[TEAM_HORDE].GetPlayerCount() >= minPlayers && m_SelectionPools[TEAM_ALLIANCE].GetPlayerCount() >= minPlayers) { //we will try to invite more groups to team with less players indexed by j ++(itr_team[j]); //this will not cause a crash, because for cycle above reached break; for (; itr_team[j] != m_QueuedGroups[bracket_id][BG_QUEUE_NORMAL_ALLIANCE + j].end(); ++(itr_team[j])) { if (!(*(itr_team[j]))->IsInvitedToBGInstanceGUID) if (!m_SelectionPools[j].AddGroup(*(itr_team[j]), m_SelectionPools[(j + 1) % PVP_TEAMS_COUNT].GetPlayerCount())) break; } // do not allow to start bg with more than 2 players more on 1 faction if (std::abs((int32)(m_SelectionPools[TEAM_HORDE].GetPlayerCount() - m_SelectionPools[TEAM_ALLIANCE].GetPlayerCount())) > 2) return false; } return CanStartMatch(); } // this method will check if we can invite players to same faction skirmish match bool BattlegroundQueue::CheckSkirmishForSameFaction(BattlegroundBracketId bracket_id, uint32 minPlayersPerTeam) { if (m_SelectionPools[TEAM_ALLIANCE].GetPlayerCount() < minPlayersPerTeam && m_SelectionPools[TEAM_HORDE].GetPlayerCount() < minPlayersPerTeam) return false; TeamId teamIndex = TEAM_ALLIANCE; TeamId otherTeam = TEAM_HORDE; if (m_SelectionPools[TEAM_HORDE].GetPlayerCount() == minPlayersPerTeam) { teamIndex = TEAM_HORDE; otherTeam = TEAM_ALLIANCE; } //clear other team's selection m_SelectionPools[otherTeam].Init(); //store last ginfo pointer GroupQueueInfo* ginfo = m_SelectionPools[teamIndex].SelectedGroups.back(); //set itr_team to group that was added to selection pool latest GroupsQueueType::iterator itr_team = m_QueuedGroups[bracket_id][BG_QUEUE_NORMAL_ALLIANCE + static_cast(teamIndex)].begin(); for (; itr_team != m_QueuedGroups[bracket_id][BG_QUEUE_NORMAL_ALLIANCE + static_cast(teamIndex)].end(); ++itr_team) if (ginfo == *itr_team) break; if (itr_team == m_QueuedGroups[bracket_id][BG_QUEUE_NORMAL_ALLIANCE + static_cast(teamIndex)].end()) return false; GroupsQueueType::iterator itr_team2 = itr_team; ++itr_team2; //invite players to other selection pool for (; itr_team2 != m_QueuedGroups[bracket_id][BG_QUEUE_NORMAL_ALLIANCE + static_cast(teamIndex)].end(); ++itr_team2) { //if selection pool is full then break; if (!(*itr_team2)->IsInvitedToBGInstanceGUID && !m_SelectionPools[otherTeam].AddGroup(*itr_team2, minPlayersPerTeam)) break; } if (m_SelectionPools[otherTeam].GetPlayerCount() != minPlayersPerTeam) return false; //here we have correct 2 selections and we need to change one teams team and move selection pool teams to other team's queue for (GroupsQueueType::iterator itr = m_SelectionPools[otherTeam].SelectedGroups.begin(); itr != m_SelectionPools[otherTeam].SelectedGroups.end(); ++itr) { //set correct team (*itr)->teamId = otherTeam; (*itr)->GroupType = static_cast(BG_QUEUE_NORMAL_ALLIANCE) + static_cast(otherTeam); //add team to other queue m_QueuedGroups[bracket_id][BG_QUEUE_NORMAL_ALLIANCE + static_cast(otherTeam)].push_front(*itr); //remove team from old queue GroupsQueueType::iterator itr2 = itr_team; ++itr2; for (; itr2 != m_QueuedGroups[bracket_id][BG_QUEUE_NORMAL_ALLIANCE + static_cast(teamIndex)].end(); ++itr2) { if (*itr2 == *itr) { m_QueuedGroups[bracket_id][BG_QUEUE_NORMAL_ALLIANCE + static_cast(teamIndex)].erase(itr2); break; } } } return true; } void BattlegroundQueue::UpdateEvents(uint32 diff) { m_events.Update(diff); } void BattlegroundQueue::BattlegroundQueueUpdate(uint32 diff, BattlegroundTypeId bgTypeId, BattlegroundBracketId bracket_id, uint8 arenaType, bool isRated, uint32 arenaRating) { // if no players in queue - do nothing if (IsAllQueuesEmpty(bracket_id)) return; auto InviteAllGroupsToBg = [this](Battleground* bg) { // invite those selection pools for (uint32 i = 0; i < PVP_TEAMS_COUNT; i++) { for (auto const& citr : m_SelectionPools[TEAM_ALLIANCE + i].SelectedGroups) { InviteGroupToBG(citr, bg, citr->teamId); } } }; // battleground with free slot for player should be always in the beggining of the queue // maybe it would be better to create bgfreeslotqueue for each bracket_id BGFreeSlotQueueContainer& bgQueues = sBattlegroundMgr->GetBGFreeSlotQueueStore(bgTypeId); for (BGFreeSlotQueueContainer::iterator itr = bgQueues.begin(); itr != bgQueues.end();) { Battleground* bg = *itr; ++itr; // DO NOT allow queue manager to invite new player to rated games if (!bg->isRated() && bg->GetBgTypeID() == bgTypeId && bg->GetBracketId() == bracket_id && bg->GetStatus() > STATUS_WAIT_QUEUE && bg->GetStatus() < STATUS_WAIT_LEAVE) { // clear selection pools m_SelectionPools[TEAM_ALLIANCE].Init(); m_SelectionPools[TEAM_HORDE].Init(); // call a function that does the job for us FillPlayersToBG(bg, bracket_id); // now everything is set, invite players InviteAllGroupsToBg(bg); if (!bg->HasFreeSlots()) bg->RemoveFromBGFreeSlotQueue(); } } Battleground* bg_template = sBattlegroundMgr->GetBattlegroundTemplate(bgTypeId); if (!bg_template) { LOG_ERROR("bg.battleground", "Battleground: Update: bg template not found for {}", bgTypeId); return; } PvPDifficultyEntry const* bracketEntry = GetBattlegroundBracketById(bg_template->GetMapId(), bracket_id); if (!bracketEntry) { LOG_ERROR("bg.battleground", "Battleground: Update: bg bracket entry not found for map {} bracket id {}", bg_template->GetMapId(), bracket_id); return; } // get min and max players per team uint32 MinPlayersPerTeam = GetMinPlayersPerTeam(bg_template, bracketEntry); uint32 MaxPlayersPerTeam = bg_template->GetMaxPlayersPerTeam(); if (bg_template->isArena()) { MinPlayersPerTeam = sBattlegroundMgr->isArenaTesting() ? 1 : arenaType; MaxPlayersPerTeam = arenaType; } else if (sBattlegroundMgr->isTesting()) MinPlayersPerTeam = 1; sScriptMgr->OnQueueUpdate(this, diff, bgTypeId, bracket_id, arenaType, isRated, arenaRating); if (!sScriptMgr->OnQueueUpdateValidity(this, diff, bgTypeId, bracket_id, arenaType, isRated, arenaRating)) return; m_SelectionPools[TEAM_ALLIANCE].Init(); m_SelectionPools[TEAM_HORDE].Init(); // check if can start new premade battleground if (bg_template->isBattleground() && bgTypeId != BATTLEGROUND_RB && CheckPremadeMatch(bracket_id, MinPlayersPerTeam, MaxPlayersPerTeam)) { // create new battleground Battleground* bg = sBattlegroundMgr->CreateNewBattleground(bgTypeId, bracketEntry, 0, false); if (!bg) { LOG_ERROR("bg.battleground", "BattlegroundQueue::Update - Cannot create battleground: {}", bgTypeId); return; } // invite those selection pools InviteAllGroupsToBg(bg); bg->StartBattleground(); // clear structures m_SelectionPools[TEAM_ALLIANCE].Init(); m_SelectionPools[TEAM_HORDE].Init(); } // check if can start new normal battleground or non-rated arena if (!isRated) { if (CheckNormalMatch(bg_template, bracket_id, MinPlayersPerTeam, MaxPlayersPerTeam) || (bg_template->isArena() && CheckSkirmishForSameFaction(bracket_id, MinPlayersPerTeam))) { // create new battleground Battleground* bg = sBattlegroundMgr->CreateNewBattleground(bgTypeId, bracketEntry, arenaType, false); if (!bg) { LOG_ERROR("bg.battleground", "BattlegroundQueue::Update - Cannot create battleground: {}", bgTypeId); return; } // invite players InviteAllGroupsToBg(bg); bg->StartBattleground(); } } // check if can start new rated arenas (can create many in single queue update) else if (bg_template->isArena()) { // found out the minimum and maximum ratings the newly added team should battle against // arenaRating is the rating of the latest joined team, or 0 // 0 is on (automatic update call) and we must set it to team's with longest wait time if (!arenaRating) { GroupQueueInfo* front1 = nullptr; GroupQueueInfo* front2 = nullptr; if (!m_QueuedGroups[bracket_id][BG_QUEUE_PREMADE_ALLIANCE].empty()) { front1 = m_QueuedGroups[bracket_id][BG_QUEUE_PREMADE_ALLIANCE].front(); arenaRating = front1->ArenaMatchmakerRating; } if (!m_QueuedGroups[bracket_id][BG_QUEUE_PREMADE_HORDE].empty()) { front2 = m_QueuedGroups[bracket_id][BG_QUEUE_PREMADE_HORDE].front(); arenaRating = front2->ArenaMatchmakerRating; } if (front1 && front2) { if (front1->JoinTime < front2->JoinTime) arenaRating = front1->ArenaMatchmakerRating; } else if (!front1 && !front2) return; // queues are empty } //set rating range uint32 arenaMinRating = (arenaRating <= sBattlegroundMgr->GetMaxRatingDifference()) ? 0 : arenaRating - sBattlegroundMgr->GetMaxRatingDifference(); uint32 arenaMaxRating = arenaRating + sBattlegroundMgr->GetMaxRatingDifference(); // if max rating difference is set and the time past since server startup is greater than the rating discard time // (after what time the ratings aren't taken into account when making teams) then // the discard time is current_time - time_to_discard, teams that joined after that, will have their ratings taken into account // else leave the discard time on 0, this way all ratings will be discarded // this has to be signed value - when the server starts, this value would be negative and thus overflow int32 discardTime = GameTime::GetGameTimeMS().count() - sBattlegroundMgr->GetRatingDiscardTimer(); // timer for previous opponents int32 discardOpponentsTime = GameTime::GetGameTimeMS().count() - sWorld->getIntConfig(CONFIG_ARENA_PREV_OPPONENTS_DISCARD_TIMER); // we need to find 2 teams which will play next game GroupsQueueType::iterator itr_teams[PVP_TEAMS_COUNT]; uint8 found = 0; uint8 team = 0; for (uint8 i = BG_QUEUE_PREMADE_ALLIANCE; i < BG_QUEUE_NORMAL_ALLIANCE; i++) { // take the group that joined first GroupsQueueType::iterator itr2 = m_QueuedGroups[bracket_id][i].begin(); for (; itr2 != m_QueuedGroups[bracket_id][i].end(); ++itr2) { // if group match conditions, then add it to pool if (!(*itr2)->IsInvitedToBGInstanceGUID && (((*itr2)->ArenaMatchmakerRating >= arenaMinRating && (*itr2)->ArenaMatchmakerRating <= arenaMaxRating) || (int32)(*itr2)->JoinTime < discardTime)) { itr_teams[found++] = itr2; team = i; break; } } } if (!found) return; if (found == 1) { for (GroupsQueueType::iterator itr3 = itr_teams[0]; itr3 != m_QueuedGroups[bracket_id][team].end(); ++itr3) { if (!(*itr3)->IsInvitedToBGInstanceGUID && (((*itr3)->ArenaMatchmakerRating >= arenaMinRating && (*itr3)->ArenaMatchmakerRating <= arenaMaxRating) || (int32)(*itr3)->JoinTime < discardTime) && ((*(itr_teams[0]))->ArenaTeamId != (*itr3)->PreviousOpponentsTeamId || ((int32)(*itr3)->JoinTime < discardOpponentsTime)) && (*(itr_teams[0]))->ArenaTeamId != (*itr3)->ArenaTeamId) { itr_teams[found++] = itr3; break; } } } //if we have 2 teams, then start new arena and invite players! if (found == 2) { GroupQueueInfo* aTeam = *(itr_teams[TEAM_ALLIANCE]); GroupQueueInfo* hTeam = *(itr_teams[TEAM_HORDE]); Battleground* arena = sBattlegroundMgr->CreateNewBattleground(bgTypeId, bracketEntry, arenaType, true); if (!arena) { LOG_ERROR("bg.battleground", "BattlegroundQueue::Update couldn't create arena instance for rated arena match!"); return; } aTeam->OpponentsTeamRating = hTeam->ArenaTeamRating; hTeam->OpponentsTeamRating = aTeam->ArenaTeamRating; aTeam->OpponentsMatchmakerRating = hTeam->ArenaMatchmakerRating; hTeam->OpponentsMatchmakerRating = aTeam->ArenaMatchmakerRating; LOG_DEBUG("bg.battleground", "setting oposite teamrating for team {} to {}", aTeam->ArenaTeamId, aTeam->OpponentsTeamRating); LOG_DEBUG("bg.battleground", "setting oposite teamrating for team {} to {}", hTeam->ArenaTeamId, hTeam->OpponentsTeamRating); // now we must move team if we changed its faction to another faction queue, because then we will spam log by errors in Queue::RemovePlayer if (aTeam->teamId != TEAM_ALLIANCE) { aTeam->GroupType = BG_QUEUE_PREMADE_ALLIANCE; m_QueuedGroups[bracket_id][BG_QUEUE_PREMADE_ALLIANCE].push_front(aTeam); m_QueuedGroups[bracket_id][BG_QUEUE_PREMADE_HORDE].erase(itr_teams[TEAM_ALLIANCE]); } if (hTeam->teamId != TEAM_HORDE) { hTeam->GroupType = BG_QUEUE_PREMADE_HORDE; m_QueuedGroups[bracket_id][BG_QUEUE_PREMADE_HORDE].push_front(hTeam); m_QueuedGroups[bracket_id][BG_QUEUE_PREMADE_ALLIANCE].erase(itr_teams[TEAM_HORDE]); } arena->SetArenaMatchmakerRating(TEAM_ALLIANCE, aTeam->ArenaMatchmakerRating); arena->SetArenaMatchmakerRating(TEAM_HORDE, hTeam->ArenaMatchmakerRating); InviteGroupToBG(aTeam, arena, TEAM_ALLIANCE); InviteGroupToBG(hTeam, arena, TEAM_HORDE); LOG_DEBUG("bg.battleground", "Starting rated arena match!"); arena->StartBattleground(); } } } void BattlegroundQueue::BattlegroundQueueAnnouncerUpdate(uint32 diff, BattlegroundQueueTypeId bgQueueTypeId, BattlegroundBracketId bracket_id) { BattlegroundTypeId bgTypeId = BattlegroundMgr::BGTemplateId(bgQueueTypeId); Battleground* bg_template = sBattlegroundMgr->GetBattlegroundTemplate(bgTypeId); if (!bg_template) { return; } PvPDifficultyEntry const* bracketEntry = GetBattlegroundBracketById(bg_template->GetMapId(), bracket_id); if (!bracketEntry) { return; } if (sWorld->getBoolConfig(CONFIG_BATTLEGROUND_QUEUE_ANNOUNCER_TIMED)) { uint32 qPlayers = 0; if (_queueAnnouncementCrossfactioned) { qPlayers = GetPlayersCountInGroupsQueue(bracket_id, BG_QUEUE_CFBG); } else { qPlayers = GetPlayersCountInGroupsQueue(bracket_id, BG_QUEUE_NORMAL_HORDE) + GetPlayersCountInGroupsQueue(bracket_id, BG_QUEUE_NORMAL_ALLIANCE); } if (!qPlayers) { _queueAnnouncementTimer[bracket_id] = -1; return; } if (_queueAnnouncementTimer[bracket_id] >= 0) { if (_queueAnnouncementTimer[bracket_id] <= static_cast(diff)) { _queueAnnouncementTimer[bracket_id] = -1; auto bgName = bg_template->GetName(); uint32 MaxPlayers = GetMinPlayersPerTeam(bg_template, bracketEntry) * 2; uint32 q_min_level = std::min(bracketEntry->minLevel, (uint32) 80); uint32 q_max_level = std::min(bracketEntry->maxLevel, (uint32) 80); ChatHandler(nullptr).SendWorldTextOptional(LANG_BG_QUEUE_ANNOUNCE_WORLD, ANNOUNCER_FLAG_DISABLE_BG_QUEUE, bgName.c_str(), q_min_level, q_max_level, qPlayers, MaxPlayers); } else { _queueAnnouncementTimer[bracket_id] -= static_cast(diff); } } } } uint32 BattlegroundQueue::GetPlayersCountInGroupsQueue(BattlegroundBracketId bracketId, BattlegroundQueueGroupTypes bgqueue) { uint32 playersCount = 0; for (auto const& itr : m_QueuedGroups[bracketId][bgqueue]) if (!itr->IsInvitedToBGInstanceGUID) playersCount += static_cast(itr->Players.size()); return playersCount; } bool BattlegroundQueue::IsAllQueuesEmpty(BattlegroundBracketId bracket_id) { uint8 queueEmptyCount = 0; for (uint8 i = 0; i < BG_QUEUE_MAX; i++) if (m_QueuedGroups[bracket_id][i].empty()) queueEmptyCount++; return queueEmptyCount == BG_QUEUE_MAX; } void BattlegroundQueue::SendMessageBGQueue(Player* leader, Battleground* bg, PvPDifficultyEntry const* bracketEntry) { if (!sScriptMgr->CanSendMessageBGQueue(this, leader, bg, bracketEntry)) { return; } if (bg->isArena()) { // Skip announce for arena skirmish return; } BattlegroundBracketId bracketId = bracketEntry->GetBracketId(); auto bgName = bg->GetName(); uint32 MinPlayers = GetMinPlayersPerTeam(bg, bracketEntry); uint32 MaxPlayers = MinPlayers * 2; uint32 q_min_level = std::min(bracketEntry->minLevel, (uint32)80); uint32 q_max_level = std::min(bracketEntry->maxLevel, (uint32)80); uint32 qHorde = GetPlayersCountInGroupsQueue(bracketId, BG_QUEUE_NORMAL_HORDE); uint32 qAlliance = GetPlayersCountInGroupsQueue(bracketId, BG_QUEUE_NORMAL_ALLIANCE); auto qTotal = qHorde + qAlliance; LOG_DEBUG("bg.battleground", "> Queue status for {} (Lvl: {} to {}) Queued: {} (Need at least {} more)", bgName, q_min_level, q_max_level, qAlliance + qHorde, MaxPlayers - qTotal); // Show queue status to player only (when joining battleground queue or Arena and arena world announcer is disabled) if (sWorld->getBoolConfig(CONFIG_BATTLEGROUND_QUEUE_ANNOUNCER_PLAYERONLY)) { ChatHandler(leader->GetSession()).PSendSysMessage(LANG_BG_QUEUE_ANNOUNCE_SELF, bgName, q_min_level, q_max_level, qAlliance, (MinPlayers > qAlliance) ? MinPlayers - qAlliance : (uint32)0, qHorde, (MinPlayers > qHorde) ? MinPlayers - qHorde : (uint32)0); } else // Show queue status to server (when joining battleground queue) { if (sWorld->getBoolConfig(CONFIG_BATTLEGROUND_QUEUE_ANNOUNCER_TIMED)) { if (_queueAnnouncementTimer[bracketId] < 0) { _queueAnnouncementTimer[bracketId] = sWorld->getIntConfig(CONFIG_BATTLEGROUND_QUEUE_ANNOUNCER_TIMER); } } else { if (!sBGSpam->CanAnnounce(leader, bg, q_min_level, qTotal)) { return; } ChatHandler(nullptr).SendWorldTextOptional(LANG_BG_QUEUE_ANNOUNCE_WORLD, ANNOUNCER_FLAG_DISABLE_BG_QUEUE, bgName.c_str(), q_min_level, q_max_level, qAlliance + qHorde, MaxPlayers); } } } void BattlegroundQueue::SendJoinMessageArenaQueue(Player* leader, GroupQueueInfo* ginfo, PvPDifficultyEntry const* bracketEntry, bool isRated) { if (!sWorld->getBoolConfig(CONFIG_ARENA_QUEUE_ANNOUNCER_ENABLE)) return; if (!sScriptMgr->OnBeforeSendJoinMessageArenaQueue(this, leader, ginfo, bracketEntry, isRated)) return; if (!isRated) { Battleground* bg = sBattlegroundMgr->GetBattlegroundTemplate(ginfo->BgTypeId); if (!bg) { LOG_ERROR("bg.arena", "> Not found bg template for bgtype id {}", uint32(ginfo->BgTypeId)); return; } if (!bg->isArena()) { // Skip announce for non arena return; } BattlegroundBracketId bracketId = bracketEntry->GetBracketId(); auto bgName = bg->GetName(); auto arenatype = Acore::StringFormat("{}v{}", ginfo->ArenaType, ginfo->ArenaType); uint32 playersNeed = ArenaTeam::GetReqPlayersForType(ginfo->ArenaType); uint32 q_min_level = std::min(bracketEntry->minLevel, (uint32)80); uint32 q_max_level = std::min(bracketEntry->maxLevel, (uint32)80); uint32 qPlayers = GetPlayersCountInGroupsQueue(bracketId, BG_QUEUE_NORMAL_HORDE) + GetPlayersCountInGroupsQueue(bracketId, BG_QUEUE_NORMAL_ALLIANCE); LOG_DEBUG("bg.arena", "> Queue status for {} (skirmish {}) (Lvl: {} to {}) Queued: {} (Need at least {} more)", bgName, arenatype, q_min_level, q_max_level, qPlayers, playersNeed - qPlayers); if (sWorld->getBoolConfig(CONFIG_ARENA_QUEUE_ANNOUNCER_PLAYERONLY)) { ChatHandler(leader->GetSession()).PSendSysMessage(LANG_ARENA_QUEUE_ANNOUNCE_SELF, bgName, arenatype, q_min_level, q_max_level, qPlayers, playersNeed - qPlayers); } else { if (!sBGSpam->CanAnnounce(leader, bg, q_min_level, qPlayers)) { return; } ChatHandler(nullptr).SendWorldTextOptional(LANG_ARENA_QUEUE_ANNOUNCE_WORLD, ANNOUNCER_FLAG_DISABLE_ARENA_QUEUE, bgName.c_str(), arenatype.c_str(), q_min_level, q_max_level, qPlayers, playersNeed); } } else { ArenaTeam* team = sArenaTeamMgr->GetArenaTeamById(ginfo->ArenaTeamId); if (!team || !ginfo->IsRated) { return; } uint8 ArenaType = ginfo->ArenaType; uint32 ArenaTeamRating = ginfo->ArenaTeamRating; std::string TeamName = team->GetName(); uint32 announcementDetail = sWorld->getIntConfig(CONFIG_ARENA_QUEUE_ANNOUNCER_DETAIL); switch (announcementDetail) { case 3: ChatHandler(nullptr).SendWorldTextOptional(LANG_ARENA_QUEUE_ANNOUNCE_WORLD_JOIN_NAME_RATING, ANNOUNCER_FLAG_DISABLE_ARENA_QUEUE, TeamName.c_str(), ArenaType, ArenaType, ArenaTeamRating); break; case 2: ChatHandler(nullptr).SendWorldTextOptional(LANG_ARENA_QUEUE_ANNOUNCE_WORLD_JOIN_NAME, ANNOUNCER_FLAG_DISABLE_ARENA_QUEUE, TeamName, ArenaType, ArenaType); break; case 1: ChatHandler(nullptr).SendWorldTextOptional(LANG_ARENA_QUEUE_ANNOUNCE_WORLD_JOIN_RATING, ANNOUNCER_FLAG_DISABLE_ARENA_QUEUE, ArenaType, ArenaType, ArenaTeamRating); break; default: ChatHandler(nullptr).SendWorldTextOptional(LANG_ARENA_QUEUE_ANNOUNCE_WORLD_JOIN, ANNOUNCER_FLAG_DISABLE_ARENA_QUEUE, ArenaType, ArenaType); break; } } } void BattlegroundQueue::SendExitMessageArenaQueue(GroupQueueInfo* ginfo) { if (!sWorld->getBoolConfig(CONFIG_ARENA_QUEUE_ANNOUNCER_ENABLE)) return; if (!sScriptMgr->OnBeforeSendExitMessageArenaQueue(this, ginfo)) return; ArenaTeam* team = sArenaTeamMgr->GetArenaTeamById(ginfo->ArenaTeamId); if (!team) return; if (!ginfo->IsRated) return; uint8 ArenaType = ginfo->ArenaType; uint32 ArenaTeamRating = ginfo->ArenaTeamRating; std::string TeamName = team->GetName(); if (ArenaType && ginfo->Players.empty()) { uint32 announcementDetail = sWorld->getIntConfig(CONFIG_ARENA_QUEUE_ANNOUNCER_DETAIL); switch (announcementDetail) { case 3: ChatHandler(nullptr).SendWorldTextOptional(LANG_ARENA_QUEUE_ANNOUNCE_WORLD_EXIT_NAME_RATING, ANNOUNCER_FLAG_DISABLE_ARENA_QUEUE, TeamName.c_str(), ArenaType, ArenaType, ArenaTeamRating); break; case 2: ChatHandler(nullptr).SendWorldTextOptional(LANG_ARENA_QUEUE_ANNOUNCE_WORLD_EXIT_NAME, ANNOUNCER_FLAG_DISABLE_ARENA_QUEUE, TeamName, ArenaType, ArenaType); break; case 1: ChatHandler(nullptr).SendWorldTextOptional(LANG_ARENA_QUEUE_ANNOUNCE_WORLD_EXIT_RATING, ANNOUNCER_FLAG_DISABLE_ARENA_QUEUE, ArenaType, ArenaType, ArenaTeamRating); break; default: ChatHandler(nullptr).SendWorldTextOptional(LANG_ARENA_QUEUE_ANNOUNCE_WORLD_EXIT, ANNOUNCER_FLAG_DISABLE_ARENA_QUEUE, ArenaType, ArenaType); break; } } } void BattlegroundQueue::SetQueueAnnouncementTimer(uint32 bracketId, int32 timer, bool isCrossFactionBG /*= true*/) { _queueAnnouncementTimer[bracketId] = timer; _queueAnnouncementCrossfactioned = isCrossFactionBG; } int32 BattlegroundQueue::GetQueueAnnouncementTimer(uint32 bracketId) const { return _queueAnnouncementTimer[bracketId]; } void BattlegroundQueue::InviteGroupToBG(GroupQueueInfo* ginfo, Battleground* bg, TeamId teamId) { // set side if needed if (teamId != TEAM_NEUTRAL) ginfo->teamId = teamId; if (ginfo->IsInvitedToBGInstanceGUID) return; // set invitation ginfo->IsInvitedToBGInstanceGUID = bg->GetInstanceID(); BattlegroundTypeId bgTypeId = bg->GetBgTypeID(); BattlegroundQueueTypeId bgQueueTypeId = BattlegroundMgr::BGQueueTypeId(ginfo->BgTypeId, ginfo->ArenaType); BattlegroundQueue& bgQueue = sBattlegroundMgr->GetBattlegroundQueue(bgQueueTypeId); // set ArenaTeamId for rated matches if (bg->isArena() && bg->isRated()) bg->SetArenaTeamIdForTeam(ginfo->teamId, ginfo->ArenaTeamId); ginfo->RemoveInviteTime = GameTime::GetGameTimeMS().count() + INVITE_ACCEPT_WAIT_TIME; // loop through the players for (auto const& itr : ginfo->Players) { // get the player Player* player = ObjectAccessor::FindConnectedPlayer(itr); if (!player) continue; // update average wait time bgQueue.PlayerInvitedToBGUpdateAverageWaitTime(ginfo); // increase invited counter for each invited player bg->IncreaseInvitedCount(ginfo->teamId); player->SetInviteForBattlegroundQueueType(bgQueueTypeId, ginfo->IsInvitedToBGInstanceGUID); // create remind invite events BGQueueInviteEvent* inviteEvent = new BGQueueInviteEvent(player->GetGUID(), ginfo->IsInvitedToBGInstanceGUID, bgTypeId, ginfo->ArenaType, ginfo->RemoveInviteTime); bgQueue.AddEvent(inviteEvent, INVITATION_REMIND_TIME); // create automatic remove events BGQueueRemoveEvent* removeEvent = new BGQueueRemoveEvent(player->GetGUID(), ginfo->IsInvitedToBGInstanceGUID, bgTypeId, bgQueueTypeId, ginfo->RemoveInviteTime); bgQueue.AddEvent(removeEvent, INVITE_ACCEPT_WAIT_TIME); // Check queueSlot uint32 queueSlot = player->GetBattlegroundQueueIndex(bgQueueTypeId); ASSERT(queueSlot < PLAYER_MAX_BATTLEGROUND_QUEUES); LOG_DEBUG("bg.battleground", "Battleground: invited player {} {} to BG instance {} queueindex {} bgtype {}", player->GetName(), player->GetGUID().ToString(), bg->GetInstanceID(), queueSlot, bgTypeId); // send status packet WorldPacket data; sBattlegroundMgr->BuildBattlegroundStatusPacket(&data, bg, queueSlot, STATUS_WAIT_JOIN, INVITE_ACCEPT_WAIT_TIME, 0, ginfo->ArenaType, TEAM_NEUTRAL, bg->isRated()); player->SendDirectMessage(&data); // pussywizard: if (bg->isArena() && bg->isRated()) bg->ArenaLogEntries[player->GetGUID()].Fill(player->GetName(), player->GetGUID().GetCounter(), player->GetSession()->GetAccountId(), ginfo->ArenaTeamId, player->GetSession()->GetRemoteAddress()); } } /*********************************************************/ /*** BATTLEGROUND QUEUE EVENTS ***/ /*********************************************************/ bool BGQueueInviteEvent::Execute(uint64 /*e_time*/, uint32 /*p_time*/) { Player* player = ObjectAccessor::FindConnectedPlayer(m_PlayerGuid); // player logged off, so he is no longer in queue if (!player) return true; Battleground* bg = sBattlegroundMgr->GetBattleground(m_BgInstanceGUID, m_BgTypeId); // if battleground ended, do nothing if (!bg) return true; // check if still in queue for this battleground BattlegroundQueueTypeId bgQueueTypeId = BattlegroundMgr::BGQueueTypeId(m_BgTypeId, m_ArenaType); uint32 queueSlot = player->GetBattlegroundQueueIndex(bgQueueTypeId); if (queueSlot < PLAYER_MAX_BATTLEGROUND_QUEUES) { // confirm the player is invited to this instance id (he didn't requeue in the meanwhile) BattlegroundQueue& bgQueue = sBattlegroundMgr->GetBattlegroundQueue(bgQueueTypeId); if (bgQueue.IsPlayerInvited(m_PlayerGuid, m_BgInstanceGUID, m_RemoveTime)) { // send remaining time in queue WorldPacket data; sBattlegroundMgr->BuildBattlegroundStatusPacket(&data, bg, queueSlot, STATUS_WAIT_JOIN, INVITE_ACCEPT_WAIT_TIME - INVITATION_REMIND_TIME, 0, m_ArenaType, TEAM_NEUTRAL, bg->isRated(), m_BgTypeId); player->SendDirectMessage(&data); } } return true; } void BGQueueInviteEvent::Abort(uint64 /*e_time*/) { //do nothing } bool BGQueueRemoveEvent::Execute(uint64 /*e_time*/, uint32 /*p_time*/) { Player* player = ObjectAccessor::FindConnectedPlayer(m_PlayerGuid); // player logged off, so he is no longer in queue if (!player) return true; Battleground* bg = sBattlegroundMgr->GetBattleground(m_BgInstanceGUID, m_BgTypeId); // battleground can be already deleted, bg may be nullptr! // check if still in queue for this battleground uint32 queueSlot = player->GetBattlegroundQueueIndex(m_BgQueueTypeId); if (queueSlot < PLAYER_MAX_BATTLEGROUND_QUEUES) // player is in queue { // confirm the player is invited to this instance id (he didn't requeue in the meanwhile) BattlegroundQueue& bgQueue = sBattlegroundMgr->GetBattlegroundQueue(m_BgQueueTypeId); if (bgQueue.IsPlayerInvited(m_PlayerGuid, m_BgInstanceGUID, m_RemoveTime)) { // track if player leaves the BG by not clicking enter button if (bg && bg->isBattleground() && (bg->GetStatus() == STATUS_IN_PROGRESS || bg->GetStatus() == STATUS_WAIT_JOIN)) { if (sWorld->getBoolConfig(CONFIG_BATTLEGROUND_TRACK_DESERTERS)) { CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_DESERTER_TRACK); stmt->SetData(0, player->GetGUID().GetCounter()); stmt->SetData(1, BG_DESERTION_TYPE_NO_ENTER_BUTTON); CharacterDatabase.Execute(stmt); } sScriptMgr->OnPlayerBattlegroundDesertion(player, BG_DESERTION_TYPE_NO_ENTER_BUTTON); } if (bg && bg->isArena() && (bg->GetStatus() == STATUS_IN_PROGRESS || bg->GetStatus() == STATUS_WAIT_JOIN)) sScriptMgr->OnPlayerBattlegroundDesertion(player, ARENA_DESERTION_TYPE_NO_ENTER_BUTTON); LOG_DEBUG("bg.battleground", "Battleground: removing player {} from bg queue for instance {} because of not pressing enter battle in time.", player->GetGUID().ToString(), m_BgInstanceGUID); player->RemoveBattlegroundQueueId(m_BgQueueTypeId); bgQueue.RemovePlayer(m_PlayerGuid, true); //update queues if battleground isn't ended if (bg && bg->isBattleground() && bg->GetStatus() != STATUS_WAIT_LEAVE) sBattlegroundMgr->ScheduleQueueUpdate(0, 0, m_BgQueueTypeId, m_BgTypeId, bg->GetBracketId()); WorldPacket data; sBattlegroundMgr->BuildBattlegroundStatusPacket(&data, bg, queueSlot, STATUS_NONE, 0, 0, 0, TEAM_NEUTRAL); player->SendDirectMessage(&data); } } return true; } void BGQueueRemoveEvent::Abort(uint64 /*e_time*/) { //do nothing }