/*
* 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
}