diff --git a/src/game/BattleGround/BattleGroundDefines.h b/src/game/BattleGround/BattleGroundDefines.h new file mode 100644 index 0000000000..a8a57df40f --- /dev/null +++ b/src/game/BattleGround/BattleGroundDefines.h @@ -0,0 +1,49 @@ +/* + * This file is part of the CMaNGOS Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _BG_DEFINES_H +#define _BG_DEFINES_H + +#include "Common.h" + +#define BATTLEGROUND_ARENA_POINT_DISTRIBUTION_DAY 86400 // seconds in a day +#define COUNT_OF_PLAYERS_TO_AVERAGE_WAIT_TIME 10 + +enum BattleGroundQueueGroupTypes +{ + BG_QUEUE_PREMADE_ALLIANCE = 0, + BG_QUEUE_PREMADE_HORDE = 1, + BG_QUEUE_NORMAL_ALLIANCE = 2, + BG_QUEUE_NORMAL_HORDE = 3 +}; + +#define BG_QUEUE_GROUP_TYPES_COUNT 4 + +enum BattleGroundGroupJoinStatus +{ + BG_GROUP_JOIN_STATUS_TEAM_LEFT_QUEUE = -7, + BG_GROUP_JOIN_STATUS_QUEUED_FOR_RATED = -6, + BG_GROUP_JOIN_STATUS_CANNOT_QUEUE_FOR_RATED = -5, + BG_GROUP_JOIN_STATUS_TOO_MANY_QUEUES = -4, + BG_GROUP_JOIN_STATUS_NOT_IN_TEAM = -3, + BG_GROUP_JOIN_STATUS_DESERTERS = -2, + BG_GROUP_JOIN_STATUS_NOT_ELIGIBLE = -1, + BG_GROUP_JOIN_STATUS_SUCCESS = 0, +}; + +#endif \ No newline at end of file diff --git a/src/game/BattleGround/BattleGroundMgr.cpp b/src/game/BattleGround/BattleGroundMgr.cpp index 97931e6255..1ea31f484a 100644 --- a/src/game/BattleGround/BattleGroundMgr.cpp +++ b/src/game/BattleGround/BattleGroundMgr.cpp @@ -43,1224 +43,14 @@ INSTANTIATE_SINGLETON_1(BattleGroundMgr); -/*********************************************************/ -/*** BATTLEGROUND QUEUE SYSTEM ***/ -/*********************************************************/ - -BattleGroundQueue::BattleGroundQueue() -{ - for (uint8 i = 0; i < PVP_TEAM_COUNT; ++i) - { - for (uint8 j = 0; j < MAX_BATTLEGROUND_BRACKETS; ++j) - { - m_sumOfWaitTimes[i][j] = 0; - m_waitTimeLastPlayer[i][j] = 0; - - for (uint8 k = 0; k < COUNT_OF_PLAYERS_TO_AVERAGE_WAIT_TIME; ++k) - m_waitTimes[i][j][k] = 0; - } - } -} - -BattleGroundQueue::~BattleGroundQueue() -{ - m_queuedPlayers.clear(); - for (auto& m_queuedGroup : m_queuedGroups) - { - for (uint8 j = 0; j < BG_QUEUE_GROUP_TYPES_COUNT; ++j) - { - for (GroupsQueueType::iterator itr = m_queuedGroup[j].begin(); itr != m_queuedGroup[j].end(); ++itr) - delete (*itr); - - m_queuedGroup[j].clear(); - } - } -} - -/*********************************************************/ -/*** BATTLEGROUND QUEUE SELECTION POOLS ***/ -/*********************************************************/ - -// selection pool initialization, used to clean up from prev selection -void BattleGroundQueue::SelectionPool::Init() -{ - selectedGroups.clear(); - playerCount = 0; -} - -/** - Function that removes group infr from pool selection - - returns true when we need to try to add new group to selection pool - - returns false when selection pool is ok or when we kicked smaller group than we need to kick - - sometimes it can be called on empty selection pool - - @param size -*/ -bool BattleGroundQueue::SelectionPool::KickGroup(uint32 size) -{ - // find maxgroup or LAST group with size == size and kick it - bool found = false; - GroupsQueueType::iterator groupToKick = selectedGroups.begin(); - - for (GroupsQueueType::iterator itr = groupToKick; itr != selectedGroups.end(); ++itr) - { - if (abs((int32)((*itr)->players.size() - size)) <= 1) - { - groupToKick = itr; - found = true; - } - else if (!found && (*itr)->players.size() >= (*groupToKick)->players.size()) - groupToKick = itr; - } - - // if pool is empty, do nothing - if (GetPlayerCount()) - { - // update player count - GroupQueueInfo* queueInfo = (*groupToKick); - selectedGroups.erase(groupToKick); - playerCount -= queueInfo->players.size(); - - // return false if we kicked smaller group or there are enough players in selection pool - if (queueInfo->players.size() <= size + 1) - return false; - } - return true; -} - -/** - Function that adds group to selection pool - - returns true if we can invite more players, or when we added group to selection pool - - returns false when selection pool is full - - @param group queue info - @param desired count -*/ -bool BattleGroundQueue::SelectionPool::AddGroup(GroupQueueInfo* queueInfo, uint32 desiredCount, uint32 bgInstanceId) -{ - // if group is larger than desired count - don't allow to add it to pool - if (!queueInfo->isInvitedToBgInstanceGuid && - (!queueInfo->desiredInstanceId || queueInfo->desiredInstanceId == bgInstanceId) && - (desiredCount >= playerCount + queueInfo->players.size())) - { - selectedGroups.push_back(queueInfo); - // increase selected players count - playerCount += queueInfo->players.size(); - - return true; - } - - return playerCount < desiredCount; -} - -/*********************************************************/ -/*** BATTLEGROUND QUEUES ***/ -/*********************************************************/ - -/** - Function that adds group or player (grp == nullptr) to battleground queue with the given leader and specifications - - @param leader player - @param group - @param battleground type id - @param bracket entry - @param arena type - @param isRated - @param isPremade - @param arena rating - @param arena team id -*/ -GroupQueueInfo* BattleGroundQueue::AddGroup(Player* leader, Group* group, BattleGroundTypeId bgTypeId, BattleGroundBracketId bracketId, ArenaType arenaType, bool isRated, bool isPremade, uint32 instanceId, uint32 arenaRating, uint32 arenaTeamId) -{ - // create new ginfo - GroupQueueInfo* queueInfo = new GroupQueueInfo; - queueInfo->bgTypeId = bgTypeId; - queueInfo->arenaType = arenaType; - queueInfo->arenaTeamId = arenaTeamId; - queueInfo->isRated = isRated; - queueInfo->isInvitedToBgInstanceGuid = 0; - queueInfo->joinTime = WorldTimer::getMSTime(); - queueInfo->removeInviteTime = 0; - queueInfo->groupTeam = leader->GetTeam(); - queueInfo->desiredInstanceId = instanceId; - queueInfo->arenaTeamRating = arenaRating; - queueInfo->opponentsTeamRating = 0; - - queueInfo->players.clear(); - - // compute index (if group is premade or joined a rated match) to queues - uint32 index = 0; - if (!isRated && !isPremade) - index += PVP_TEAM_COUNT; // BG_QUEUE_PREMADE_* -> BG_QUEUE_NORMAL_* - - if (queueInfo->groupTeam == HORDE) - ++index; // BG_QUEUE_*_ALLIANCE -> BG_QUEUE_*_HORDE - - DEBUG_LOG("Adding Group to BattleGroundQueue bgTypeId : %u, bracket_id : %u, index : %u", bgTypeId, bracketId, index); - - uint32 lastOnlineTime = WorldTimer::getMSTime(); - - // announce world (this don't need mutex) - if (isRated && sWorld.getConfig(CONFIG_BOOL_ARENA_QUEUE_ANNOUNCER_JOIN)) - { - sWorld.SendWorldText(LANG_ARENA_QUEUE_ANNOUNCE_WORLD_JOIN, queueInfo->arenaType, queueInfo->arenaType, queueInfo->arenaTeamRating); - } - - // add players from group to ginfo - { - // std::lock_guard guard(m_Lock); - if (group) - { - for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) - { - Player* member = itr->getSource(); - if (!member) - continue; // this should never happen - PlayerQueueInfo& playerInfo = m_queuedPlayers[member->GetObjectGuid()]; - playerInfo.lastOnlineTime = lastOnlineTime; - playerInfo.groupInfo = queueInfo; - // add the pinfo to ginfo's list - queueInfo->players[member->GetObjectGuid()] = &playerInfo; - } - } - else - { - PlayerQueueInfo& playerInfo = m_queuedPlayers[leader->GetObjectGuid()]; - playerInfo.lastOnlineTime = lastOnlineTime; - playerInfo.groupInfo = queueInfo; - queueInfo->players[leader->GetObjectGuid()] = &playerInfo; - } - - // add GroupInfo to m_QueuedGroups - m_queuedGroups[bracketId][index].push_back(queueInfo); - - // announce to world, this code needs mutex - if (arenaType == ARENA_TYPE_NONE && !isRated && !isPremade && sWorld.getConfig(CONFIG_UINT32_BATTLEGROUND_QUEUE_ANNOUNCER_JOIN)) - { - if (BattleGround* bg = sBattleGroundMgr.GetBattleGroundTemplate(queueInfo->bgTypeId)) - { - char const* bgName = bg->GetName(); - uint32 minPlayers = bg->GetMinPlayersPerTeam(); - uint32 qHorde = 0; - uint32 qAlliance = 0; - uint32 q_min_level = leader->GetMinLevelForBattleGroundBracketId(bracketId, bgTypeId); - uint32 qMaxLevel = leader->GetMaxLevelForBattleGroundBracketId(bracketId, bgTypeId); - GroupsQueueType::const_iterator itr; - for (itr = m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE].begin(); itr != m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE].end(); ++itr) - if (!(*itr)->isInvitedToBgInstanceGuid) - qAlliance += (*itr)->players.size(); - - for (itr = m_queuedGroups[bracketId][BG_QUEUE_NORMAL_HORDE].begin(); itr != m_queuedGroups[bracketId][BG_QUEUE_NORMAL_HORDE].end(); ++itr) - if (!(*itr)->isInvitedToBgInstanceGuid) - qHorde += (*itr)->players.size(); - - // Show queue status to player only (when joining queue) - if (sWorld.getConfig(CONFIG_UINT32_BATTLEGROUND_QUEUE_ANNOUNCER_JOIN) == 1) - { - ChatHandler(leader).PSendSysMessage(LANG_BG_QUEUE_ANNOUNCE_SELF, bgName, q_min_level, qMaxLevel, - qAlliance, (minPlayers > qAlliance) ? minPlayers - qAlliance : (uint32)0, qHorde, (minPlayers > qHorde) ? minPlayers - qHorde : (uint32)0); - } - // System message - else - { - sWorld.SendWorldText(LANG_BG_QUEUE_ANNOUNCE_WORLD, bgName, q_min_level, qMaxLevel, - qAlliance, (minPlayers > qAlliance) ? minPlayers - qAlliance : (uint32)0, qHorde, (minPlayers > qHorde) ? minPlayers - qHorde : (uint32)0); - } - } - } - // release mutex - } - - return queueInfo; -} - -/** - Method that updates average update wait time - - @param group queue info - @param bracket id -*/ -void BattleGroundQueue::PlayerInvitedToBgUpdateAverageWaitTime(GroupQueueInfo* queueInfo, BattleGroundBracketId bracketId) -{ - uint32 timeInQueue = WorldTimer::getMSTimeDiff(queueInfo->joinTime, WorldTimer::getMSTime()); - uint8 teamIndex = TEAM_INDEX_ALLIANCE; // default set to BG_TEAM_ALLIANCE - or non rated arenas! - - if (queueInfo->arenaType == ARENA_TYPE_NONE) - { - if (queueInfo->groupTeam == HORDE) - teamIndex = TEAM_INDEX_HORDE; - } - else - { - if (queueInfo->isRated) - teamIndex = TEAM_INDEX_HORDE; // for rated arenas use BG_TEAM_HORDE - } - - // store pointer to arrayindex of player that was added first - uint32* lastPlayerAddedPointer = &(m_waitTimeLastPlayer[teamIndex][bracketId]); - - // remove his time from sum - m_sumOfWaitTimes[teamIndex][bracketId] -= m_waitTimes[teamIndex][bracketId][(*lastPlayerAddedPointer)]; - - // set average time to new - m_waitTimes[teamIndex][bracketId][(*lastPlayerAddedPointer)] = timeInQueue; - - // add new time to sum - m_sumOfWaitTimes[teamIndex][bracketId] += timeInQueue; - - // set index of last player added to next one - (*lastPlayerAddedPointer)++; - (*lastPlayerAddedPointer) %= COUNT_OF_PLAYERS_TO_AVERAGE_WAIT_TIME; -} - -/** - Function that returns averate queue wait time - - @param group queue info - @param bracket id -*/ -uint32 BattleGroundQueue::GetAverageQueueWaitTime(GroupQueueInfo* queueInfo, BattleGroundBracketId bracketId) -{ - uint8 teamIndex = TEAM_INDEX_ALLIANCE; // default set to BG_TEAM_ALLIANCE - or non rated arenas! - if (queueInfo->arenaType == ARENA_TYPE_NONE) - { - if (queueInfo->groupTeam == HORDE) - teamIndex = TEAM_INDEX_HORDE; - } - else - { - if (queueInfo->isRated) - teamIndex = TEAM_INDEX_HORDE; // for rated arenas use BG_TEAM_HORDE - } - - // check if there is enought values(we always add values > 0) - if (m_waitTimes[teamIndex][bracketId][COUNT_OF_PLAYERS_TO_AVERAGE_WAIT_TIME - 1]) - return (m_sumOfWaitTimes[teamIndex][bracketId] / COUNT_OF_PLAYERS_TO_AVERAGE_WAIT_TIME); - - // if there aren't enough values return 0 - not available - return 0; -} - -/** - Method that removes player from queue and from group info, if group info is empty then remove it too - - @param guid - @param decrease invite count -*/ -void BattleGroundQueue::RemovePlayer(ObjectGuid guid, bool decreaseInvitedCount) -{ - // Player *plr = sObjectMgr.GetPlayer(guid); - // std::lock_guard guard(m_Lock); - - int32 bracketId = -1; // signed for proper for-loop finish - - // remove player from map, if he's there - QueuedPlayersMap::iterator itr = m_queuedPlayers.find(guid); - if (itr == m_queuedPlayers.end()) - { - sLog.outError("BattleGroundQueue: couldn't find for remove: %s", guid.GetString().c_str()); - return; - } - - GroupQueueInfo* group = itr->second.groupInfo; - GroupsQueueType::iterator group_itr; - // mostly people with the highest levels are in battlegrounds, thats why - // we count from MAX_BATTLEGROUND_QUEUES - 1 to 0 - // variable index removes useless searching in other team's queue - uint32 index = GetTeamIndexByTeamId(group->groupTeam); - - for (int8 bracketIdTmp = MAX_BATTLEGROUND_BRACKETS - 1; bracketIdTmp >= 0 && bracketId == -1; --bracketIdTmp) - { - // we must check premade and normal team's queue - because when players from premade are joining bg, - // they leave groupinfo so we can't use its players size to find out index - for (uint8 j = index; j < BG_QUEUE_GROUP_TYPES_COUNT; j += BG_QUEUE_NORMAL_ALLIANCE) - { - for (GroupsQueueType::iterator group_itr_tmp = m_queuedGroups[bracketIdTmp][j].begin(); group_itr_tmp != m_queuedGroups[bracketIdTmp][j].end(); ++group_itr_tmp) - { - if ((*group_itr_tmp) == group) - { - bracketId = bracketIdTmp; - group_itr = group_itr_tmp; - // we must store index to be able to erase iterator - index = j; - break; - } - } - } - } - - // player can't be in queue without group, but just in case - if (bracketId == -1) - { - sLog.outError("BattleGroundQueue: ERROR Cannot find groupinfo for %s", guid.GetString().c_str()); - return; - } - DEBUG_LOG("BattleGroundQueue: Removing %s, from bracket_id %u", guid.GetString().c_str(), (uint32)bracketId); - - // ALL variables are correctly set - // We can ignore leveling up in queue - it should not cause crash - // remove player from group - // if only one player there, remove group - - // remove player queue info from group queue info - GroupQueueInfoPlayers::iterator pitr = group->players.find(guid); - if (pitr != group->players.end()) - group->players.erase(pitr); - - // if invited to bg, and should decrease invited count, then do it - if (decreaseInvitedCount && group->isInvitedToBgInstanceGuid) - { - BattleGround* bg = sBattleGroundMgr.GetBattleGround(group->isInvitedToBgInstanceGuid, group->bgTypeId); - if (bg) - bg->DecreaseInvitedCount(group->groupTeam); - } - - // remove player queue info - m_queuedPlayers.erase(itr); - - // announce to world if arena team left queue for rated match, show only once - if (group->arenaType != ARENA_TYPE_NONE && group->isRated && group->players.empty() && sWorld.getConfig(CONFIG_BOOL_ARENA_QUEUE_ANNOUNCER_EXIT)) - sWorld.SendWorldText(LANG_ARENA_QUEUE_ANNOUNCE_WORLD_EXIT, group->arenaType, group->arenaType, group->arenaTeamRating); - - // if player leaves queue and he is invited to rated arena match, then he have to loose - if (group->isInvitedToBgInstanceGuid && group->isRated && decreaseInvitedCount) - { - ArenaTeam* at = sObjectMgr.GetArenaTeamById(group->arenaTeamId); - if (at) - { - DEBUG_LOG("UPDATING memberLost's personal arena rating for %s by opponents rating: %u", guid.GetString().c_str(), group->opponentsTeamRating); - Player* plr = sObjectMgr.GetPlayer(guid); - if (plr) - at->MemberLost(plr, group->opponentsTeamRating); - else - at->OfflineMemberLost(guid, group->opponentsTeamRating); - - at->SaveToDB(); - } - } - - // remove group queue info if needed - if (group->players.empty()) - { - m_queuedGroups[bracketId][index].erase(group_itr); - delete group; - } - // if group wasn't empty, so it wasn't deleted, and player have left a rated - // queue -> everyone from the group should leave too - // don't remove recursively if already invited to bg! - else if (!group->isInvitedToBgInstanceGuid && group->isRated) - { - // remove next player, this is recursive - // first send removal information - if (Player* plr2 = sObjectMgr.GetPlayer(group->players.begin()->first)) - { - BattleGround* bg = sBattleGroundMgr.GetBattleGroundTemplate(group->bgTypeId); - BattleGroundQueueTypeId bgQueueTypeId = BattleGroundMgr::BgQueueTypeId(group->bgTypeId, group->arenaType); - - uint32 queueSlot = plr2->GetBattleGroundQueueIndex(bgQueueTypeId); - plr2->RemoveBattleGroundQueueId(bgQueueTypeId); // must be called this way, because if you move this call to - - // queue->removeplayer, it causes bugs - WorldPacket data; - sBattleGroundMgr.BuildBattleGroundStatusPacket(data, bg, queueSlot, STATUS_NONE, 0, 0, ARENA_TYPE_NONE, TEAM_NONE); - plr2->GetSession()->SendPacket(data); - } - - // then actually delete, this may delete the group as well! - RemovePlayer(group->players.begin()->first, decreaseInvitedCount); - } -} - -/** - Function that returns true when player is in queue and is invited to bgInstanceGuid - - @param player guid - @param battleground instance guid - @param remove time -*/ -bool BattleGroundQueue::IsPlayerInvited(ObjectGuid playerGuid, const uint32 bgInstanceGuid, const uint32 removeTime) -{ - // std::lock_guard g(m_Lock); - QueuedPlayersMap::const_iterator qItr = m_queuedPlayers.find(playerGuid); - return (qItr != m_queuedPlayers.end() - && qItr->second.groupInfo->isInvitedToBgInstanceGuid == bgInstanceGuid - && qItr->second.groupInfo->removeInviteTime == removeTime); -} - -/** - Function that returns player group info data - - returns true when the player is found in queue - - @param player guid - @param group queue info -*/ -bool BattleGroundQueue::GetPlayerGroupInfoData(ObjectGuid guid, GroupQueueInfo* queueInfo) -{ - // std::lock_guard g(m_Lock); - QueuedPlayersMap::const_iterator qItr = m_queuedPlayers.find(guid); - if (qItr == m_queuedPlayers.end()) - return false; - - *queueInfo = *(qItr->second.groupInfo); - return true; -} - -/** - Function that invites group to battleground - - @param group queue info - @param battleground - @param team -*/ -bool BattleGroundQueue::InviteGroupToBg(GroupQueueInfo* queueInfo, BattleGround* bg, Team team) -{ - // set side if needed - if (team == ALLIANCE || team == HORDE) - queueInfo->groupTeam = team; - - if (!queueInfo->isInvitedToBgInstanceGuid) - { - // not yet invited - // set invitation - queueInfo->isInvitedToBgInstanceGuid = bg->GetInstanceId(); - BattleGroundTypeId bgTypeId = bg->GetTypeId(); - BattleGroundQueueTypeId bgQueueTypeId = BattleGroundMgr::BgQueueTypeId(bgTypeId, bg->GetArenaType()); - BattleGroundBracketId bracket_id = bg->GetBracketId(); - - // set ArenaTeamId for rated matches - if (bg->IsArena() && bg->IsRated()) - bg->SetArenaTeamIdForTeam(queueInfo->groupTeam, queueInfo->arenaTeamId); - - queueInfo->removeInviteTime = WorldTimer::getMSTime() + INVITE_ACCEPT_WAIT_TIME; - - // loop through the players - for (GroupQueueInfoPlayers::iterator itr = queueInfo->players.begin(); itr != queueInfo->players.end(); ++itr) - { - // get the player - Player* plr = sObjectMgr.GetPlayer(itr->first); - // if offline, skip him, this should not happen - player is removed from queue when he logs out - if (!plr) - continue; - - // invite the player - PlayerInvitedToBgUpdateAverageWaitTime(queueInfo, bracket_id); - // sBattleGroundMgr.InvitePlayer(plr, bg, ginfo->Team); - - // set invited player counters - bg->IncreaseInvitedCount(queueInfo->groupTeam); - - plr->SetInviteForBattleGroundQueueType(bgQueueTypeId, queueInfo->isInvitedToBgInstanceGuid); - - // create remind invite events - BgQueueInviteEvent* inviteEvent = new BgQueueInviteEvent(plr->GetObjectGuid(), queueInfo->isInvitedToBgInstanceGuid, bgTypeId, queueInfo->arenaType, queueInfo->removeInviteTime); - plr->m_events.AddEvent(inviteEvent, plr->m_events.CalculateTime(INVITATION_REMIND_TIME)); - - // create automatic remove events - BgQueueRemoveEvent* removeEvent = new BgQueueRemoveEvent(plr->GetObjectGuid(), queueInfo->isInvitedToBgInstanceGuid, bgTypeId, bgQueueTypeId, queueInfo->removeInviteTime); - plr->m_events.AddEvent(removeEvent, plr->m_events.CalculateTime(INVITE_ACCEPT_WAIT_TIME)); - - WorldPacket data; - - uint32 queueSlot = plr->GetBattleGroundQueueIndex(bgQueueTypeId); - - DEBUG_LOG("Battleground: invited %s to BG instance %u queueindex %u bgtype %u, I can't help it if they don't press the enter battle button.", plr->GetGuidStr().c_str(), bg->GetInstanceId(), queueSlot, bg->GetTypeId()); - - // send status packet - sBattleGroundMgr.BuildBattleGroundStatusPacket(data, bg, queueSlot, STATUS_WAIT_JOIN, INVITE_ACCEPT_WAIT_TIME, 0, queueInfo->arenaType, TEAM_NONE); - - plr->GetSession()->SendPacket(data); - } - return true; - } - - return false; -} - -/** - Method that invites players to an already running battleground - - invitation is based on config file - - large groups are disadvantageous, because they will be kicked first if invitation type = 1 - - @param battleground - @param bracket id -*/ -void BattleGroundQueue::FillPlayersToBg(BattleGround* bg, BattleGroundBracketId bracketId) -{ - int32 hordeFree = bg->GetFreeSlotsForTeam(HORDE); - int32 aliFree = bg->GetFreeSlotsForTeam(ALLIANCE); - - // iterator for iterating through bg queue - GroupsQueueType::const_iterator Ali_itr = m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE].begin(); - // count of groups in queue - used to stop cycles - uint32 aliCount = m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE].size(); - // index to queue which group is current - uint32 aliIndex = 0; - for (; aliIndex < aliCount && m_selectionPools[TEAM_INDEX_ALLIANCE].AddGroup((*Ali_itr), aliFree, bg->GetClientInstanceId()); ++aliIndex) - ++Ali_itr; - - // the same thing for horde - GroupsQueueType::const_iterator Horde_itr = m_queuedGroups[bracketId][BG_QUEUE_NORMAL_HORDE].begin(); - uint32 hordeCount = m_queuedGroups[bracketId][BG_QUEUE_NORMAL_HORDE].size(); - uint32 hordeIndex = 0; - for (; hordeIndex < hordeCount && m_selectionPools[TEAM_INDEX_HORDE].AddGroup((*Horde_itr), hordeFree, bg->GetClientInstanceId()); ++hordeIndex) - ++Horde_itr; - - // if ofc like BG queue invitation is set in config, then we are happy - if (sWorld.getConfig(CONFIG_UINT32_BATTLEGROUND_INVITATION_TYPE) == 0) - 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_INDEX_ALLIANCE].GetPlayerCount()); - int32 diffHorde = hordeFree - int32(m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount()); - while (abs(diffAli - diffHorde) > 1 && (m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount() > 0 || m_selectionPools[TEAM_INDEX_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_INDEX_ALLIANCE].KickGroup(diffHorde - diffAli)) - { - for (; aliIndex < aliCount && m_selectionPools[TEAM_INDEX_ALLIANCE].AddGroup((*Ali_itr), (aliFree >= diffHorde) ? aliFree - diffHorde : 0, bg->GetClientInstanceId()); ++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_INDEX_ALLIANCE].GetPlayerCount()) - { - if (aliFree <= diffHorde + 1) - break; - m_selectionPools[TEAM_INDEX_HORDE].KickGroup(diffHorde - diffAli); - } - } - else - { - // kick horde group, add to pool new group if needed - if (m_selectionPools[TEAM_INDEX_HORDE].KickGroup(diffAli - diffHorde)) - { - for (; hordeIndex < hordeCount && m_selectionPools[TEAM_INDEX_HORDE].AddGroup((*Horde_itr), (hordeFree >= diffAli) ? hordeFree - diffAli : 0, bg->GetClientInstanceId()); ++hordeIndex) - ++Horde_itr; - } - if (!m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount()) - { - if (hordeFree <= diffAli + 1) - break; - m_selectionPools[TEAM_INDEX_ALLIANCE].KickGroup(diffAli - diffHorde); - } - } - - // count diffs after small update - diffAli = aliFree - int32(m_selectionPools[TEAM_INDEX_ALLIANCE].GetPlayerCount()); - diffHorde = hordeFree - int32(m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount()); - } -} - -/** - Method that checks if (premade vs premade) battlegrouns is possible - - then after 30 mins (default) in queue it moves premade group to normal queue - - it tries to invite as much players as it can - to maxPlayersPerTeam, because premade groups have more than minPlayersPerTeam players - - @param bracket id - @param min players per team - @param max players per team -*/ -bool BattleGroundQueue::CheckPremadeMatch(BattleGroundBracketId bracketId, uint32 minPlayersPerTeam, uint32 maxPlayersPerTeam) -{ - // check match - if (!m_queuedGroups[bracketId][BG_QUEUE_PREMADE_ALLIANCE].empty() && !m_queuedGroups[bracketId][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[bracketId][BG_QUEUE_PREMADE_ALLIANCE].begin(); ali_group != m_queuedGroups[bracketId][BG_QUEUE_PREMADE_ALLIANCE].end(); ++ali_group) - if (!(*ali_group)->isInvitedToBgInstanceGuid) - break; - - for (horde_group = m_queuedGroups[bracketId][BG_QUEUE_PREMADE_HORDE].begin(); horde_group != m_queuedGroups[bracketId][BG_QUEUE_PREMADE_HORDE].end(); ++horde_group) - if (!(*horde_group)->isInvitedToBgInstanceGuid) - break; - - if (ali_group != m_queuedGroups[bracketId][BG_QUEUE_PREMADE_ALLIANCE].end() && horde_group != m_queuedGroups[bracketId][BG_QUEUE_PREMADE_HORDE].end()) - { - m_selectionPools[TEAM_INDEX_ALLIANCE].AddGroup((*ali_group), maxPlayersPerTeam, 0); - m_selectionPools[TEAM_INDEX_HORDE].AddGroup((*horde_group), maxPlayersPerTeam, 0); - - // add groups/players from normal queue to size of bigger group - uint32 maxPlayers = std::max(m_selectionPools[TEAM_INDEX_ALLIANCE].GetPlayerCount(), m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount()); - for (uint8 i = 0; i < PVP_TEAM_COUNT; ++i) - { - for (GroupsQueueType::const_iterator itr = m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE + i].begin(); itr != m_queuedGroups[bracketId][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, 0)) - 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 = WorldTimer::getMSTime() - sWorld.getConfig(CONFIG_UINT32_BATTLEGROUND_PREMADE_GROUP_WAIT_FOR_MATCH); - for (uint8 i = 0; i < PVP_TEAM_COUNT; ++i) - { - if (!m_queuedGroups[bracketId][BG_QUEUE_PREMADE_ALLIANCE + i].empty()) - { - GroupsQueueType::iterator itr = m_queuedGroups[bracketId][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 - m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE + i].push_front((*itr)); - m_queuedGroups[bracketId][BG_QUEUE_PREMADE_ALLIANCE + i].erase(itr); - } - } - } - - // selection pools are not set - return false; -} - -/** - Method that tries to create battleground or arena with minPlayersPerTeam against maxPlayersPerTeam - - @param battleground - @param bracket id - @param min players - @param max players -*/ -bool BattleGroundQueue::CheckNormalMatch(BattleGround* bgTemplate, BattleGroundBracketId bracketId, uint32 minPlayers, uint32 maxPlayers) -{ - GroupsQueueType::const_iterator itr_team[PVP_TEAM_COUNT]; - for (uint8 i = 0; i < PVP_TEAM_COUNT; ++i) - { - itr_team[i] = m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE + i].begin(); - for (; itr_team[i] != m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE + i].end(); ++(itr_team[i])) - { - if (!(*(itr_team[i]))->isInvitedToBgInstanceGuid) - { - m_selectionPools[i].AddGroup(*(itr_team[i]), maxPlayers, 0); - 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_INDEX_ALLIANCE; - if (m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount() < m_selectionPools[TEAM_INDEX_ALLIANCE].GetPlayerCount()) - j = TEAM_INDEX_HORDE; - - if (sWorld.getConfig(CONFIG_UINT32_BATTLEGROUND_INVITATION_TYPE) != 0 - && m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount() >= minPlayers && m_selectionPools[TEAM_INDEX_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[bracketId][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_TEAM_COUNT].GetPlayerCount(), 0)) - break; - } - // do not allow to start bg with more than 2 players more on 1 faction - if (abs((int32)(m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount() - m_selectionPools[TEAM_INDEX_ALLIANCE].GetPlayerCount())) > 2) - return false; - } - - // allow 1v0 if debug bg - if (sBattleGroundMgr.IsTesting() && bgTemplate->IsBattleGround() && (m_selectionPools[TEAM_INDEX_ALLIANCE].GetPlayerCount() || m_selectionPools[TEAM_INDEX_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_INDEX_ALLIANCE].GetPlayerCount() >= minPlayers && m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount() >= minPlayers; -} - -/** - Method that will check if we can invite players to same faction skirmish match - - @param bracket id - @param min players -*/ -bool BattleGroundQueue::CheckSkirmishForSameFaction(BattleGroundBracketId bracketId, uint32 minPlayersPerTeam) -{ - if (m_selectionPools[TEAM_INDEX_ALLIANCE].GetPlayerCount() < minPlayersPerTeam && m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount() < minPlayersPerTeam) - return false; - - PvpTeamIndex teamIdx = TEAM_INDEX_ALLIANCE; - PvpTeamIndex otherTeamIdx = TEAM_INDEX_HORDE; - Team otherTeamId = HORDE; - - if (m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount() == minPlayersPerTeam) - { - teamIdx = TEAM_INDEX_HORDE; - otherTeamIdx = TEAM_INDEX_ALLIANCE; - otherTeamId = ALLIANCE; - } - - // clear other team's selection - m_selectionPools[otherTeamIdx].Init(); - // store last ginfo pointer - GroupQueueInfo* ginfo = m_selectionPools[teamIdx].selectedGroups.back(); - // set itr_team to group that was added to selection pool latest - GroupsQueueType::iterator itr_team = m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE + teamIdx].begin(); - for (; itr_team != m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE + teamIdx].end(); ++itr_team) - if (ginfo == *itr_team) - break; - - if (itr_team == m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE + teamIdx].end()) - return false; - - GroupsQueueType::iterator itr_team2 = itr_team; - ++itr_team2; - // invite players to other selection pool - for (; itr_team2 != m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE + teamIdx].end(); ++itr_team2) - { - // if selection pool is full then break; - if (!(*itr_team2)->isInvitedToBgInstanceGuid && !m_selectionPools[otherTeamIdx].AddGroup(*itr_team2, minPlayersPerTeam, 0)) - break; - } - - if (m_selectionPools[otherTeamIdx].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[otherTeamIdx].selectedGroups.begin(); itr != m_selectionPools[otherTeamIdx].selectedGroups.end(); ++itr) - { - // set correct team - (*itr)->groupTeam = otherTeamId; - - // add team to other queue - m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE + otherTeamIdx].push_front(*itr); - - // remove team from old queue - GroupsQueueType::iterator itr2 = itr_team; - ++itr2; - for (; itr2 != m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE + teamIdx].end(); ++itr2) - { - if (*itr2 == *itr) - { - m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE + teamIdx].erase(itr2); - break; - } - } - } - return true; -} - -/** - Method that is called when group is inserted, or player / group is removed from BG Queue - there is only one player's status changed, so we don't use while(true) cycles to invite whole queue - - it must be called after fully adding the members of a group to ensure group joining - - should be called from BattleGround::RemovePlayer function in some cases - - @param bg type id - @param bracket id - @param arena type - @param isRated - @param arenaRating -*/ -void BattleGroundQueue::Update(BattleGroundTypeId bgTypeId, BattleGroundBracketId bracketId, ArenaType arenaType, bool isRated, uint32 arenaRating) -{ - // std::lock_guard guard(m_Lock); - // if no players in queue - do nothing - if (m_queuedGroups[bracketId][BG_QUEUE_PREMADE_ALLIANCE].empty() && - m_queuedGroups[bracketId][BG_QUEUE_PREMADE_HORDE].empty() && - m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE].empty() && - m_queuedGroups[bracketId][BG_QUEUE_NORMAL_HORDE].empty()) - return; - - // 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 - BgFreeSlotQueueType::iterator next; - for (BgFreeSlotQueueType::iterator itr = sBattleGroundMgr.BgFreeSlotQueue[bgTypeId].begin(); itr != sBattleGroundMgr.BgFreeSlotQueue[bgTypeId].end(); itr = next) - { - next = itr; - ++next; - // DO NOT allow queue manager to invite new player to arena - if ((*itr)->IsBattleGround() && (*itr)->GetTypeId() == bgTypeId && (*itr)->GetBracketId() == bracketId && - (*itr)->GetStatus() > STATUS_WAIT_QUEUE && (*itr)->GetStatus() < STATUS_WAIT_LEAVE) - { - BattleGround* bg = *itr; // we have to store battleground pointer here, because when battleground is full, it is removed from free queue (not yet implemented!!) - // and iterator is invalid - - // clear selection pools - m_selectionPools[TEAM_INDEX_ALLIANCE].Init(); - m_selectionPools[TEAM_INDEX_HORDE].Init(); - - // call a function that does the job for us - FillPlayersToBg(bg, bracketId); - - // now everything is set, invite players - for (GroupsQueueType::const_iterator citr = m_selectionPools[TEAM_INDEX_ALLIANCE].selectedGroups.begin(); citr != m_selectionPools[TEAM_INDEX_ALLIANCE].selectedGroups.end(); ++citr) - InviteGroupToBg((*citr), bg, (*citr)->groupTeam); - for (GroupsQueueType::const_iterator citr = m_selectionPools[TEAM_INDEX_HORDE].selectedGroups.begin(); citr != m_selectionPools[TEAM_INDEX_HORDE].selectedGroups.end(); ++citr) - InviteGroupToBg((*citr), bg, (*citr)->groupTeam); - - if (!bg->HasFreeSlots()) - { - // remove BG from BGFreeSlotQueue - bg->RemoveFromBgFreeSlotQueue(); - } - } - } - - // finished iterating through the bgs with free slots, maybe we need to create a new bg - - BattleGround* bgTemplate = sBattleGroundMgr.GetBattleGroundTemplate(bgTypeId); - if (!bgTemplate) - { - sLog.outError("Battleground: Update: bg template not found for %u", bgTypeId); - return; - } - - // get the min. players per team, properly for larger arenas as well. (must have full teams for arena matches!) - uint32 minPlayersPerTeam = bgTemplate->GetMinPlayersPerTeam(); - uint32 maxPlayersPerTeam = bgTemplate->GetMaxPlayersPerTeam(); - - if (sBattleGroundMgr.IsTesting()) - minPlayersPerTeam = 1; - - if (bgTemplate->IsArena()) - { - if (sBattleGroundMgr.IsArenaTesting()) - { - maxPlayersPerTeam = 1; - minPlayersPerTeam = 1; - } - else - { - // this switch can be much shorter - maxPlayersPerTeam = arenaType; - minPlayersPerTeam = arenaType; - /*switch(arenaType) - { - case ARENA_TYPE_2v2: - MaxPlayersPerTeam = 2; - MinPlayersPerTeam = 2; - break; - case ARENA_TYPE_3v3: - MaxPlayersPerTeam = 3; - MinPlayersPerTeam = 3; - break; - case ARENA_TYPE_5v5: - MaxPlayersPerTeam = 5; - MinPlayersPerTeam = 5; - break; - }*/ - } - } - - m_selectionPools[TEAM_INDEX_ALLIANCE].Init(); - m_selectionPools[TEAM_INDEX_HORDE].Init(); - - if (bgTemplate->IsBattleGround()) - { - // check if there is premade against premade match - if (CheckPremadeMatch(bracketId, minPlayersPerTeam, maxPlayersPerTeam)) - { - // create new battleground - BattleGround* bg2 = sBattleGroundMgr.CreateNewBattleGround(bgTypeId, bracketId, ARENA_TYPE_NONE, false); - if (!bg2) - { - sLog.outError("BattleGroundQueue::Update - Cannot create battleground: %u", bgTypeId); - return; - } - - // invite those selection pools - for (uint8 i = 0; i < PVP_TEAM_COUNT; ++i) - for (GroupsQueueType::const_iterator citr = m_selectionPools[TEAM_INDEX_ALLIANCE + i].selectedGroups.begin(); citr != m_selectionPools[TEAM_INDEX_ALLIANCE + i].selectedGroups.end(); ++citr) - InviteGroupToBg((*citr), bg2, (*citr)->groupTeam); - - // start bg - bg2->StartBattleGround(); - // clear structures - m_selectionPools[TEAM_INDEX_ALLIANCE].Init(); - m_selectionPools[TEAM_INDEX_HORDE].Init(); - } - } - - // now check if there are in queues enough players to start new game of (normal battleground, or non-rated arena) - if (!isRated) - { - // if there are enough players in pools, start new battleground or non rated arena - if (CheckNormalMatch(bgTemplate, bracketId, minPlayersPerTeam, maxPlayersPerTeam) - || (bgTemplate->IsArena() && CheckSkirmishForSameFaction(bracketId, minPlayersPerTeam))) - { - // we successfully created a pool - BattleGround* bg2 = sBattleGroundMgr.CreateNewBattleGround(bgTypeId, bracketId, arenaType, false); - if (!bg2) - { - sLog.outError("BattleGroundQueue::Update - Cannot create battleground: %u", bgTypeId); - return; - } - - // invite those selection pools - for (uint8 i = 0; i < PVP_TEAM_COUNT; ++i) - for (GroupsQueueType::const_iterator citr = m_selectionPools[TEAM_INDEX_ALLIANCE + i].selectedGroups.begin(); citr != m_selectionPools[TEAM_INDEX_ALLIANCE + i].selectedGroups.end(); ++citr) - InviteGroupToBg((*citr), bg2, (*citr)->groupTeam); - - // start bg - bg2->StartBattleGround(); - } - } - else if (bgTemplate->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[bracketId][BG_QUEUE_PREMADE_ALLIANCE].empty()) - { - front1 = m_queuedGroups[bracketId][BG_QUEUE_PREMADE_ALLIANCE].front(); - arenaRating = front1->arenaTeamRating; - } - - if (!m_queuedGroups[bracketId][BG_QUEUE_PREMADE_HORDE].empty()) - { - front2 = m_queuedGroups[bracketId][BG_QUEUE_PREMADE_HORDE].front(); - arenaRating = front2->arenaTeamRating; - } - - if (front1 && front2) - { - if (front1->joinTime < front2->joinTime) - arenaRating = front1->arenaTeamRating; - } - 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 - uint32 discardTime = WorldTimer::getMSTime() - sBattleGroundMgr.GetRatingDiscardTimer(); - - // we need to find 2 teams which will play next game - - GroupsQueueType::iterator itr_team[PVP_TEAM_COUNT]; - - // optimalization : --- we dont need to use selection_pools - each update we select max 2 groups - - for (uint8 i = BG_QUEUE_PREMADE_ALLIANCE; i < BG_QUEUE_NORMAL_ALLIANCE; ++i) - { - // take the group that joined first - itr_team[i] = m_queuedGroups[bracketId][i].begin(); - for (; itr_team[i] != m_queuedGroups[bracketId][i].end(); ++(itr_team[i])) - { - // if group match conditions, then add it to pool - if (!(*itr_team[i])->isInvitedToBgInstanceGuid - && (((*itr_team[i])->arenaTeamRating >= arenaMinRating && (*itr_team[i])->arenaTeamRating <= arenaMaxRating) - || (*itr_team[i])->joinTime < discardTime)) - { - m_selectionPools[i].AddGroup((*itr_team[i]), maxPlayersPerTeam, 0); - // break for cycle to be able to start selecting another group from same faction queue - break; - } - } - } - // now we are done if we have 2 groups - ali vs horde! - // if we don't have, we must try to continue search in same queue - // tmp variables are correctly set - // this code isn't much userfriendly - but it is supposed to continue search for mathing group in HORDE queue - if (m_selectionPools[TEAM_INDEX_ALLIANCE].GetPlayerCount() == 0 && m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount()) - { - itr_team[TEAM_INDEX_ALLIANCE] = itr_team[TEAM_INDEX_HORDE]; - ++itr_team[TEAM_INDEX_ALLIANCE]; - - for (; itr_team[TEAM_INDEX_ALLIANCE] != m_queuedGroups[bracketId][BG_QUEUE_PREMADE_HORDE].end(); ++(itr_team[TEAM_INDEX_ALLIANCE])) - { - if (!(*itr_team[TEAM_INDEX_ALLIANCE])->isInvitedToBgInstanceGuid - && (((*itr_team[TEAM_INDEX_ALLIANCE])->arenaTeamRating >= arenaMinRating && (*itr_team[TEAM_INDEX_ALLIANCE])->arenaTeamRating <= arenaMaxRating) - || (*itr_team[TEAM_INDEX_ALLIANCE])->joinTime < discardTime)) - { - m_selectionPools[TEAM_INDEX_ALLIANCE].AddGroup((*itr_team[TEAM_INDEX_ALLIANCE]), maxPlayersPerTeam, 0); - break; - } - } - } - // this code isn't much userfriendly - but it is supposed to continue search for mathing group in ALLIANCE queue - if (m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount() == 0 && m_selectionPools[TEAM_INDEX_ALLIANCE].GetPlayerCount()) - { - itr_team[TEAM_INDEX_HORDE] = itr_team[TEAM_INDEX_ALLIANCE]; - ++itr_team[TEAM_INDEX_HORDE]; - - for (; itr_team[TEAM_INDEX_HORDE] != m_queuedGroups[bracketId][BG_QUEUE_PREMADE_ALLIANCE].end(); ++(itr_team[TEAM_INDEX_HORDE])) - { - if (!(*itr_team[TEAM_INDEX_HORDE])->isInvitedToBgInstanceGuid - && (((*itr_team[TEAM_INDEX_HORDE])->arenaTeamRating >= arenaMinRating && (*itr_team[TEAM_INDEX_HORDE])->arenaTeamRating <= arenaMaxRating) - || (*itr_team[TEAM_INDEX_HORDE])->joinTime < discardTime)) - { - m_selectionPools[TEAM_INDEX_HORDE].AddGroup((*itr_team[TEAM_INDEX_HORDE]), maxPlayersPerTeam, 0); - break; - } - } - } - - // if we have 2 teams, then start new arena and invite players! - if (m_selectionPools[TEAM_INDEX_ALLIANCE].GetPlayerCount() && m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount()) - { - BattleGround* arena = sBattleGroundMgr.CreateNewBattleGround(bgTypeId, bracketId, arenaType, true); - if (!arena) - { - sLog.outError("BattlegroundQueue::Update couldn't create arena instance for rated arena match!"); - return; - } - - (*(itr_team[TEAM_INDEX_ALLIANCE]))->opponentsTeamRating = (*(itr_team[TEAM_INDEX_HORDE]))->arenaTeamRating; - DEBUG_LOG("setting oposite teamrating for team %u to %u", (*(itr_team[TEAM_INDEX_ALLIANCE]))->arenaTeamId, (*(itr_team[TEAM_INDEX_ALLIANCE]))->opponentsTeamRating); - (*(itr_team[TEAM_INDEX_HORDE]))->opponentsTeamRating = (*(itr_team[TEAM_INDEX_ALLIANCE]))->arenaTeamRating; - DEBUG_LOG("setting oposite teamrating for team %u to %u", (*(itr_team[TEAM_INDEX_HORDE]))->arenaTeamId, (*(itr_team[TEAM_INDEX_HORDE]))->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 ((*(itr_team[TEAM_INDEX_ALLIANCE]))->groupTeam != ALLIANCE) - { - // add to alliance queue - m_queuedGroups[bracketId][BG_QUEUE_PREMADE_ALLIANCE].push_front(*(itr_team[TEAM_INDEX_ALLIANCE])); - // erase from horde queue - m_queuedGroups[bracketId][BG_QUEUE_PREMADE_HORDE].erase(itr_team[TEAM_INDEX_ALLIANCE]); - itr_team[TEAM_INDEX_ALLIANCE] = m_queuedGroups[bracketId][BG_QUEUE_PREMADE_ALLIANCE].begin(); - } - - if ((*(itr_team[TEAM_INDEX_HORDE]))->groupTeam != HORDE) - { - m_queuedGroups[bracketId][BG_QUEUE_PREMADE_HORDE].push_front(*(itr_team[TEAM_INDEX_HORDE])); - m_queuedGroups[bracketId][BG_QUEUE_PREMADE_ALLIANCE].erase(itr_team[TEAM_INDEX_HORDE]); - itr_team[TEAM_INDEX_HORDE] = m_queuedGroups[bracketId][BG_QUEUE_PREMADE_HORDE].begin(); - } - - InviteGroupToBg(*(itr_team[TEAM_INDEX_ALLIANCE]), arena, ALLIANCE); - InviteGroupToBg(*(itr_team[TEAM_INDEX_HORDE]), arena, HORDE); - - DEBUG_LOG("Starting rated arena match!"); - - arena->StartBattleGround(); - } - } -} - -/*********************************************************/ -/*** BATTLEGROUND QUEUE EVENTS ***/ -/*********************************************************/ - -/** - Function that executes battleground queue invite event -*/ -bool BgQueueInviteEvent::Execute(uint64 /*e_time*/, uint32 /*p_time*/) -{ - sWorld.GetMessager().AddMessage([event = *this](World* /*world*/) - { - Player* plr = sObjectMgr.GetPlayer(event.m_playerGuid); - // player logged off (we should do nothing, he is correctly removed from queue in another procedure) - if (!plr) - return; - - BattleGround* bg = sBattleGroundMgr.GetBattleGround(event.m_bgInstanceGuid, event.m_bgTypeId); - // if battleground ended and its instance deleted - do nothing - if (!bg) - return; - - BattleGroundQueueTypeId bgQueueTypeId = BattleGroundMgr::BgQueueTypeId(bg->GetTypeId(), bg->GetArenaType()); - uint32 queueSlot = plr->GetBattleGroundQueueIndex(bgQueueTypeId); - if (queueSlot < PLAYER_MAX_BATTLEGROUND_QUEUES) // player is in queue or in battleground - { - // check if player is invited to this bg - BattleGroundQueue& bgQueue = sBattleGroundMgr.m_battleGroundQueues[bgQueueTypeId]; - if (bgQueue.IsPlayerInvited(event.m_playerGuid, event.m_bgInstanceGuid, event.m_removeTime)) - { - WorldPacket data; - // we must send remaining time in queue - sBattleGroundMgr.BuildBattleGroundStatusPacket(data, bg, queueSlot, STATUS_WAIT_JOIN, INVITE_ACCEPT_WAIT_TIME - INVITATION_REMIND_TIME, 0, event.m_arenaType, TEAM_NONE); - plr->GetSession()->SendPacket(data); - } - } - }); - return true; // event will be deleted -} - -void BgQueueInviteEvent::Abort(uint64 /*e_time*/) -{ - // do nothing -} - -/** - Function that executes battleground queue remove event - this event has many possibilities when it is executed: - 1. player is in battleground ( he clicked enter on invitation window ) - 2. player left battleground queue and he isn't there any more - 3. player left battleground queue and he joined it again and IsInvitedToBGInstanceGUID = 0 - 4. player left queue and he joined again and he has been invited to same battleground again -> we should not remove him from queue yet - 5. player is invited to bg and he didn't choose what to do and timer expired - only in this condition we should call queue::RemovePlayer - we must remove player in the 5. case even if battleground object doesn't exist! -*/ -bool BgQueueRemoveEvent::Execute(uint64 /*e_time*/, uint32 /*p_time*/) -{ - sWorld.GetMessager().AddMessage([event = *this](World* /*world*/) - { - Player* plr = sObjectMgr.GetPlayer(event.m_playerGuid); - if (!plr) - // player logged off (we should do nothing, he is correctly removed from queue in another procedure) - return; - - BattleGround* bg = sBattleGroundMgr.GetBattleGround(event.m_bgInstanceGuid, event.m_bgTypeId); - // battleground can be deleted already when we are removing queue info - // bg pointer can be nullptr! so use it carefully! - - uint32 queueSlot = plr->GetBattleGroundQueueIndex(event.m_bgQueueTypeId); - if (queueSlot < PLAYER_MAX_BATTLEGROUND_QUEUES) // player is in queue, or in Battleground - { - // check if player is in queue for this BG and if we are removing his invite event - BattleGroundQueue& bgQueue = sBattleGroundMgr.m_battleGroundQueues[event.m_bgQueueTypeId]; - if (bgQueue.IsPlayerInvited(event.m_playerGuid, event.m_bgInstanceGuid, event.m_removeTime)) - { - DEBUG_LOG("Battleground: removing player %u from bg queue for instance %u because of not pressing enter battle in time.", plr->GetGUIDLow(), event.m_bgInstanceGuid); - - plr->RemoveBattleGroundQueueId(event.m_bgQueueTypeId); - bgQueue.RemovePlayer(event.m_playerGuid, true); - - // update queues if battleground isn't ended - if (bg && bg->IsBattleGround() && bg->GetStatus() != STATUS_WAIT_LEAVE) - sBattleGroundMgr.ScheduleQueueUpdate(0, ARENA_TYPE_NONE, event.m_bgQueueTypeId, event.m_bgTypeId, bg->GetBracketId()); - - WorldPacket data; - sBattleGroundMgr.BuildBattleGroundStatusPacket(data, bg, queueSlot, STATUS_NONE, 0, 0, ARENA_TYPE_NONE, TEAM_NONE); - plr->GetSession()->SendPacket(data); - } - } - }); - - // event will be deleted - return true; -} - -void BgQueueRemoveEvent::Abort(uint64 /*e_time*/) -{ - // do nothing -} - /*********************************************************/ /*** BATTLEGROUND MANAGER ***/ /*********************************************************/ -BattleGroundMgr::BattleGroundMgr() : m_nextAutoDistributionTime(0), m_autoDistributionTimeChecker(0), m_arenaTesting(false) +BattleGroundMgr::BattleGroundMgr() : m_arenaTesting(false) { for (uint8 i = BATTLEGROUND_TYPE_NONE; i < MAX_BATTLEGROUND_TYPE_ID; ++i) m_battleGrounds[i].clear(); - m_nextRatingDiscardUpdate = sWorld.getConfig(CONFIG_UINT32_ARENA_RATING_DISCARD_TIMER); m_testing = false; } @@ -1293,64 +83,7 @@ void BattleGroundMgr::DeleteAllBattleGrounds() */ void BattleGroundMgr::Update(uint32 diff) { - // update scheduled queues - if (!m_queueUpdateScheduler.empty()) - { - std::vector scheduled; - { - // create mutex - // std::lock_guard guard(SchedulerLock); - // copy vector and clear the other - scheduled = std::vector(m_queueUpdateScheduler); - m_queueUpdateScheduler.clear(); - // release lock - } - - for (unsigned long long i : scheduled) - { - uint32 arenaRating = i >> 32; - ArenaType arenaType = ArenaType(i >> 24 & 255); - BattleGroundQueueTypeId bgQueueTypeId = BattleGroundQueueTypeId(i >> 16 & 255); - BattleGroundTypeId bgTypeId = BattleGroundTypeId((i >> 8) & 255); - BattleGroundBracketId bracket_id = BattleGroundBracketId(i & 255); - - m_battleGroundQueues[bgQueueTypeId].Update(bgTypeId, bracket_id, arenaType, arenaRating > 0, arenaRating); - } - } - - // if rating difference counts, maybe force-update queues - if (sWorld.getConfig(CONFIG_UINT32_ARENA_MAX_RATING_DIFFERENCE) && sWorld.getConfig(CONFIG_UINT32_ARENA_RATING_DISCARD_TIMER)) - { - // it's time to force update - if (m_nextRatingDiscardUpdate < diff) - { - // forced update for level 70 rated arenas - DEBUG_LOG("BattleGroundMgr: UPDATING ARENA QUEUES"); - m_battleGroundQueues[BATTLEGROUND_QUEUE_2v2].Update(BATTLEGROUND_AA, BG_BRACKET_ID_FIRST, ARENA_TYPE_2v2, true, 0); - m_battleGroundQueues[BATTLEGROUND_QUEUE_3v3].Update(BATTLEGROUND_AA, BG_BRACKET_ID_FIRST, ARENA_TYPE_3v3, true, 0); - m_battleGroundQueues[BATTLEGROUND_QUEUE_5v5].Update(BATTLEGROUND_AA, BG_BRACKET_ID_FIRST, ARENA_TYPE_5v5, true, 0); - - m_nextRatingDiscardUpdate = sWorld.getConfig(CONFIG_UINT32_ARENA_RATING_DISCARD_TIMER); - } - else - m_nextRatingDiscardUpdate -= diff; - } - - if (sWorld.getConfig(CONFIG_BOOL_ARENA_AUTO_DISTRIBUTE_POINTS)) - { - if (m_autoDistributionTimeChecker < diff) - { - if (sWorld.GetGameTime() > m_nextAutoDistributionTime) - { - DistributeArenaPoints(); - m_nextAutoDistributionTime = time_t(m_nextAutoDistributionTime + BATTLEGROUND_ARENA_POINT_DISTRIBUTION_DAY * sWorld.getConfig(CONFIG_UINT32_ARENA_AUTO_DISTRIBUTE_INTERVAL_DAYS)); - CharacterDatabase.PExecute("UPDATE saved_variables SET NextArenaPointDistributionTime = '" UI64FMTD "'", uint64(m_nextAutoDistributionTime)); - } - m_autoDistributionTimeChecker = 600000; // check 10 minutes - } - else - m_autoDistributionTimeChecker -= diff; - } + } /** @@ -1951,61 +684,6 @@ void BattleGroundMgr::CreateInitialBattleGrounds() sLog.outString(); } -/** - Method that initiates the automatic arena points distribution -*/ -void BattleGroundMgr::InitAutomaticArenaPointDistribution() -{ - if (sWorld.getConfig(CONFIG_BOOL_ARENA_AUTO_DISTRIBUTE_POINTS)) - { - auto queryResult = CharacterDatabase.Query("SELECT NextArenaPointDistributionTime FROM saved_variables"); - bool save = false; - bool insert = false; - if (!queryResult) // if not set generate time for next wednesday - insert = true; - else - { - m_nextAutoDistributionTime = time_t((*queryResult)[0].GetUInt64()); - if (m_nextAutoDistributionTime == 0) // uninitialized - save = true; - else // if time already exists - check for config changes - { - tm distribTime = *localtime(&m_nextAutoDistributionTime); - if (distribTime.tm_hour != sWorld.getConfig(CONFIG_UINT32_QUEST_DAILY_RESET_HOUR)) - { - if (time(nullptr) >= m_nextAutoDistributionTime) // if it already expired, do not save and only adjust hour - { - distribTime.tm_hour = sWorld.getConfig(CONFIG_UINT32_QUEST_DAILY_RESET_HOUR); - m_nextAutoDistributionTime = mktime(&distribTime); - } - else - save = true; - } - } - } - - if (save || insert) - { - // generate time by config on first server launch - time_t curTime = time(nullptr); - tm localTm = *localtime(&curTime); - localTm.tm_hour = sWorld.getConfig(CONFIG_UINT32_QUEST_DAILY_RESET_HOUR); - localTm.tm_min = 0; - localTm.tm_sec = 0; - localTm.tm_mday += ((7 - localTm.tm_wday + sWorld.getConfig(CONFIG_UINT32_ARENA_FIRST_RESET_DAY)) % 7); - localTm.tm_isdst = -1; - m_nextAutoDistributionTime = mktime(&localTm); - - if (insert) - CharacterDatabase.PExecute("INSERT INTO saved_variables (NextArenaPointDistributionTime) VALUES ('" UI64FMTD "')", uint64(m_nextAutoDistributionTime)); - if (save) - CharacterDatabase.PExecute("UPDATE saved_variables SET NextArenaPointDistributionTime = '" UI64FMTD "'", uint64(m_nextAutoDistributionTime)); - } - - //uint32 dayofweek = sWorld.getConfig(CONFIG_UINT32_ARENA_AUTO_DISTRIBUTE_INTERVAL_DAYS); - } -} - /** Method that distributes the arena points distribution */ @@ -2486,7 +1164,6 @@ void BattleGroundMgr::ToggleArenaTesting() */ void BattleGroundMgr::ScheduleQueueUpdate(uint32 arenaRating, ArenaType arenaType, BattleGroundQueueTypeId bgQueueTypeId, BattleGroundTypeId bgTypeId, BattleGroundBracketId bracketId) { - // std::lock_guard guard(SchedulerLock); // we will use only 1 number created of bgTypeId and bracket_id uint64 schedule_id = (uint64(arenaRating) << 32) | (uint64(arenaType) << 24) | (uint64(bgQueueTypeId) << 16) | (uint64(bgTypeId) << 8) | bracketId; bool found = false; diff --git a/src/game/BattleGround/BattleGroundMgr.h b/src/game/BattleGround/BattleGroundMgr.h index f8c32c1bfd..f609fb677c 100644 --- a/src/game/BattleGround/BattleGroundMgr.h +++ b/src/game/BattleGround/BattleGroundMgr.h @@ -24,182 +24,17 @@ #include "Globals/SharedDefines.h" #include "Server/DBCEnums.h" #include "BattleGround.h" +#include "BattleGround/BattleGroundDefines.h" #include typedef std::map BattleGroundSet; -// this container can't be deque, because deque doesn't like removing the last element - if you remove it, it invalidates next iterator and crash appears -typedef std::list BgFreeSlotQueueType; - typedef std::unordered_map BattleMastersMap; typedef std::unordered_map CreatureBattleEventIndexesMap; typedef std::unordered_map GameObjectBattleEventIndexesMap; -#define BATTLEGROUND_ARENA_POINT_DISTRIBUTION_DAY 86400 // seconds in a day -#define COUNT_OF_PLAYERS_TO_AVERAGE_WAIT_TIME 10 - -struct GroupQueueInfo; // type predefinition -struct PlayerQueueInfo // stores information for players in queue -{ - uint32 lastOnlineTime; // for tracking and removing offline players from queue after 5 minutes - GroupQueueInfo* groupInfo; // pointer to the associated groupqueueinfo -}; - -typedef std::map GroupQueueInfoPlayers; - -struct GroupQueueInfo // stores information about the group in queue (also used when joined as solo!) -{ - GroupQueueInfoPlayers players; // player queue info map - Team groupTeam; // Player team (ALLIANCE/HORDE) - BattleGroundTypeId bgTypeId; // battleground type id - bool isRated; // rated - ArenaType arenaType; // 2v2, 3v3, 5v5 or 0 when BG - uint32 arenaTeamId; // team id if rated match - uint32 joinTime; // time when group was added - uint32 removeInviteTime; // time when we will remove invite for players in group - uint32 isInvitedToBgInstanceGuid; // was invited to certain BG - uint32 desiredInstanceId; // queued for this instance specifically - uint32 arenaTeamRating; // if rated match, inited to the rating of the team - uint32 opponentsTeamRating; // for rated arena matches -}; - -enum BattleGroundQueueGroupTypes -{ - BG_QUEUE_PREMADE_ALLIANCE = 0, - BG_QUEUE_PREMADE_HORDE = 1, - BG_QUEUE_NORMAL_ALLIANCE = 2, - BG_QUEUE_NORMAL_HORDE = 3 -}; - -#define BG_QUEUE_GROUP_TYPES_COUNT 4 - -enum BattleGroundGroupJoinStatus -{ - BG_GROUP_JOIN_STATUS_TEAM_LEFT_QUEUE = -7, - BG_GROUP_JOIN_STATUS_QUEUED_FOR_RATED = -6, - BG_GROUP_JOIN_STATUS_CANNOT_QUEUE_FOR_RATED = -5, - BG_GROUP_JOIN_STATUS_TOO_MANY_QUEUES = -4, - BG_GROUP_JOIN_STATUS_NOT_IN_TEAM = -3, - BG_GROUP_JOIN_STATUS_DESERTERS = -2, - BG_GROUP_JOIN_STATUS_NOT_ELIGIBLE = -1, - BG_GROUP_JOIN_STATUS_SUCCESS = 0, -}; - class BattleGround; -class BattleGroundQueue -{ - public: - BattleGroundQueue(); - ~BattleGroundQueue(); - - void Update(BattleGroundTypeId /*bgTypeId*/, BattleGroundBracketId /*bracketId*/, ArenaType arenaType = ARENA_TYPE_NONE, bool isRated = false, uint32 arenaRating = 0); - - void FillPlayersToBg(BattleGround* /*bg*/, BattleGroundBracketId /*bracketId*/); - bool CheckPremadeMatch(BattleGroundBracketId /*bracketId*/, uint32 /*minPlayersPerTeam*/, uint32 /*maxPlayersPerTeam*/); - bool CheckNormalMatch(BattleGround* /*bgTemplate*/, BattleGroundBracketId /*bracketId*/, uint32 /*minPlayers*/, uint32 /*maxPlayers*/); - bool CheckSkirmishForSameFaction(BattleGroundBracketId /*bracketId*/, uint32 /*minPlayersPerTeam*/); - GroupQueueInfo* AddGroup(Player* /*leader*/, Group* /*group*/, BattleGroundTypeId /*bgTypeId*/, BattleGroundBracketId /*bracketEntry*/, ArenaType /*arenaType*/, bool /*isRated*/, bool /*isPremade*/, uint32 /*instanceId*/, uint32 /*arenaRating*/, uint32 arenaTeamId = 0); - void RemovePlayer(ObjectGuid /*guid*/, bool /*decreaseInvitedCount*/); - bool IsPlayerInvited(ObjectGuid /*playerGuid*/, const uint32 /*bgInstanceGuid*/, const uint32 /*removeTime*/); - bool GetPlayerGroupInfoData(ObjectGuid /*guid*/, GroupQueueInfo* /*groupInfo*/); - void PlayerInvitedToBgUpdateAverageWaitTime(GroupQueueInfo* /*groupInfo*/, BattleGroundBracketId /*bracketId*/); - uint32 GetAverageQueueWaitTime(GroupQueueInfo* /*groupInfo*/, BattleGroundBracketId /*bracketId*/); - - private: - // mutex that should not allow changing private data, nor allowing to update Queue during private data change. - std::recursive_mutex m_lock; - - - typedef std::map QueuedPlayersMap; - QueuedPlayersMap m_queuedPlayers; - - // we need constant add to begin and constant remove / add from the end, therefore deque suits our problem well - typedef std::list GroupsQueueType; - - /* - This two dimensional array is used to store All queued groups - First dimension specifies the bgTypeId - Second dimension specifies the player's group types - - BG_QUEUE_PREMADE_ALLIANCE is used for premade alliance groups and alliance rated arena teams - BG_QUEUE_PREMADE_HORDE is used for premade horde groups and horde rated arena teams - BG_QUEUE_NORMAL_ALLIANCE is used for normal (or small) alliance groups or non-rated arena matches - BG_QUEUE_NORMAL_HORDE is used for normal (or small) horde groups or non-rated arena matches - */ - GroupsQueueType m_queuedGroups[MAX_BATTLEGROUND_BRACKETS][BG_QUEUE_GROUP_TYPES_COUNT]; - - // class to select and invite groups to bg - class SelectionPool - { - public: - SelectionPool() : playerCount(0) {} - void Init(); - bool AddGroup(GroupQueueInfo* ginfo, uint32 desiredCount, uint32 bgInstanceId); - bool KickGroup(uint32 size); - uint32 GetPlayerCount() const {return playerCount;} - GroupsQueueType selectedGroups; - private: - uint32 playerCount; - }; - - // one selection pool for horde, other one for alliance - SelectionPool m_selectionPools[PVP_TEAM_COUNT]; - - bool InviteGroupToBg(GroupQueueInfo* /*groupInfo*/, BattleGround* /*bg*/, Team /*side*/); - - uint32 m_waitTimes[PVP_TEAM_COUNT][MAX_BATTLEGROUND_BRACKETS][COUNT_OF_PLAYERS_TO_AVERAGE_WAIT_TIME]; - uint32 m_waitTimeLastPlayer[PVP_TEAM_COUNT][MAX_BATTLEGROUND_BRACKETS]; - uint32 m_sumOfWaitTimes[PVP_TEAM_COUNT][MAX_BATTLEGROUND_BRACKETS]; -}; - -/* - This class is used to invite player to BG again, when minute lasts from his first invitation - it is capable to solve all possibilities -*/ -class BgQueueInviteEvent : public BasicEvent -{ - public: - BgQueueInviteEvent(ObjectGuid playerGuid, uint32 bgInstanceGuid, BattleGroundTypeId bgTypeId, ArenaType arenaType, uint32 removeTime) : - m_playerGuid(playerGuid), m_bgInstanceGuid(bgInstanceGuid), m_bgTypeId(bgTypeId), m_arenaType(arenaType), m_removeTime(removeTime) - { - }; - virtual ~BgQueueInviteEvent() {}; - - virtual bool Execute(uint64 e_time, uint32 p_time) override; - virtual void Abort(uint64 e_time) override; - - private: - ObjectGuid m_playerGuid; - uint32 m_bgInstanceGuid; - BattleGroundTypeId m_bgTypeId; - ArenaType m_arenaType; - uint32 m_removeTime; -}; - -/* - This class is used to remove player from BG queue after 1 minute 20 seconds from first invitation - We must store removeInvite time in case player left queue and joined and is invited again - We must store bgQueueTypeId, because battleground can be deleted already, when player entered it -*/ -class BgQueueRemoveEvent : public BasicEvent -{ - public: - BgQueueRemoveEvent(ObjectGuid playerGuid, uint32 bgInstanceGuid, BattleGroundTypeId bgTypeId, BattleGroundQueueTypeId bgQueueTypeId, uint32 removeTime) - : m_playerGuid(playerGuid), m_bgInstanceGuid(bgInstanceGuid), m_removeTime(removeTime), m_bgTypeId(bgTypeId), m_bgQueueTypeId(bgQueueTypeId) - {} - - virtual ~BgQueueRemoveEvent() {} - - virtual bool Execute(uint64 e_time, uint32 p_time) override; - virtual void Abort(uint64 e_time) override; - - private: - ObjectGuid m_playerGuid; - uint32 m_bgInstanceGuid; - uint32 m_removeTime; - BattleGroundTypeId m_bgTypeId; - BattleGroundQueueTypeId m_bgQueueTypeId; -}; class BattleGroundMgr { @@ -242,18 +77,12 @@ class BattleGroundMgr void SendToBattleGround(Player* /*player*/, uint32 /*instanceId*/, BattleGroundTypeId /*bgTypeId*/); - /* Battleground queues */ - // these queues are instantiated when creating BattlegroundMrg - BattleGroundQueue m_battleGroundQueues[MAX_BATTLEGROUND_QUEUE_TYPES]; // public, because we need to access them in BG handler code - - BgFreeSlotQueueType BgFreeSlotQueue[MAX_BATTLEGROUND_TYPE_ID]; - void ScheduleQueueUpdate(uint32 /*arenaRating*/, ArenaType /*arenaType*/, BattleGroundQueueTypeId /*bgQueueTypeId*/, BattleGroundTypeId /*bgTypeId*/, BattleGroundBracketId /*bracketId*/); uint32 GetMaxRatingDifference() const; uint32 GetRatingDiscardTimer() const; uint32 GetPrematureFinishTime() const; - void InitAutomaticArenaPointDistribution(); + void DistributeArenaPoints() const; void RewardArenaSeason(uint32 /*seasonId*/); void ResetAllArenaData(); @@ -311,12 +140,8 @@ class BattleGroundMgr /* Battlegrounds */ BattleGroundSet m_battleGrounds[MAX_BATTLEGROUND_TYPE_ID]; - std::vector m_queueUpdateScheduler; typedef std::set ClientBattleGroundIdSet; ClientBattleGroundIdSet m_clientBattleGroundIds[MAX_BATTLEGROUND_TYPE_ID][MAX_BATTLEGROUND_BRACKETS]; // the instanceids just visible for the client - uint32 m_nextRatingDiscardUpdate; - time_t m_nextAutoDistributionTime; - uint32 m_autoDistributionTimeChecker; bool m_arenaTesting; bool m_testing; std::set m_usedRefloot; diff --git a/src/game/BattleGround/BattleGroundQueue.cpp b/src/game/BattleGround/BattleGroundQueue.cpp index d3ed7878cb..3016b7f2e8 100644 --- a/src/game/BattleGround/BattleGroundQueue.cpp +++ b/src/game/BattleGround/BattleGroundQueue.cpp @@ -16,3 +16,1342 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include "Battleground/BattleGroundQueue.h" +#include "Tools/Language.h" +#include "World/World.h" +#include "Battleground/BattleGroundMgr.h" +#include "Arena/ArenaTeam.h" + + /*********************************************************/ + /*** BATTLEGROUND QUEUE SYSTEM ***/ + /*********************************************************/ + +BattleGroundQueueItem::BattleGroundQueueItem() +{ + for (uint8 i = 0; i < PVP_TEAM_COUNT; ++i) + { + for (uint8 j = 0; j < MAX_BATTLEGROUND_BRACKETS; ++j) + { + m_sumOfWaitTimes[i][j] = 0; + m_waitTimeLastPlayer[i][j] = 0; + + for (uint8 k = 0; k < COUNT_OF_PLAYERS_TO_AVERAGE_WAIT_TIME; ++k) + m_waitTimes[i][j][k] = 0; + } + } +} + +BattleGroundQueueItem::~BattleGroundQueueItem() +{ + m_queuedPlayers.clear(); + for (auto& m_queuedGroup : m_queuedGroups) + { + for (uint8 j = 0; j < BG_QUEUE_GROUP_TYPES_COUNT; ++j) + { + for (GroupsQueueType::iterator itr = m_queuedGroup[j].begin(); itr != m_queuedGroup[j].end(); ++itr) + delete (*itr); + + m_queuedGroup[j].clear(); + } + } +} + +/*********************************************************/ +/*** BATTLEGROUND QUEUE SELECTION POOLS ***/ +/*********************************************************/ + +// selection pool initialization, used to clean up from prev selection +void BattleGroundQueueItem::SelectionPool::Init() +{ + selectedGroups.clear(); + playerCount = 0; +} + +/** + Function that removes group infr from pool selection + - returns true when we need to try to add new group to selection pool + - returns false when selection pool is ok or when we kicked smaller group than we need to kick + - sometimes it can be called on empty selection pool + + @param size +*/ +bool BattleGroundQueueItem::SelectionPool::KickGroup(uint32 size) +{ + // find maxgroup or LAST group with size == size and kick it + bool found = false; + GroupsQueueType::iterator groupToKick = selectedGroups.begin(); + + for (GroupsQueueType::iterator itr = groupToKick; itr != selectedGroups.end(); ++itr) + { + if (abs((int32)((*itr)->players.size() - size)) <= 1) + { + groupToKick = itr; + found = true; + } + else if (!found && (*itr)->players.size() >= (*groupToKick)->players.size()) + groupToKick = itr; + } + + // if pool is empty, do nothing + if (GetPlayerCount()) + { + // update player count + GroupQueueInfo* queueInfo = (*groupToKick); + selectedGroups.erase(groupToKick); + playerCount -= queueInfo->players.size(); + + // return false if we kicked smaller group or there are enough players in selection pool + if (queueInfo->players.size() <= size + 1) + return false; + } + return true; +} + +/** + Function that adds group to selection pool + - returns true if we can invite more players, or when we added group to selection pool + - returns false when selection pool is full + + @param group queue info + @param desired count +*/ +bool BattleGroundQueueItem::SelectionPool::AddGroup(GroupQueueInfo* queueInfo, uint32 desiredCount, uint32 bgInstanceId) +{ + // if group is larger than desired count - don't allow to add it to pool + if (!queueInfo->isInvitedToBgInstanceGuid && + (!queueInfo->desiredInstanceId || queueInfo->desiredInstanceId == bgInstanceId) && + (desiredCount >= playerCount + queueInfo->players.size())) + { + selectedGroups.push_back(queueInfo); + // increase selected players count + playerCount += queueInfo->players.size(); + + return true; + } + + return playerCount < desiredCount; +} + +/*********************************************************/ +/*** BATTLEGROUND QUEUES ***/ +/*********************************************************/ + +/** + Function that adds group or player (grp == nullptr) to battleground queue with the given leader and specifications + + @param leader player + @param group + @param battleground type id + @param bracket entry + @param arena type + @param isRated + @param isPremade + @param arena rating + @param arena team id +*/ +GroupQueueInfo* BattleGroundQueueItem::AddGroup(Player* leader, Group* group, BattleGroundTypeId bgTypeId, BattleGroundBracketId bracketId, ArenaType arenaType, bool isRated, bool isPremade, uint32 instanceId, uint32 arenaRating, uint32 arenaTeamId) +{ + // create new ginfo + GroupQueueInfo* queueInfo = new GroupQueueInfo; + queueInfo->bgTypeId = bgTypeId; + queueInfo->arenaType = arenaType; + queueInfo->arenaTeamId = arenaTeamId; + queueInfo->isRated = isRated; + queueInfo->isInvitedToBgInstanceGuid = 0; + queueInfo->joinTime = WorldTimer::getMSTime(); + queueInfo->removeInviteTime = 0; + queueInfo->groupTeam = leader->GetTeam(); + queueInfo->desiredInstanceId = instanceId; + queueInfo->arenaTeamRating = arenaRating; + queueInfo->opponentsTeamRating = 0; + + queueInfo->players.clear(); + + // compute index (if group is premade or joined a rated match) to queues + uint32 index = 0; + if (!isRated && !isPremade) + index += PVP_TEAM_COUNT; // BG_QUEUE_PREMADE_* -> BG_QUEUE_NORMAL_* + + if (queueInfo->groupTeam == HORDE) + ++index; // BG_QUEUE_*_ALLIANCE -> BG_QUEUE_*_HORDE + + DEBUG_LOG("Adding Group to BattleGroundQueueItem bgTypeId : %u, bracket_id : %u, index : %u", bgTypeId, bracketId, index); + + uint32 lastOnlineTime = WorldTimer::getMSTime(); + + // announce world (this don't need mutex) + if (isRated && sWorld.getConfig(CONFIG_BOOL_ARENA_QUEUE_ANNOUNCER_JOIN)) + { + sWorld.SendWorldText(LANG_ARENA_QUEUE_ANNOUNCE_WORLD_JOIN, queueInfo->arenaType, queueInfo->arenaType, queueInfo->arenaTeamRating); + } + + // add players from group to ginfo + { + // std::lock_guard guard(m_Lock); + if (group) + { + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* member = itr->getSource(); + if (!member) + continue; // this should never happen + PlayerQueueInfo& playerInfo = m_queuedPlayers[member->GetObjectGuid()]; + playerInfo.lastOnlineTime = lastOnlineTime; + playerInfo.groupInfo = queueInfo; + // add the pinfo to ginfo's list + queueInfo->players[member->GetObjectGuid()] = &playerInfo; + } + } + else + { + PlayerQueueInfo& playerInfo = m_queuedPlayers[leader->GetObjectGuid()]; + playerInfo.lastOnlineTime = lastOnlineTime; + playerInfo.groupInfo = queueInfo; + queueInfo->players[leader->GetObjectGuid()] = &playerInfo; + } + + // add GroupInfo to m_QueuedGroups + m_queuedGroups[bracketId][index].push_back(queueInfo); + + // announce to world, this code needs mutex + if (arenaType == ARENA_TYPE_NONE && !isRated && !isPremade && sWorld.getConfig(CONFIG_UINT32_BATTLEGROUND_QUEUE_ANNOUNCER_JOIN)) + { + if (BattleGround* bg = sBattleGroundMgr.GetBattleGroundTemplate(queueInfo->bgTypeId)) + { + char const* bgName = bg->GetName(); + uint32 minPlayers = bg->GetMinPlayersPerTeam(); + uint32 qHorde = 0; + uint32 qAlliance = 0; + uint32 q_min_level = leader->GetMinLevelForBattleGroundBracketId(bracketId, bgTypeId); + uint32 qMaxLevel = leader->GetMaxLevelForBattleGroundBracketId(bracketId, bgTypeId); + GroupsQueueType::const_iterator itr; + for (itr = m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE].begin(); itr != m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE].end(); ++itr) + if (!(*itr)->isInvitedToBgInstanceGuid) + qAlliance += (*itr)->players.size(); + + for (itr = m_queuedGroups[bracketId][BG_QUEUE_NORMAL_HORDE].begin(); itr != m_queuedGroups[bracketId][BG_QUEUE_NORMAL_HORDE].end(); ++itr) + if (!(*itr)->isInvitedToBgInstanceGuid) + qHorde += (*itr)->players.size(); + + // Show queue status to player only (when joining queue) + if (sWorld.getConfig(CONFIG_UINT32_BATTLEGROUND_QUEUE_ANNOUNCER_JOIN) == 1) + { + ChatHandler(leader).PSendSysMessage(LANG_BG_QUEUE_ANNOUNCE_SELF, bgName, q_min_level, qMaxLevel, + qAlliance, (minPlayers > qAlliance) ? minPlayers - qAlliance : (uint32)0, qHorde, (minPlayers > qHorde) ? minPlayers - qHorde : (uint32)0); + } + // System message + else + { + sWorld.SendWorldText(LANG_BG_QUEUE_ANNOUNCE_WORLD, bgName, q_min_level, qMaxLevel, + qAlliance, (minPlayers > qAlliance) ? minPlayers - qAlliance : (uint32)0, qHorde, (minPlayers > qHorde) ? minPlayers - qHorde : (uint32)0); + } + } + } + // release mutex + } + + return queueInfo; +} + +/** + Method that updates average update wait time + + @param group queue info + @param bracket id +*/ +void BattleGroundQueueItem::PlayerInvitedToBgUpdateAverageWaitTime(GroupQueueInfo* queueInfo, BattleGroundBracketId bracketId) +{ + uint32 timeInQueue = WorldTimer::getMSTimeDiff(queueInfo->joinTime, WorldTimer::getMSTime()); + uint8 teamIndex = TEAM_INDEX_ALLIANCE; // default set to BG_TEAM_ALLIANCE - or non rated arenas! + + if (queueInfo->arenaType == ARENA_TYPE_NONE) + { + if (queueInfo->groupTeam == HORDE) + teamIndex = TEAM_INDEX_HORDE; + } + else + { + if (queueInfo->isRated) + teamIndex = TEAM_INDEX_HORDE; // for rated arenas use BG_TEAM_HORDE + } + + // store pointer to arrayindex of player that was added first + uint32* lastPlayerAddedPointer = &(m_waitTimeLastPlayer[teamIndex][bracketId]); + + // remove his time from sum + m_sumOfWaitTimes[teamIndex][bracketId] -= m_waitTimes[teamIndex][bracketId][(*lastPlayerAddedPointer)]; + + // set average time to new + m_waitTimes[teamIndex][bracketId][(*lastPlayerAddedPointer)] = timeInQueue; + + // add new time to sum + m_sumOfWaitTimes[teamIndex][bracketId] += timeInQueue; + + // set index of last player added to next one + (*lastPlayerAddedPointer)++; + (*lastPlayerAddedPointer) %= COUNT_OF_PLAYERS_TO_AVERAGE_WAIT_TIME; +} + +/** + Function that returns averate queue wait time + + @param group queue info + @param bracket id +*/ +uint32 BattleGroundQueueItem::GetAverageQueueWaitTime(GroupQueueInfo* queueInfo, BattleGroundBracketId bracketId) +{ + uint8 teamIndex = TEAM_INDEX_ALLIANCE; // default set to BG_TEAM_ALLIANCE - or non rated arenas! + if (queueInfo->arenaType == ARENA_TYPE_NONE) + { + if (queueInfo->groupTeam == HORDE) + teamIndex = TEAM_INDEX_HORDE; + } + else + { + if (queueInfo->isRated) + teamIndex = TEAM_INDEX_HORDE; // for rated arenas use BG_TEAM_HORDE + } + + // check if there is enought values(we always add values > 0) + if (m_waitTimes[teamIndex][bracketId][COUNT_OF_PLAYERS_TO_AVERAGE_WAIT_TIME - 1]) + return (m_sumOfWaitTimes[teamIndex][bracketId] / COUNT_OF_PLAYERS_TO_AVERAGE_WAIT_TIME); + + // if there aren't enough values return 0 - not available + return 0; +} + +/** + Method that removes player from queue and from group info, if group info is empty then remove it too + + @param guid + @param decrease invite count +*/ +void BattleGroundQueueItem::RemovePlayer(ObjectGuid guid, bool decreaseInvitedCount) +{ + // Player *plr = sObjectMgr.GetPlayer(guid); + // std::lock_guard guard(m_Lock); + + int32 bracketId = -1; // signed for proper for-loop finish + + // remove player from map, if he's there + QueuedPlayersMap::iterator itr = m_queuedPlayers.find(guid); + if (itr == m_queuedPlayers.end()) + { + sLog.outError("BattleGroundQueueItem: couldn't find for remove: %s", guid.GetString().c_str()); + return; + } + + GroupQueueInfo* group = itr->second.groupInfo; + GroupsQueueType::iterator group_itr; + // mostly people with the highest levels are in battlegrounds, thats why + // we count from MAX_BATTLEGROUND_QUEUES - 1 to 0 + // variable index removes useless searching in other team's queue + uint32 index = GetTeamIndexByTeamId(group->groupTeam); + + for (int8 bracketIdTmp = MAX_BATTLEGROUND_BRACKETS - 1; bracketIdTmp >= 0 && bracketId == -1; --bracketIdTmp) + { + // we must check premade and normal team's queue - because when players from premade are joining bg, + // they leave groupinfo so we can't use its players size to find out index + for (uint8 j = index; j < BG_QUEUE_GROUP_TYPES_COUNT; j += BG_QUEUE_NORMAL_ALLIANCE) + { + for (GroupsQueueType::iterator group_itr_tmp = m_queuedGroups[bracketIdTmp][j].begin(); group_itr_tmp != m_queuedGroups[bracketIdTmp][j].end(); ++group_itr_tmp) + { + if ((*group_itr_tmp) == group) + { + bracketId = bracketIdTmp; + group_itr = group_itr_tmp; + // we must store index to be able to erase iterator + index = j; + break; + } + } + } + } + + // player can't be in queue without group, but just in case + if (bracketId == -1) + { + sLog.outError("BattleGroundQueueItem: ERROR Cannot find groupinfo for %s", guid.GetString().c_str()); + return; + } + DEBUG_LOG("BattleGroundQueueItem: Removing %s, from bracket_id %u", guid.GetString().c_str(), (uint32)bracketId); + + // ALL variables are correctly set + // We can ignore leveling up in queue - it should not cause crash + // remove player from group + // if only one player there, remove group + + // remove player queue info from group queue info + GroupQueueInfoPlayers::iterator pitr = group->players.find(guid); + if (pitr != group->players.end()) + group->players.erase(pitr); + + // if invited to bg, and should decrease invited count, then do it + if (decreaseInvitedCount && group->isInvitedToBgInstanceGuid) + { + BattleGround* bg = sBattleGroundMgr.GetBattleGround(group->isInvitedToBgInstanceGuid, group->bgTypeId); + if (bg) + bg->DecreaseInvitedCount(group->groupTeam); + } + + // remove player queue info + m_queuedPlayers.erase(itr); + + // announce to world if arena team left queue for rated match, show only once + if (group->arenaType != ARENA_TYPE_NONE && group->isRated && group->players.empty() && sWorld.getConfig(CONFIG_BOOL_ARENA_QUEUE_ANNOUNCER_EXIT)) + sWorld.SendWorldText(LANG_ARENA_QUEUE_ANNOUNCE_WORLD_EXIT, group->arenaType, group->arenaType, group->arenaTeamRating); + + // if player leaves queue and he is invited to rated arena match, then he have to loose + if (group->isInvitedToBgInstanceGuid && group->isRated && decreaseInvitedCount) + { + ArenaTeam* at = sObjectMgr.GetArenaTeamById(group->arenaTeamId); + if (at) + { + DEBUG_LOG("UPDATING memberLost's personal arena rating for %s by opponents rating: %u", guid.GetString().c_str(), group->opponentsTeamRating); + Player* plr = sObjectMgr.GetPlayer(guid); + if (plr) + at->MemberLost(plr, group->opponentsTeamRating); + else + at->OfflineMemberLost(guid, group->opponentsTeamRating); + + at->SaveToDB(); + } + } + + // remove group queue info if needed + if (group->players.empty()) + { + m_queuedGroups[bracketId][index].erase(group_itr); + delete group; + } + // if group wasn't empty, so it wasn't deleted, and player have left a rated + // queue -> everyone from the group should leave too + // don't remove recursively if already invited to bg! + else if (!group->isInvitedToBgInstanceGuid && group->isRated) + { + // remove next player, this is recursive + // first send removal information + if (Player* plr2 = sObjectMgr.GetPlayer(group->players.begin()->first)) + { + BattleGround* bg = sBattleGroundMgr.GetBattleGroundTemplate(group->bgTypeId); + BattleGroundQueueTypeId bgQueueTypeId = BattleGroundMgr::BgQueueTypeId(group->bgTypeId, group->arenaType); + + uint32 queueSlot = plr2->GetBattleGroundQueueIndex(bgQueueTypeId); + plr2->RemoveBattleGroundQueueId(bgQueueTypeId); // must be called this way, because if you move this call to + + // queue->removeplayer, it causes bugs + WorldPacket data; + sBattleGroundMgr.BuildBattleGroundStatusPacket(data, bg, queueSlot, STATUS_NONE, 0, 0, ARENA_TYPE_NONE, TEAM_NONE); + plr2->GetSession()->SendPacket(data); + } + + // then actually delete, this may delete the group as well! + RemovePlayer(group->players.begin()->first, decreaseInvitedCount); + } +} + +/** + Function that returns true when player is in queue and is invited to bgInstanceGuid + + @param player guid + @param battleground instance guid + @param remove time +*/ +bool BattleGroundQueueItem::IsPlayerInvited(ObjectGuid playerGuid, const uint32 bgInstanceGuid, const uint32 removeTime) +{ + // std::lock_guard g(m_Lock); + QueuedPlayersMap::const_iterator qItr = m_queuedPlayers.find(playerGuid); + return (qItr != m_queuedPlayers.end() + && qItr->second.groupInfo->isInvitedToBgInstanceGuid == bgInstanceGuid + && qItr->second.groupInfo->removeInviteTime == removeTime); +} + +/** + Function that returns player group info data + - returns true when the player is found in queue + + @param player guid + @param group queue info +*/ +bool BattleGroundQueueItem::GetPlayerGroupInfoData(ObjectGuid guid, GroupQueueInfo* queueInfo) +{ + // std::lock_guard g(m_Lock); + QueuedPlayersMap::const_iterator qItr = m_queuedPlayers.find(guid); + if (qItr == m_queuedPlayers.end()) + return false; + + *queueInfo = *(qItr->second.groupInfo); + return true; +} + +/** + Function that invites group to battleground + + @param group queue info + @param battleground + @param team +*/ +bool BattleGroundQueueItem::InviteGroupToBg(GroupQueueInfo* queueInfo, BattleGround* bg, Team team) +{ + // set side if needed + if (team == ALLIANCE || team == HORDE) + queueInfo->groupTeam = team; + + if (!queueInfo->isInvitedToBgInstanceGuid) + { + // not yet invited + // set invitation + queueInfo->isInvitedToBgInstanceGuid = bg->GetInstanceId(); + BattleGroundTypeId bgTypeId = bg->GetTypeId(); + BattleGroundQueueTypeId bgQueueTypeId = BattleGroundMgr::BgQueueTypeId(bgTypeId, bg->GetArenaType()); + BattleGroundBracketId bracket_id = bg->GetBracketId(); + + // set ArenaTeamId for rated matches + if (bg->IsArena() && bg->IsRated()) + bg->SetArenaTeamIdForTeam(queueInfo->groupTeam, queueInfo->arenaTeamId); + + queueInfo->removeInviteTime = WorldTimer::getMSTime() + INVITE_ACCEPT_WAIT_TIME; + + // loop through the players + for (auto itr = queueInfo->players.begin(); itr != queueInfo->players.end(); ++itr) + { + // get the player + Player* plr = sObjectMgr.GetPlayer(itr->first); + // if offline, skip him, this should not happen - player is removed from queue when he logs out + if (!plr) + continue; + + // invite the player + PlayerInvitedToBgUpdateAverageWaitTime(queueInfo, bracket_id); + // sBattleGroundMgr.InvitePlayer(plr, bg, ginfo->Team); + + // set invited player counters + bg->IncreaseInvitedCount(queueInfo->groupTeam); + + plr->SetInviteForBattleGroundQueueType(bgQueueTypeId, queueInfo->isInvitedToBgInstanceGuid); + + // create remind invite events + BgQueueInviteEvent* inviteEvent = new BgQueueInviteEvent(plr->GetObjectGuid(), queueInfo->isInvitedToBgInstanceGuid, bgTypeId, queueInfo->arenaType, queueInfo->removeInviteTime); + plr->m_events.AddEvent(inviteEvent, plr->m_events.CalculateTime(INVITATION_REMIND_TIME)); + + // create automatic remove events + BgQueueRemoveEvent* removeEvent = new BgQueueRemoveEvent(plr->GetObjectGuid(), queueInfo->isInvitedToBgInstanceGuid, bgTypeId, bgQueueTypeId, queueInfo->removeInviteTime); + plr->m_events.AddEvent(removeEvent, plr->m_events.CalculateTime(INVITE_ACCEPT_WAIT_TIME)); + + WorldPacket data; + + uint32 queueSlot = plr->GetBattleGroundQueueIndex(bgQueueTypeId); + + DEBUG_LOG("Battleground: invited %s to BG instance %u queueindex %u bgtype %u, I can't help it if they don't press the enter battle button.", plr->GetGuidStr().c_str(), bg->GetInstanceId(), queueSlot, bg->GetTypeId()); + + // send status packet + sBattleGroundMgr.BuildBattleGroundStatusPacket(data, bg, queueSlot, STATUS_WAIT_JOIN, INVITE_ACCEPT_WAIT_TIME, 0, queueInfo->arenaType, TEAM_NONE); + + plr->GetSession()->SendPacket(data); + } + return true; + } + + return false; +} + +/** + Method that invites players to an already running battleground + - invitation is based on config file + - large groups are disadvantageous, because they will be kicked first if invitation type = 1 + + @param battleground + @param bracket id +*/ +void BattleGroundQueueItem::FillPlayersToBg(BattleGround* bg, BattleGroundBracketId bracketId) +{ + int32 hordeFree = bg->GetFreeSlotsForTeam(HORDE); + int32 aliFree = bg->GetFreeSlotsForTeam(ALLIANCE); + + // iterator for iterating through bg queue + GroupsQueueType::const_iterator Ali_itr = m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE].begin(); + // count of groups in queue - used to stop cycles + uint32 aliCount = m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE].size(); + // index to queue which group is current + uint32 aliIndex = 0; + for (; aliIndex < aliCount && m_selectionPools[TEAM_INDEX_ALLIANCE].AddGroup((*Ali_itr), aliFree, bg->GetClientInstanceId()); ++aliIndex) + ++Ali_itr; + + // the same thing for horde + GroupsQueueType::const_iterator Horde_itr = m_queuedGroups[bracketId][BG_QUEUE_NORMAL_HORDE].begin(); + uint32 hordeCount = m_queuedGroups[bracketId][BG_QUEUE_NORMAL_HORDE].size(); + uint32 hordeIndex = 0; + for (; hordeIndex < hordeCount && m_selectionPools[TEAM_INDEX_HORDE].AddGroup((*Horde_itr), hordeFree, bg->GetClientInstanceId()); ++hordeIndex) + ++Horde_itr; + + // if ofc like BG queue invitation is set in config, then we are happy + if (sWorld.getConfig(CONFIG_UINT32_BATTLEGROUND_INVITATION_TYPE) == 0) + 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 BattleGroundQueueItem::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 BattleGroundQueueItem::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_INDEX_ALLIANCE].GetPlayerCount()); + int32 diffHorde = hordeFree - int32(m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount()); + while (abs(diffAli - diffHorde) > 1 && (m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount() > 0 || m_selectionPools[TEAM_INDEX_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_INDEX_ALLIANCE].KickGroup(diffHorde - diffAli)) + { + for (; aliIndex < aliCount && m_selectionPools[TEAM_INDEX_ALLIANCE].AddGroup((*Ali_itr), (aliFree >= diffHorde) ? aliFree - diffHorde : 0, bg->GetClientInstanceId()); ++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_INDEX_ALLIANCE].GetPlayerCount()) + { + if (aliFree <= diffHorde + 1) + break; + m_selectionPools[TEAM_INDEX_HORDE].KickGroup(diffHorde - diffAli); + } + } + else + { + // kick horde group, add to pool new group if needed + if (m_selectionPools[TEAM_INDEX_HORDE].KickGroup(diffAli - diffHorde)) + { + for (; hordeIndex < hordeCount && m_selectionPools[TEAM_INDEX_HORDE].AddGroup((*Horde_itr), (hordeFree >= diffAli) ? hordeFree - diffAli : 0, bg->GetClientInstanceId()); ++hordeIndex) + ++Horde_itr; + } + if (!m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount()) + { + if (hordeFree <= diffAli + 1) + break; + m_selectionPools[TEAM_INDEX_ALLIANCE].KickGroup(diffAli - diffHorde); + } + } + + // count diffs after small update + diffAli = aliFree - int32(m_selectionPools[TEAM_INDEX_ALLIANCE].GetPlayerCount()); + diffHorde = hordeFree - int32(m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount()); + } +} + +/** + Method that checks if (premade vs premade) battlegrouns is possible + - then after 30 mins (default) in queue it moves premade group to normal queue + - it tries to invite as much players as it can - to maxPlayersPerTeam, because premade groups have more than minPlayersPerTeam players + + @param bracket id + @param min players per team + @param max players per team +*/ +bool BattleGroundQueueItem::CheckPremadeMatch(BattleGroundBracketId bracketId, uint32 minPlayersPerTeam, uint32 maxPlayersPerTeam) +{ + // check match + if (!m_queuedGroups[bracketId][BG_QUEUE_PREMADE_ALLIANCE].empty() && !m_queuedGroups[bracketId][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[bracketId][BG_QUEUE_PREMADE_ALLIANCE].begin(); ali_group != m_queuedGroups[bracketId][BG_QUEUE_PREMADE_ALLIANCE].end(); ++ali_group) + if (!(*ali_group)->isInvitedToBgInstanceGuid) + break; + + for (horde_group = m_queuedGroups[bracketId][BG_QUEUE_PREMADE_HORDE].begin(); horde_group != m_queuedGroups[bracketId][BG_QUEUE_PREMADE_HORDE].end(); ++horde_group) + if (!(*horde_group)->isInvitedToBgInstanceGuid) + break; + + if (ali_group != m_queuedGroups[bracketId][BG_QUEUE_PREMADE_ALLIANCE].end() && horde_group != m_queuedGroups[bracketId][BG_QUEUE_PREMADE_HORDE].end()) + { + m_selectionPools[TEAM_INDEX_ALLIANCE].AddGroup((*ali_group), maxPlayersPerTeam, 0); + m_selectionPools[TEAM_INDEX_HORDE].AddGroup((*horde_group), maxPlayersPerTeam, 0); + + // add groups/players from normal queue to size of bigger group + uint32 maxPlayers = std::max(m_selectionPools[TEAM_INDEX_ALLIANCE].GetPlayerCount(), m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount()); + for (uint8 i = 0; i < PVP_TEAM_COUNT; ++i) + { + for (GroupsQueueType::const_iterator itr = m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE + i].begin(); itr != m_queuedGroups[bracketId][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, 0)) + 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 = WorldTimer::getMSTime() - sWorld.getConfig(CONFIG_UINT32_BATTLEGROUND_PREMADE_GROUP_WAIT_FOR_MATCH); + for (uint8 i = 0; i < PVP_TEAM_COUNT; ++i) + { + if (!m_queuedGroups[bracketId][BG_QUEUE_PREMADE_ALLIANCE + i].empty()) + { + GroupsQueueType::iterator itr = m_queuedGroups[bracketId][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 + m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE + i].push_front((*itr)); + m_queuedGroups[bracketId][BG_QUEUE_PREMADE_ALLIANCE + i].erase(itr); + } + } + } + + // selection pools are not set + return false; +} + +/** + Method that tries to create battleground or arena with minPlayersPerTeam against maxPlayersPerTeam + + @param battleground + @param bracket id + @param min players + @param max players +*/ +bool BattleGroundQueueItem::CheckNormalMatch(BattleGround* bgTemplate, BattleGroundBracketId bracketId, uint32 minPlayers, uint32 maxPlayers) +{ + GroupsQueueType::const_iterator itr_team[PVP_TEAM_COUNT]; + for (uint8 i = 0; i < PVP_TEAM_COUNT; ++i) + { + itr_team[i] = m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE + i].begin(); + for (; itr_team[i] != m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE + i].end(); ++(itr_team[i])) + { + if (!(*(itr_team[i]))->isInvitedToBgInstanceGuid) + { + m_selectionPools[i].AddGroup(*(itr_team[i]), maxPlayers, 0); + 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_INDEX_ALLIANCE; + if (m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount() < m_selectionPools[TEAM_INDEX_ALLIANCE].GetPlayerCount()) + j = TEAM_INDEX_HORDE; + + if (sWorld.getConfig(CONFIG_UINT32_BATTLEGROUND_INVITATION_TYPE) != 0 + && m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount() >= minPlayers && m_selectionPools[TEAM_INDEX_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[bracketId][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_TEAM_COUNT].GetPlayerCount(), 0)) + break; + } + // do not allow to start bg with more than 2 players more on 1 faction + if (abs((int32)(m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount() - m_selectionPools[TEAM_INDEX_ALLIANCE].GetPlayerCount())) > 2) + return false; + } + + // allow 1v0 if debug bg + if (sBattleGroundMgr.IsTesting() && bgTemplate->IsBattleGround() && (m_selectionPools[TEAM_INDEX_ALLIANCE].GetPlayerCount() || m_selectionPools[TEAM_INDEX_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_INDEX_ALLIANCE].GetPlayerCount() >= minPlayers && m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount() >= minPlayers; +} + +/** + Method that will check if we can invite players to same faction skirmish match + + @param bracket id + @param min players +*/ +bool BattleGroundQueueItem::CheckSkirmishForSameFaction(BattleGroundBracketId bracketId, uint32 minPlayersPerTeam) +{ + if (m_selectionPools[TEAM_INDEX_ALLIANCE].GetPlayerCount() < minPlayersPerTeam && m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount() < minPlayersPerTeam) + return false; + + PvpTeamIndex teamIdx = TEAM_INDEX_ALLIANCE; + PvpTeamIndex otherTeamIdx = TEAM_INDEX_HORDE; + Team otherTeamId = HORDE; + + if (m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount() == minPlayersPerTeam) + { + teamIdx = TEAM_INDEX_HORDE; + otherTeamIdx = TEAM_INDEX_ALLIANCE; + otherTeamId = ALLIANCE; + } + + // clear other team's selection + m_selectionPools[otherTeamIdx].Init(); + // store last ginfo pointer + GroupQueueInfo* ginfo = m_selectionPools[teamIdx].selectedGroups.back(); + // set itr_team to group that was added to selection pool latest + GroupsQueueType::iterator itr_team = m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE + teamIdx].begin(); + for (; itr_team != m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE + teamIdx].end(); ++itr_team) + if (ginfo == *itr_team) + break; + + if (itr_team == m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE + teamIdx].end()) + return false; + + GroupsQueueType::iterator itr_team2 = itr_team; + ++itr_team2; + // invite players to other selection pool + for (; itr_team2 != m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE + teamIdx].end(); ++itr_team2) + { + // if selection pool is full then break; + if (!(*itr_team2)->isInvitedToBgInstanceGuid && !m_selectionPools[otherTeamIdx].AddGroup(*itr_team2, minPlayersPerTeam, 0)) + break; + } + + if (m_selectionPools[otherTeamIdx].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[otherTeamIdx].selectedGroups.begin(); itr != m_selectionPools[otherTeamIdx].selectedGroups.end(); ++itr) + { + // set correct team + (*itr)->groupTeam = otherTeamId; + + // add team to other queue + m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE + otherTeamIdx].push_front(*itr); + + // remove team from old queue + GroupsQueueType::iterator itr2 = itr_team; + ++itr2; + for (; itr2 != m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE + teamIdx].end(); ++itr2) + { + if (*itr2 == *itr) + { + m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE + teamIdx].erase(itr2); + break; + } + } + } + return true; +} + +/** + Method that is called when group is inserted, or player / group is removed from BG Queue - there is only one player's status changed, so we don't use while(true) cycles to invite whole queue + - it must be called after fully adding the members of a group to ensure group joining + - should be called from BattleGround::RemovePlayer function in some cases + + @param bg type id + @param bracket id + @param arena type + @param isRated + @param arenaRating +*/ +void BattleGroundQueueItem::Update(BattleGroundTypeId bgTypeId, BattleGroundBracketId bracketId, ArenaType arenaType, bool isRated, uint32 arenaRating) +{ + // std::lock_guard guard(m_Lock); + // if no players in queue - do nothing + if (m_queuedGroups[bracketId][BG_QUEUE_PREMADE_ALLIANCE].empty() && + m_queuedGroups[bracketId][BG_QUEUE_PREMADE_HORDE].empty() && + m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE].empty() && + m_queuedGroups[bracketId][BG_QUEUE_NORMAL_HORDE].empty()) + return; + + // 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 + BgFreeSlotQueueType::iterator next; + for (BgFreeSlotQueueType::iterator itr = sBattleGroundMgr.BgFreeSlotQueue[bgTypeId].begin(); itr != sBattleGroundMgr.BgFreeSlotQueue[bgTypeId].end(); itr = next) + { + next = itr; + ++next; + // DO NOT allow queue manager to invite new player to arena + if ((*itr)->IsBattleGround() && (*itr)->GetTypeId() == bgTypeId && (*itr)->GetBracketId() == bracketId && + (*itr)->GetStatus() > STATUS_WAIT_QUEUE && (*itr)->GetStatus() < STATUS_WAIT_LEAVE) + { + BattleGround* bg = *itr; // we have to store battleground pointer here, because when battleground is full, it is removed from free queue (not yet implemented!!) + // and iterator is invalid + + // clear selection pools + m_selectionPools[TEAM_INDEX_ALLIANCE].Init(); + m_selectionPools[TEAM_INDEX_HORDE].Init(); + + // call a function that does the job for us + FillPlayersToBg(bg, bracketId); + + // now everything is set, invite players + for (GroupsQueueType::const_iterator citr = m_selectionPools[TEAM_INDEX_ALLIANCE].selectedGroups.begin(); citr != m_selectionPools[TEAM_INDEX_ALLIANCE].selectedGroups.end(); ++citr) + InviteGroupToBg((*citr), bg, (*citr)->groupTeam); + for (GroupsQueueType::const_iterator citr = m_selectionPools[TEAM_INDEX_HORDE].selectedGroups.begin(); citr != m_selectionPools[TEAM_INDEX_HORDE].selectedGroups.end(); ++citr) + InviteGroupToBg((*citr), bg, (*citr)->groupTeam); + + if (!bg->HasFreeSlots()) + { + // remove BG from BGFreeSlotQueue + bg->RemoveFromBgFreeSlotQueue(); + } + } + } + + // finished iterating through the bgs with free slots, maybe we need to create a new bg + + BattleGround* bgTemplate = sBattleGroundMgr.GetBattleGroundTemplate(bgTypeId); + if (!bgTemplate) + { + sLog.outError("Battleground: Update: bg template not found for %u", bgTypeId); + return; + } + + // get the min. players per team, properly for larger arenas as well. (must have full teams for arena matches!) + uint32 minPlayersPerTeam = bgTemplate->GetMinPlayersPerTeam(); + uint32 maxPlayersPerTeam = bgTemplate->GetMaxPlayersPerTeam(); + + if (sBattleGroundMgr.IsTesting()) + minPlayersPerTeam = 1; + + if (bgTemplate->IsArena()) + { + if (sBattleGroundMgr.IsArenaTesting()) + { + maxPlayersPerTeam = 1; + minPlayersPerTeam = 1; + } + else + { + // this switch can be much shorter + maxPlayersPerTeam = arenaType; + minPlayersPerTeam = arenaType; + /*switch(arenaType) + { + case ARENA_TYPE_2v2: + MaxPlayersPerTeam = 2; + MinPlayersPerTeam = 2; + break; + case ARENA_TYPE_3v3: + MaxPlayersPerTeam = 3; + MinPlayersPerTeam = 3; + break; + case ARENA_TYPE_5v5: + MaxPlayersPerTeam = 5; + MinPlayersPerTeam = 5; + break; + }*/ + } + } + + m_selectionPools[TEAM_INDEX_ALLIANCE].Init(); + m_selectionPools[TEAM_INDEX_HORDE].Init(); + + if (bgTemplate->IsBattleGround()) + { + // check if there is premade against premade match + if (CheckPremadeMatch(bracketId, minPlayersPerTeam, maxPlayersPerTeam)) + { + // create new battleground + BattleGround* bg2 = sBattleGroundMgr.CreateNewBattleGround(bgTypeId, bracketId, ARENA_TYPE_NONE, false); + if (!bg2) + { + sLog.outError("BattleGroundQueueItem::Update - Cannot create battleground: %u", bgTypeId); + return; + } + + // invite those selection pools + for (uint8 i = 0; i < PVP_TEAM_COUNT; ++i) + for (GroupsQueueType::const_iterator citr = m_selectionPools[TEAM_INDEX_ALLIANCE + i].selectedGroups.begin(); citr != m_selectionPools[TEAM_INDEX_ALLIANCE + i].selectedGroups.end(); ++citr) + InviteGroupToBg((*citr), bg2, (*citr)->groupTeam); + + // start bg + bg2->StartBattleGround(); + // clear structures + m_selectionPools[TEAM_INDEX_ALLIANCE].Init(); + m_selectionPools[TEAM_INDEX_HORDE].Init(); + } + } + + // now check if there are in queues enough players to start new game of (normal battleground, or non-rated arena) + if (!isRated) + { + // if there are enough players in pools, start new battleground or non rated arena + if (CheckNormalMatch(bgTemplate, bracketId, minPlayersPerTeam, maxPlayersPerTeam) + || (bgTemplate->IsArena() && CheckSkirmishForSameFaction(bracketId, minPlayersPerTeam))) + { + // we successfully created a pool + BattleGround* bg2 = sBattleGroundMgr.CreateNewBattleGround(bgTypeId, bracketId, arenaType, false); + if (!bg2) + { + sLog.outError("BattleGroundQueueItem::Update - Cannot create battleground: %u", bgTypeId); + return; + } + + // invite those selection pools + for (uint8 i = 0; i < PVP_TEAM_COUNT; ++i) + for (GroupsQueueType::const_iterator citr = m_selectionPools[TEAM_INDEX_ALLIANCE + i].selectedGroups.begin(); citr != m_selectionPools[TEAM_INDEX_ALLIANCE + i].selectedGroups.end(); ++citr) + InviteGroupToBg((*citr), bg2, (*citr)->groupTeam); + + // start bg + bg2->StartBattleGround(); + } + } + else if (bgTemplate->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[bracketId][BG_QUEUE_PREMADE_ALLIANCE].empty()) + { + front1 = m_queuedGroups[bracketId][BG_QUEUE_PREMADE_ALLIANCE].front(); + arenaRating = front1->arenaTeamRating; + } + + if (!m_queuedGroups[bracketId][BG_QUEUE_PREMADE_HORDE].empty()) + { + front2 = m_queuedGroups[bracketId][BG_QUEUE_PREMADE_HORDE].front(); + arenaRating = front2->arenaTeamRating; + } + + if (front1 && front2) + { + if (front1->joinTime < front2->joinTime) + arenaRating = front1->arenaTeamRating; + } + 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 + uint32 discardTime = WorldTimer::getMSTime() - sBattleGroundMgr.GetRatingDiscardTimer(); + + // we need to find 2 teams which will play next game + + GroupsQueueType::iterator itr_team[PVP_TEAM_COUNT]; + + // optimalization : --- we dont need to use selection_pools - each update we select max 2 groups + + for (uint8 i = BG_QUEUE_PREMADE_ALLIANCE; i < BG_QUEUE_NORMAL_ALLIANCE; ++i) + { + // take the group that joined first + itr_team[i] = m_queuedGroups[bracketId][i].begin(); + for (; itr_team[i] != m_queuedGroups[bracketId][i].end(); ++(itr_team[i])) + { + // if group match conditions, then add it to pool + if (!(*itr_team[i])->isInvitedToBgInstanceGuid + && (((*itr_team[i])->arenaTeamRating >= arenaMinRating && (*itr_team[i])->arenaTeamRating <= arenaMaxRating) + || (*itr_team[i])->joinTime < discardTime)) + { + m_selectionPools[i].AddGroup((*itr_team[i]), maxPlayersPerTeam, 0); + // break for cycle to be able to start selecting another group from same faction queue + break; + } + } + } + // now we are done if we have 2 groups - ali vs horde! + // if we don't have, we must try to continue search in same queue + // tmp variables are correctly set + // this code isn't much userfriendly - but it is supposed to continue search for mathing group in HORDE queue + if (m_selectionPools[TEAM_INDEX_ALLIANCE].GetPlayerCount() == 0 && m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount()) + { + itr_team[TEAM_INDEX_ALLIANCE] = itr_team[TEAM_INDEX_HORDE]; + ++itr_team[TEAM_INDEX_ALLIANCE]; + + for (; itr_team[TEAM_INDEX_ALLIANCE] != m_queuedGroups[bracketId][BG_QUEUE_PREMADE_HORDE].end(); ++(itr_team[TEAM_INDEX_ALLIANCE])) + { + if (!(*itr_team[TEAM_INDEX_ALLIANCE])->isInvitedToBgInstanceGuid + && (((*itr_team[TEAM_INDEX_ALLIANCE])->arenaTeamRating >= arenaMinRating && (*itr_team[TEAM_INDEX_ALLIANCE])->arenaTeamRating <= arenaMaxRating) + || (*itr_team[TEAM_INDEX_ALLIANCE])->joinTime < discardTime)) + { + m_selectionPools[TEAM_INDEX_ALLIANCE].AddGroup((*itr_team[TEAM_INDEX_ALLIANCE]), maxPlayersPerTeam, 0); + break; + } + } + } + // this code isn't much userfriendly - but it is supposed to continue search for mathing group in ALLIANCE queue + if (m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount() == 0 && m_selectionPools[TEAM_INDEX_ALLIANCE].GetPlayerCount()) + { + itr_team[TEAM_INDEX_HORDE] = itr_team[TEAM_INDEX_ALLIANCE]; + ++itr_team[TEAM_INDEX_HORDE]; + + for (; itr_team[TEAM_INDEX_HORDE] != m_queuedGroups[bracketId][BG_QUEUE_PREMADE_ALLIANCE].end(); ++(itr_team[TEAM_INDEX_HORDE])) + { + if (!(*itr_team[TEAM_INDEX_HORDE])->isInvitedToBgInstanceGuid + && (((*itr_team[TEAM_INDEX_HORDE])->arenaTeamRating >= arenaMinRating && (*itr_team[TEAM_INDEX_HORDE])->arenaTeamRating <= arenaMaxRating) + || (*itr_team[TEAM_INDEX_HORDE])->joinTime < discardTime)) + { + m_selectionPools[TEAM_INDEX_HORDE].AddGroup((*itr_team[TEAM_INDEX_HORDE]), maxPlayersPerTeam, 0); + break; + } + } + } + + // if we have 2 teams, then start new arena and invite players! + if (m_selectionPools[TEAM_INDEX_ALLIANCE].GetPlayerCount() && m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount()) + { + BattleGround* arena = sBattleGroundMgr.CreateNewBattleGround(bgTypeId, bracketId, arenaType, true); + if (!arena) + { + sLog.outError("BattlegroundQueue::Update couldn't create arena instance for rated arena match!"); + return; + } + + (*(itr_team[TEAM_INDEX_ALLIANCE]))->opponentsTeamRating = (*(itr_team[TEAM_INDEX_HORDE]))->arenaTeamRating; + DEBUG_LOG("setting oposite teamrating for team %u to %u", (*(itr_team[TEAM_INDEX_ALLIANCE]))->arenaTeamId, (*(itr_team[TEAM_INDEX_ALLIANCE]))->opponentsTeamRating); + (*(itr_team[TEAM_INDEX_HORDE]))->opponentsTeamRating = (*(itr_team[TEAM_INDEX_ALLIANCE]))->arenaTeamRating; + DEBUG_LOG("setting oposite teamrating for team %u to %u", (*(itr_team[TEAM_INDEX_HORDE]))->arenaTeamId, (*(itr_team[TEAM_INDEX_HORDE]))->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 ((*(itr_team[TEAM_INDEX_ALLIANCE]))->groupTeam != ALLIANCE) + { + // add to alliance queue + m_queuedGroups[bracketId][BG_QUEUE_PREMADE_ALLIANCE].push_front(*(itr_team[TEAM_INDEX_ALLIANCE])); + // erase from horde queue + m_queuedGroups[bracketId][BG_QUEUE_PREMADE_HORDE].erase(itr_team[TEAM_INDEX_ALLIANCE]); + itr_team[TEAM_INDEX_ALLIANCE] = m_queuedGroups[bracketId][BG_QUEUE_PREMADE_ALLIANCE].begin(); + } + + if ((*(itr_team[TEAM_INDEX_HORDE]))->groupTeam != HORDE) + { + m_queuedGroups[bracketId][BG_QUEUE_PREMADE_HORDE].push_front(*(itr_team[TEAM_INDEX_HORDE])); + m_queuedGroups[bracketId][BG_QUEUE_PREMADE_ALLIANCE].erase(itr_team[TEAM_INDEX_HORDE]); + itr_team[TEAM_INDEX_HORDE] = m_queuedGroups[bracketId][BG_QUEUE_PREMADE_HORDE].begin(); + } + + InviteGroupToBg(*(itr_team[TEAM_INDEX_ALLIANCE]), arena, ALLIANCE); + InviteGroupToBg(*(itr_team[TEAM_INDEX_HORDE]), arena, HORDE); + + DEBUG_LOG("Starting rated arena match!"); + + arena->StartBattleGround(); + } + } +} + +/*********************************************************/ +/*** BATTLEGROUND QUEUE EVENTS ***/ +/*********************************************************/ + +/** + Function that executes battleground queue invite event +*/ +bool BgQueueInviteEvent::Execute(uint64 /*e_time*/, uint32 /*p_time*/) +{ + sWorld.GetMessager().AddMessage([event = *this](World* /*world*/) + { + Player* plr = sObjectMgr.GetPlayer(event.m_playerGuid); + // player logged off (we should do nothing, he is correctly removed from queue in another procedure) + if (!plr) + return; + + BattleGround* bg = sBattleGroundMgr.GetBattleGround(event.m_bgInstanceGuid, event.m_bgTypeId); + // if battleground ended and its instance deleted - do nothing + if (!bg) + return; + + BattleGroundQueueTypeId bgQueueTypeId = BattleGroundMgr::BgQueueTypeId(bg->GetTypeId(), bg->GetArenaType()); + uint32 queueSlot = plr->GetBattleGroundQueueIndex(bgQueueTypeId); + if (queueSlot < PLAYER_MAX_BATTLEGROUND_QUEUES) // player is in queue or in battleground + { + // check if player is invited to this bg + BattleGroundQueueItem& bgQueue = sBattleGroundMgr.m_battleGroundQueues[bgQueueTypeId]; + if (bgQueue.IsPlayerInvited(event.m_playerGuid, event.m_bgInstanceGuid, event.m_removeTime)) + { + WorldPacket data; + // we must send remaining time in queue + sBattleGroundMgr.BuildBattleGroundStatusPacket(data, bg, queueSlot, STATUS_WAIT_JOIN, INVITE_ACCEPT_WAIT_TIME - INVITATION_REMIND_TIME, 0, event.m_arenaType, TEAM_NONE); + plr->GetSession()->SendPacket(data); + } + } + }); + return true; // event will be deleted +} + +void BgQueueInviteEvent::Abort(uint64 /*e_time*/) +{ + // do nothing +} + +/** + Function that executes battleground queue remove event + this event has many possibilities when it is executed: + 1. player is in battleground ( he clicked enter on invitation window ) + 2. player left battleground queue and he isn't there any more + 3. player left battleground queue and he joined it again and IsInvitedToBGInstanceGUID = 0 + 4. player left queue and he joined again and he has been invited to same battleground again -> we should not remove him from queue yet + 5. player is invited to bg and he didn't choose what to do and timer expired - only in this condition we should call queue::RemovePlayer + we must remove player in the 5. case even if battleground object doesn't exist! +*/ +bool BgQueueRemoveEvent::Execute(uint64 /*e_time*/, uint32 /*p_time*/) +{ + sWorld.GetMessager().AddMessage([event = *this](World* /*world*/) + { + Player* plr = sObjectMgr.GetPlayer(event.m_playerGuid); + if (!plr) + // player logged off (we should do nothing, he is correctly removed from queue in another procedure) + return; + + BattleGround* bg = sBattleGroundMgr.GetBattleGround(event.m_bgInstanceGuid, event.m_bgTypeId); + // battleground can be deleted already when we are removing queue info + // bg pointer can be nullptr! so use it carefully! + + uint32 queueSlot = plr->GetBattleGroundQueueIndex(event.m_bgQueueTypeId); + if (queueSlot < PLAYER_MAX_BATTLEGROUND_QUEUES) // player is in queue, or in Battleground + { + // check if player is in queue for this BG and if we are removing his invite event + BattleGroundQueueItem& bgQueue = sBattleGroundMgr.m_battleGroundQueues[event.m_bgQueueTypeId]; + if (bgQueue.IsPlayerInvited(event.m_playerGuid, event.m_bgInstanceGuid, event.m_removeTime)) + { + DEBUG_LOG("Battleground: removing player %u from bg queue for instance %u because of not pressing enter battle in time.", plr->GetGUIDLow(), event.m_bgInstanceGuid); + + plr->RemoveBattleGroundQueueId(event.m_bgQueueTypeId); + bgQueue.RemovePlayer(event.m_playerGuid, true); + + // update queues if battleground isn't ended + if (bg && bg->IsBattleGround() && bg->GetStatus() != STATUS_WAIT_LEAVE) + sBattleGroundMgr.ScheduleQueueUpdate(0, ARENA_TYPE_NONE, event.m_bgQueueTypeId, event.m_bgTypeId, bg->GetBracketId()); + + WorldPacket data; + sBattleGroundMgr.BuildBattleGroundStatusPacket(data, bg, queueSlot, STATUS_NONE, 0, 0, ARENA_TYPE_NONE, TEAM_NONE); + plr->GetSession()->SendPacket(data); + } + } + }); + + // event will be deleted + return true; +} + +void BgQueueRemoveEvent::Abort(uint64 /*e_time*/) +{ + // do nothing +} + +BattleGroundQueue::BattleGroundQueue() +{ + TimePoint now = std::chrono::time_point_cast(Clock::now());; + m_nextRatingDiscardUpdate = now + std::chrono::milliseconds(sWorld.getConfig(CONFIG_UINT32_ARENA_RATING_DISCARD_TIMER)); +} + +void BattleGroundQueue::Update() +{ + TimePoint previously = sWorld.GetCurrentClockTime(); + InitAutomaticArenaPointDistribution(); + while (!World::IsStopped()) + { + TimePoint now = std::chrono::time_point_cast(Clock::now()); + GetMessager().Execute(this); + + // update scheduled queues + if (!m_queueUpdateScheduler.empty()) + { + std::vector scheduled; + { + // copy vector and clear the other + scheduled = std::vector(m_queueUpdateScheduler); + m_queueUpdateScheduler.clear(); + } + + for (unsigned long long i : scheduled) + { + uint32 arenaRating = i >> 32; + ArenaType arenaType = ArenaType(i >> 24 & 255); + BattleGroundQueueTypeId bgQueueTypeId = BattleGroundQueueTypeId(i >> 16 & 255); + BattleGroundTypeId bgTypeId = BattleGroundTypeId((i >> 8) & 255); + BattleGroundBracketId bracket_id = BattleGroundBracketId(i & 255); + + m_battleGroundQueues[bgQueueTypeId].Update(bgTypeId, bracket_id, arenaType, arenaRating > 0, arenaRating); + } + } + + // if rating difference counts, maybe force-update queues + if (sWorld.getConfig(CONFIG_UINT32_ARENA_MAX_RATING_DIFFERENCE) && sWorld.getConfig(CONFIG_UINT32_ARENA_RATING_DISCARD_TIMER)) + { + // it's time to force update + if (m_nextRatingDiscardUpdate <= now) + { + // forced update for level 70 rated arenas + DEBUG_LOG("BattleGroundMgr: UPDATING ARENA QUEUES"); + m_battleGroundQueues[BATTLEGROUND_QUEUE_2v2].Update(BATTLEGROUND_AA, BG_BRACKET_ID_FIRST, ARENA_TYPE_2v2, true, 0); + m_battleGroundQueues[BATTLEGROUND_QUEUE_3v3].Update(BATTLEGROUND_AA, BG_BRACKET_ID_FIRST, ARENA_TYPE_3v3, true, 0); + m_battleGroundQueues[BATTLEGROUND_QUEUE_5v5].Update(BATTLEGROUND_AA, BG_BRACKET_ID_FIRST, ARENA_TYPE_5v5, true, 0); + + m_nextRatingDiscardUpdate = now + std::chrono::milliseconds(sWorld.getConfig(CONFIG_UINT32_ARENA_RATING_DISCARD_TIMER)); + } + } + + if (sWorld.getConfig(CONFIG_BOOL_ARENA_AUTO_DISTRIBUTE_POINTS)) + { + if (m_nextAutoDistributionTime <= now) + { + sWorld.GetMessager().AddMessage([](World* world) + { + sBattleGroundMgr.DistributeArenaPoints(); // TODO: Is meant to be done as battlegroup, not world + }); + + m_nextAutoDistributionTime = now + std::chrono::seconds(BATTLEGROUND_ARENA_POINT_DISTRIBUTION_DAY * sWorld.getConfig(CONFIG_UINT32_ARENA_AUTO_DISTRIBUTE_INTERVAL_DAYS)); + CharacterDatabase.PExecute("UPDATE saved_variables SET NextArenaPointDistributionTime = '" UI64FMTD "'", uint64(std::chrono::time_point_cast(m_nextAutoDistributionTime).time_since_epoch().count())); + } + } + + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + }; +} + +void BattleGroundQueue::InitAutomaticArenaPointDistribution() +{ + if (sWorld.getConfig(CONFIG_BOOL_ARENA_AUTO_DISTRIBUTE_POINTS)) + { + auto queryResult = CharacterDatabase.Query("SELECT NextArenaPointDistributionTime FROM saved_variables"); + bool save = false; + bool insert = false; + if (!queryResult) // if not set generate time for next wednesday + insert = true; + else + { + uint64 dbTimePoint = (*queryResult)[0].GetUInt64(); + m_nextAutoDistributionTime = TimePoint(std::chrono::seconds(dbTimePoint));; + if (dbTimePoint == 0) // uninitialized + save = true; + else // if time already exists - check for config changes + { + time_t distrTime = std::chrono::system_clock::to_time_t(m_nextAutoDistributionTime); + tm distribTime = *localtime(&distrTime); + if (distribTime.tm_hour != sWorld.getConfig(CONFIG_UINT32_QUEST_DAILY_RESET_HOUR)) + { + TimePoint now = std::chrono::time_point_cast(Clock::now()); + if (now >= m_nextAutoDistributionTime) // if it already expired, do not save and only adjust hour + { + distribTime.tm_hour = sWorld.getConfig(CONFIG_UINT32_QUEST_DAILY_RESET_HOUR); + m_nextAutoDistributionTime = TimePoint(std::chrono::seconds(mktime(&distribTime))); + } + else + save = true; + } + } + } + + if (save || insert) + { + // generate time by config on first server launch + time_t curTime = time(nullptr); + tm localTm = *localtime(&curTime); + localTm.tm_hour = sWorld.getConfig(CONFIG_UINT32_QUEST_DAILY_RESET_HOUR); + localTm.tm_min = 0; + localTm.tm_sec = 0; + localTm.tm_mday += ((7 - localTm.tm_wday + sWorld.getConfig(CONFIG_UINT32_ARENA_FIRST_RESET_DAY)) % 7); + localTm.tm_isdst = -1; + m_nextAutoDistributionTime = TimePoint(std::chrono::seconds(mktime(&localTm))); + + uint64 distributionTime = uint64(std::chrono::time_point_cast(m_nextAutoDistributionTime).time_since_epoch().count()); + if (insert) + CharacterDatabase.PExecute("INSERT INTO saved_variables (NextArenaPointDistributionTime) VALUES ('" UI64FMTD "')", distributionTime); + if (save) + CharacterDatabase.PExecute("UPDATE saved_variables SET NextArenaPointDistributionTime = '" UI64FMTD "'", distributionTime); + } + } +} diff --git a/src/game/BattleGround/BattleGroundQueue.h b/src/game/BattleGround/BattleGroundQueue.h index 63fd86faac..4de3d41947 100644 --- a/src/game/BattleGround/BattleGroundQueue.h +++ b/src/game/BattleGround/BattleGroundQueue.h @@ -19,4 +19,172 @@ #ifndef __BATTLEGROUNDQUEUE_H #define __BATTLEGROUNDQUEUE_H +#include "Common.h" +#include "BattleGround/BattleGroundDefines.h" + +// this container can't be deque, because deque doesn't like removing the last element - if you remove it, it invalidates next iterator and crash appears +typedef std::list BgFreeSlotQueueType; + +struct GroupQueueInfo; // type predefinition +struct PlayerQueueInfo // stores information for players in queue +{ + uint32 lastOnlineTime; // for tracking and removing offline players from queue after 5 minutes + GroupQueueInfo* groupInfo; // pointer to the associated groupqueueinfo +}; + +typedef std::map GroupQueueInfoPlayers; + +struct GroupQueueInfo // stores information about the group in queue (also used when joined as solo!) +{ + GroupQueueInfoPlayers players; // player queue info map + Team groupTeam; // Player team (ALLIANCE/HORDE) + BattleGroundTypeId bgTypeId; // battleground type id + bool isRated; // rated + ArenaType arenaType; // 2v2, 3v3, 5v5 or 0 when BG + uint32 arenaTeamId; // team id if rated match + uint32 joinTime; // time when group was added + uint32 removeInviteTime; // time when we will remove invite for players in group + uint32 isInvitedToBgInstanceGuid; // was invited to certain BG + uint32 desiredInstanceId; // queued for this instance specifically + uint32 arenaTeamRating; // if rated match, inited to the rating of the team + uint32 opponentsTeamRating; // for rated arena matches +}; + +class BattleGroundQueueItem +{ + public: + BattleGroundQueueItem(); + ~BattleGroundQueueItem(); + + void Update(BattleGroundTypeId /*bgTypeId*/, BattleGroundBracketId /*bracketId*/, ArenaType arenaType = ARENA_TYPE_NONE, bool isRated = false, uint32 arenaRating = 0); + + void FillPlayersToBg(BattleGround* /*bg*/, BattleGroundBracketId /*bracketId*/); + bool CheckPremadeMatch(BattleGroundBracketId /*bracketId*/, uint32 /*minPlayersPerTeam*/, uint32 /*maxPlayersPerTeam*/); + bool CheckNormalMatch(BattleGround* /*bgTemplate*/, BattleGroundBracketId /*bracketId*/, uint32 /*minPlayers*/, uint32 /*maxPlayers*/); + bool CheckSkirmishForSameFaction(BattleGroundBracketId /*bracketId*/, uint32 /*minPlayersPerTeam*/); + GroupQueueInfo* AddGroup(Player* /*leader*/, Group* /*group*/, BattleGroundTypeId /*bgTypeId*/, BattleGroundBracketId /*bracketEntry*/, ArenaType /*arenaType*/, bool /*isRated*/, bool /*isPremade*/, uint32 /*instanceId*/, uint32 /*arenaRating*/, uint32 arenaTeamId = 0); + void RemovePlayer(ObjectGuid /*guid*/, bool /*decreaseInvitedCount*/); + bool IsPlayerInvited(ObjectGuid /*playerGuid*/, const uint32 /*bgInstanceGuid*/, const uint32 /*removeTime*/); + bool GetPlayerGroupInfoData(ObjectGuid /*guid*/, GroupQueueInfo* /*groupInfo*/); + void PlayerInvitedToBgUpdateAverageWaitTime(GroupQueueInfo* /*groupInfo*/, BattleGroundBracketId /*bracketId*/); + uint32 GetAverageQueueWaitTime(GroupQueueInfo* /*groupInfo*/, BattleGroundBracketId /*bracketId*/); + + private: + // mutex that should not allow changing private data, nor allowing to update Queue during private data change. + std::recursive_mutex m_lock; + + + typedef std::map QueuedPlayersMap; + QueuedPlayersMap m_queuedPlayers; + + // we need constant add to begin and constant remove / add from the end, therefore deque suits our problem well + typedef std::list GroupsQueueType; + + /* + This two dimensional array is used to store All queued groups + First dimension specifies the bgTypeId + Second dimension specifies the player's group types - + BG_QUEUE_PREMADE_ALLIANCE is used for premade alliance groups and alliance rated arena teams + BG_QUEUE_PREMADE_HORDE is used for premade horde groups and horde rated arena teams + BG_QUEUE_NORMAL_ALLIANCE is used for normal (or small) alliance groups or non-rated arena matches + BG_QUEUE_NORMAL_HORDE is used for normal (or small) horde groups or non-rated arena matches + */ + GroupsQueueType m_queuedGroups[MAX_BATTLEGROUND_BRACKETS][BG_QUEUE_GROUP_TYPES_COUNT]; + + // class to select and invite groups to bg + class SelectionPool + { + public: + SelectionPool() : playerCount(0) {} + void Init(); + bool AddGroup(GroupQueueInfo* ginfo, uint32 desiredCount, uint32 bgInstanceId); + bool KickGroup(uint32 size); + uint32 GetPlayerCount() const { return playerCount; } + GroupsQueueType selectedGroups; + private: + uint32 playerCount; + }; + + // one selection pool for horde, other one for alliance + SelectionPool m_selectionPools[PVP_TEAM_COUNT]; + + bool InviteGroupToBg(GroupQueueInfo* /*groupInfo*/, BattleGround* /*bg*/, Team /*side*/); + + uint32 m_waitTimes[PVP_TEAM_COUNT][MAX_BATTLEGROUND_BRACKETS][COUNT_OF_PLAYERS_TO_AVERAGE_WAIT_TIME]; + uint32 m_waitTimeLastPlayer[PVP_TEAM_COUNT][MAX_BATTLEGROUND_BRACKETS]; + uint32 m_sumOfWaitTimes[PVP_TEAM_COUNT][MAX_BATTLEGROUND_BRACKETS]; +}; + +/* + This class is used to invite player to BG again, when minute lasts from his first invitation + it is capable to solve all possibilities +*/ +class BgQueueInviteEvent : public BasicEvent +{ + public: + BgQueueInviteEvent(ObjectGuid playerGuid, uint32 bgInstanceGuid, BattleGroundTypeId bgTypeId, ArenaType arenaType, uint32 removeTime) : + m_playerGuid(playerGuid), m_bgInstanceGuid(bgInstanceGuid), m_bgTypeId(bgTypeId), m_arenaType(arenaType), m_removeTime(removeTime) + { + }; + virtual ~BgQueueInviteEvent() {}; + + virtual bool Execute(uint64 e_time, uint32 p_time) override; + virtual void Abort(uint64 e_time) override; + + private: + ObjectGuid m_playerGuid; + uint32 m_bgInstanceGuid; + BattleGroundTypeId m_bgTypeId; + ArenaType m_arenaType; + uint32 m_removeTime; +}; + +/* + This class is used to remove player from BG queue after 1 minute 20 seconds from first invitation + We must store removeInvite time in case player left queue and joined and is invited again + We must store bgQueueTypeId, because battleground can be deleted already, when player entered it +*/ +class BgQueueRemoveEvent : public BasicEvent +{ + public: + BgQueueRemoveEvent(ObjectGuid playerGuid, uint32 bgInstanceGuid, BattleGroundTypeId bgTypeId, BattleGroundQueueTypeId bgQueueTypeId, uint32 removeTime) + : m_playerGuid(playerGuid), m_bgInstanceGuid(bgInstanceGuid), m_removeTime(removeTime), m_bgTypeId(bgTypeId), m_bgQueueTypeId(bgQueueTypeId) + {} + + virtual ~BgQueueRemoveEvent() {} + + virtual bool Execute(uint64 e_time, uint32 p_time) override; + virtual void Abort(uint64 e_time) override; + + private: + ObjectGuid m_playerGuid; + uint32 m_bgInstanceGuid; + uint32 m_removeTime; + BattleGroundTypeId m_bgTypeId; + BattleGroundQueueTypeId m_bgQueueTypeId; +}; + +class BattleGroundQueue +{ + public: + BattleGroundQueue(); + + void Update(); + + Messager& GetMessager() { return m_messager; } + + void InitAutomaticArenaPointDistribution(); + private: + BattleGroundQueueItem m_battleGroundQueues[MAX_BATTLEGROUND_QUEUE_TYPES]; // public, because we need to access them in BG handler code + + BgFreeSlotQueueType BgFreeSlotQueue[MAX_BATTLEGROUND_TYPE_ID]; + + std::vector m_queueUpdateScheduler; + TimePoint m_nextRatingDiscardUpdate; + TimePoint m_nextAutoDistributionTime; + TimePoint m_autoDistributionTimeChecker; + + Messager m_messager; +}; + #endif \ No newline at end of file diff --git a/src/game/World/World.cpp b/src/game/World/World.cpp index 5119e9a124..de63082a27 100644 --- a/src/game/World/World.cpp +++ b/src/game/World/World.cpp @@ -160,6 +160,7 @@ World::~World() MMAP::MMapFactory::clear(); m_lfgQueueThread.join(); + m_bgQueueThread.join(); } /// Cleanups before world stop @@ -2888,3 +2889,11 @@ void World::StartLFGQueueThread() m_lfgQueue.Update(); }); } + +void World::StartBGQueueThread() +{ + m_bgQueueThread = std::thread([&]() + { + m_bgQueue.Update(); + }); +} diff --git a/src/game/World/World.h b/src/game/World/World.h index 57fc46e510..45fd6282e6 100644 --- a/src/game/World/World.h +++ b/src/game/World/World.h @@ -683,6 +683,7 @@ class World LFGQueue& GetLFGQueue() { return m_lfgQueue; } void StartLFGQueueThread(); + void StartBGQueueThread(); protected: void _UpdateGameTime(); // callback for UpdateRealmCharacters @@ -811,9 +812,11 @@ class World GraveyardManager m_graveyardManager; - // Housing this here but logically it is completely asynchronous - TODO: Separate this and unify with BG queue + // Housing this here but logically it is completely asynchronous LFGQueue m_lfgQueue; std::thread m_lfgQueueThread; + BattleGroundQueue m_bgQueue; + std::thread m_bgQueueThread; }; extern uint32 realmID; diff --git a/src/mangosd/Master.cpp b/src/mangosd/Master.cpp index 69b4cf7dd7..a8334d7b2c 100644 --- a/src/mangosd/Master.cpp +++ b/src/mangosd/Master.cpp @@ -150,6 +150,7 @@ int Master::Run() } sWorld.StartLFGQueueThread(); + sWorld.StartBGQueueThread(); MaNGOS::Thread* cliThread = nullptr;