/*
* Copyright (C) 2005-2008 MaNGOS
*
* Copyright (C) 2008 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 "Player.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 "SharedDefines.h"
#include "Policies/SingletonImp.h"
#include "MapManager.h"
#include "Map.h"
#include "MapInstanced.h"
#include "ObjectMgr.h"
#include "ProgressBar.h"
#include "World.h"
#include "ArenaTeam.h"
#include "Chat.h"
INSTANTIATE_SINGLETON_1( BattleGroundMgr );
/*********************************************************/
/*** BATTLEGROUND QUEUE SYSTEM ***/
/*********************************************************/
BattleGroundQueue::BattleGroundQueue()
{
//queues are empty, we don't have to call clear()
/* for (int i = 0; i < MAX_BATTLEGROUND_QUEUES; i++)
{
//m_QueuedPlayers[i].Horde = 0;
//m_QueuedPlayers[i].Alliance = 0;
//m_QueuedPlayers[i].AverageTime = 0;
}*/
}
BattleGroundQueue::~BattleGroundQueue()
{
for (int i = 0; i < MAX_BATTLEGROUND_QUEUES; i++)
{
m_QueuedPlayers[i].clear();
for(QueuedGroupsList::iterator itr = m_QueuedGroups[i].begin(); itr!= m_QueuedGroups[i].end(); ++itr)
{
delete (*itr);
}
m_QueuedGroups[i].clear();
}
}
// initialize eligible groups from the given source matching the given specifications
void BattleGroundQueue::EligibleGroups::Init(BattleGroundQueue::QueuedGroupsList *source, uint32 BgTypeId, uint32 side, uint32 MaxPlayers, uint8 ArenaType, bool IsRated, uint32 MinRating, uint32 MaxRating, uint32 DisregardTime, uint32 excludeTeam)
{
// clear from prev initialization
clear();
BattleGroundQueue::QueuedGroupsList::iterator itr, next;
// iterate through the source
for(itr = source->begin(); itr!= source->end(); itr = next)
{
next = itr;
++next;
if( (*itr)->BgTypeId == BgTypeId && // bg type must match
(*itr)->ArenaType == ArenaType && // arena type must match
(*itr)->IsRated == IsRated && // israted must match
(*itr)->IsInvitedToBGInstanceGUID == 0 && // leave out already invited groups
(*itr)->Team == side && // match side
(*itr)->Players.size() <= MaxPlayers && // the group must fit in the bg
( !excludeTeam || (*itr)->ArenaTeamId != excludeTeam ) && // if excludeTeam is specified, leave out those arena team ids
( !IsRated || (*itr)->Players.size() == MaxPlayers ) && // if rated, then pass only if the player count is exact NEEDS TESTING! (but now this should never happen)
( (*itr)->JoinTime <= DisregardTime // pass if disregard time is greater than join time
|| (*itr)->ArenaTeamRating == 0 // pass if no rating info
|| ( (*itr)->ArenaTeamRating >= MinRating // pass if matches the rating range
&& (*itr)->ArenaTeamRating <= MaxRating ) ) )
{
// the group matches the conditions
// insert it in order of groupsize, and join time
uint32 size = (*itr)->Players.size();
uint32 jointime = (*itr)->JoinTime;
bool inserted = false;
for(std::list::iterator elig_itr = begin(); elig_itr != end(); ++elig_itr)
{
// if the next one's size is smaller, then insert
// also insert if the next one's size is equal, but it joined the queue later
if( ((*elig_itr)->Players.size()Players.size() == size && (*elig_itr)->JoinTime > jointime) )
{
insert(elig_itr,(*itr));
inserted = true;
break;
}
}
// if not inserted -> this is the smallest group -> push_back
if(!inserted)
{
push_back((*itr));
}
}
}
}
// remove group from eligible groups
// used when building selection pools
void BattleGroundQueue::EligibleGroups::RemoveGroup(GroupQueueInfo * ginfo)
{
for(std::list::iterator itr = begin(); itr != end(); ++itr)
{
if((*itr)==ginfo)
{
erase(itr);
return;
}
}
}
// selection pool initialization, used to clean up from prev selection
void BattleGroundQueue::SelectionPool::Init()
{
SelectedGroups.clear();
MaxGroup = 0;
PlayerCount = 0;
}
// get the maximal group from the selection pool
// used when building the pool, and have to remove the largest
GroupQueueInfo * BattleGroundQueue::SelectionPool::GetMaximalGroup()
{
if(SelectedGroups.empty())
{
sLog.outError("Getting max group when selection pool is empty, this should never happen.");
MaxGroup = NULL;
return 0;
}
// actually select the max group if it's not set
if(MaxGroup==0 && !SelectedGroups.empty())
{
uint32 max_size = 0;
for(std::list::iterator itr = SelectedGroups.begin(); itr != SelectedGroups.end(); ++itr)
{
if(max_size<(*itr)->Players.size())
{
MaxGroup =(*itr);
max_size = MaxGroup->Players.size();
}
}
}
return MaxGroup;
}
// remove group info from selection pool
// used when building selection pools and have to remove maximal group
void BattleGroundQueue::SelectionPool::RemoveGroup(GroupQueueInfo *ginfo)
{
// uninitiate max group info if needed
if(MaxGroup == ginfo)
MaxGroup = 0;
// find what to remove
for(std::list::iterator itr = SelectedGroups.begin(); itr != SelectedGroups.end(); ++itr)
{
if((*itr)==ginfo)
{
SelectedGroups.erase(itr);
// decrease selected players count
PlayerCount -= ginfo->Players.size();
return;
}
}
}
// add group to selection
// used when building selection pools
void BattleGroundQueue::SelectionPool::AddGroup(GroupQueueInfo * ginfo)
{
SelectedGroups.push_back(ginfo);
// increase selected players count
PlayerCount+=ginfo->Players.size();
if(!MaxGroup || ginfo->Players.size() > MaxGroup->Players.size())
{
// update max group info if needed
MaxGroup = ginfo;
}
}
// add group to bg queue with the given leader and bg specifications
GroupQueueInfo * BattleGroundQueue::AddGroup(Player *leader, uint32 BgTypeId, uint8 ArenaType, bool isRated, uint32 arenaRating, uint32 arenateamid)
{
uint32 queue_id = leader->GetBattleGroundQueueIdFromLevel();
// create new ginfo
// cannot use the method like in addplayer, because that could modify an in-queue group's stats
// (e.g. leader leaving queue then joining as individual again)
GroupQueueInfo* ginfo = new GroupQueueInfo;
ginfo->BgTypeId = BgTypeId;
ginfo->ArenaType = ArenaType;
ginfo->ArenaTeamId = arenateamid;
ginfo->IsRated = isRated;
ginfo->IsInvitedToBGInstanceGUID = 0; // maybe this should be modifiable by function arguments to enable selection of running instances?
ginfo->JoinTime = getMSTime();
ginfo->Team = leader->GetTeam();
if(sBattleGroundMgr.GetMaxRatingDifference()) // if max difference is set, then store rating info for queue
ginfo->ArenaTeamRating = arenaRating;
else
ginfo->ArenaTeamRating = 0; // don't if it doesn't matter
ginfo->Players.clear();
m_QueuedGroups[queue_id].push_back(ginfo);
// return ginfo, because it is needed to add players to this group info
return ginfo;
}
void BattleGroundQueue::AddPlayer(Player *plr, GroupQueueInfo *ginfo)
{
uint32 queue_id = plr->GetBattleGroundQueueIdFromLevel();
//if player isn't in queue, he is added, if already is, then values are overwritten, no memory leak
PlayerQueueInfo& info = m_QueuedPlayers[queue_id][plr->GetGUID()];
info.InviteTime = 0;
info.LastInviteTime = 0;
info.LastOnlineTime = getMSTime();
info.GroupInfo = ginfo;
// add the pinfo to ginfo's list
ginfo->Players[plr->GetGUID()] = &info;
}
void BattleGroundQueue::RemovePlayer(uint64 guid, bool decreaseInvitedCount)
{
Player *plr = objmgr.GetPlayer(guid);
uint32 queue_id = 0;
QueuedPlayersMap::iterator itr;
GroupQueueInfo * group;
QueuedGroupsList::iterator group_itr;
bool IsSet = false;
if(plr)
{
queue_id = plr->GetBattleGroundQueueIdFromLevel();
itr = m_QueuedPlayers[queue_id].find(guid);
if(itr != m_QueuedPlayers[queue_id].end())
IsSet = true;
}
if(!IsSet)
{
// either player is offline, or he levelled up to another queue category
// sLog.outError("Battleground: removing offline player from BG queue - this might not happen, but it should not cause crash");
for (uint32 i = 0; i < MAX_BATTLEGROUND_QUEUES; i++)
{
itr = m_QueuedPlayers[i].find(guid);
if(itr != m_QueuedPlayers[i].end())
{
queue_id = i;
IsSet = true;
break;
}
}
}
// couldn't find the player in bg queue, return
if(!IsSet)
{
sLog.outError("Battleground: couldn't find player to remove.");
return;
}
group = itr->second.GroupInfo;
for(group_itr=m_QueuedGroups[queue_id].begin(); group_itr != m_QueuedGroups[queue_id].end(); ++group_itr)
{
if(group == (GroupQueueInfo*)(*group_itr))
break;
}
// variables are set (what about leveling up when in queue????)
// remove player from group
// if only 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);
// check for iterator correctness
if (group_itr != m_QueuedGroups[queue_id].end() && itr != m_QueuedPlayers[queue_id].end())
{
// used when player left the queue, NOT used when porting to bg
if (decreaseInvitedCount)
{
// if invited to bg, and should decrease invited count, then do it
if(group->IsInvitedToBGInstanceGUID)
{
BattleGround* bg = sBattleGroundMgr.GetBattleGround(group->IsInvitedToBGInstanceGUID);
if (bg)
bg->DecreaseInvitedCount(group->Team);
if (bg && !bg->GetPlayersSize() && !bg->GetInvitedCount(ALLIANCE) && !bg->GetInvitedCount(HORDE))
{
// no more players on battleground, set delete it
bg->SetDeleteThis();
}
}
// update the join queue, maybe now the player's group fits in a queue!
// not yet implemented (should store bgTypeId in group queue info?)
}
// remove player queue info
m_QueuedPlayers[queue_id].erase(itr);
// remove group queue info if needed
if(group->Players.empty())
{
m_QueuedGroups[queue_id].erase(group_itr);
delete group;
}
// NEEDS TESTING!
// 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 && decreaseInvitedCount && 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);
uint32 bgQueueTypeId = sBattleGroundMgr.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, plr2->GetTeam(), queueSlot, STATUS_NONE, 0, 0);
plr2->GetSession()->SendPacket(&data);
}
// then actually delete, this may delete the group as well!
RemovePlayer(group->Players.begin()->first,decreaseInvitedCount);
}
}
}
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();
uint32 bgQueueTypeId = sBattleGroundMgr.BGQueueTypeId(bg->GetTypeID(), bg->GetArenaType());
// loop through the players
for(std::map::iterator itr = ginfo->Players.begin(); itr != ginfo->Players.end(); ++itr)
{
// set status
itr->second->InviteTime = getMSTime();
itr->second->LastInviteTime = getMSTime();
// get the player
Player* plr = objmgr.GetPlayer(itr->first);
// if offline, skip him
if(!plr)
continue;
// invite the player
sBattleGroundMgr.InvitePlayer(plr, bg->GetInstanceID(),ginfo->Team);
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, side?side:plr->GetTeam(), queueSlot, STATUS_WAIT_JOIN, INVITE_ACCEPT_WAIT_TIME, 0);
plr->GetSession()->SendPacket(&data);
}
return true;
}
return false;
}
// this function is responsible for the selection of queued groups when trying to create new battlegrounds
bool BattleGroundQueue::BuildSelectionPool(uint32 bgTypeId, uint32 queue_id, uint32 MinPlayers, uint32 MaxPlayers, SelectionPoolBuildMode mode, uint8 ArenaType, bool isRated, uint32 MinRating, uint32 MaxRating, uint32 DisregardTime, uint32 excludeTeam)
{
uint32 side;
switch(mode)
{
case NORMAL_ALLIANCE:
case ONESIDE_ALLIANCE_TEAM1:
case ONESIDE_ALLIANCE_TEAM2:
side = ALLIANCE;
break;
case NORMAL_HORDE:
case ONESIDE_HORDE_TEAM1:
case ONESIDE_HORDE_TEAM2:
side = HORDE;
break;
default:
//unknown mode, return false
sLog.outDebug("Battleground: unknown selection pool build mode, returning...");
return false;
break;
}
// inititate the groups eligible to create the bg
m_EligibleGroups.Init(&(m_QueuedGroups[queue_id]), bgTypeId, side, MaxPlayers, ArenaType, isRated, MinRating, MaxRating, DisregardTime, excludeTeam);
// init the selected groups (clear)
m_SelectionPools[mode].Init();
while(!(m_EligibleGroups.empty()))
{
sLog.outDebug("m_EligibleGroups is not empty, continue building selection pool");
// in decreasing group size, add groups to join if they fit in the MaxPlayersPerTeam players
for(EligibleGroups::iterator itr= m_EligibleGroups.begin(); itr!=m_EligibleGroups.end(); ++itr)
{
// get the maximal not yet checked group
GroupQueueInfo * MaxGroup = (*itr);
// if it fits in the maxplayer size, add it
if( (m_SelectionPools[mode].GetPlayerCount() + MaxGroup->Players.size()) <= MaxPlayers )
{
m_SelectionPools[mode].AddGroup(MaxGroup);
}
}
if(m_SelectionPools[mode].GetPlayerCount()>=MinPlayers)
{
// the selection pool is set, return
sLog.outDebug("pool build succeeded, return true");
return true;
}
// if the selection pool's not set, then remove the group with the highest player count, and try again with the rest.
GroupQueueInfo * MaxGroup = m_SelectionPools[mode].GetMaximalGroup();
m_EligibleGroups.RemoveGroup(MaxGroup);
m_SelectionPools[mode].RemoveGroup(MaxGroup);
}
// failed to build a selection pool matching the given values
return false;
}
// used to remove the Enter Battle window if the battle has already, but someone still has it
// (this can happen in arenas mainly, since the preparation is shorter than the timer for the bgqueueremove event
void BattleGroundQueue::BGEndedRemoveInvites(BattleGround *bg)
{
uint32 queue_id = bg->GetQueueType();
uint32 bgInstanceId = bg->GetInstanceID();
uint32 bgQueueTypeId = sBattleGroundMgr.BGQueueTypeId(bg->GetTypeID(), bg->GetArenaType());
QueuedGroupsList::iterator itr, next;
for(itr = m_QueuedGroups[queue_id].begin(); itr != m_QueuedGroups[queue_id].end(); itr = next)
{
// must do this way, because the groupinfo will be deleted when all playerinfos are removed
GroupQueueInfo * ginfo = (*itr);
next = itr;
++next;
// if group was invited to this bg instance, then remove all references
if(ginfo->IsInvitedToBGInstanceGUID == bgInstanceId)
{
// after removing this much playerinfos, the ginfo will be deleted, so we'll use a for loop
uint32 to_remove = ginfo->Players.size();
uint32 team = ginfo->Team;
for(int i = 0; i < to_remove; ++i)
{
// always remove the first one in the group
std::map::iterator itr2 = ginfo->Players.begin();
if(itr2 == ginfo->Players.end())
{
sLog.outError("Empty Players in ginfo, this should never happen!");
return;
}
// get the player
Player * plr = objmgr.GetPlayer(itr2->first);
if(!plr)
{
sLog.outError("Player offline when trying to remove from GroupQueueInfo, this should never happen.");
continue;
}
// get the queueslot
uint32 queueSlot = plr->GetBattleGroundQueueIndex(bgQueueTypeId);
if (queueSlot < PLAYER_MAX_BATTLEGROUND_QUEUES) // player is in queue
{
plr->RemoveBattleGroundQueueId(bgQueueTypeId);
// remove player from queue, this might delete the ginfo as well! don't use that pointer after this!
RemovePlayer(itr2->first, true);
// this is probably unneeded, since this player was already invited -> does not fit when initing eligible groups
// but updateing the queue can't hurt
Update(bgQueueTypeId, bg->GetQueueType());
// send info to client
WorldPacket data;
sBattleGroundMgr.BuildBattleGroundStatusPacket(&data, bg, team, queueSlot, STATUS_NONE, 0, 0);
plr->GetSession()->SendPacket(&data);
}
}
}
}
}
/*
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 after removeplayer functions in some cases
*/
void BattleGroundQueue::Update(uint32 bgTypeId, uint32 queue_id, uint8 arenatype, bool isRated, uint32 arenaRating)
{
if (queue_id >= MAX_BATTLEGROUND_QUEUES)
{
//this is error, that caused crashes (not in , but now it shouldn't)
sLog.outError("BattleGroundQueue::Update() called for non existing queue type - this can cause crash, pls report problem, if this is the last line of error log before crash");
return;
}
//if no players in queue ... do nothing
if (this->m_QueuedGroups[queue_id].size() == 0)
return;
uint32 bgQueueTypeId = sBattleGroundMgr.BGQueueTypeId(bgTypeId, arenatype);
//battleground with free slot for player should be always the last in this queue
BGFreeSlotQueueType::iterator itr, next;
for (itr = sBattleGroundMgr.BGFreeSlotQueue[bgTypeId].begin(); itr != sBattleGroundMgr.BGFreeSlotQueue[bgTypeId].end(); itr = next)
{
next = itr;
++next;
// battleground is running, so if:
// DO NOT allow queue manager to invite new player to running arena
if ((*itr)->isBattleGround() && (*itr)->GetTypeID() == bgTypeId && (*itr)->GetQueueType() == queue_id && (*itr)->GetStatus() > STATUS_WAIT_QUEUE && (*itr)->GetStatus() < STATUS_WAIT_LEAVE)
{
//we must check both teams
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
for(QueuedGroupsList::iterator itr = m_QueuedGroups[queue_id].begin(); itr != m_QueuedGroups[queue_id].end(); ++itr)
{
// did the group join for this bg type?
if((*itr)->BgTypeId != bgTypeId)
continue;
// if so, check if fits in
if(bg->GetFreeSlotsForTeam((*itr)->Team) >= (*itr)->Players.size())
{
// if group fits in, invite it
InviteGroupToBG((*itr),bg,(*itr)->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;
}
// 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(bg_template->isArena())
{
if(sBattleGroundMgr.isArenaTesting())
{
MaxPlayersPerTeam = 1;
MinPlayersPerTeam = 1;
}
else
{
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;
}
}
}
// found out the minimum and maximum ratings the newly added team should battle against
// arenaRating is the rating of the latest joined team
uint32 arenaMinRating = (arenaRating <= sBattleGroundMgr.GetMaxRatingDifference()) ? 0 : arenaRating - sBattleGroundMgr.GetMaxRatingDifference();
// if no rating is specified, set maxrating to 0
uint32 arenaMaxRating = (arenaRating == 0)? 0 : arenaRating + sBattleGroundMgr.GetMaxRatingDifference();
uint32 discardTime = 0;
// 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
if(sBattleGroundMgr.GetMaxRatingDifference() && getMSTime() >= sBattleGroundMgr.GetRatingDiscardTimer())
discardTime = getMSTime() - sBattleGroundMgr.GetRatingDiscardTimer();
// try to build the selection pools
bool bAllyOK = BuildSelectionPool(bgTypeId, queue_id, MinPlayersPerTeam, MaxPlayersPerTeam, NORMAL_ALLIANCE, arenatype, isRated, arenaMinRating, arenaMaxRating, discardTime);
if(bAllyOK)
sLog.outDebug("Battleground: ally pool succesfully build");
else
sLog.outDebug("Battleground: ally pool wasn't created");
bool bHordeOK = BuildSelectionPool(bgTypeId, queue_id, MinPlayersPerTeam, MaxPlayersPerTeam, NORMAL_HORDE, arenatype, isRated, arenaMinRating, arenaMaxRating, discardTime);
if(bHordeOK)
sLog.outDebug("Battleground: horde pool succesfully built");
else
sLog.outDebug("Battleground: horde pool wasn't created");
// if selection pools are ready, create the new bg
if (bAllyOK && bHordeOK)
{
BattleGround * bg2 = 0;
// special handling for arenas
if(bg_template->isArena())
{
// Find a random arena, that can be created
uint8 arenas[] = {BATTLEGROUND_NA, BATTLEGROUND_BE, BATTLEGROUND_RL};
uint32 arena_num = urand(0,2);
if( !(bg2 = sBattleGroundMgr.CreateNewBattleGround(arenas[arena_num%3])) &&
!(bg2 = sBattleGroundMgr.CreateNewBattleGround(arenas[(arena_num+1)%3])) &&
!(bg2 = sBattleGroundMgr.CreateNewBattleGround(arenas[(arena_num+2)%3])) )
{
sLog.outError("Battleground: couldn't create arena");
return;
}
// set the MaxPlayersPerTeam values based on arenatype
// setting the min player values isn't needed, since we won't be using that value later on.
if(sBattleGroundMgr.isArenaTesting())
{
bg2->SetMaxPlayersPerTeam(1);
bg2->SetMaxPlayers(2);
}
else
{
switch(arenatype)
{
case ARENA_TYPE_2v2:
bg2->SetMaxPlayersPerTeam(2);
bg2->SetMaxPlayers(4);
break;
case ARENA_TYPE_3v3:
bg2->SetMaxPlayersPerTeam(3);
bg2->SetMaxPlayers(6);
break;
case ARENA_TYPE_5v5:
bg2->SetMaxPlayersPerTeam(5);
bg2->SetMaxPlayers(10);
break;
default:
break;
}
}
}
else
{
// create new battleground
bg2 = sBattleGroundMgr.CreateNewBattleGround(bgTypeId);
}
if(!bg2)
{
sLog.outError("Battleground: couldn't create bg %u",bgTypeId);
return;
}
// start the joining of the bg
bg2->SetStatus(STATUS_WAIT_JOIN);
bg2->SetQueueType(queue_id);
// initialize arena / rating info
bg2->SetArenaType(arenatype);
// set rating
bg2->SetRated(isRated);
std::list::iterator itr;
// invite groups from horde selection pool
for(itr = m_SelectionPools[NORMAL_HORDE].SelectedGroups.begin(); itr != m_SelectionPools[NORMAL_HORDE].SelectedGroups.end(); ++itr)
{
InviteGroupToBG((*itr),bg2,HORDE);
}
// invite groups from ally selection pools
for(itr = m_SelectionPools[NORMAL_ALLIANCE].SelectedGroups.begin(); itr != m_SelectionPools[NORMAL_ALLIANCE].SelectedGroups.end(); ++itr)
{
InviteGroupToBG((*itr),bg2,ALLIANCE);
}
// start the battleground
bg2->StartBattleGround();
}
// there weren't enough players for a "normal" match
// if arena, enable horde versus horde or alliance versus alliance teams here
else if(bg_template->isArena())
{
bool bOneSideHordeTeam1 = false, bOneSideHordeTeam2 = false;
bool bOneSideAllyTeam1 = false, bOneSideAllyTeam2 = false;
bOneSideHordeTeam1 = BuildSelectionPool(bgTypeId, queue_id,MaxPlayersPerTeam,MaxPlayersPerTeam,ONESIDE_HORDE_TEAM1,arenatype, isRated, arenaMinRating, arenaMaxRating, discardTime);
if(bOneSideHordeTeam1)
{
// one team has been selected, find out if other can be selected too
std::list::iterator itr;
// temporarily change the team side to enable building the next pool excluding the already selected groups
for(itr = m_SelectionPools[ONESIDE_HORDE_TEAM1].SelectedGroups.begin(); itr != m_SelectionPools[ONESIDE_HORDE_TEAM1].SelectedGroups.end(); ++itr)
(*itr)->Team=ALLIANCE;
bOneSideHordeTeam2 = BuildSelectionPool(bgTypeId, queue_id,MaxPlayersPerTeam,MaxPlayersPerTeam,ONESIDE_HORDE_TEAM2,arenatype, isRated, arenaMinRating, arenaMaxRating, discardTime, (*(m_SelectionPools[ONESIDE_HORDE_TEAM1].SelectedGroups.begin()))->ArenaTeamId);
// change back the team to horde
for(itr = m_SelectionPools[ONESIDE_HORDE_TEAM1].SelectedGroups.begin(); itr != m_SelectionPools[ONESIDE_HORDE_TEAM1].SelectedGroups.end(); ++itr)
(*itr)->Team=HORDE;
if(!bOneSideHordeTeam2)
bOneSideHordeTeam1 = false;
}
if(!bOneSideHordeTeam1)
{
// check for one sided ally
bOneSideAllyTeam1 = BuildSelectionPool(bgTypeId, queue_id,MaxPlayersPerTeam,MaxPlayersPerTeam,ONESIDE_ALLIANCE_TEAM1,arenatype, isRated, arenaMinRating, arenaMaxRating, discardTime);
if(bOneSideAllyTeam1)
{
// one team has been selected, find out if other can be selected too
std::list::iterator itr;
// temporarily change the team side to enable building the next pool excluding the already selected groups
for(itr = m_SelectionPools[ONESIDE_ALLIANCE_TEAM1].SelectedGroups.begin(); itr != m_SelectionPools[ONESIDE_ALLIANCE_TEAM1].SelectedGroups.end(); ++itr)
(*itr)->Team=HORDE;
bOneSideAllyTeam2 = BuildSelectionPool(bgTypeId, queue_id,MaxPlayersPerTeam,MaxPlayersPerTeam,ONESIDE_ALLIANCE_TEAM2,arenatype, isRated, arenaMinRating, arenaMaxRating, discardTime,(*(m_SelectionPools[ONESIDE_ALLIANCE_TEAM1].SelectedGroups.begin()))->ArenaTeamId);
// change back the team to ally
for(itr = m_SelectionPools[ONESIDE_ALLIANCE_TEAM1].SelectedGroups.begin(); itr != m_SelectionPools[ONESIDE_ALLIANCE_TEAM1].SelectedGroups.end(); ++itr)
(*itr)->Team=ALLIANCE;
}
if(!bOneSideAllyTeam2)
bOneSideAllyTeam1 = false;
}
// 1-sided BuildSelectionPool() will work, because the MinPlayersPerTeam == MaxPlayersPerTeam in every arena!!!!
if( (bOneSideHordeTeam1 && bOneSideHordeTeam2) ||
(bOneSideAllyTeam1 && bOneSideAllyTeam2) )
{
// which side has enough players?
uint32 side = 0;
SelectionPoolBuildMode mode1, mode2;
// find out what pools are we using
if(bOneSideAllyTeam1 && bOneSideAllyTeam2)
{
side = ALLIANCE;
mode1 = ONESIDE_ALLIANCE_TEAM1;
mode2 = ONESIDE_ALLIANCE_TEAM2;
}
else
{
side = HORDE;
mode1 = ONESIDE_HORDE_TEAM1;
mode2 = ONESIDE_HORDE_TEAM2;
}
// create random arena
uint8 arenas[] = {BATTLEGROUND_NA, BATTLEGROUND_BE, BATTLEGROUND_RL};
uint32 arena_num = urand(0,2);
BattleGround* bg2 = NULL;
if( !(bg2 = sBattleGroundMgr.CreateNewBattleGround(arenas[arena_num%3])) &&
!(bg2 = sBattleGroundMgr.CreateNewBattleGround(arenas[(arena_num+1)%3])) &&
!(bg2 = sBattleGroundMgr.CreateNewBattleGround(arenas[(arena_num+2)%3])) )
{
sLog.outError("Could not create arena.");
return;
}
sLog.outDebug("Battleground: One-faction arena created.");
// init stats
if(sBattleGroundMgr.isArenaTesting())
{
bg2->SetMaxPlayersPerTeam(1);
bg2->SetMaxPlayers(2);
}
else
{
switch(arenatype)
{
case ARENA_TYPE_2v2:
bg2->SetMaxPlayersPerTeam(2);
bg2->SetMaxPlayers(4);
break;
case ARENA_TYPE_3v3:
bg2->SetMaxPlayersPerTeam(3);
bg2->SetMaxPlayers(6);
break;
case ARENA_TYPE_5v5:
bg2->SetMaxPlayersPerTeam(5);
bg2->SetMaxPlayers(10);
break;
default:
break;
}
}
bg2->SetRated(isRated);
// assigned team of the other group
uint32 other_side;
if(side == ALLIANCE)
other_side = HORDE;
else
other_side = ALLIANCE;
// start the joining of the bg
bg2->SetStatus(STATUS_WAIT_JOIN);
bg2->SetQueueType(queue_id);
// initialize arena / rating info
bg2->SetArenaType(arenatype);
std::list::iterator itr;
// invite players from the first group as horde players (actually green team)
for(itr = m_SelectionPools[mode1].SelectedGroups.begin(); itr != m_SelectionPools[mode1].SelectedGroups.end(); ++itr)
{
InviteGroupToBG((*itr),bg2,HORDE);
}
// invite players from the second group as ally players (actually gold team)
for(itr = m_SelectionPools[mode2].SelectedGroups.begin(); itr != m_SelectionPools[mode2].SelectedGroups.end(); ++itr)
{
InviteGroupToBG((*itr),bg2,ALLIANCE);
}
bg2->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;
// Player can be in another BG queue and must be removed in normal way in any case
// // player is already in battleground ... do nothing (battleground queue status is deleted when player is teleported to BG)
// if (plr->GetBattleGroundId() > 0)
// return true;
BattleGround* bg = sBattleGroundMgr.GetBattleGround(m_BgInstanceGUID);
if (!bg)
return true;
uint32 queueSlot = plr->GetBattleGroundQueueIndex(bg->GetTypeID());
if (queueSlot < PLAYER_MAX_BATTLEGROUND_QUEUES) // player is in queue
{
uint32 bgQueueTypeId = sBattleGroundMgr.BGQueueTypeId(bg->GetTypeID(), bg->GetArenaType());
uint32 queueSlot = plr->GetBattleGroundQueueIndex(bgQueueTypeId);
if (queueSlot < PLAYER_MAX_BATTLEGROUND_QUEUES) // player is in queue
{
// check if player is invited to this bg ... this check must be here, because when player leaves queue and joins another, it would cause a problems
BattleGroundQueue::QueuedPlayersMap const& qpMap = sBattleGroundMgr.m_BattleGroundQueues[bgQueueTypeId].m_QueuedPlayers[plr->GetBattleGroundQueueIdFromLevel()];
BattleGroundQueue::QueuedPlayersMap::const_iterator qItr = qpMap.find(m_PlayerGuid);
if (qItr != qpMap.end() && qItr->second.GroupInfo->IsInvitedToBGInstanceGUID == m_BgInstanceGUID)
{
WorldPacket data;
sBattleGroundMgr.BuildBattleGroundStatusPacket(&data, bg, qItr->second.GroupInfo->Team, queueSlot, STATUS_WAIT_JOIN, INVITE_ACCEPT_WAIT_TIME/2, 0);
plr->GetSession()->SendPacket(&data);
}
}
}
return true; //event will be deleted
}
void BGQueueInviteEvent::Abort(uint64 /*e_time*/)
{
//this should not be called
sLog.outError("Battleground invite event ABORTED!");
}
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;
// Player can be in another BG queue and must be removed in normal way in any case
//if (plr->InBattleGround())
// // player is already in battleground ... do nothing (battleground queue status is deleted when player is teleported to BG)
// return true;
BattleGround* bg = sBattleGroundMgr.GetBattleGround(m_BgInstanceGUID);
if (!bg)
return true;
sLog.outDebug("Battleground: removing player %u from bg queue for instance %u because of not pressing enter battle in time.",plr->GetGUIDLow(),m_BgInstanceGUID);
uint32 bgQueueTypeId = sBattleGroundMgr.BGQueueTypeId(bg->GetTypeID(), bg->GetArenaType());
uint32 queueSlot = plr->GetBattleGroundQueueIndex(bgQueueTypeId);
if (queueSlot < PLAYER_MAX_BATTLEGROUND_QUEUES) // player is in queue
{
// check if player is invited to this bg ... this check must be here, because when player leaves queue and joins another, it would cause a problems
BattleGroundQueue::QueuedPlayersMap::iterator qMapItr = sBattleGroundMgr.m_BattleGroundQueues[bgQueueTypeId].m_QueuedPlayers[plr->GetBattleGroundQueueIdFromLevel()].find(m_PlayerGuid);
if (qMapItr != sBattleGroundMgr.m_BattleGroundQueues[bgQueueTypeId].m_QueuedPlayers[plr->GetBattleGroundQueueIdFromLevel()].end() && qMapItr->second.GroupInfo && qMapItr->second.GroupInfo->IsInvitedToBGInstanceGUID == m_BgInstanceGUID)
{
plr->RemoveBattleGroundQueueId(bgQueueTypeId);
sBattleGroundMgr.m_BattleGroundQueues[bgQueueTypeId].RemovePlayer(m_PlayerGuid, true);
sBattleGroundMgr.m_BattleGroundQueues[bgQueueTypeId].Update(bgQueueTypeId, bg->GetQueueType());
WorldPacket data;
sBattleGroundMgr.BuildBattleGroundStatusPacket(&data, bg, m_PlayersTeam, queueSlot, STATUS_NONE, 0, 0);
plr->GetSession()->SendPacket(&data);
}
}
else
sLog.outDebug("Battleground: Player was already removed from queue");
//event will be deleted
return true;
}
void BGQueueRemoveEvent::Abort(uint64 /*e_time*/)
{
//this should not be called
sLog.outError("Battleground remove event ABORTED!");
}
/*********************************************************/
/*** BATTLEGROUND MANAGER ***/
/*********************************************************/
BattleGroundMgr::BattleGroundMgr()
{
m_BattleGrounds.clear();
m_AutoDistributePoints = (bool)sWorld.getConfig(CONFIG_ARENA_AUTO_DISTRIBUTE_POINTS);
m_MaxRatingDifference = sWorld.getConfig(CONFIG_ARENA_MAX_RATING_DIFFERENCE);
m_RatingDiscardTimer = sWorld.getConfig(CONFIG_ARENA_RATING_DISCARD_TIMER);
m_PrematureFinishTimer = sWorld.getConfig(CONFIG_BATTLEGROUND_PREMATURE_FINISH_TIMER);
m_NextRatingDiscardUpdate = m_RatingDiscardTimer;
m_AutoDistributionTimeChecker = 0;
m_ArenaTesting = false;
}
BattleGroundMgr::~BattleGroundMgr()
{
BattleGroundSet::iterator itr, next;
for(itr = m_BattleGrounds.begin(); itr != m_BattleGrounds.end(); itr = next)
{
next = itr;
++next;
BattleGround * bg = itr->second;
m_BattleGrounds.erase(itr);
delete bg;
}
m_BattleGrounds.clear();
}
// used to update running battlegrounds, and delete finished ones
void BattleGroundMgr::Update(time_t diff)
{
BattleGroundSet::iterator itr, next;
for(itr = m_BattleGrounds.begin(); itr != m_BattleGrounds.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.erase(itr);
delete bg;
}
}
// if rating difference counts, maybe force-update queues
if(m_MaxRatingDifference)
{
// it's time to force update
if(m_NextRatingDiscardUpdate < diff)
{
// forced update for level 70 rated arenas
m_BattleGroundQueues[BATTLEGROUND_QUEUE_2v2].Update(BATTLEGROUND_AA,6,ARENA_TYPE_2v2,true,0);
m_BattleGroundQueues[BATTLEGROUND_QUEUE_3v3].Update(BATTLEGROUND_AA,6,ARENA_TYPE_3v3,true,0);
m_BattleGroundQueues[BATTLEGROUND_QUEUE_5v5].Update(BATTLEGROUND_AA,6,ARENA_TYPE_5v5,true,0);
m_NextRatingDiscardUpdate = m_RatingDiscardTimer;
}
else
m_NextRatingDiscardUpdate -= diff;
}
if(m_AutoDistributePoints)
{
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);
CharacterDatabase.PExecute("UPDATE saved_variables SET NextArenaPointDistributionTime = FROM_UNIXTIME('"I64FMTD"')",(uint64)m_NextAutoDistributionTime);
}
m_AutoDistributionTimeChecker = 600000; // check 10 minutes
}
else
m_AutoDistributionTimeChecker -= diff;
}
}
void BattleGroundMgr::BuildBattleGroundStatusPacket(WorldPacket *data, BattleGround *bg, uint32 team, uint8 QueueSlot, uint8 StatusID, uint32 Time1, uint32 Time2, uint32 arenatype, uint8 israted)
{
// we can be in 3 queues in same time...
if(StatusID == 0)
{
data->Initialize(SMSG_BATTLEFIELD_STATUS, 4*3);
*data << uint32(QueueSlot); // queue id (0...2)
*data << uint64(0);
return;
}
data->Initialize(SMSG_BATTLEFIELD_STATUS, (4+1+1+4+2+4+1+4+4+4));
*data << uint32(QueueSlot); // queue id (0...2) - player can be in 3 queues in time
// uint64 in client
*data << uint64( uint64(arenatype ? arenatype : bg->GetArenaType()) | (uint64(0x0D) << 8) | (uint64(bg->GetTypeID()) << 16) | (uint64(0x1F90) << 48) );
*data << uint32(0); // unknown
// alliance/horde for BG and skirmish/rated for Arenas
*data << uint8(bg->isArena() ? ( israted ? israted : bg->isRated() ) : bg->GetTeamIndexByTeamId(team));
/* *data << uint8(arenatype ? arenatype : bg->GetArenaType()); // team type (0=BG, 2=2x2, 3=3x3, 5=5x5), for arenas // NOT PROPER VALUE IF ARENA ISN'T RUNNING YET!!!!
switch(bg->GetTypeID()) // value depends on bg id
{
case BATTLEGROUND_AV:
*data << uint8(1);
break;
case BATTLEGROUND_WS:
*data << uint8(2);
break;
case BATTLEGROUND_AB:
*data << uint8(3);
break;
case BATTLEGROUND_NA:
*data << uint8(4);
break;
case BATTLEGROUND_BE:
*data << uint8(5);
break;
case BATTLEGROUND_AA:
*data << uint8(6);
break;
case BATTLEGROUND_EY:
*data << uint8(7);
break;
case BATTLEGROUND_RL:
*data << uint8(8);
break;
default: // unknown
*data << uint8(0);
break;
}
if(bg->isArena() && (StatusID == STATUS_WAIT_QUEUE))
*data << uint32(BATTLEGROUND_AA); // all arenas I don't think so.
else
*data << uint32(bg->GetTypeID()); // BG id from DBC
*data << uint16(0x1F90); // unk value 8080
*data << uint32(bg->GetInstanceID()); // instance id
if(bg->isBattleGround())
*data << uint8(bg->GetTeamIndexByTeamId(team)); // team
else
*data << uint8(israted?israted:bg->isRated()); // is rated battle
*/
*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?
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); // 0 at bg start, 120000 after bg end, time to bg auto leave, milliseconds
*data << uint32(Time2); // time from bg start, milliseconds
*data << uint8(0x1); // unk sometimes 0x0!
break;
default:
sLog.outError("Unknown BG status!");
break;
}
}
void BattleGroundMgr::BuildPvpLogDataPacket(WorldPacket *data, BattleGround *bg)
{
uint8 type = (bg->isArena() ? 1 : 0);
// last check on 2.4.1
data->Initialize(MSG_PVP_LOG_DATA, (1+1+4+40*bg->GetPlayerScoresSize()));
*data << uint8(type); // seems to be 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)
{
*data << uint32(3000-bg->m_ArenaTeamRatingChanges[i]); // rating change: showed value - 3000
*data << uint32(3999); // huge thanks for TOM_RUS for this!
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->GetWinner() == 2)
{
*data << uint8(0); // bg in progress
}
else
{
*data << uint8(1); // bg ended
*data << uint8(bg->GetWinner()); // who win
}
*data << (int32)(bg->GetPlayerScoresSize());
for(std::map::const_iterator itr = bg->GetPlayerScoresBegin(); itr != bg->GetPlayerScoresEnd(); ++itr)
{
*data << (uint64)itr->first;
*data << (int32)itr->second->KillingBlows;
Player *plr = objmgr.GetPlayer(itr->first);
uint32 team = bg->GetPlayerTeam(itr->first);
if(!team && plr) team = plr->GetTeam();
if(type == 0)
{
*data << (int32)itr->second->HonorableKills;
*data << (int32)itr->second->Deaths;
*data << (int32)(itr->second->BonusHonor);
}
else
{
// that part probably wrong
if(plr)
{
if(team == HORDE)
*data << uint8(0);
else if(team == ALLIANCE)
{
*data << uint8(1);
}
else
*data << uint8(0);
}
else
*data << uint8(0);
}
*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:
*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, uint32 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, Player *plr)
{
data->Initialize(SMSG_BATTLEGROUND_PLAYER_LEFT, 8);
*data << uint64(plr->GetGUID());
}
void BattleGroundMgr::BuildPlayerJoinedBattleGroundPacket(WorldPacket *data, Player *plr)
{
data->Initialize(SMSG_BATTLEGROUND_PLAYER_JOINED, 8);
*data << uint64(plr->GetGUID());
}
void BattleGroundMgr::InvitePlayer(Player* plr, uint32 bgInstanceGUID, uint32 team)
{
// set invited player counters:
BattleGround* bg = this->GetBattleGround(bgInstanceGUID);
if(!bg)
return;
bg->IncreaseInvitedCount(team);
plr->SetInviteForBattleGroundQueueType(BGQueueTypeId(bg->GetTypeID(),bg->GetArenaType()), bgInstanceGUID);
// set the arena teams for rated matches
if(bg->isArena() && bg->isRated())
{
switch(bg->GetArenaType())
{
case ARENA_TYPE_2v2:
bg->SetArenaTeamIdForTeam(team, plr->GetArenaTeamId(0));
break;
case ARENA_TYPE_3v3:
bg->SetArenaTeamIdForTeam(team, plr->GetArenaTeamId(1));
break;
case ARENA_TYPE_5v5:
bg->SetArenaTeamIdForTeam(team, plr->GetArenaTeamId(2));
break;
default:
break;
}
}
// create invite events:
//add events to player's counters ---- this is not good way - there should be something like global event processor, where we should add those events
BGQueueInviteEvent* inviteEvent = new BGQueueInviteEvent(plr->GetGUID(), bgInstanceGUID);
plr->m_Events.AddEvent(inviteEvent, plr->m_Events.CalculateTime(INVITE_ACCEPT_WAIT_TIME/2));
BGQueueRemoveEvent* removeEvent = new BGQueueRemoveEvent(plr->GetGUID(), bgInstanceGUID, team);
plr->m_Events.AddEvent(removeEvent, plr->m_Events.CalculateTime(INVITE_ACCEPT_WAIT_TIME));
}
BattleGround * BattleGroundMgr::GetBattleGroundTemplate(uint32 bgTypeId)
{
return BGFreeSlotQueue[bgTypeId].empty() ? NULL : BGFreeSlotQueue[bgTypeId].back();
}
// create a new battleground that will really be used to play
BattleGround * BattleGroundMgr::CreateNewBattleGround(uint32 bgTypeId)
{
BattleGround *bg = NULL;
// get the template BG
BattleGround *bg_template = GetBattleGroundTemplate(bgTypeId);
if(!bg_template)
{
sLog.outError("BattleGround: CreateNewBattleGround - bg template not found for %u", bgTypeId);
return 0;
}
// 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;
default:
//bg = new BattleGround;
return 0;
break; // placeholder for non implemented BG
}
// generate a new instance id
bg->SetInstanceID(MapManager::Instance().GenerateInstanceId()); // set instance id
// reset the new bg (set status to status_wait_queue from status_none)
bg->Reset();
/* will be setup in BG::Update() when the first player is ported in
if(!(bg->SetupBattleGround()))
{
sLog.outError("BattleGround: CreateNewBattleGround: SetupBattleGround failed for bg %u", bgTypeId);
delete bg;
return 0;
}
*/
// add BG to free slot queue
bg->AddToBGFreeSlotQueue();
// add bg to update list
AddBattleGround(bg->GetInstanceID(), bg);
return bg;
}
// used to create the BG templates
uint32 BattleGroundMgr::CreateBattleGround(uint32 bgTypeId, 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;
default:bg = new BattleGround; break; // placeholder for non implemented BG
}
bg->SetMapId(MapID);
bg->Reset();
BattlemasterListEntry const *bl = sBattlemasterListStore.LookupEntry(bgTypeId);
//in previous method is checked if exists entry in sBattlemasterListStore, so no check needed
if (bl)
{
bg->SetArenaorBGType(bl->type == TYPE_ARENA);
}
bg->SetTypeID(bgTypeId);
bg->SetInstanceID(0); // template bg, instance id is 0
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 BattleGround instance to FreeSlotQueue (.back() will return the template!)
bg->AddToBGFreeSlotQueue();
// do NOT add to update list, since this is a template battleground!
// 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;
uint32 count = 0;
// 0 1 2 3 4 5 6 7 8
QueryResult *result = WorldDatabase.Query("SELECT id, MinPlayersPerTeam,MaxPlayersPerTeam,MinLvl,MaxLvl,AllianceStartLoc,AllianceStartO,HordeStartLoc,HordeStartO FROM battleground_template");
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 overwrited 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;
}
MaxPlayersPerTeam = bl->maxplayersperteam;
MinPlayersPerTeam = bl->maxplayersperteam/2;
MinLvl = bl->minlvl;
MaxLvl = bl->maxlvl;
if(fields[1].GetUInt32())
MinPlayersPerTeam = fields[1].GetUInt32();
if(fields[2].GetUInt32())
MaxPlayersPerTeam = fields[2].GetUInt32();
if(fields[3].GetUInt32())
MinLvl = fields[3].GetUInt32();
if(fields[4].GetUInt32())
MaxLvl = fields[4].GetUInt32();
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)
{
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)
{
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, 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;
++count;
} while (result->NextRow());
delete result;
sLog.outString();
sLog.outString( ">> Loaded %u battlegrounds", count );
}
void BattleGroundMgr::InitAutomaticArenaPointDistribution()
{
if(m_AutoDistributePoints)
{
sLog.outDebug("Initializing Automatic Arena Point Distribution");
QueryResult * result = CharacterDatabase.Query("SELECT UNIX_TIMESTAMP(NextArenaPointDistributionTime) FROM saved_variables");
if(!result)
{
sLog.outDebug("Battleground: Next arena point distribution time not found in SavedVariables, reseting it now.");
m_NextAutoDistributionTime = time(NULL) + BATTLEGROUND_ARENA_POINT_DISTRIBUTION_DAY * sWorld.getConfig(CONFIG_ARENA_AUTO_DISTRIBUTE_INTERVAL_DAYS);
CharacterDatabase.PExecute("INSERT INTO saved_variables (NextArenaPointDistributionTime) VALUES ( FROM_UNIXTIME('"I64FMTD"') )",(uint64)m_NextAutoDistributionTime);
}
else
{
m_NextAutoDistributionTime = (*result)[0].GetUInt64();
delete result;
}
sLog.outDebug("Automatic Arena Point Distribution initialized.");
}
}
void BattleGroundMgr::DistributeArenaPoints()
{
// used to distribute arena points based on last week's stats
CharacterDatabase.BeginTransaction();
// direct execute, because of the later GetUInt32ValueFromDB() calls
// 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10
CharacterDatabase.DirectPExecute("UPDATE characters b, arena_team_member a SET b.data = CONCAT( SUBSTRING_INDEX(b.data, ' ', '%u'),' ', CAST( IF ( ((CAST( SUBSTRING( b.data FROM (CHAR_LENGTH(SUBSTRING_INDEX(b.data,' ','%u')) + 2) FOR (CHAR_LENGTH(SUBSTRING_INDEX(b.data,' ','%u')) - CHAR_LENGTH(SUBSTRING_INDEX(b.data,' ','%u')) - 1) ) AS UNSIGNED) + (SELECT MAX(c.points_to_add) FROM arena_team_member c WHERE c.guid = b.guid GROUP BY c.guid) ) < '%u'), CAST(SUBSTRING(b.data FROM (CHAR_LENGTH(SUBSTRING_INDEX(b.data,' ','%u')) + 2) FOR (CHAR_LENGTH(SUBSTRING_INDEX(b.data,' ','%u')) - CHAR_LENGTH(SUBSTRING_INDEX(b.data,' ','%u')) - 1) ) AS UNSIGNED) + (SELECT MAX(d.points_to_add) FROM arena_team_member d WHERE d.guid = b.guid GROUP BY d.guid), '%u') AS CHAR),' ',SUBSTRING(b.data FROM (CHAR_LENGTH(SUBSTRING_INDEX(b.data,' ','%u')) + 2))) WHERE b.guid = a.guid",PLAYER_FIELD_ARENA_CURRENCY, PLAYER_FIELD_ARENA_CURRENCY, PLAYER_FIELD_ARENA_CURRENCY+1, PLAYER_FIELD_ARENA_CURRENCY, sWorld.getConfig(CONFIG_MAX_ARENA_POINTS),PLAYER_FIELD_ARENA_CURRENCY, PLAYER_FIELD_ARENA_CURRENCY+1, PLAYER_FIELD_ARENA_CURRENCY, sWorld.getConfig(CONFIG_MAX_ARENA_POINTS), PLAYER_FIELD_ARENA_CURRENCY+1);
for(int i=0; i<3; ++i)
{
// reset weekly played matches
uint32 position = PLAYER_FIELD_ARENA_TEAM_INFO_1_1 + 6 * i + 2;
CharacterDatabase.DirectPExecute("UPDATE characters SET data = CONCAT( SUBSTRING_INDEX(data,' ','%u'),' ','0',' ',SUBSTRING(data FROM (CHAR_LENGTH(SUBSTRING_INDEX(data,' ','%u')) + 2)))",position, position + 1);
}
CharacterDatabase.DirectExecute("UPDATE arena_team_member SET points_to_add = '0', played_week = '0', wons_week = '0'");
CharacterDatabase.DirectExecute("UPDATE arena_team_stats SET games = '0', wins = '0'");
CharacterDatabase.CommitTransaction();
QueryResult *result = CharacterDatabase.PQuery("SELECT guid, data FROM characters WHERE online = '1'");
if( result )
{
do
{
Field *fields = result->Fetch();
uint32 guid = fields[0].GetUInt32();
if(Player * pl = objmgr.GetPlayer(MAKE_NEW_GUID(guid, 0, HIGHGUID_PLAYER)))
{
Tokens data = StrSplit(fields[1].GetCppString(), " ");
// update arena currency
pl->SetUInt32Value(PLAYER_FIELD_ARENA_CURRENCY, Player::GetUInt32ValueFromArray(data, PLAYER_FIELD_ARENA_CURRENCY));
// reset played this week count for all teams
for(int i= 0; i < 3; ++i)
pl->SetUInt32Value(PLAYER_FIELD_ARENA_TEAM_INFO_1_1 + 6 * i + 2, 0);
}
} while (result->NextRow());
delete result;
}
for(ObjectMgr::ArenaTeamSet::iterator titr = objmgr.GetArenaTeamSetBegin(); titr != objmgr.GetArenaTeamSetEnd(); ++titr)
{
if(ArenaTeam * at = (*titr))
{
at->FinishWeek(); // set played this week etc values to 0 in memory, too
// at->SaveToDB(); // no need, the modified values are already saved above
at->NotifyStatsChanged(); // notify the players of the changes
}
}
}
void BattleGroundMgr::BuildBattleGroundListPacket(WorldPacket *data, uint64 guid, Player* plr, uint32 bgTypeId)
{
uint32 PlayerLevel = 10;
if(plr)
PlayerLevel = plr->getLevel();
data->Initialize(SMSG_BATTLEFIELD_LIST);
*data << uint64(guid); // battlemaster guid
*data << uint32(bgTypeId); // battleground id
if(bgTypeId == BATTLEGROUND_AA) // arena
{
*data << uint8(5); // unk
*data << uint32(0); // unk
}
else // battleground
{
*data << uint8(0x00); // unk
size_t count_pos = data->wpos();
uint32 count = 0;
*data << uint32(0x00); // number of bg instances
for(std::map::iterator itr = m_BattleGrounds.begin(); itr != m_BattleGrounds.end(); ++itr)
{
if(itr->second->GetTypeID() == bgTypeId && (PlayerLevel >= itr->second->GetMinLevel()) && (PlayerLevel <= itr->second->GetMaxLevel()))
{
*data << uint32(itr->second->GetInstanceID());
++count;
}
}
data->put( count_pos , count);
}
}
void BattleGroundMgr::SendToBattleGround(Player *pl, uint32 instanceId)
{
BattleGround *bg = GetBattleGround(instanceId);
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, 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);
}
void BattleGroundMgr::RemoveBattleGround(uint32 instanceID)
{
BattleGroundSet::iterator itr = m_BattleGrounds.find(instanceID);
if(itr!=m_BattleGrounds.end())
m_BattleGrounds.erase(itr);
}
bool BattleGroundMgr::IsArenaType(uint32 bgTypeId) const
{
return ( bgTypeId == BATTLEGROUND_AA ||
bgTypeId == BATTLEGROUND_BE ||
bgTypeId == BATTLEGROUND_NA ||
bgTypeId == BATTLEGROUND_RL );
}
bool BattleGroundMgr::IsBattleGroundType(uint32 bgTypeId) const
{
return !IsArenaType(bgTypeId);
}
uint32 BattleGroundMgr::BGQueueTypeId(uint32 bgTypeId, uint8 arenaType) const
{
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_AA:
case BATTLEGROUND_NA:
case BATTLEGROUND_RL:
case BATTLEGROUND_BE:
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 0;
}
default:
return 0;
}
}
uint32 BattleGroundMgr::BGTemplateId(uint32 bgQueueTypeId) const
{
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_2v2:
case BATTLEGROUND_QUEUE_3v3:
case BATTLEGROUND_QUEUE_5v5:
return BATTLEGROUND_AA;
default:
return 0;
}
}
uint8 BattleGroundMgr::BGArenaType(uint32 bgQueueTypeId) const
{
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::ToggleArenaTesting()
{
m_ArenaTesting = !m_ArenaTesting;
sWorld.SendWorldText(LANG_ARENA_TESTING, m_ArenaTesting ? "on" : "off");
}
void BattleGroundMgr::SetHolidayWeekends(uint32 mask)
{
for(uint32 bgtype = 1; bgtype <= 8; ++bgtype)
{
if(BattleGround * bg = GetBattleGroundTemplate(bgtype))
{
bg->SetHoliday(mask & (1 << bgtype));
}
}
}