/* * Copyright (C) 2005-2009 MaNGOS * * Copyright (C) 2008-2010 Trinity * * 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 */ #include "Common.h" #include "ObjectMgr.h" #include "World.h" #include "WorldPacket.h" #include "Policies/SingletonImp.h" #include "ArenaTeam.h" #include "BattleGroundMgr.h" #include "BattleGroundAV.h" #include "BattleGroundAB.h" #include "BattleGroundEY.h" #include "BattleGroundWS.h" #include "BattleGroundNA.h" #include "BattleGroundBE.h" #include "BattleGroundAA.h" #include "BattleGroundRL.h" #include "BattleGroundSA.h" #include "BattleGroundDS.h" #include "BattleGroundRV.h" #include "BattleGroundIC.h" #include "BattleGroundABG.h" #include "Chat.h" #include "Map.h" #include "MapInstanced.h" #include "MapManager.h" #include "Player.h" #include "GameEventMgr.h" #include "ProgressBar.h" #include "SharedDefines.h" INSTANTIATE_SINGLETON_1( BattleGroundMgr ); /*********************************************************/ /*** BATTLEGROUND QUEUE SYSTEM ***/ /*********************************************************/ BattleGroundQueue::BattleGroundQueue() { for (uint32 i = 0; i < BG_TEAMS_COUNT; ++i) { for (uint32 j = 0; j < MAX_BATTLEGROUND_BRACKETS; ++j) { m_SumOfWaitTimes[i][j] = 0; m_WaitTimeLastPlayer[i][j] = 0; for (uint32 k = 0; k < COUNT_OF_PLAYERS_TO_AVERAGE_WAIT_TIME; ++k) m_WaitTimes[i][j][k] = 0; } } } BattleGroundQueue::~BattleGroundQueue() { m_QueuedPlayers.clear(); for (int i = 0; i < MAX_BATTLEGROUND_BRACKETS; ++i) { for (uint32 j = 0; j < BG_QUEUE_GROUP_TYPES_COUNT; ++j) { for (GroupsQueueType::iterator itr = m_QueuedGroups[i][j].begin(); itr!= m_QueuedGroups[i][j].end(); ++itr) delete (*itr); m_QueuedGroups[i][j].clear(); } } } /*********************************************************/ /*** BATTLEGROUND QUEUE SELECTION POOLS ***/ /*********************************************************/ // selection pool initialization, used to clean up from prev selection void BattleGroundQueue::SelectionPool::Init() { SelectedGroups.clear(); PlayerCount = 0; } // remove group info from selection pool // 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 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* ginfo = (*groupToKick); SelectedGroups.erase(groupToKick); PlayerCount -= ginfo->Players.size(); //return false if we kicked smaller group or there are enough players in selection pool if (ginfo->Players.size() <= size + 1) return false; } return true; } // add group to selection pool // used when building selection pools // returns true if we can invite more players, or when we added group to selection pool // returns false when selection pool is full bool BattleGroundQueue::SelectionPool::AddGroup(GroupQueueInfo *ginfo, uint32 desiredCount) { //if group is larger than desired count - don't allow to add it to pool if (!ginfo->IsInvitedToBGInstanceGUID && desiredCount >= PlayerCount + ginfo->Players.size()) { SelectedGroups.push_back(ginfo); // increase selected players count PlayerCount += ginfo->Players.size(); return true; } if (PlayerCount < desiredCount) return true; return false; } /*********************************************************/ /*** BATTLEGROUND QUEUES ***/ /*********************************************************/ // add group or player (grp == NULL) to bg queue with the given leader and bg specifications GroupQueueInfo * BattleGroundQueue::AddGroup(Player *leader, Group* grp, BattleGroundTypeId BgTypeId, PvPDifficultyEntry const* backetEntry, uint8 ArenaType, bool isRated, bool isPremade, uint32 arenaRating, uint32 arenateamid) { BattleGroundBracketId bracketId = backetEntry->GetBracketId(); // create new ginfo GroupQueueInfo* ginfo = new GroupQueueInfo; ginfo->BgTypeId = BgTypeId; ginfo->ArenaType = ArenaType; ginfo->ArenaTeamId = arenateamid; ginfo->IsRated = isRated; ginfo->IsInvitedToBGInstanceGUID = 0; ginfo->JoinTime = getMSTime(); ginfo->RemoveInviteTime = 0; ginfo->Team = leader->GetTeam(); ginfo->ArenaTeamRating = arenaRating; ginfo->OpponentsTeamRating = 0; ginfo->Players.clear(); //compute index (if group is premade or joined a rated match) to queues uint32 index = 0; if (!isRated && !isPremade) index += BG_TEAMS_COUNT; if (ginfo->Team == HORDE) index++; sLog.outDebug("Adding Group to BattleGroundQueue bgTypeId : %u, bracket_id : %u, index : %u", BgTypeId, bracketId, index); uint32 lastOnlineTime = getMSTime(); //announce world (this don't need mutex) if (isRated && sWorld.getConfig(CONFIG_ARENA_QUEUE_ANNOUNCER_ENABLE)) { ArenaTeam *Team = objmgr.GetArenaTeamById(arenateamid); if(Team) sWorld.SendWorldText(LANG_ARENA_QUEUE_ANNOUNCE_WORLD_JOIN, Team->GetName().c_str(), ginfo->ArenaType, ginfo->ArenaType, ginfo->ArenaTeamRating); } //add players from group to ginfo { //ACE_Guard guard(m_Lock); if (grp) { for(GroupReference *itr = grp->GetFirstMember(); itr != NULL; itr = itr->next()) { Player *member = itr->getSource(); if(!member) continue; // this should never happen PlayerQueueInfo& pl_info = m_QueuedPlayers[member->GetGUID()]; pl_info.LastOnlineTime = lastOnlineTime; pl_info.GroupInfo = ginfo; // add the pinfo to ginfo's list ginfo->Players[member->GetGUID()] = &pl_info; } } else { PlayerQueueInfo& pl_info = m_QueuedPlayers[leader->GetGUID()]; pl_info.LastOnlineTime = lastOnlineTime; pl_info.GroupInfo = ginfo; ginfo->Players[leader->GetGUID()] = &pl_info; } //add GroupInfo to m_QueuedGroups m_QueuedGroups[bracketId][index].push_back(ginfo); //announce to world, this code needs mutex if (!isRated && !isPremade && sWorld.getConfig(CONFIG_BATTLEGROUND_QUEUE_ANNOUNCER_ENABLE)) { if (BattleGround* bg = sBattleGroundMgr.GetBattleGroundTemplate(ginfo->BgTypeId)) { char const* bgName = bg->GetName(); uint32 MinPlayers = bg->GetMinPlayersPerTeam(); uint32 qHorde = 0; uint32 qAlliance = 0; uint32 q_min_level = backetEntry->minLevel; uint32 q_max_level = backetEntry->maxLevel; 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_BATTLEGROUND_QUEUE_ANNOUNCER_PLAYERONLY)) { ChatHandler(leader).PSendSysMessage(LANG_BG_QUEUE_ANNOUNCE_SELF, bgName, q_min_level, q_max_level, qAlliance, (MinPlayers > qAlliance) ? MinPlayers - qAlliance : (uint32)0, qHorde, (MinPlayers > qHorde) ? MinPlayers - qHorde : (uint32)0); } // System message else { sWorld.SendWorldText(LANG_BG_QUEUE_ANNOUNCE_WORLD, bgName, q_min_level, q_max_level, qAlliance, (MinPlayers > qAlliance) ? MinPlayers - qAlliance : (uint32)0, qHorde, (MinPlayers > qHorde) ? MinPlayers - qHorde : (uint32)0); } } } //release mutex } return ginfo; } void BattleGroundQueue::PlayerInvitedToBGUpdateAverageWaitTime(GroupQueueInfo* ginfo, BattleGroundBracketId bracket_id) { uint32 timeInQueue = getMSTimeDiff(ginfo->JoinTime, getMSTime()); uint8 team_index = BG_TEAM_ALLIANCE; //default set to BG_TEAM_ALLIANCE - or non rated arenas! if (!ginfo->ArenaType) { if (ginfo->Team == HORDE) team_index = BG_TEAM_HORDE; } else { if (ginfo->IsRated) team_index = BG_TEAM_HORDE; //for rated arenas use BG_TEAM_HORDE } //store pointer to arrayindex of player that was added first uint32* lastPlayerAddedPointer = &(m_WaitTimeLastPlayer[team_index][bracket_id]); //remove his time from sum m_SumOfWaitTimes[team_index][bracket_id] -= m_WaitTimes[team_index][bracket_id][(*lastPlayerAddedPointer)]; //set average time to new m_WaitTimes[team_index][bracket_id][(*lastPlayerAddedPointer)] = timeInQueue; //add new time to sum m_SumOfWaitTimes[team_index][bracket_id] += timeInQueue; //set index of last player added to next one (*lastPlayerAddedPointer)++; (*lastPlayerAddedPointer) %= COUNT_OF_PLAYERS_TO_AVERAGE_WAIT_TIME; } uint32 BattleGroundQueue::GetAverageQueueWaitTime(GroupQueueInfo* ginfo, BattleGroundBracketId bracket_id) { uint8 team_index = BG_TEAM_ALLIANCE; //default set to BG_TEAM_ALLIANCE - or non rated arenas! if (!ginfo->ArenaType) { if (ginfo->Team == HORDE) team_index = BG_TEAM_HORDE; } else { if (ginfo->IsRated) team_index = BG_TEAM_HORDE; //for rated arenas use BG_TEAM_HORDE } //check if there is enought values(we always add values > 0) if (m_WaitTimes[team_index][bracket_id][COUNT_OF_PLAYERS_TO_AVERAGE_WAIT_TIME - 1] ) return (m_SumOfWaitTimes[team_index][bracket_id] / COUNT_OF_PLAYERS_TO_AVERAGE_WAIT_TIME); else //if there aren't enough values return 0 - not available return 0; } //remove player from queue and from group info, if group info is empty then remove it too void BattleGroundQueue::RemovePlayer(const uint64& guid, bool decreaseInvitedCount) { //Player *plr = objmgr.GetPlayer(guid); int32 bracket_id = -1; // signed for proper for-loop finish QueuedPlayersMap::iterator itr; //remove player from map, if he's there itr = m_QueuedPlayers.find(guid); if (itr == m_QueuedPlayers.end()) { sLog.outError("BattleGroundQueue: couldn't find player to remove GUID: %u", GUID_LOPART(guid)); return; } GroupQueueInfo* group = itr->second.GroupInfo; GroupsQueueType::iterator group_itr, group_itr_tmp; // 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 = (group->Team == HORDE) ? BG_TEAM_HORDE : BG_TEAM_ALLIANCE; for (int32 bracket_id_tmp = MAX_BATTLEGROUND_BRACKETS - 1; bracket_id_tmp >= 0 && bracket_id == -1; --bracket_id_tmp) { //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 (uint32 j = index; j < BG_QUEUE_GROUP_TYPES_COUNT; j += BG_QUEUE_NORMAL_ALLIANCE) { for(group_itr_tmp = m_QueuedGroups[bracket_id_tmp][j].begin(); group_itr_tmp != m_QueuedGroups[bracket_id_tmp][j].end(); ++group_itr_tmp) { if ((*group_itr_tmp) == group) { bracket_id = bracket_id_tmp; 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 (bracket_id == -1) { sLog.outError("BattleGroundQueue: ERROR Cannot find groupinfo for player GUID: %u", GUID_LOPART(guid)); return; } sLog.outDebug("BattleGroundQueue: Removing player GUID %u, from bracket_id %u", GUID_LOPART(guid), (uint32)bracket_id); // 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 std::map::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->Team); } // 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 && group->IsRated && group->Players.empty() && sWorld.getConfig(CONFIG_ARENA_QUEUE_ANNOUNCER_ENABLE)) { ArenaTeam *Team = objmgr.GetArenaTeamById(group->ArenaTeamId); if(Team) sWorld.SendWorldText(LANG_ARENA_QUEUE_ANNOUNCE_WORLD_EXIT, Team->GetName().c_str(), 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 = objmgr.GetArenaTeamById(group->ArenaTeamId); if (at) { sLog.outDebug("UPDATING memberLost's personal arena rating for %u by opponents rating: %u", GUID_LOPART(guid), group->OpponentsTeamRating); Player *plr = objmgr.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[bracket_id][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 = objmgr.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, 0); plr2->GetSession()->SendPacket(&data); } // then actually delete, this may delete the group as well! RemovePlayer(group->Players.begin()->first, decreaseInvitedCount); } } //returns true when player pl_guid is in queue and is invited to bgInstanceGuid bool BattleGroundQueue::IsPlayerInvited(const uint64& pl_guid, const uint32 bgInstanceGuid, const uint32 removeTime) { QueuedPlayersMap::const_iterator qItr = m_QueuedPlayers.find(pl_guid); return ( qItr != m_QueuedPlayers.end() && qItr->second.GroupInfo->IsInvitedToBGInstanceGUID == bgInstanceGuid && qItr->second.GroupInfo->RemoveInviteTime == removeTime ); } bool BattleGroundQueue::GetPlayerGroupInfoData(const uint64& guid, GroupQueueInfo* ginfo) { QueuedPlayersMap::const_iterator qItr = m_QueuedPlayers.find(guid); if (qItr == m_QueuedPlayers.end()) return false; *ginfo = *(qItr->second.GroupInfo); return true; } bool BattleGroundQueue::InviteGroupToBG(GroupQueueInfo * ginfo, BattleGround * bg, uint32 side) { // set side if needed if (side) ginfo->Team = side; if (!ginfo->IsInvitedToBGInstanceGUID) { // not yet invited // set invitation ginfo->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(ginfo->Team, ginfo->ArenaTeamId); ginfo->RemoveInviteTime = getMSTime() + INVITE_ACCEPT_WAIT_TIME; // loop through the players for (std::map::iterator itr = ginfo->Players.begin(); itr != ginfo->Players.end(); ++itr) { // get the player Player* plr = objmgr.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(ginfo, bracket_id); //sBattleGroundMgr.InvitePlayer(plr, bg, ginfo->Team); // set invited player counters bg->IncreaseInvitedCount(ginfo->Team); plr->SetInviteForBattleGroundQueueType(bgQueueTypeId, ginfo->IsInvitedToBGInstanceGUID); // create remind invite events BGQueueInviteEvent* inviteEvent = new BGQueueInviteEvent(plr->GetGUID(), ginfo->IsInvitedToBGInstanceGUID, bgTypeId, ginfo->ArenaType, ginfo->RemoveInviteTime); plr->m_Events.AddEvent(inviteEvent, plr->m_Events.CalculateTime(INVITATION_REMIND_TIME)); // create automatic remove events BGQueueRemoveEvent* removeEvent = new BGQueueRemoveEvent(plr->GetGUID(), ginfo->IsInvitedToBGInstanceGUID, bgTypeId, bgQueueTypeId, ginfo->RemoveInviteTime); plr->m_Events.AddEvent(removeEvent, plr->m_Events.CalculateTime(INVITE_ACCEPT_WAIT_TIME)); WorldPacket data; uint32 queueSlot = plr->GetBattleGroundQueueIndex(bgQueueTypeId); sLog.outDebug("Battleground: invited plr %s (%u) to BG instance %u queueindex %u bgtype %u, I can't help it if they don't press the enter battle button.",plr->GetName(),plr->GetGUIDLow(),bg->GetInstanceID(),queueSlot,bg->GetTypeID()); // send status packet sBattleGroundMgr.BuildBattleGroundStatusPacket(&data, bg, queueSlot, STATUS_WAIT_JOIN, INVITE_ACCEPT_WAIT_TIME, 0, ginfo->ArenaType); plr->GetSession()->SendPacket(&data); } return true; } return false; } /* This function is inviting players to already running battlegrounds Invitation type is based on config file large groups are disadvantageous, because they will be kicked first if invitation type = 1 */ void BattleGroundQueue::FillPlayersToBG(BattleGround* bg, BattleGroundBracketId bracket_id) { int32 hordeFree = bg->GetFreeSlotsForTeam(HORDE); int32 aliFree = bg->GetFreeSlotsForTeam(ALLIANCE); //iterator for iterating through bg queue GroupsQueueType::const_iterator Ali_itr = m_QueuedGroups[bracket_id][BG_QUEUE_NORMAL_ALLIANCE].begin(); //count of groups in queue - used to stop cycles uint32 aliCount = m_QueuedGroups[bracket_id][BG_QUEUE_NORMAL_ALLIANCE].size(); //index to queue which group is current uint32 aliIndex = 0; for (; aliIndex < aliCount && m_SelectionPools[BG_TEAM_ALLIANCE].AddGroup((*Ali_itr), aliFree); aliIndex++) ++Ali_itr; //the same thing for horde GroupsQueueType::const_iterator Horde_itr = m_QueuedGroups[bracket_id][BG_QUEUE_NORMAL_HORDE].begin(); uint32 hordeCount = m_QueuedGroups[bracket_id][BG_QUEUE_NORMAL_HORDE].size(); uint32 hordeIndex = 0; for (; hordeIndex < hordeCount && m_SelectionPools[BG_TEAM_HORDE].AddGroup((*Horde_itr), hordeFree); hordeIndex++) ++Horde_itr; //if ofc like BG queue invitation is set in config, then we are happy if (sWorld.getConfig(CONFIG_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[BG_TEAM_ALLIANCE].GetPlayerCount()); int32 diffHorde = hordeFree - int32(m_SelectionPools[BG_TEAM_HORDE].GetPlayerCount()); while( abs(diffAli - diffHorde) > 1 && (m_SelectionPools[BG_TEAM_HORDE].GetPlayerCount() > 0 || m_SelectionPools[BG_TEAM_ALLIANCE].GetPlayerCount() > 0) ) { //each cycle execution we need to kick at least 1 group if (diffAli < diffHorde) { //kick alliance group, add to pool new group if needed if (m_SelectionPools[BG_TEAM_ALLIANCE].KickGroup(diffHorde - diffAli)) { for (; aliIndex < aliCount && m_SelectionPools[BG_TEAM_ALLIANCE].AddGroup((*Ali_itr), (aliFree >= diffHorde) ? aliFree - diffHorde : 0); aliIndex++) ++Ali_itr; } //if ali selection is already empty, then kick horde group, but if there are less horde than ali in bg - break; if (!m_SelectionPools[BG_TEAM_ALLIANCE].GetPlayerCount()) { if (aliFree <= diffHorde + 1) break; m_SelectionPools[BG_TEAM_HORDE].KickGroup(diffHorde - diffAli); } } else { //kick horde group, add to pool new group if needed if (m_SelectionPools[BG_TEAM_HORDE].KickGroup(diffAli - diffHorde)) { for (; hordeIndex < hordeCount && m_SelectionPools[BG_TEAM_HORDE].AddGroup((*Horde_itr), (hordeFree >= diffAli) ? hordeFree - diffAli : 0); hordeIndex++) ++Horde_itr; } if (!m_SelectionPools[BG_TEAM_HORDE].GetPlayerCount()) { if (hordeFree <= diffAli + 1) break; m_SelectionPools[BG_TEAM_ALLIANCE].KickGroup(diffAli - diffHorde); } } //count diffs after small update diffAli = aliFree - int32(m_SelectionPools[BG_TEAM_ALLIANCE].GetPlayerCount()); diffHorde = hordeFree - int32(m_SelectionPools[BG_TEAM_HORDE].GetPlayerCount()); } } // this method checks if premade versus premade battleground is possible // then after 30 mins (default) in queue it moves premade group to normal queue // it tries to invite as much players as it can - to MaxPlayersPerTeam, because premade groups have more than MinPlayersPerTeam players bool BattleGroundQueue::CheckPremadeMatch(BattleGroundBracketId bracket_id, uint32 MinPlayersPerTeam, uint32 MaxPlayersPerTeam) { //check match if (!m_QueuedGroups[bracket_id][BG_QUEUE_PREMADE_ALLIANCE].empty() && !m_QueuedGroups[bracket_id][BG_QUEUE_PREMADE_HORDE].empty()) { //start premade match //if groups aren't invited GroupsQueueType::const_iterator ali_group, horde_group; for( ali_group = m_QueuedGroups[bracket_id][BG_QUEUE_PREMADE_ALLIANCE].begin(); ali_group != m_QueuedGroups[bracket_id][BG_QUEUE_PREMADE_ALLIANCE].end(); ++ali_group) if (!(*ali_group)->IsInvitedToBGInstanceGUID) break; for( horde_group = m_QueuedGroups[bracket_id][BG_QUEUE_PREMADE_HORDE].begin(); horde_group != m_QueuedGroups[bracket_id][BG_QUEUE_PREMADE_HORDE].end(); ++horde_group) if (!(*horde_group)->IsInvitedToBGInstanceGUID) break; if (ali_group != m_QueuedGroups[bracket_id][BG_QUEUE_PREMADE_ALLIANCE].end() && horde_group != m_QueuedGroups[bracket_id][BG_QUEUE_PREMADE_HORDE].end()) { m_SelectionPools[BG_TEAM_ALLIANCE].AddGroup((*ali_group), MaxPlayersPerTeam); m_SelectionPools[BG_TEAM_HORDE].AddGroup((*horde_group), MaxPlayersPerTeam); //add groups/players from normal queue to size of bigger group uint32 maxPlayers = std::min(m_SelectionPools[BG_TEAM_ALLIANCE].GetPlayerCount(), m_SelectionPools[BG_TEAM_HORDE].GetPlayerCount()); GroupsQueueType::const_iterator itr; for (uint32 i = 0; i < BG_TEAMS_COUNT; i++) { for(itr = m_QueuedGroups[bracket_id][BG_QUEUE_NORMAL_ALLIANCE + i].begin(); itr != m_QueuedGroups[bracket_id][BG_QUEUE_NORMAL_ALLIANCE + i].end(); ++itr) { //if itr can join BG and player count is less that maxPlayers, then add group to selectionpool if (!(*itr)->IsInvitedToBGInstanceGUID && !m_SelectionPools[i].AddGroup((*itr), maxPlayers)) break; } } //premade selection pools are set return true; } } // now check if we can move group from Premade queue to normal queue (timer has expired) or group size lowered!! // this could be 2 cycles but i'm checking only first team in queue - it can cause problem - // if first is invited to BG and seconds timer expired, but we can ignore it, because players have only 80 seconds to click to enter bg // and when they click or after 80 seconds the queue info is removed from queue uint32 time_before = getMSTime() - sWorld.getConfig(CONFIG_BATTLEGROUND_PREMADE_GROUP_WAIT_FOR_MATCH); for (uint32 i = 0; i < BG_TEAMS_COUNT; i++) { if (!m_QueuedGroups[bracket_id][BG_QUEUE_PREMADE_ALLIANCE + i].empty()) { GroupsQueueType::iterator itr = m_QueuedGroups[bracket_id][BG_QUEUE_PREMADE_ALLIANCE + i].begin(); if (!(*itr)->IsInvitedToBGInstanceGUID && ((*itr)->JoinTime < time_before || (*itr)->Players.size() < MinPlayersPerTeam)) { //we must insert group to normal queue and erase pointer from premade queue m_QueuedGroups[bracket_id][BG_QUEUE_NORMAL_ALLIANCE + i].push_front((*itr)); m_QueuedGroups[bracket_id][BG_QUEUE_PREMADE_ALLIANCE + i].erase(itr); } } } //selection pools are not set return false; } // this method tries to create battleground or arena with MinPlayersPerTeam against MinPlayersPerTeam bool BattleGroundQueue::CheckNormalMatch(BattleGround* bg_template, BattleGroundBracketId bracket_id, uint32 minPlayers, uint32 maxPlayers) { GroupsQueueType::const_iterator itr_team[BG_TEAMS_COUNT]; for (uint32 i = 0; i < BG_TEAMS_COUNT; i++) { itr_team[i] = m_QueuedGroups[bracket_id][BG_QUEUE_NORMAL_ALLIANCE + i].begin(); for(; itr_team[i] != m_QueuedGroups[bracket_id][BG_QUEUE_NORMAL_ALLIANCE + i].end(); ++(itr_team[i])) { if (!(*(itr_team[i]))->IsInvitedToBGInstanceGUID) { m_SelectionPools[i].AddGroup(*(itr_team[i]), maxPlayers); if (m_SelectionPools[i].GetPlayerCount() >= minPlayers) break; } } } //try to invite same number of players - this cycle may cause longer wait time even if there are enough players in queue, but we want ballanced bg uint32 j = BG_TEAM_ALLIANCE; if (m_SelectionPools[BG_TEAM_HORDE].GetPlayerCount() < m_SelectionPools[BG_TEAM_ALLIANCE].GetPlayerCount()) j = BG_TEAM_HORDE; if( sWorld.getConfig(CONFIG_BATTLEGROUND_INVITATION_TYPE) != 0 && m_SelectionPools[BG_TEAM_HORDE].GetPlayerCount() >= minPlayers && m_SelectionPools[BG_TEAM_ALLIANCE].GetPlayerCount() >= minPlayers ) { //we will try to invite more groups to team with less players indexed by j ++(itr_team[j]); //this will not cause a crash, because for cycle above reached break; for(; itr_team[j] != m_QueuedGroups[bracket_id][BG_QUEUE_NORMAL_ALLIANCE + j].end(); ++(itr_team[j])) { if (!(*(itr_team[j]))->IsInvitedToBGInstanceGUID) if (!m_SelectionPools[j].AddGroup(*(itr_team[j]), m_SelectionPools[(j + 1) % BG_TEAMS_COUNT].GetPlayerCount())) break; } // do not allow to start bg with more than 2 players more on 1 faction if (abs((int32)(m_SelectionPools[BG_TEAM_HORDE].GetPlayerCount() - m_SelectionPools[BG_TEAM_ALLIANCE].GetPlayerCount())) > 2) return false; } //allow 1v0 if debug bg if (sBattleGroundMgr.isTesting() && bg_template->isBattleGround() && (m_SelectionPools[BG_TEAM_ALLIANCE].GetPlayerCount() || m_SelectionPools[BG_TEAM_HORDE].GetPlayerCount())) return true; //return true if there are enough players in selection pools - enable to work .debug bg command correctly return m_SelectionPools[BG_TEAM_ALLIANCE].GetPlayerCount() >= minPlayers && m_SelectionPools[BG_TEAM_HORDE].GetPlayerCount() >= minPlayers; } // this method will check if we can invite players to same faction skirmish match bool BattleGroundQueue::CheckSkirmishForSameFaction(BattleGroundBracketId bracket_id, uint32 minPlayersPerTeam) { if (m_SelectionPools[BG_TEAM_ALLIANCE].GetPlayerCount() < minPlayersPerTeam && m_SelectionPools[BG_TEAM_HORDE].GetPlayerCount() < minPlayersPerTeam) return false; uint32 teamIndex = BG_TEAM_ALLIANCE; uint32 otherTeam = BG_TEAM_HORDE; uint32 otherTeamId = HORDE; if (m_SelectionPools[BG_TEAM_HORDE].GetPlayerCount() == minPlayersPerTeam ) { teamIndex = BG_TEAM_HORDE; otherTeam = BG_TEAM_ALLIANCE; otherTeamId = ALLIANCE; } //clear other team's selection m_SelectionPools[otherTeam].Init(); //store last ginfo pointer GroupQueueInfo* ginfo = m_SelectionPools[teamIndex].SelectedGroups.back(); //set itr_team to group that was added to selection pool latest GroupsQueueType::iterator itr_team = m_QueuedGroups[bracket_id][BG_QUEUE_NORMAL_ALLIANCE + teamIndex].begin(); for(; itr_team != m_QueuedGroups[bracket_id][BG_QUEUE_NORMAL_ALLIANCE + teamIndex].end(); ++itr_team) if (ginfo == *itr_team) break; if (itr_team == m_QueuedGroups[bracket_id][BG_QUEUE_NORMAL_ALLIANCE + teamIndex].end()) return false; GroupsQueueType::iterator itr_team2 = itr_team; ++itr_team2; //invite players to other selection pool for(; itr_team2 != m_QueuedGroups[bracket_id][BG_QUEUE_NORMAL_ALLIANCE + teamIndex].end(); ++itr_team2) { //if selection pool is full then break; if (!(*itr_team2)->IsInvitedToBGInstanceGUID && !m_SelectionPools[otherTeam].AddGroup(*itr_team2, minPlayersPerTeam)) break; } if (m_SelectionPools[otherTeam].GetPlayerCount() != minPlayersPerTeam) return false; //here we have correct 2 selections and we need to change one teams team and move selection pool teams to other team's queue for (GroupsQueueType::iterator itr = m_SelectionPools[otherTeam].SelectedGroups.begin(); itr != m_SelectionPools[otherTeam].SelectedGroups.end(); ++itr) { //set correct team (*itr)->Team = otherTeamId; //add team to other queue m_QueuedGroups[bracket_id][BG_QUEUE_NORMAL_ALLIANCE + otherTeam].push_front(*itr); //remove team from old queue GroupsQueueType::iterator itr2 = itr_team; ++itr2; for(; itr2 != m_QueuedGroups[bracket_id][BG_QUEUE_NORMAL_ALLIANCE + teamIndex].end(); ++itr2) { if (*itr2 == *itr) { m_QueuedGroups[bracket_id][BG_QUEUE_NORMAL_ALLIANCE + teamIndex].erase(itr2); break; } } } return true; } /* this method 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 */ void BattleGroundQueue::Update(BattleGroundTypeId bgTypeId, BattleGroundBracketId bracket_id, uint8 arenaType, bool isRated, uint32 arenaRating) { //if no players in queue - do nothing if( m_QueuedGroups[bracket_id][BG_QUEUE_PREMADE_ALLIANCE].empty() && m_QueuedGroups[bracket_id][BG_QUEUE_PREMADE_HORDE].empty() && m_QueuedGroups[bracket_id][BG_QUEUE_NORMAL_ALLIANCE].empty() && m_QueuedGroups[bracket_id][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 itr, next; for (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() == bracket_id && (*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[BG_TEAM_ALLIANCE].Init(); m_SelectionPools[BG_TEAM_HORDE].Init(); // call a function that does the job for us FillPlayersToBG(bg, bracket_id); // now everything is set, invite players for (GroupsQueueType::const_iterator citr = m_SelectionPools[BG_TEAM_ALLIANCE].SelectedGroups.begin(); citr != m_SelectionPools[BG_TEAM_ALLIANCE].SelectedGroups.end(); ++citr) InviteGroupToBG((*citr), bg, (*citr)->Team); for (GroupsQueueType::const_iterator citr = m_SelectionPools[BG_TEAM_HORDE].SelectedGroups.begin(); citr != m_SelectionPools[BG_TEAM_HORDE].SelectedGroups.end(); ++citr) InviteGroupToBG((*citr), bg, (*citr)->Team); 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 * bg_template = sBattleGroundMgr.GetBattleGroundTemplate(bgTypeId); if (!bg_template) { sLog.outError("Battleground: Update: bg template not found for %u", bgTypeId); return; } PvPDifficultyEntry const* bracketEntry = GetBattlegroundBracketById(bg_template->GetMapId(),bracket_id); if (!bracketEntry) { sLog.outError("Battleground: Update: bg bracket entry not found for map %u bracket id %u", bg_template->GetMapId(), bracket_id); return; } // get the min. players per team, properly for larger arenas as well. (must have full teams for arena matches!) uint32 MinPlayersPerTeam = bg_template->GetMinPlayersPerTeam(); uint32 MaxPlayersPerTeam = bg_template->GetMaxPlayersPerTeam(); if (sBattleGroundMgr.isTesting()) MinPlayersPerTeam = 1; if (bg_template->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[BG_TEAM_ALLIANCE].Init(); m_SelectionPools[BG_TEAM_HORDE].Init(); if (bg_template->isBattleGround()) { //check if there is premade against premade match if (CheckPremadeMatch(bracket_id, MinPlayersPerTeam, MaxPlayersPerTeam)) { //create new battleground BattleGround * bg2 = sBattleGroundMgr.CreateNewBattleGround(bgTypeId, bracketEntry, 0, false); if (!bg2) { sLog.outError("BattleGroundQueue::Update - Cannot create battleground: %u", bgTypeId); return; } //invite those selection pools for (uint32 i = 0; i < BG_TEAMS_COUNT; i++) for (GroupsQueueType::const_iterator citr = m_SelectionPools[BG_TEAM_ALLIANCE + i].SelectedGroups.begin(); citr != m_SelectionPools[BG_TEAM_ALLIANCE + i].SelectedGroups.end(); ++citr) InviteGroupToBG((*citr), bg2, (*citr)->Team); //start bg bg2->StartBattleGround(); //clear structures m_SelectionPools[BG_TEAM_ALLIANCE].Init(); m_SelectionPools[BG_TEAM_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(bg_template, bracket_id, MinPlayersPerTeam, MaxPlayersPerTeam) || (bg_template->isArena() && CheckSkirmishForSameFaction(bracket_id, MinPlayersPerTeam)) ) { // we successfully created a pool BattleGround * bg2 = sBattleGroundMgr.CreateNewBattleGround(bgTypeId, bracketEntry, arenaType, false); if (!bg2) { sLog.outError("BattleGroundQueue::Update - Cannot create battleground: %u", bgTypeId); return; } // invite those selection pools for (uint32 i = 0; i < BG_TEAMS_COUNT; i++) for (GroupsQueueType::const_iterator citr = m_SelectionPools[BG_TEAM_ALLIANCE + i].SelectedGroups.begin(); citr != m_SelectionPools[BG_TEAM_ALLIANCE + i].SelectedGroups.end(); ++citr) InviteGroupToBG((*citr), bg2, (*citr)->Team); // start bg bg2->StartBattleGround(); } } else if (bg_template->isArena()) { // found out the minimum and maximum ratings the newly added team should battle against // arenaRating is the rating of the latest joined team, or 0 // 0 is on (automatic update call) and we must set it to team's with longest wait time if (!arenaRating ) { GroupQueueInfo* front1 = NULL; GroupQueueInfo* front2 = NULL; if (!m_QueuedGroups[bracket_id][BG_QUEUE_PREMADE_ALLIANCE].empty()) { front1 = m_QueuedGroups[bracket_id][BG_QUEUE_PREMADE_ALLIANCE].front(); arenaRating = front1->ArenaTeamRating; } if (!m_QueuedGroups[bracket_id][BG_QUEUE_PREMADE_HORDE].empty()) { front2 = m_QueuedGroups[bracket_id][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 = getMSTime() - sBattleGroundMgr.GetRatingDiscardTimer(); // we need to find 2 teams which will play next game GroupsQueueType::iterator itr_team[BG_TEAMS_COUNT]; //optimalization : --- we dont need to use selection_pools - each update we select max 2 groups for (uint32 i = BG_QUEUE_PREMADE_ALLIANCE; i < BG_QUEUE_NORMAL_ALLIANCE; i++) { // take the group that joined first itr_team[i] = m_QueuedGroups[bracket_id][i].begin(); for(; itr_team[i] != m_QueuedGroups[bracket_id][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); // 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[BG_TEAM_ALLIANCE].GetPlayerCount() == 0 && m_SelectionPools[BG_TEAM_HORDE].GetPlayerCount()) { itr_team[BG_TEAM_ALLIANCE] = itr_team[BG_TEAM_HORDE]; ++itr_team[BG_TEAM_ALLIANCE]; for(; itr_team[BG_TEAM_ALLIANCE] != m_QueuedGroups[bracket_id][BG_QUEUE_PREMADE_HORDE].end(); ++(itr_team[BG_TEAM_ALLIANCE])) { if( !(*itr_team[BG_TEAM_ALLIANCE])->IsInvitedToBGInstanceGUID && (((*itr_team[BG_TEAM_ALLIANCE])->ArenaTeamRating >= arenaMinRating && (*itr_team[BG_TEAM_ALLIANCE])->ArenaTeamRating <= arenaMaxRating) || (*itr_team[BG_TEAM_ALLIANCE])->JoinTime < discardTime) ) { m_SelectionPools[BG_TEAM_ALLIANCE].AddGroup((*itr_team[BG_TEAM_ALLIANCE]), MaxPlayersPerTeam); break; } } } // this code isn't much userfriendly - but it is supposed to continue search for mathing group in ALLIANCE queue if (m_SelectionPools[BG_TEAM_HORDE].GetPlayerCount() == 0 && m_SelectionPools[BG_TEAM_ALLIANCE].GetPlayerCount()) { itr_team[BG_TEAM_HORDE] = itr_team[BG_TEAM_ALLIANCE]; ++itr_team[BG_TEAM_HORDE]; for(; itr_team[BG_TEAM_HORDE] != m_QueuedGroups[bracket_id][BG_QUEUE_PREMADE_ALLIANCE].end(); ++(itr_team[BG_TEAM_HORDE])) { if( !(*itr_team[BG_TEAM_HORDE])->IsInvitedToBGInstanceGUID && (((*itr_team[BG_TEAM_HORDE])->ArenaTeamRating >= arenaMinRating && (*itr_team[BG_TEAM_HORDE])->ArenaTeamRating <= arenaMaxRating) || (*itr_team[BG_TEAM_HORDE])->JoinTime < discardTime) ) { m_SelectionPools[BG_TEAM_HORDE].AddGroup((*itr_team[BG_TEAM_HORDE]), MaxPlayersPerTeam); break; } } } //if we have 2 teams, then start new arena and invite players! if (m_SelectionPools[BG_TEAM_ALLIANCE].GetPlayerCount() && m_SelectionPools[BG_TEAM_HORDE].GetPlayerCount()) { BattleGround* arena = sBattleGroundMgr.CreateNewBattleGround(bgTypeId, bracketEntry, arenaType, true); if (!arena) { sLog.outError("BattlegroundQueue::Update couldn't create arena instance for rated arena match!"); return; } (*(itr_team[BG_TEAM_ALLIANCE]))->OpponentsTeamRating = (*(itr_team[BG_TEAM_HORDE]))->ArenaTeamRating; sLog.outDebug("setting oposite teamrating for team %u to %u", (*(itr_team[BG_TEAM_ALLIANCE]))->ArenaTeamId, (*(itr_team[BG_TEAM_ALLIANCE]))->OpponentsTeamRating); (*(itr_team[BG_TEAM_HORDE]))->OpponentsTeamRating = (*(itr_team[BG_TEAM_ALLIANCE]))->ArenaTeamRating; sLog.outDebug("setting oposite teamrating for team %u to %u", (*(itr_team[BG_TEAM_HORDE]))->ArenaTeamId, (*(itr_team[BG_TEAM_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[BG_TEAM_ALLIANCE]))->Team != ALLIANCE) { // add to alliance queue m_QueuedGroups[bracket_id][BG_QUEUE_PREMADE_ALLIANCE].push_front(*(itr_team[BG_TEAM_ALLIANCE])); // erase from horde queue m_QueuedGroups[bracket_id][BG_QUEUE_PREMADE_HORDE].erase(itr_team[BG_TEAM_ALLIANCE]); itr_team[BG_TEAM_ALLIANCE] = m_QueuedGroups[bracket_id][BG_QUEUE_PREMADE_ALLIANCE].begin(); } if ((*(itr_team[BG_TEAM_HORDE]))->Team != HORDE) { m_QueuedGroups[bracket_id][BG_QUEUE_PREMADE_HORDE].push_front(*(itr_team[BG_TEAM_HORDE])); m_QueuedGroups[bracket_id][BG_QUEUE_PREMADE_ALLIANCE].erase(itr_team[BG_TEAM_HORDE]); itr_team[BG_TEAM_HORDE] = m_QueuedGroups[bracket_id][BG_QUEUE_PREMADE_HORDE].begin(); } InviteGroupToBG(*(itr_team[BG_TEAM_ALLIANCE]), arena, ALLIANCE); InviteGroupToBG(*(itr_team[BG_TEAM_HORDE]), arena, HORDE); sLog.outDebug("Starting rated arena match!"); arena->StartBattleGround(); } } } /*********************************************************/ /*** BATTLEGROUND QUEUE EVENTS ***/ /*********************************************************/ bool BGQueueInviteEvent::Execute(uint64 /*e_time*/, uint32 /*p_time*/) { Player* plr = objmgr.GetPlayer( m_PlayerGuid ); // player logged off (we should do nothing, he is correctly removed from queue in another procedure) if (!plr) return true; BattleGround* bg = sBattleGroundMgr.GetBattleGround(m_BgInstanceGUID, m_BgTypeId); //if battleground ended and its instance deleted - do nothing if (!bg) return true; 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(m_PlayerGuid, m_BgInstanceGUID, 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, m_ArenaType); plr->GetSession()->SendPacket(&data); } } return true; //event will be deleted } void BGQueueInviteEvent::Abort(uint64 /*e_time*/) { //do nothing } /* 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*/) { Player* plr = objmgr.GetPlayer( m_PlayerGuid ); if (!plr) // player logged off (we should do nothing, he is correctly removed from queue in another procedure) return true; BattleGround* bg = sBattleGroundMgr.GetBattleGround(m_BgInstanceGUID, m_BgTypeId); //battleground can be deleted already when we are removing queue info //bg pointer can be NULL! so use it carefully! uint32 queueSlot = plr->GetBattleGroundQueueIndex(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[m_BgQueueTypeId]; if (bgQueue.IsPlayerInvited(m_PlayerGuid, m_BgInstanceGUID, m_RemoveTime)) { sLog.outDebug("Battleground: removing player %u from bg queue for instance %u because of not pressing enter battle in time.",plr->GetGUIDLow(),m_BgInstanceGUID); plr->RemoveBattleGroundQueueId(m_BgQueueTypeId); bgQueue.RemovePlayer(m_PlayerGuid, true); //update queues if battleground isn't ended if (bg && bg->isBattleGround() && bg->GetStatus() != STATUS_WAIT_LEAVE) sBattleGroundMgr.ScheduleQueueUpdate(0, 0, m_BgQueueTypeId, m_BgTypeId, bg->GetBracketId()); WorldPacket data; sBattleGroundMgr.BuildBattleGroundStatusPacket(&data, bg, queueSlot, STATUS_NONE, 0, 0, 0); plr->GetSession()->SendPacket(&data); } } //event will be deleted return true; } void BGQueueRemoveEvent::Abort(uint64 /*e_time*/) { //do nothing } /*********************************************************/ /*** BATTLEGROUND MANAGER ***/ /*********************************************************/ BattleGroundMgr::BattleGroundMgr() : m_AutoDistributionTimeChecker(0), m_ArenaTesting(false) { for (uint32 i = BATTLEGROUND_TYPE_NONE; i < MAX_BATTLEGROUND_TYPE_ID; i++) m_BattleGrounds[i].clear(); m_NextRatingDiscardUpdate = sWorld.getConfig(CONFIG_ARENA_RATING_DISCARD_TIMER); m_Testing=false; } BattleGroundMgr::~BattleGroundMgr() { DeleteAllBattleGrounds(); } void BattleGroundMgr::DeleteAllBattleGrounds() { for (uint32 i = BATTLEGROUND_TYPE_NONE; i < MAX_BATTLEGROUND_TYPE_ID; ++i) { for (BattleGroundSet::iterator itr = m_BattleGrounds[i].begin(); itr != m_BattleGrounds[i].end();) { BattleGround * bg = itr->second; m_BattleGrounds[i].erase(itr++); if (!m_ClientBattleGroundIds[i][bg->GetBracketId()].empty()) m_ClientBattleGroundIds[i][bg->GetBracketId()].erase(bg->GetClientInstanceID()); delete bg; } } // destroy template battlegrounds that listed only in queues (other already terminated) for (uint32 bgTypeId = 0; bgTypeId < MAX_BATTLEGROUND_TYPE_ID; ++bgTypeId) { // ~BattleGround call unregistring BG from queue while(!BGFreeSlotQueue[bgTypeId].empty()) delete BGFreeSlotQueue[bgTypeId].front(); } } // used to update running battlegrounds, and delete finished ones void BattleGroundMgr::Update(uint32 diff) { BattleGroundSet::iterator itr, next; for (uint32 i = BATTLEGROUND_TYPE_NONE; i < MAX_BATTLEGROUND_TYPE_ID; ++i) { itr = m_BattleGrounds[i].begin(); // skip updating battleground template if (itr != m_BattleGrounds[i].end()) ++itr; for (; itr != m_BattleGrounds[i].end(); itr = next) { next = itr; ++next; itr->second->Update(diff); // use the SetDeleteThis variable // direct deletion caused crashes if (itr->second->m_SetDeleteThis) { BattleGround * bg = itr->second; m_BattleGrounds[i].erase(itr); if (!m_ClientBattleGroundIds[i][bg->GetBracketId()].empty()) m_ClientBattleGroundIds[i][bg->GetBracketId()].erase(bg->GetClientInstanceID()); delete bg; } } } // update scheduled queues if (!m_QueueUpdateScheduler.empty()) { std::vector scheduled; { //copy vector and clear the other scheduled = std::vector(m_QueueUpdateScheduler); m_QueueUpdateScheduler.clear(); //release lock } for (uint8 i = 0; i < scheduled.size(); i++) { uint32 arenaRating = scheduled[i] >> 32; uint8 arenaType = scheduled[i] >> 24 & 255; BattleGroundQueueTypeId bgQueueTypeId = BattleGroundQueueTypeId(scheduled[i] >> 16 & 255); BattleGroundTypeId bgTypeId = BattleGroundTypeId((scheduled[i] >> 8) & 255); BattleGroundBracketId bracket_id = BattleGroundBracketId(scheduled[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_ARENA_MAX_RATING_DIFFERENCE) && sWorld.getConfig(CONFIG_ARENA_RATING_DISCARD_TIMER)) { // it's time to force update if (m_NextRatingDiscardUpdate < diff) { // forced update for rated arenas (scan all, but skipped non rated) sLog.outDebug("BattleGroundMgr: UPDATING ARENA QUEUES"); for(int qtype = BATTLEGROUND_QUEUE_2v2; qtype <= BATTLEGROUND_QUEUE_5v5; ++qtype) for(int bracket = BG_BRACKET_ID_FIRST; bracket < MAX_BATTLEGROUND_BRACKETS; ++bracket) m_BattleGroundQueues[qtype].Update( BATTLEGROUND_AA, BattleGroundBracketId(bracket), BattleGroundMgr::BGArenaType(BattleGroundQueueTypeId(qtype)), true, 0); m_NextRatingDiscardUpdate = sWorld.getConfig(CONFIG_ARENA_RATING_DISCARD_TIMER); } else m_NextRatingDiscardUpdate -= diff; } if (sWorld.getConfig(CONFIG_ARENA_AUTO_DISTRIBUTE_POINTS)) { if (m_AutoDistributionTimeChecker < diff) { if(time(NULL) > m_NextAutoDistributionTime) { DistributeArenaPoints(); m_NextAutoDistributionTime = time(NULL) + BATTLEGROUND_ARENA_POINT_DISTRIBUTION_DAY * sWorld.getConfig(CONFIG_ARENA_AUTO_DISTRIBUTE_INTERVAL_DAYS); sWorld.setWorldState(WS_ARENA_DISTRIBUTION_TIME, uint64(m_NextAutoDistributionTime)); } m_AutoDistributionTimeChecker = 600000; // check 10 minutes } else m_AutoDistributionTimeChecker -= diff; } } void BattleGroundMgr::BuildBattleGroundStatusPacket(WorldPacket *data, BattleGround *bg, uint8 QueueSlot, uint8 StatusID, uint32 Time1, uint32 Time2, uint8 arenatype) { // we can be in 2 queues in same time... if (StatusID == 0 || !bg) { data->Initialize(SMSG_BATTLEFIELD_STATUS, 4+8); *data << uint32(QueueSlot); // queue id (0...1) *data << uint64(0); return; } data->Initialize(SMSG_BATTLEFIELD_STATUS, (4+8+1+1+4+1+4+4+4)); *data << uint32(QueueSlot); // queue id (0...1) - player can be in 2 queues in time // The following segment is read as uint64 in client but can be appended as their original type. *data << uint8(arenatype); sLog.outDebug("BattleGroundMgr::BuildBattleGroundStatusPacket: arenatype = %u for bg instanceID %u, TypeID %u.", arenatype, bg->GetClientInstanceID(), bg->GetTypeID()); *data << uint8(bg->isArena() ? 0xC : 0x2); *data << uint32(bg->GetTypeID()); *data << uint16(0x1F90); // End of uint64 segment, decomposed this way for simplicity *data << uint8(0); // 3.3.0 *data << uint8(0); // 3.3.0 *data << uint32(bg->GetClientInstanceID()); // alliance/horde for BG and skirmish/rated for Arenas // following displays the minimap-icon 0 = faction icon 1 = arenaicon *data << uint8(bg->isRated()); // 1 for rated match, 0 for bg or non rated match *data << uint32(StatusID); // status switch(StatusID) { case STATUS_WAIT_QUEUE: // status_in_queue *data << uint32(Time1); // average wait time, milliseconds *data << uint32(Time2); // time in queue, updated every minute!, milliseconds break; case STATUS_WAIT_JOIN: // status_invite *data << uint32(bg->GetMapId()); // map id *data << uint32(Time1); // time to remove from queue, milliseconds break; case STATUS_IN_PROGRESS: // status_in_progress *data << uint32(bg->GetMapId()); // map id *data << uint32(Time1); // time to bg auto leave, 0 at bg start, 120000 after bg end, milliseconds *data << uint32(Time2); // time from bg start, milliseconds *data << uint8(/*bg->isArena() ? 0 :*/ 1); // unk, possibly 0 == preparation phase, 1 == battle break; default: sLog.outError("Unknown BG status!"); break; } } void BattleGroundMgr::BuildPvpLogDataPacket(WorldPacket *data, BattleGround *bg) { uint8 type = (bg->isArena() ? 1 : 0); // last check on 3.0.3 data->Initialize(MSG_PVP_LOG_DATA, (1+1+4+40*bg->GetPlayerScoresSize())); *data << uint8(type); // type (battleground=0/arena=1) if(type) // arena { // it seems this must be according to BG_WINNER_A/H and _NOT_ BG_TEAM_A/H for (int i = 1; i >= 0; --i) { uint32 pointsLost = bg->m_ArenaTeamRatingChanges[i] < 0 ? abs(bg->m_ArenaTeamRatingChanges[i]) : 0; uint32 pointsGained = bg->m_ArenaTeamRatingChanges[i] > 0 ? bg->m_ArenaTeamRatingChanges[i] : 0; *data << uint32(pointsLost); // Rating Lost *data << uint32(pointsGained); // Rating gained *data << uint32(0); // Matchmaking Value sLog.outDebug("rating change: %d", bg->m_ArenaTeamRatingChanges[i]); } for (int i = 1; i >= 0; --i) { uint32 at_id = bg->m_ArenaTeamIds[i]; ArenaTeam * at = objmgr.GetArenaTeamById(at_id); if (at) *data << at->GetName(); else *data << (uint8)0; } } if (bg->GetStatus() != STATUS_WAIT_LEAVE) { *data << uint8(0); // bg not ended } else { *data << uint8(1); // bg ended *data << uint8(bg->GetWinner()); // who win } *data << (int32)(bg->GetPlayerScoresSize()); for (BattleGround::BattleGroundScoreMap::const_iterator itr = bg->GetPlayerScoresBegin(); itr != bg->GetPlayerScoresEnd(); ++itr) { *data << (uint64)itr->first; *data << (int32)itr->second->KillingBlows; if (type == 0) { *data << (int32)itr->second->HonorableKills; *data << (int32)itr->second->Deaths; *data << (int32)(itr->second->BonusHonor); } else { Player *plr = objmgr.GetPlayer(itr->first); uint32 team = bg->GetPlayerTeam(itr->first); if (!team && plr) team = plr->GetBGTeam(); *data << uint8(team == ALLIANCE ? 1 : 0); // green or yellow } *data << (int32)itr->second->DamageDone; // damage done *data << (int32)itr->second->HealingDone; // healing done switch(bg->GetTypeID()) // battleground specific things { case BATTLEGROUND_AV: *data << (uint32)0x00000005; // count of next fields *data << (uint32)((BattleGroundAVScore*)itr->second)->GraveyardsAssaulted; // GraveyardsAssaulted *data << (uint32)((BattleGroundAVScore*)itr->second)->GraveyardsDefended; // GraveyardsDefended *data << (uint32)((BattleGroundAVScore*)itr->second)->TowersAssaulted; // TowersAssaulted *data << (uint32)((BattleGroundAVScore*)itr->second)->TowersDefended; // TowersDefended *data << (uint32)((BattleGroundAVScore*)itr->second)->MinesCaptured; // MinesCaptured break; case BATTLEGROUND_WS: *data << (uint32)0x00000002; // count of next fields *data << (uint32)((BattleGroundWGScore*)itr->second)->FlagCaptures; // flag captures *data << (uint32)((BattleGroundWGScore*)itr->second)->FlagReturns; // flag returns break; case BATTLEGROUND_AB: *data << (uint32)0x00000002; // count of next fields *data << (uint32)((BattleGroundABScore*)itr->second)->BasesAssaulted; // bases asssulted *data << (uint32)((BattleGroundABScore*)itr->second)->BasesDefended; // bases defended break; case BATTLEGROUND_EY: *data << (uint32)0x00000001; // count of next fields *data << (uint32)((BattleGroundEYScore*)itr->second)->FlagCaptures; // flag captures break; case BATTLEGROUND_NA: case BATTLEGROUND_BE: case BATTLEGROUND_AA: case BATTLEGROUND_RL: case BATTLEGROUND_SA: *data << uint32(2); *data << uint32(((BattleGroundSAScore*)itr->second)->demolishers_destroyed); *data << uint32(((BattleGroundSAScore*)itr->second)->gates_destroyed); break; case BATTLEGROUND_DS: // wotlk case BATTLEGROUND_RV: // wotlk case BATTLEGROUND_IC: // wotlk case BATTLEGROUND_ABG: // wotlk *data << (int32)0; // 0 break; default: sLog.outDebug("Unhandled MSG_PVP_LOG_DATA for BG id %u", bg->GetTypeID()); *data << (int32)0; break; } } } void BattleGroundMgr::BuildGroupJoinedBattlegroundPacket(WorldPacket *data, BattleGroundTypeId bgTypeId) { /*bgTypeId is: 0 - Your group has joined a battleground queue, but you are not eligible 1 - Your group has joined the queue for AV 2 - Your group has joined the queue for WS 3 - Your group has joined the queue for AB 4 - Your group has joined the queue for NA 5 - Your group has joined the queue for BE Arena 6 - Your group has joined the queue for All Arenas 7 - Your group has joined the queue for EotS*/ data->Initialize(SMSG_GROUP_JOINED_BATTLEGROUND, 4); *data << uint32(bgTypeId); } void BattleGroundMgr::BuildUpdateWorldStatePacket(WorldPacket *data, uint32 field, uint32 value) { data->Initialize(SMSG_UPDATE_WORLD_STATE, 4+4); *data << uint32(field); *data << uint32(value); } void BattleGroundMgr::BuildPlaySoundPacket(WorldPacket *data, uint32 soundid) { data->Initialize(SMSG_PLAY_SOUND, 4); *data << uint32(soundid); } void BattleGroundMgr::BuildPlayerLeftBattleGroundPacket(WorldPacket *data, const uint64& guid) { data->Initialize(SMSG_BATTLEGROUND_PLAYER_LEFT, 8); *data << uint64(guid); } void BattleGroundMgr::BuildPlayerJoinedBattleGroundPacket(WorldPacket *data, Player *plr) { data->Initialize(SMSG_BATTLEGROUND_PLAYER_JOINED, 8); *data << uint64(plr->GetGUID()); } BattleGround * BattleGroundMgr::GetBattleGroundThroughClientInstance(uint32 instanceId, BattleGroundTypeId bgTypeId) { //cause at HandleBattleGroundJoinOpcode the clients sends the instanceid he gets from //SMSG_BATTLEFIELD_LIST we need to find the battleground with this clientinstance-id BattleGround* bg = GetBattleGroundTemplate(bgTypeId); if (!bg) return NULL; if (bg->isArena()) return GetBattleGround(instanceId, bgTypeId); for (BattleGroundSet::iterator itr = m_BattleGrounds[bgTypeId].begin(); itr != m_BattleGrounds[bgTypeId].end(); ++itr) { if (itr->second->GetClientInstanceID() == instanceId) return itr->second; } return NULL; } BattleGround * BattleGroundMgr::GetBattleGround(uint32 InstanceID, BattleGroundTypeId bgTypeId) { if (!InstanceID) return NULL; //search if needed BattleGroundSet::iterator itr; if (bgTypeId == BATTLEGROUND_TYPE_NONE) { for (uint32 i = BATTLEGROUND_AV; i < MAX_BATTLEGROUND_TYPE_ID; i++) { itr = m_BattleGrounds[i].find(InstanceID); if (itr != m_BattleGrounds[i].end()) return itr->second; } return NULL; } itr = m_BattleGrounds[bgTypeId].find(InstanceID); return ( (itr != m_BattleGrounds[bgTypeId].end()) ? itr->second : NULL ); } BattleGround * BattleGroundMgr::GetBattleGroundTemplate(BattleGroundTypeId bgTypeId) { //map is sorted and we can be sure that lowest instance id has only BG template return m_BattleGrounds[bgTypeId].empty() ? NULL : m_BattleGrounds[bgTypeId].begin()->second; } uint32 BattleGroundMgr::CreateClientVisibleInstanceId(BattleGroundTypeId bgTypeId, BattleGroundBracketId bracket_id) { if (IsArenaType(bgTypeId)) return 0; //arenas don't have client-instanceids // we create here an instanceid, which is just for // displaying this to the client and without any other use.. // the client-instanceIds are unique for each battleground-type // the instance-id just needs to be as low as possible, beginning with 1 // the following works, because std::set is default ordered with "<" // the optimalization would be to use as bitmask std::vector - but that would only make code unreadable uint32 lastId = 0; for(std::set::iterator itr = m_ClientBattleGroundIds[bgTypeId][bracket_id].begin(); itr != m_ClientBattleGroundIds[bgTypeId][bracket_id].end();) { if( (++lastId) != *itr) //if there is a gap between the ids, we will break.. break; lastId = *itr; } m_ClientBattleGroundIds[bgTypeId][bracket_id].insert(lastId + 1); return lastId + 1; } // create a new battleground that will really be used to play BattleGround * BattleGroundMgr::CreateNewBattleGround(BattleGroundTypeId bgTypeId, PvPDifficultyEntry const* bracketEntry, uint8 arenaType, bool isRated) { // get the template BG BattleGround *bg_template = GetBattleGroundTemplate(bgTypeId); if (!bg_template) { sLog.outError("BattleGround: CreateNewBattleGround - bg template not found for %u", bgTypeId); return NULL; } //for arenas there is random map used if (bg_template->isArena()) { if (!isAnyArenaEnabled()) // it's checked in handler but just to be sure return NULL; uint8 size = m_EnabledArenas.size() - 1; bgTypeId = m_EnabledArenas[urand(0,size)]; bg_template = GetBattleGroundTemplate(bgTypeId); if (!bg_template) { sLog.outError("BattleGround: CreateNewBattleGround - bg template not found for %u", bgTypeId); return NULL; } } BattleGround *bg = NULL; // create a copy of the BG template switch(bgTypeId) { case BATTLEGROUND_AV: bg = new BattleGroundAV(*(BattleGroundAV*)bg_template); break; case BATTLEGROUND_WS: bg = new BattleGroundWS(*(BattleGroundWS*)bg_template); break; case BATTLEGROUND_AB: bg = new BattleGroundAB(*(BattleGroundAB*)bg_template); break; case BATTLEGROUND_NA: bg = new BattleGroundNA(*(BattleGroundNA*)bg_template); break; case BATTLEGROUND_BE: bg = new BattleGroundBE(*(BattleGroundBE*)bg_template); break; case BATTLEGROUND_AA: bg = new BattleGroundAA(*(BattleGroundAA*)bg_template); break; case BATTLEGROUND_EY: bg = new BattleGroundEY(*(BattleGroundEY*)bg_template); break; case BATTLEGROUND_RL: bg = new BattleGroundRL(*(BattleGroundRL*)bg_template); break; case BATTLEGROUND_SA: bg = new BattleGroundSA(*(BattleGroundSA*)bg_template); break; case BATTLEGROUND_DS: bg = new BattleGroundDS(*(BattleGroundDS*)bg_template); break; case BATTLEGROUND_RV: bg = new BattleGroundRV(*(BattleGroundRV*)bg_template); break; case BATTLEGROUND_IC: bg = new BattleGroundIC(*(BattleGroundIC*)bg_template); break; case BATTLEGROUND_ABG: bg = new BattleGroundABG(*(BattleGroundABG*)bg_template); break; default: //error, but it is handled few lines above return 0; } // generate a new instance id bg->SetInstanceID(MapManager::Instance().GenerateInstanceId()); // set instance id bg->SetClientInstanceID(CreateClientVisibleInstanceId(bgTypeId, bracketEntry->GetBracketId())); // reset the new bg (set status to status_wait_queue from status_none) bg->Reset(); // start the joining of the bg bg->SetStatus(STATUS_WAIT_JOIN); bg->SetBracket(bracketEntry); bg->SetArenaType(arenaType); bg->SetRated(isRated); return bg; } // used to create the BG templates uint32 BattleGroundMgr::CreateBattleGround(BattleGroundTypeId bgTypeId, bool IsArena, uint32 MinPlayersPerTeam, uint32 MaxPlayersPerTeam, uint32 LevelMin, uint32 LevelMax, char* BattleGroundName, uint32 MapID, float Team1StartLocX, float Team1StartLocY, float Team1StartLocZ, float Team1StartLocO, float Team2StartLocX, float Team2StartLocY, float Team2StartLocZ, float Team2StartLocO) { // Create the BG BattleGround *bg = NULL; switch(bgTypeId) { case BATTLEGROUND_AV: bg = new BattleGroundAV; break; case BATTLEGROUND_WS: bg = new BattleGroundWS; break; case BATTLEGROUND_AB: bg = new BattleGroundAB; break; case BATTLEGROUND_NA: bg = new BattleGroundNA; break; case BATTLEGROUND_BE: bg = new BattleGroundBE; break; case BATTLEGROUND_AA: bg = new BattleGroundAA; break; case BATTLEGROUND_EY: bg = new BattleGroundEY; break; case BATTLEGROUND_RL: bg = new BattleGroundRL; break; case BATTLEGROUND_SA: bg = new BattleGroundSA; break; case BATTLEGROUND_DS: bg = new BattleGroundDS; break; case BATTLEGROUND_RV: bg = new BattleGroundRV; break; case BATTLEGROUND_IC: bg = new BattleGroundIC; break; case BATTLEGROUND_ABG: bg = new BattleGroundABG; break; default: bg = new BattleGround; break; } bg->SetMapId(MapID); bg->SetTypeID(bgTypeId); bg->SetInstanceID(0); bg->SetArenaorBGType(IsArena); bg->SetMinPlayersPerTeam(MinPlayersPerTeam); bg->SetMaxPlayersPerTeam(MaxPlayersPerTeam); bg->SetMinPlayers(MinPlayersPerTeam * 2); bg->SetMaxPlayers(MaxPlayersPerTeam * 2); bg->SetName(BattleGroundName); bg->SetTeamStartLoc(ALLIANCE, Team1StartLocX, Team1StartLocY, Team1StartLocZ, Team1StartLocO); bg->SetTeamStartLoc(HORDE, Team2StartLocX, Team2StartLocY, Team2StartLocZ, Team2StartLocO); bg->SetLevelRange(LevelMin, LevelMax); // add bg to update list AddBattleGround(bg->GetInstanceID(), bg->GetTypeID(), bg); // return some not-null value, bgTypeId is good enough for me return bgTypeId; } void BattleGroundMgr::CreateInitialBattleGrounds() { float AStartLoc[4]; float HStartLoc[4]; uint32 MaxPlayersPerTeam, MinPlayersPerTeam, MinLvl, MaxLvl, start1, start2; BattlemasterListEntry const *bl; WorldSafeLocsEntry const *start; bool IsArena; uint32 count = 0; // 0 1 2 3 4 5 6 7 8 QueryResult_AutoPtr result = WorldDatabase.Query("SELECT id, MinPlayersPerTeam,MaxPlayersPerTeam,MinLvl,MaxLvl,AllianceStartLoc,AllianceStartO,HordeStartLoc,HordeStartO FROM battleground_template WHERE disable = 0"); if (!result) { barGoLink bar(1); bar.step(); sLog.outString(); sLog.outErrorDb(">> Loaded 0 battlegrounds. DB table `battleground_template` is empty."); return; } barGoLink bar(result->GetRowCount()); do { Field *fields = result->Fetch(); bar.step(); uint32 bgTypeID_ = fields[0].GetUInt32(); // can be overwrite by values from DB bl = sBattlemasterListStore.LookupEntry(bgTypeID_); if (!bl) { sLog.outError("Battleground ID %u not found in BattlemasterList.dbc. Battleground not created.", bgTypeID_); continue; } BattleGroundTypeId bgTypeID = BattleGroundTypeId(bgTypeID_); IsArena = (bl->type == TYPE_ARENA); MinPlayersPerTeam = fields[1].GetUInt32(); MaxPlayersPerTeam = fields[2].GetUInt32(); MinLvl = fields[3].GetUInt32(); MaxLvl = fields[4].GetUInt32(); //check values from DB if (MaxPlayersPerTeam == 0 || MinPlayersPerTeam == 0 || MinPlayersPerTeam > MaxPlayersPerTeam) { MaxPlayersPerTeam = bl->maxplayersperteam; MinPlayersPerTeam = bl->maxplayersperteam / 2; } if (MinLvl == 0 || MaxLvl == 0 || MinLvl > MaxLvl) { //TO-DO: FIX ME MinLvl = 0;//bl->minlvl; MaxLvl = 80;//bl->maxlvl; } start1 = fields[5].GetUInt32(); start = sWorldSafeLocsStore.LookupEntry(start1); if (start) { AStartLoc[0] = start->x; AStartLoc[1] = start->y; AStartLoc[2] = start->z; AStartLoc[3] = fields[6].GetFloat(); } else if (bgTypeID == BATTLEGROUND_AA || bgTypeID == BATTLEGROUND_ABG) { AStartLoc[0] = 0; AStartLoc[1] = 0; AStartLoc[2] = 0; AStartLoc[3] = fields[6].GetFloat(); } else { sLog.outErrorDb("Table `battleground_template` for id %u have non-existed WorldSafeLocs.dbc id %u in field `AllianceStartLoc`. BG not created.", bgTypeID, start1); continue; } start2 = fields[7].GetUInt32(); start = sWorldSafeLocsStore.LookupEntry(start2); if (start) { HStartLoc[0] = start->x; HStartLoc[1] = start->y; HStartLoc[2] = start->z; HStartLoc[3] = fields[8].GetFloat(); } else if (bgTypeID == BATTLEGROUND_AA || bgTypeID == BATTLEGROUND_ABG) { HStartLoc[0] = 0; HStartLoc[1] = 0; HStartLoc[2] = 0; HStartLoc[3] = fields[8].GetFloat(); } else { sLog.outErrorDb("Table `battleground_template` for id %u have non-existed WorldSafeLocs.dbc id %u in field `HordeStartLoc`. BG not created.", bgTypeID, start2); continue; } //sLog.outDetail("Creating battleground %s, %u-%u", bl->name[sWorld.GetDBClang()], MinLvl, MaxLvl); if (!CreateBattleGround(bgTypeID, IsArena, MinPlayersPerTeam, MaxPlayersPerTeam, MinLvl, MaxLvl, bl->name[sWorld.GetDefaultDbcLocale()], bl->mapid[0], AStartLoc[0], AStartLoc[1], AStartLoc[2], AStartLoc[3], HStartLoc[0], HStartLoc[1], HStartLoc[2], HStartLoc[3])) continue; if (IsArena && bgTypeID != BATTLEGROUND_AA) m_EnabledArenas.push_back(bgTypeID); ++count; } while (result->NextRow()); sLog.outString(); sLog.outString( ">> Loaded %u battlegrounds", count ); } void BattleGroundMgr::InitAutomaticArenaPointDistribution() { if (sWorld.getConfig(CONFIG_ARENA_AUTO_DISTRIBUTE_POINTS)) { sLog.outDebug("Initializing Automatic Arena Point Distribution"); uint64 wstime = sWorld.getWorldState(WS_ARENA_DISTRIBUTION_TIME); if (!wstime) { sLog.outDebug("Battleground: Next arena point distribution time not found, reseting it now."); m_NextAutoDistributionTime = time(NULL) + BATTLEGROUND_ARENA_POINT_DISTRIBUTION_DAY * sWorld.getConfig(CONFIG_ARENA_AUTO_DISTRIBUTE_INTERVAL_DAYS); sWorld.setWorldState(WS_ARENA_DISTRIBUTION_TIME, uint64(m_NextAutoDistributionTime)); } else m_NextAutoDistributionTime = time_t(wstime); sLog.outDebug("Automatic Arena Point Distribution initialized."); } } void BattleGroundMgr::DistributeArenaPoints() { // used to distribute arena points based on last week's stats sWorld.SendWorldText(LANG_DIST_ARENA_POINTS_START); sWorld.SendWorldText(LANG_DIST_ARENA_POINTS_ONLINE_START); //temporary structure for storing maximum points to add values for all players std::map PlayerPoints; //at first update all points for all team members for (ObjectMgr::ArenaTeamMap::iterator team_itr = objmgr.GetArenaTeamMapBegin(); team_itr != objmgr.GetArenaTeamMapEnd(); ++team_itr) if (ArenaTeam * at = team_itr->second) at->UpdateArenaPointsHelper(PlayerPoints); //cycle that gives points to all players for (std::map::iterator plr_itr = PlayerPoints.begin(); plr_itr != PlayerPoints.end(); ++plr_itr) { //update to database CharacterDatabase.PExecute("UPDATE characters SET arenaPoints = arenaPoints + '%u' WHERE guid = '%u'", plr_itr->second, plr_itr->first); //add points to player if online Player* pl = objmgr.GetPlayer(plr_itr->first); if (pl) pl->ModifyArenaPoints(plr_itr->second); } PlayerPoints.clear(); sWorld.SendWorldText(LANG_DIST_ARENA_POINTS_ONLINE_END); sWorld.SendWorldText(LANG_DIST_ARENA_POINTS_TEAM_START); for (ObjectMgr::ArenaTeamMap::iterator titr = objmgr.GetArenaTeamMapBegin(); titr != objmgr.GetArenaTeamMapEnd(); ++titr) { if (ArenaTeam * at = titr->second) { at->FinishWeek(); // set played this week etc values to 0 in memory, too at->SaveToDB(); // save changes at->NotifyStatsChanged(); // notify the players of the changes } } sWorld.SendWorldText(LANG_DIST_ARENA_POINTS_TEAM_END); sWorld.SendWorldText(LANG_DIST_ARENA_POINTS_END); } void BattleGroundMgr::BuildBattleGroundListPacket(WorldPacket *data, const uint64& guid, Player* plr, BattleGroundTypeId bgTypeId, uint8 fromWhere) { if (!plr) return; data->Initialize(SMSG_BATTLEFIELD_LIST); *data << uint64(guid); // battlemaster guid *data << uint8(fromWhere); // from where you joined *data << uint32(bgTypeId); // battleground id if(bgTypeId == BATTLEGROUND_AA) // arena { *data << uint8(4); // unk *data << uint8(0); // unk *data << uint32(0); // unk (count?) } else // battleground { *data << uint8(0); // unk, different for each bg type *data << uint8(0); // unk size_t count_pos = data->wpos(); uint32 count = 0; *data << uint32(0); // number of bg instances if(BattleGround* bgTemplate = sBattleGroundMgr.GetBattleGroundTemplate(bgTypeId)) { // expected bracket entry if(PvPDifficultyEntry const* bracketEntry = GetBattlegroundBracketByLevel(bgTemplate->GetMapId(),plr->getLevel())) { BattleGroundBracketId bracketId = bracketEntry->GetBracketId(); for(std::set::iterator itr = m_ClientBattleGroundIds[bgTypeId][bracketId].begin(); itr != m_ClientBattleGroundIds[bgTypeId][bracketId].end();++itr) { *data << uint32(*itr); ++count; } data->put( count_pos , count); } } } } void BattleGroundMgr::SendToBattleGround(Player *pl, uint32 instanceId, BattleGroundTypeId bgTypeId) { BattleGround *bg = GetBattleGround(instanceId, bgTypeId); if (bg) { uint32 mapid = bg->GetMapId(); float x, y, z, O; uint32 team = pl->GetBGTeam(); if (team==0) team = pl->GetTeam(); bg->GetTeamStartLoc(team, x, y, z, O); sLog.outDetail("BATTLEGROUND: Sending %s to map %u, X %f, Y %f, Z %f, O %f", pl->GetName(), mapid, x, y, z, O); pl->TeleportTo(mapid, x, y, z, O); } else { sLog.outError("player %u trying to port to non-existent bg instance %u",pl->GetGUIDLow(), instanceId); } } void BattleGroundMgr::SendAreaSpiritHealerQueryOpcode(Player *pl, BattleGround *bg, const uint64& guid) { WorldPacket data(SMSG_AREA_SPIRIT_HEALER_TIME, 12); uint32 time_ = 30000 - bg->GetLastResurrectTime(); // resurrect every 30 seconds if (time_ == uint32(-1)) time_ = 0; data << guid << time_; pl->GetSession()->SendPacket(&data); } bool BattleGroundMgr::IsArenaType(BattleGroundTypeId bgTypeId) { return ( bgTypeId == BATTLEGROUND_AA || bgTypeId == BATTLEGROUND_BE || bgTypeId == BATTLEGROUND_NA || bgTypeId == BATTLEGROUND_DS || bgTypeId == BATTLEGROUND_RV || bgTypeId == BATTLEGROUND_RL || bgTypeId == BATTLEGROUND_DS ); } BattleGroundQueueTypeId BattleGroundMgr::BGQueueTypeId(BattleGroundTypeId bgTypeId, uint8 arenaType) { switch(bgTypeId) { case BATTLEGROUND_WS: return BATTLEGROUND_QUEUE_WS; case BATTLEGROUND_AB: return BATTLEGROUND_QUEUE_AB; case BATTLEGROUND_AV: return BATTLEGROUND_QUEUE_AV; case BATTLEGROUND_EY: return BATTLEGROUND_QUEUE_EY; case BATTLEGROUND_SA: return BATTLEGROUND_QUEUE_SA; case BATTLEGROUND_IC: return BATTLEGROUND_QUEUE_IC; case BATTLEGROUND_ABG: return BATTLEGROUND_QUEUE_NONE; case BATTLEGROUND_AA: case BATTLEGROUND_NA: case BATTLEGROUND_RL: case BATTLEGROUND_BE: case BATTLEGROUND_DS: case BATTLEGROUND_RV: switch(arenaType) { case ARENA_TYPE_2v2: return BATTLEGROUND_QUEUE_2v2; case ARENA_TYPE_3v3: return BATTLEGROUND_QUEUE_3v3; case ARENA_TYPE_5v5: return BATTLEGROUND_QUEUE_5v5; default: return BATTLEGROUND_QUEUE_NONE; } default: return BATTLEGROUND_QUEUE_NONE; } } BattleGroundTypeId BattleGroundMgr::BGTemplateId(BattleGroundQueueTypeId bgQueueTypeId) { switch(bgQueueTypeId) { case BATTLEGROUND_QUEUE_WS: return BATTLEGROUND_WS; case BATTLEGROUND_QUEUE_AB: return BATTLEGROUND_AB; case BATTLEGROUND_QUEUE_AV: return BATTLEGROUND_AV; case BATTLEGROUND_QUEUE_EY: return BATTLEGROUND_EY; case BATTLEGROUND_QUEUE_SA: return BATTLEGROUND_SA; case BATTLEGROUND_QUEUE_IC: return BATTLEGROUND_IC; case BATTLEGROUND_QUEUE_2v2: case BATTLEGROUND_QUEUE_3v3: case BATTLEGROUND_QUEUE_5v5: return BATTLEGROUND_AA; default: return BattleGroundTypeId(0); // used for unknown template (it existed and do nothing) } } uint8 BattleGroundMgr::BGArenaType(BattleGroundQueueTypeId bgQueueTypeId) { switch(bgQueueTypeId) { case BATTLEGROUND_QUEUE_2v2: return ARENA_TYPE_2v2; case BATTLEGROUND_QUEUE_3v3: return ARENA_TYPE_3v3; case BATTLEGROUND_QUEUE_5v5: return ARENA_TYPE_5v5; default: return 0; } } void BattleGroundMgr::ToggleTesting() { m_Testing = !m_Testing; if (m_Testing) sWorld.SendWorldText(LANG_DEBUG_BG_ON); else sWorld.SendWorldText(LANG_DEBUG_BG_OFF); } void BattleGroundMgr::ToggleArenaTesting() { m_ArenaTesting = !m_ArenaTesting; if (m_ArenaTesting) sWorld.SendWorldText(LANG_DEBUG_ARENA_ON); else sWorld.SendWorldText(LANG_DEBUG_ARENA_OFF); } void BattleGroundMgr::SetHolidayWeekends(uint32 mask) { for (uint32 bgtype = 1; bgtype < MAX_BATTLEGROUND_TYPE_ID; ++bgtype) { if(BattleGround * bg = GetBattleGroundTemplate(BattleGroundTypeId(bgtype))) { bg->SetHoliday(mask & (1 << bgtype)); } } } void BattleGroundMgr::ScheduleQueueUpdate(uint32 arenaRating, uint8 arenaType, BattleGroundQueueTypeId bgQueueTypeId, BattleGroundTypeId bgTypeId, BattleGroundBracketId bracket_id) { //This method must be atomic, TODO add mutex //we will use only 1 number created of bgTypeId and bracket_id uint64 schedule_id = ((uint64)arenaRating << 32) | (arenaType << 24) | (bgQueueTypeId << 16) | (bgTypeId << 8) | bracket_id; bool found = false; for (uint8 i = 0; i < m_QueueUpdateScheduler.size(); i++) { if (m_QueueUpdateScheduler[i] == schedule_id) { found = true; break; } } if (!found) m_QueueUpdateScheduler.push_back(schedule_id); } uint32 BattleGroundMgr::GetMaxRatingDifference() const { // this is for stupid people who can't use brain and set max rating difference to 0 uint32 diff = sWorld.getConfig(CONFIG_ARENA_MAX_RATING_DIFFERENCE); if (diff == 0) diff = 5000; return diff; } uint32 BattleGroundMgr::GetRatingDiscardTimer() const { return sWorld.getConfig(CONFIG_ARENA_RATING_DISCARD_TIMER); } uint32 BattleGroundMgr::GetPrematureFinishTime() const { return sWorld.getConfig(CONFIG_BATTLEGROUND_PREMATURE_FINISH_TIMER); } void BattleGroundMgr::LoadBattleMastersEntry() { mBattleMastersMap.clear(); // need for reload case QueryResult_AutoPtr result = WorldDatabase.Query( "SELECT entry,bg_template FROM battlemaster_entry" ); uint32 count = 0; if (!result) { barGoLink bar( 1 ); bar.step(); sLog.outString(); sLog.outString( ">> Loaded 0 battlemaster entries - table is empty!" ); return; } barGoLink bar( result->GetRowCount() ); do { ++count; bar.step(); Field *fields = result->Fetch(); uint32 entry = fields[0].GetUInt32(); uint32 bgTypeId = fields[1].GetUInt32(); if (!sBattlemasterListStore.LookupEntry(bgTypeId)) { sLog.outErrorDb("Table `battlemaster_entry` contain entry %u for not existed battleground type %u, ignored.",entry,bgTypeId); continue; } mBattleMastersMap[entry] = BattleGroundTypeId(bgTypeId); } while( result->NextRow() ); sLog.outString(); sLog.outString( ">> Loaded %u battlemaster entries", count ); } bool BattleGroundMgr::IsBGWeekend(BattleGroundTypeId bgTypeId) { switch (bgTypeId) { case BATTLEGROUND_AV: return IsHolidayActive(HOLIDAY_CALL_TO_ARMS_AV); case BATTLEGROUND_EY: return IsHolidayActive(HOLIDAY_CALL_TO_ARMS_EY); case BATTLEGROUND_WS: return IsHolidayActive(HOLIDAY_CALL_TO_ARMS_WS); case BATTLEGROUND_SA: return IsHolidayActive(HOLIDAY_CALL_TO_ARMS_SA); default: return false; } } void BattleGroundMgr::DoCompleteAchievement(uint32 achievement, Player * player) { AchievementEntry const* AE = GetAchievementStore()->LookupEntry(achievement); if(!player) { //Map::PlayerList const &PlayerList = this->GetPlayers(); //GroupsQueueType::iterator group = SelectedGroups.begin(); //if (!PlayerList.isEmpty()) //for (Map::PlayerList::const_iterator i = PlayerList.begin(); i != PlayerList.end(); ++i) // for (GroupsQueueType::iterator itr = group; itr != SelectedGroups.end(); ++itr) // if (Player *pPlayer = itr->getSource()) // pPlayer->CompletedAchievement(AE); } else { player->CompletedAchievement(AE); } }