From f0e5bdc4d1cf53ffeeb1142b5f7290b464b9984a Mon Sep 17 00:00:00 2001 From: killerwife Date: Tue, 10 Sep 2024 13:30:30 +0200 Subject: [PATCH] BG: Rework BG queue to be asynchronous and resolve race conditions Thx to insunaa for making it compile on linux --- src/game/BattleGround/BattleGround.cpp | 85 +- src/game/BattleGround/BattleGround.h | 10 +- src/game/BattleGround/BattleGroundDefines.h | 74 + src/game/BattleGround/BattleGroundHandler.cpp | 523 +++--- src/game/BattleGround/BattleGroundMgr.cpp | 1508 +-------------- src/game/BattleGround/BattleGroundMgr.h | 215 +-- src/game/BattleGround/BattleGroundQueue.cpp | 1638 +++++++++++++++++ src/game/BattleGround/BattleGroundQueue.h | 294 +++ src/game/Entities/Creature.cpp | 2 +- src/game/Entities/Player.cpp | 37 - src/game/Entities/Player.h | 5 +- src/game/Globals/SharedDefines.h | 25 - src/game/Groups/Group.cpp | 5 +- src/game/Maps/MapManager.cpp | 7 +- src/game/Maps/MapManager.h | 4 +- src/game/Server/WorldSession.cpp | 5 +- src/game/Server/WorldSession.h | 1 + src/game/World/World.cpp | 13 +- src/game/World/World.h | 7 +- src/mangosd/Master.cpp | 1 + 20 files changed, 2494 insertions(+), 1965 deletions(-) create mode 100644 src/game/BattleGround/BattleGroundDefines.h create mode 100644 src/game/BattleGround/BattleGroundQueue.cpp create mode 100644 src/game/BattleGround/BattleGroundQueue.h diff --git a/src/game/BattleGround/BattleGround.cpp b/src/game/BattleGround/BattleGround.cpp index 90ba790107c..4de35cadf3d 100644 --- a/src/game/BattleGround/BattleGround.cpp +++ b/src/game/BattleGround/BattleGround.cpp @@ -264,7 +264,12 @@ BattleGround::~BattleGround() // skip template bgs as they were never added to visible bg list BattleGroundBracketId bracketId = GetBracketId(); if (bracketId != BG_BRACKET_ID_TEMPLATE) - sBattleGroundMgr.DeleteClientVisibleInstanceId(GetTypeId(), bracketId, GetClientInstanceId()); + { + sWorld.GetBGQueue().GetMessager().AddMessage([bgTypeId = GetTypeId(), bracketId, clientInstanceId = GetClientInstanceId()](BattleGroundQueue* queue) + { + queue->DeleteClientVisibleInstanceId(bgTypeId, bracketId, clientInstanceId); + }); + } // unload map // map can be null at bg destruction @@ -272,7 +277,7 @@ BattleGround::~BattleGround() m_bgMap->SetUnload(); // remove from bg free slot queue - this->RemoveFromBgFreeSlotQueue(); + this->RemovedFromBgFreeSlotQueue(true); for (BattleGroundScoreMap::const_iterator itr = m_playerScores.begin(); itr != m_playerScores.end(); ++itr) delete itr->second; @@ -402,7 +407,7 @@ void BattleGround::Update(uint32 diff) BattleGroundTypeId BgTypeId = GetTypeId(); if (!IsArena()) - sWorld.SendWorldText(LANG_BG_STARTED_ANNOUNCE_WORLD, GetName(), Player::GetMinLevelForBattleGroundBracketId(bracketId, BgTypeId), Player::GetMaxLevelForBattleGroundBracketId(bracketId, BgTypeId)); + sWorld.SendWorldText(LANG_BG_STARTED_ANNOUNCE_WORLD, GetName(), sBattleGroundMgr.GetMinLevelForBattleGroundBracketId(bracketId, BgTypeId), sBattleGroundMgr.GetMaxLevelForBattleGroundBracketId(bracketId, BgTypeId)); else sWorld.SendWorldText(LANG_ARENA_STARTED_ANNOUNCE_WORLD, GetName(), GetArenaType(), GetArenaType()); } @@ -735,7 +740,7 @@ void BattleGround::UpdateWorldStateForPlayer(uint32 field, uint32 value, Player* */ void BattleGround::EndBattleGround(Team winner) { - this->RemoveFromBgFreeSlotQueue(); + this->RemovedFromBgFreeSlotQueue(true); ArenaTeam* winner_arena_team = nullptr; ArenaTeam* loser_arena_team = nullptr; @@ -928,7 +933,7 @@ void BattleGround::EndBattleGround(Team winner) plr->GetSession()->SendPacket(data); BattleGroundQueueTypeId bgQueueTypeId = BattleGroundMgr::BgQueueTypeId(GetTypeId(), GetArenaType()); - sBattleGroundMgr.BuildBattleGroundStatusPacket(data, this, plr->GetBattleGroundQueueIndex(bgQueueTypeId), STATUS_IN_PROGRESS, TIME_TO_AUTOREMOVE, GetStartTime(), GetArenaType(), plr->GetBGTeam()); + sBattleGroundMgr.BuildBattleGroundStatusPacket(data, true, GetTypeId(), GetClientInstanceId(), IsRated(), GetMapId(), plr->GetBattleGroundQueueIndex(bgQueueTypeId), STATUS_IN_PROGRESS, TIME_TO_AUTOREMOVE, GetStartTime(), GetArenaType(), plr->GetBGTeam()); plr->GetSession()->SendPacket(data); } @@ -962,6 +967,16 @@ uint32 BattleGround::GetBonusHonorFromKill(uint32 kills) const return (uint32)MaNGOS::Honor::hk_honor_at_level(GetMaxLevel(), kills); } +void BattleGround::SetStatus(BattleGroundStatus status) +{ + m_status = status; + sWorld.GetBGQueue().GetMessager().AddMessage([status, bgTypeId = GetTypeId(), instanceId = GetInstanceId()](BattleGroundQueue* queue) + { + if (BattleGroundInQueueInfo* bgInstance = queue->GetFreeSlotInstance(bgTypeId, instanceId)) + bgInstance->status = status; + }); +} + /** Function that returns the battleground master entry */ @@ -1237,7 +1252,7 @@ void BattleGround::RemovePlayerAtLeave(ObjectGuid playerGuid, bool isOnTransport if (doSendPacket) { WorldPacket data; - sBattleGroundMgr.BuildBattleGroundStatusPacket(data, this, player->GetBattleGroundQueueIndex(bgQueueTypeId), STATUS_NONE, 0, 0, ARENA_TYPE_NONE, TEAM_NONE); + sBattleGroundMgr.BuildBattleGroundStatusPacket(data, true, GetTypeId(), GetClientInstanceId(), IsRated(), GetMapId(), player->GetBattleGroundQueueIndex(bgQueueTypeId), STATUS_NONE, 0, 0, ARENA_TYPE_NONE, TEAM_NONE); player->GetSession()->SendPacket(data); } @@ -1266,13 +1281,24 @@ void BattleGround::RemovePlayerAtLeave(ObjectGuid playerGuid, bool isOnTransport delete group; } } - DecreaseInvitedCount(team); + + SetInvitedCount(team, GetInvitedCount(team) - 1); // change ahead of free slot queue - will be synched again after // we should update battleground queue, but only if bg isn't ending if (IsBattleGround() && GetStatus() < STATUS_WAIT_LEAVE) { // a player has left the battleground, so there are free slots -> add to queue - AddToBgFreeSlotQueue(); - sBattleGroundMgr.ScheduleQueueUpdate(0, ARENA_TYPE_NONE, bgQueueTypeId, bgTypeId, GetBracketId()); + if (!AddToBgFreeSlotQueue()) // avoid setting two messages - if was already in queue, just update count + { + sWorld.GetBGQueue().GetMessager().AddMessage([bgTypeId, instanceId = GetInstanceId(), team](BattleGroundQueue* queue) + { + if (BattleGroundInQueueInfo* bgInstance = queue->GetFreeSlotInstance(bgTypeId, instanceId)) + bgInstance->DecreaseInvitedCount(team); + }); + } + sWorld.GetBGQueue().GetMessager().AddMessage([bgQueueTypeId, bgTypeId, bracketId = GetBracketId()](BattleGroundQueue* queue) + { + queue->ScheduleQueueUpdate(0, ARENA_TYPE_NONE, bgQueueTypeId, bgTypeId, bracketId); + }); } // Let others know @@ -1340,8 +1366,7 @@ void BattleGround::StartBattleGround() { SetStartTime(0); - // add BG to free slot queue - AddToBgFreeSlotQueue(); + // expects to be already added in free queue // add bg to update list // This must be done here, because we need to have already invited some players when first BG::Update() method is executed @@ -1504,33 +1529,45 @@ void BattleGround::EventPlayerLoggedOut(Player* player) /** Function that returns the number of players that can join a battleground based on the provided team */ -void BattleGround::AddToBgFreeSlotQueue() +bool BattleGround::AddToBgFreeSlotQueue() { // make sure to add only once if (!m_hasBgFreeSlotQueue && IsBattleGround()) { - sBattleGroundMgr.BgFreeSlotQueue[m_typeId].push_front(this); m_hasBgFreeSlotQueue = true; + BattleGroundInQueueInfo bgInfo; + bgInfo.Fill(this); + sWorld.GetBGQueue().GetMessager().AddMessage([bgInfo](BattleGroundQueue* queue) + { + queue->AddBgToFreeSlots(bgInfo); + }); + return true; } + return false; } /** Method that removes this battleground from free queue - it must be called when deleting battleground */ -void BattleGround::RemoveFromBgFreeSlotQueue() +void BattleGround::RemovedFromBgFreeSlotQueue(bool removeFromQueue) { // set to be able to re-add if needed - m_hasBgFreeSlotQueue = false; - BgFreeSlotQueueType& bgFreeSlot = sBattleGroundMgr.BgFreeSlotQueue[m_typeId]; - - for (BgFreeSlotQueueType::iterator itr = bgFreeSlot.begin(); itr != bgFreeSlot.end(); ++itr) + if (m_hasBgFreeSlotQueue && removeFromQueue) { - if ((*itr)->GetInstanceId() == GetInstanceId()) + sWorld.GetBGQueue().GetMessager().AddMessage([bgTypeId = GetTypeId(), instanceId = GetInstanceId()](BattleGroundQueue* queue) { - bgFreeSlot.erase(itr); - return; - } + queue->RemoveBgFromFreeSlots(bgTypeId, instanceId); + }); } + m_hasBgFreeSlotQueue = false; +} + +void BattleGround::SetInvitedCount(Team team, uint32 count) +{ + if (team == ALLIANCE) + m_invitedAlliance = count; + else + m_invitedHorde = count; } /** @@ -1963,7 +2000,7 @@ void BattleGround::SendBcdToTeam(int32 bcdEntry, ChatMsg msgtype, Creature const */ void BattleGround::EndNow() { - RemoveFromBgFreeSlotQueue(); + RemovedFromBgFreeSlotQueue(true); SetStatus(STATUS_WAIT_LEAVE); SetEndTime(0); } @@ -2058,7 +2095,7 @@ void BattleGround::PlayerAddedToBgCheckIfBgIsRunning(Player* player) sBattleGroundMgr.BuildPvpLogDataPacket(data, this); player->GetSession()->SendPacket(data); - sBattleGroundMgr.BuildBattleGroundStatusPacket(data, this, player->GetBattleGroundQueueIndex(bgQueueTypeId), STATUS_IN_PROGRESS, GetEndTime(), GetStartTime(), GetArenaType(), player->GetBGTeam()); + sBattleGroundMgr.BuildBattleGroundStatusPacket(data, true, GetTypeId(), GetClientInstanceId(), IsRated(), GetMapId(), player->GetBattleGroundQueueIndex(bgQueueTypeId), STATUS_IN_PROGRESS, GetEndTime(), GetStartTime(), GetArenaType(), player->GetBGTeam()); player->GetSession()->SendPacket(data); } diff --git a/src/game/BattleGround/BattleGround.h b/src/game/BattleGround/BattleGround.h index 5a59893dffb..9e5fc701590 100644 --- a/src/game/BattleGround/BattleGround.h +++ b/src/game/BattleGround/BattleGround.h @@ -24,6 +24,7 @@ #include "Maps/Map.h" #include "Util/ByteBuffer.h" #include "Entities/ObjectGuid.h" +#include "BattleGround/BattleGroundDefines.h" // magic event-numbers #define BG_EVENT_NONE 255 @@ -335,7 +336,7 @@ class BattleGround void SetName(char const* name) { m_name = name; } void SetTypeId(BattleGroundTypeId typeId) { m_typeId = typeId; } void SetBracketId(BattleGroundBracketId id) { m_bracketId = id; } - void SetStatus(BattleGroundStatus status) { m_status = status; } + void SetStatus(BattleGroundStatus status); void SetClientInstanceId(uint32 instanceId) { m_clientInstanceId = instanceId; } void SetStartTime(uint32 time) { m_startTime = time; } void SetEndTime(uint32 time) { m_endTime = time; } @@ -353,12 +354,11 @@ class BattleGround void SetMaxPlayersPerTeam(uint32 maxPlayers) { m_maxPlayersPerTeam = maxPlayers; } void SetMinPlayersPerTeam(uint32 minPlayers) { m_minPlayersPerTeam = minPlayers; } - void AddToBgFreeSlotQueue(); // this queue will be useful when more battlegrounds instances will be available - void RemoveFromBgFreeSlotQueue(); // this method could delete whole BG instance, if another free is available + bool AddToBgFreeSlotQueue(); // this queue will be useful when more battlegrounds instances will be available + void RemovedFromBgFreeSlotQueue(bool removeFromQueue); // this method could delete whole BG instance, if another free is available // Functions to decrease or increase player count - void DecreaseInvitedCount(Team team) { (team == ALLIANCE) ? --m_invitedAlliance : --m_invitedHorde; } - void IncreaseInvitedCount(Team team) { (team == ALLIANCE) ? ++m_invitedAlliance : ++m_invitedHorde; } + void SetInvitedCount(Team team, uint32 count); uint32 GetInvitedCount(Team team) const { if (team == ALLIANCE) diff --git a/src/game/BattleGround/BattleGroundDefines.h b/src/game/BattleGround/BattleGroundDefines.h new file mode 100644 index 00000000000..307a711d8eb --- /dev/null +++ b/src/game/BattleGround/BattleGroundDefines.h @@ -0,0 +1,74 @@ +/* + * This file is part of the CMaNGOS Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _BG_DEFINES_H +#define _BG_DEFINES_H + +#include "Common.h" + +#define BATTLEGROUND_ARENA_POINT_DISTRIBUTION_DAY 86400 // seconds in a day +#define COUNT_OF_PLAYERS_TO_AVERAGE_WAIT_TIME 10 + +enum BattleGroundQueueGroupTypes +{ + BG_QUEUE_PREMADE_ALLIANCE = 0, + BG_QUEUE_PREMADE_HORDE = 1, + BG_QUEUE_NORMAL_ALLIANCE = 2, + BG_QUEUE_NORMAL_HORDE = 3 +}; + +#define BG_QUEUE_GROUP_TYPES_COUNT 4 + +enum BattleGroundGroupJoinStatus +{ + BG_GROUP_JOIN_STATUS_TEAM_LEFT_QUEUE = -7, + BG_GROUP_JOIN_STATUS_QUEUED_FOR_RATED = -6, + BG_GROUP_JOIN_STATUS_CANNOT_QUEUE_FOR_RATED = -5, + BG_GROUP_JOIN_STATUS_TOO_MANY_QUEUES = -4, + BG_GROUP_JOIN_STATUS_NOT_IN_TEAM = -3, + BG_GROUP_JOIN_STATUS_DESERTERS = -2, + BG_GROUP_JOIN_STATUS_NOT_ELIGIBLE = -1, + BG_GROUP_JOIN_STATUS_SUCCESS = 0, +}; + +// indexes of BattlemasterList.dbc +enum BattleGroundTypeId +{ + BATTLEGROUND_TYPE_NONE = 0, + BATTLEGROUND_AV = 1, + BATTLEGROUND_WS = 2, + BATTLEGROUND_AB = 3, + BATTLEGROUND_NA = 4, + BATTLEGROUND_BE = 5, + BATTLEGROUND_AA = 6, // all arenas + BATTLEGROUND_EY = 7, + BATTLEGROUND_RL = 8 +}; +#define MAX_BATTLEGROUND_TYPE_ID 9 + +enum ArenaType +{ + ARENA_TYPE_NONE = 0, // used for mark non-arenas or problematic cases + ARENA_TYPE_2v2 = 2, + ARENA_TYPE_3v3 = 3, + ARENA_TYPE_5v5 = 5 +}; + +inline bool IsArenaTypeValid(ArenaType type) { return type == ARENA_TYPE_2v2 || type == ARENA_TYPE_3v3 || type == ARENA_TYPE_5v5; } + +#endif \ No newline at end of file diff --git a/src/game/BattleGround/BattleGroundHandler.cpp b/src/game/BattleGround/BattleGroundHandler.cpp index 3df8066248c..ce49b0c5e76 100644 --- a/src/game/BattleGround/BattleGroundHandler.cpp +++ b/src/game/BattleGround/BattleGroundHandler.cpp @@ -77,9 +77,16 @@ Send battleground list */ void WorldSession::SendBattleGroundList(ObjectGuid guid, BattleGroundTypeId bgTypeId) const { - WorldPacket data; - sBattleGroundMgr.BuildBattleGroundListPacket(data, guid, _player, bgTypeId); - SendPacket(data); + sWorld.GetBGQueue().GetMessager().AddMessage([playerGuid = _player->GetObjectGuid(), masterGuid = guid, playerLevel = _player->GetLevel(), bgTypeId](BattleGroundQueue* queue) + { + WorldPacket data; + queue->BuildBattleGroundListPacket(data, masterGuid, playerLevel, BattleGroundTypeId(bgTypeId)); + sWorld.GetMessager().AddMessage([playerGuid, data](World* world) + { + if (Player* player = sObjectMgr.GetPlayer(playerGuid)) + player->GetSession()->SendPacket(data); + }); + }); } // Sent by client when player wants to join a battleground @@ -90,7 +97,8 @@ void WorldSession::HandleBattlemasterJoinOpcode(WorldPacket& recv_data) uint32 instanceId; uint8 joinAsGroup; bool isPremade = false; - Group* grp = nullptr; + Group* group = nullptr; + uint32 mapId = 0; recv_data >> guid; // battlemaster guid recv_data >> receivedBgTypeId; // battleground type id (DBC id) @@ -135,7 +143,9 @@ void WorldSession::HandleBattlemasterJoinOpcode(WorldPacket& recv_data) return; } - BattleGroundBracketId bgBracketId = _player->GetBattleGroundBracketIdFromLevel(bgTypeId); + mapId = bg->GetMapId(); + + BattleGroundBracketId bgBracketId = sBattleGroundMgr.GetBattleGroundBracketIdFromLevel(bgTypeId, _player->GetLevel()); // check queue conditions if (!joinAsGroup) @@ -163,13 +173,13 @@ void WorldSession::HandleBattlemasterJoinOpcode(WorldPacket& recv_data) } else { - grp = _player->GetGroup(); + group = _player->GetGroup(); // no group found, error - if (!grp) + if (!group) return; - uint32 err = grp->CanJoinBattleGroundQueue(bgTypeId, bgQueueTypeId, 0, bg->GetMaxPlayersPerTeam(), false, 0); + uint32 err = group->CanJoinBattleGroundQueue(bgTypeId, bgQueueTypeId, 0, bg->GetMaxPlayersPerTeam(), false, 0); isPremade = sWorld.getConfig(CONFIG_UINT32_BATTLEGROUND_PREMADE_GROUP_WAIT_FOR_MATCH) && - (grp->GetMembersCount() >= bg->GetMinPlayersPerTeam()); + (group->GetMembersCount() >= bg->GetMinPlayersPerTeam()); if (err != BG_JOIN_ERR_OK) { SendBattleGroundOrArenaJoinError(err); @@ -178,51 +188,77 @@ void WorldSession::HandleBattlemasterJoinOpcode(WorldPacket& recv_data) } // if we're here, then the conditions to join a bg are met. We can proceed in joining. - // _player->GetGroup() was already checked, grp is already initialized - BattleGroundQueue& bgQueue = sBattleGroundMgr.m_battleGroundQueues[bgQueueTypeId]; + // _player->GetGroup() was already checked, group is already initialized + AddGroupToQueueInfo info; + info.team = _player->GetTeam(); + info.clientInstanceId = instanceId; + info.mapId = mapId; if (joinAsGroup) { + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + if (Player* member = itr->getSource()) + info.members.push_back(member->GetObjectGuid()); DEBUG_LOG("Battleground: the following players are joining as group:"); - GroupQueueInfo* queueInfo = bgQueue.AddGroup(_player, grp, bgTypeId, bgBracketId, ARENA_TYPE_NONE, false, isPremade, instanceId, 0); - uint32 avgTime = bgQueue.GetAverageQueueWaitTime(queueInfo, _player->GetBattleGroundBracketIdFromLevel(bgTypeId)); - for (GroupReference* itr = grp->GetFirstMember(); itr != nullptr; itr = itr->next()) + sWorld.GetBGQueue().GetMessager().AddMessage([bgQueueTypeId, leaderGuid = group->GetLeaderGuid(), info, bgTypeId, bgBracketId, isPremade, instanceId, mapId](BattleGroundQueue* queue) { - Player* member = itr->getSource(); - if (!member) - continue; // this should never happen + BattleGroundQueueItem& queueItem = queue->GetBattleGroundQueue(bgQueueTypeId); + GroupQueueInfo* groupInfo = queueItem.AddGroup(leaderGuid, info, bgTypeId, bgBracketId, ARENA_TYPE_NONE, false, isPremade, instanceId, 0); + uint32 avgTime = queueItem.GetAverageQueueWaitTime(groupInfo, bgBracketId); - uint32 queueSlot = member->AddBattleGroundQueueId(bgQueueTypeId); // add to queue - - // store entry point coords (same as leader entry point) - member->SetBattleGroundEntryPoint(_player); + sWorld.GetMessager().AddMessage([leaderGuid, members = info.members, bgQueueTypeId, bgTypeId, bgClientInstanceId = instanceId, avgTime, arenaType = groupInfo->arenaType, isRated = groupInfo->isRated, mapId](World* world) + { + Player* leader = sObjectMgr.GetPlayer(leaderGuid); + for (ObjectGuid guid : members) + { + Player* member = sObjectMgr.GetPlayer(guid); + if (!member) + continue; + + uint32 queueSlot = member->AddBattleGroundQueueId(bgQueueTypeId); // add to queue + + // store entry point coords (same as leader entry point) + member->SetBattleGroundEntryPoint(leader); + + // send status packet (in queue) + WorldPacket data; + sBattleGroundMgr.BuildBattleGroundStatusPacket(data, true, bgTypeId, bgClientInstanceId, isRated, mapId, queueSlot, STATUS_WAIT_QUEUE, avgTime, 0, arenaType, TEAM_NONE); + member->GetSession()->SendPacket(data); + sBattleGroundMgr.BuildGroupJoinedBattlegroundPacket(data, bgTypeId, BG_GROUP_JOIN_STATUS_SUCCESS); + member->GetSession()->SendPacket(data); + DEBUG_LOG("Battleground: player joined queue for bg queue type %u bg type %u: GUID %u, NAME %s", bgQueueTypeId, bgTypeId, member->GetGUIDLow(), member->GetName()); + } - // send status packet (in queue) - WorldPacket data; - sBattleGroundMgr.BuildBattleGroundStatusPacket(data, bg, queueSlot, STATUS_WAIT_QUEUE, avgTime, 0, queueInfo->arenaType, TEAM_NONE); - member->GetSession()->SendPacket(data); - sBattleGroundMgr.BuildGroupJoinedBattlegroundPacket(data, bgTypeId, BG_GROUP_JOIN_STATUS_SUCCESS); - member->GetSession()->SendPacket(data); - DEBUG_LOG("Battleground: player joined queue for bg queue type %u bg type %u: GUID %u, NAME %s", bgQueueTypeId, bgTypeId, member->GetGUIDLow(), member->GetName()); - } - DEBUG_LOG("Battleground: group end"); + DEBUG_LOG("Battleground: group end"); + }); + queue->ScheduleQueueUpdate(0, ARENA_TYPE_NONE, bgQueueTypeId, bgTypeId, bgBracketId); + }); } else { - GroupQueueInfo* queueInfo = bgQueue.AddGroup(_player, nullptr, bgTypeId, bgBracketId, ARENA_TYPE_NONE, false, isPremade, instanceId, 0); - uint32 avgTime = bgQueue.GetAverageQueueWaitTime(queueInfo, _player->GetBattleGroundBracketIdFromLevel(bgTypeId)); - // already checked if queueSlot is valid, now just get it - uint32 queueSlot = _player->AddBattleGroundQueueId(bgQueueTypeId); - // store entry point coords - _player->SetBattleGroundEntryPoint(); - - WorldPacket data; - // send status packet (in queue) - sBattleGroundMgr.BuildBattleGroundStatusPacket(data, bg, queueSlot, STATUS_WAIT_QUEUE, avgTime, 0, queueInfo->arenaType, TEAM_NONE); - SendPacket(data); - DEBUG_LOG("Battleground: player joined queue for bg queue type %u bg type %u: GUID %u, NAME %s", bgQueueTypeId, bgTypeId, _player->GetGUIDLow(), _player->GetName()); + sWorld.GetBGQueue().GetMessager().AddMessage([bgQueueTypeId, playerGuid = _player->GetObjectGuid(), info, bgTypeId, bgBracketId, isPremade, instanceId, mapId](BattleGroundQueue* queue) + { + BattleGroundQueueItem& queueItem = queue->GetBattleGroundQueue(bgQueueTypeId); + GroupQueueInfo* groupInfo = queueItem.AddGroup(playerGuid, info, bgTypeId, bgBracketId, ARENA_TYPE_NONE, false, isPremade, instanceId, 0); + uint32 avgTime = queueItem.GetAverageQueueWaitTime(groupInfo, bgBracketId); + sWorld.GetMessager().AddMessage([playerGuid, bgQueueTypeId, bgTypeId, bgClientInstanceId = instanceId, avgTime, arenaType = groupInfo->arenaType, isRated = groupInfo->isRated, mapId](World* world) + { + if (Player* player = sObjectMgr.GetPlayer(playerGuid)) + { + // already checked if queueSlot is valid, now just get it + uint32 queueSlot = player->AddBattleGroundQueueId(bgQueueTypeId); + // store entry point coords + player->SetBattleGroundEntryPoint(); + + WorldPacket data; + // send status packet (in queue) + sBattleGroundMgr.BuildBattleGroundStatusPacket(data, true, bgTypeId, bgClientInstanceId, isRated, mapId, queueSlot, STATUS_WAIT_QUEUE, avgTime, 0, arenaType, TEAM_NONE); + player->GetSession()->SendPacket(data); + DEBUG_LOG("Battleground: player joined queue for bg queue type %u bg type %u: GUID %u, NAME %s", bgQueueTypeId, bgTypeId, player->GetGUIDLow(), player->GetName()); + } + }); + queue->ScheduleQueueUpdate(0, ARENA_TYPE_NONE, bgQueueTypeId, bgTypeId, bgBracketId); + }); } - - sBattleGroundMgr.ScheduleQueueUpdate(0, ARENA_TYPE_NONE, bgQueueTypeId, bgTypeId, _player->GetBattleGroundBracketIdFromLevel(bgTypeId)); } // Sent by client while inside battleground; depends on the battleground type @@ -347,10 +383,17 @@ void WorldSession::HandleBattlefieldListOpcode(WorldPacket& recv_data) sLog.outError("Battleground: invalid bgtype received."); return; } - - WorldPacket data; - sBattleGroundMgr.BuildBattleGroundListPacket(data, _player->GetObjectGuid(), _player, BattleGroundTypeId(bgTypeId)); - SendPacket(data); + + sWorld.GetBGQueue().GetMessager().AddMessage([playerGuid = _player->GetObjectGuid(), masterGuid = _player->GetObjectGuid(), playerLevel = _player->GetLevel(), bgTypeId](BattleGroundQueue* queue) + { + WorldPacket data; + queue->BuildBattleGroundListPacket(data, masterGuid, playerLevel, BattleGroundTypeId(bgTypeId)); + sWorld.GetMessager().AddMessage([playerGuid, data](World* world) + { + if (Player* player = sObjectMgr.GetPlayer(playerGuid)) + player->GetSession()->SendPacket(data); + }); + }); } // Sent by client when requesting teleport to the battleground location @@ -387,142 +430,151 @@ void WorldSession::HandleBattlefieldPortOpcode(WorldPacket& recv_data) // get GroupQueueInfo from BattleGroundQueue BattleGroundTypeId bgTypeId = BattleGroundTypeId(receivedBgTypeId); BattleGroundQueueTypeId bgQueueTypeId = BattleGroundMgr::BgQueueTypeId(bgTypeId, ArenaType(type)); - BattleGroundQueue& bgQueue = sBattleGroundMgr.m_battleGroundQueues[bgQueueTypeId]; - - // we must use temporary variable, because GroupQueueInfo pointer can be deleted in BattleGroundQueue::RemovePlayer() function - GroupQueueInfo queueInfo; - if (!bgQueue.GetPlayerGroupInfoData(_player->GetObjectGuid(), &queueInfo)) - { - sLog.outError("BattlegroundHandler: itrplayerstatus not found."); - return; - } - - // if action == 1, then instanceId is required - if (!queueInfo.isInvitedToBgInstanceGuid && action == 1) - { - sLog.outError("BattlegroundHandler: instance not found."); - return; - } - - BattleGround* bg = sBattleGroundMgr.GetBattleGround(queueInfo.isInvitedToBgInstanceGuid, bgTypeId); - - // bg template might and must be used in case of leaving queue, when instance is not created yet - if (!bg && action == 0) - bg = sBattleGroundMgr.GetBattleGroundTemplate(bgTypeId); - if (!bg) - { - sLog.outError("BattlegroundHandler: bg_template not found for type id %u.", bgTypeId); - return; - } + bool canJoinToBg = _player->CanJoinToBattleground(); + uint32 queueSlot = _player->GetBattleGroundQueueIndex(bgQueueTypeId); - // some checks if player isn't cheating - it is not exactly cheating, but we cannot allow it - if (action == 1 && queueInfo.arenaType == ARENA_TYPE_NONE) + sWorld.GetBGQueue().GetMessager().AddMessage([bgQueueTypeId, playerGuid = _player->GetObjectGuid(), actionTemp = action, canJoinToBg, bgTypeId, playerLevel = _player->GetLevel(), queueSlot](BattleGroundQueue* queue) { - // if player is trying to enter battleground (not arena!) and he has deserter debuff, we must just remove him from queue - if (!_player->CanJoinToBattleground()) + uint8 action = actionTemp; + BattleGroundQueueItem& queueItem = queue->GetBattleGroundQueue(bgQueueTypeId); + GroupQueueInfo queueInfo; + if (!queueItem.GetPlayerGroupInfoData(playerGuid, &queueInfo)) { - // send bg command result to show nice message - WorldPacket data2; - sBattleGroundMgr.BuildGroupJoinedBattlegroundPacket(data2, bgTypeId, BG_GROUP_JOIN_STATUS_DESERTERS); - _player->GetSession()->SendPacket(data2); - action = 0; - - DEBUG_LOG("Battleground: player %s (%u) has a deserter debuff, do not port him to battleground!", _player->GetName(), _player->GetGUIDLow()); + sLog.outError("BattlegroundHandler: itrplayerstatus not found."); + return; } - // if player don't match battleground max level, then do not allow him to enter! (this might happen when player leveled up during his waiting in queue - if (_player->GetLevel() > bg->GetMaxLevel()) + + if (!queueInfo.isInvitedToBgInstanceGuid && action == 1) { - sLog.outError("Battleground: Player %s (%u) has level (%u) higher than maxlevel (%u) of battleground (%u)! Do not port him to battleground!", - _player->GetName(), _player->GetGUIDLow(), _player->GetLevel(), bg->GetMaxLevel(), bg->GetTypeId()); - action = 0; + sLog.outError("BattlegroundHandler: instance not found."); + return; } - } - uint32 queueSlot = _player->GetBattleGroundQueueIndex(bgQueueTypeId); - WorldPacket data; + // some checks if player isn't cheating - it is not exactly cheating, but we cannot allow it + if (action == 1 && queueInfo.arenaType == ARENA_TYPE_NONE) + { + // if player is trying to enter battleground (not arena!) and he has deserter debuff, we must just remove him from queue + if (!canJoinToBg) + { + // send bg command result to show nice message + WorldPacket data2; + sBattleGroundMgr.BuildGroupJoinedBattlegroundPacket(data2, bgTypeId, BG_GROUP_JOIN_STATUS_DESERTERS); + sWorld.GetMessager().AddMessage([playerGuid, data2](World* world) + { + if (Player* player = sObjectMgr.GetPlayer(playerGuid)) + { + player->GetSession()->SendPacket(data2); + } + }); - switch (action) - { - case 1: // port to battleground - if (!_player->IsInvitedForBattleGroundQueueType(bgQueueTypeId)) - return; // cheating? + action = 0; + } + } - // resurrect the player - if (!_player->IsAlive()) + switch (action) + { + case 1: // port to battleground { - _player->ResurrectPlayer(1.0f); - _player->SpawnCorpseBones(); - } + BattleGroundInQueueInfo* bgInQueue = queue->GetFreeSlotInstance(bgTypeId, queueInfo.isInvitedToBgInstanceGuid); + MANGOS_ASSERT(bgInQueue); // at this point must always exist - _player->TaxiFlightInterrupt(); + // remove battleground queue status from BGmgr + queueItem.RemovePlayer(*queue, playerGuid, false); - sBattleGroundMgr.BuildBattleGroundStatusPacket(data, bg, queueSlot, STATUS_IN_PROGRESS, 0, bg->GetStartTime(), bg->GetArenaType(), _player->GetBGTeam()); - _player->GetSession()->SendPacket(data); + sWorld.GetMessager().AddMessage([playerGuid, invitedTo = queueInfo.isInvitedToBgInstanceGuid, bgTypeId, bgQueueTypeId, groupTeam = queueInfo.groupTeam, queueSlot, bgClientInstanceId = bgInQueue->GetClientInstanceId(), isRated = bgInQueue->IsRated(), mapId = bgInQueue->GetMapId(), arenaType = bgInQueue->GetArenaType()](World* world) + { + Player* player = sObjectMgr.GetPlayer(playerGuid); + if (!player) + return; - // remove battleground queue status from BGmgr - bgQueue.RemovePlayer(_player->GetObjectGuid(), false); + // resurrect the player + if (!player->IsAlive()) + { + player->ResurrectPlayer(1.0f); + player->SpawnCorpseBones(); + } - // this is still needed here if battleground "jumping" shouldn't add deserter debuff - // also this is required to prevent stuck at old battleground after SetBattleGroundId set to new - if (BattleGround* currentBg = _player->GetBattleGround()) - currentBg->RemovePlayerAtLeave(_player->GetObjectGuid(), false, true); + player->TaxiFlightInterrupt(); - // set the destination instance id - _player->SetBattleGroundId(bg->GetInstanceId(), bgTypeId); - // set the destination team - _player->SetBGTeam(queueInfo.groupTeam); - // bg->HandleBeforeTeleportToBattleGround(_player); - sBattleGroundMgr.SendToBattleGround(_player, queueInfo.isInvitedToBgInstanceGuid, bgTypeId); - // add only in HandleMoveWorldPortAck() - // bg->AddPlayer(_player,team); + uint32 startTime = 0; + if (BattleGround* bg = sBattleGroundMgr.GetBattleGround(invitedTo, bgTypeId)) + startTime = bg->GetStartTime(); - DEBUG_LOG("Battleground: player %s (%u) joined battle for bg %u, bgtype %u, queue type %u.", _player->GetName(), _player->GetGUIDLow(), bg->GetInstanceId(), bg->GetTypeId(), bgQueueTypeId); + WorldPacket data; + sBattleGroundMgr.BuildBattleGroundStatusPacket(data, true, bgTypeId, bgClientInstanceId, isRated, mapId, queueSlot, STATUS_IN_PROGRESS, 0, startTime, arenaType, player->GetBGTeam()); + player->GetSession()->SendPacket(data); - break; - case 0: // leave queue - // if player leaves rated arena match before match start, it is counted as he played but he lost - if (queueInfo.isRated && queueInfo.isInvitedToBgInstanceGuid) - { - ArenaTeam* at = sObjectMgr.GetArenaTeamById(queueInfo.arenaTeamId); - if (at) - { - DEBUG_LOG("UPDATING memberLost's personal arena rating for %s by opponents rating: %u, because he has left queue!", _player->GetGuidStr().c_str(), queueInfo.opponentsTeamRating); - at->MemberLost(_player, queueInfo.opponentsTeamRating); - at->SaveToDB(); - } - } + // this is still needed here if battleground "jumping" shouldn't add deserter debuff + // also this is required to prevent stuck at old battleground after SetBattleGroundId set to new + if (BattleGround* currentBg = player->GetBattleGround()) + currentBg->RemovePlayerAtLeave(player->GetObjectGuid(), false, true); - _player->RemoveBattleGroundQueueId(bgQueueTypeId); // must be called this way, because if you move this call to queue->removeplayer, it causes bugs - sBattleGroundMgr.BuildBattleGroundStatusPacket(data, bg, queueSlot, STATUS_NONE, 0, 0, ARENA_TYPE_NONE, TEAM_NONE); - bgQueue.RemovePlayer(_player->GetObjectGuid(), true); + // set the destination instance id + player->SetBattleGroundId(invitedTo, bgTypeId); + // set the destination team + player->SetBGTeam(groupTeam); - // player left queue, we should update it - do not update Arena Queue - if (queueInfo.arenaType == ARENA_TYPE_NONE) - sBattleGroundMgr.ScheduleQueueUpdate(queueInfo.arenaTeamRating, queueInfo.arenaType, bgQueueTypeId, bgTypeId, _player->GetBattleGroundBracketIdFromLevel(bgTypeId)); + sBattleGroundMgr.SendToBattleGround(player, invitedTo, bgTypeId); - SendPacket(data); + DEBUG_LOG("Battleground: player %s (%u) joined battle for bg %u, bgtype %u, queue type %u.", player->GetName(), player->GetGUIDLow(), invitedTo, bgTypeId, bgQueueTypeId); + }); - DEBUG_LOG("Battleground: player %s (%u) left queue for bgtype %u, queue type %u.", _player->GetName(), _player->GetGUIDLow(), bg->GetTypeId(), bgQueueTypeId); + break; + } + case 0: // leave queue + // if player leaves rated arena match before match start, it is counted as he played but he lost + if (queueInfo.isRated && queueInfo.isInvitedToBgInstanceGuid) + { + sWorld.GetMessager().AddMessage([arenaTeamId = queueInfo.arenaTeamId, playerGuid, opponentRating = queueInfo.opponentsTeamRating](World* world) + { + ArenaTeam* at = sObjectMgr.GetArenaTeamById(arenaTeamId); + if (at) + { + Player* player = sObjectMgr.GetPlayer(playerGuid); + if (player) + at->MemberLost(player, opponentRating); + else + at->OfflineMemberLost(playerGuid, opponentRating); + at->SaveToDB(); + } + }); + } - break; - default: - sLog.outError("Battleground port: unknown action %u", action); - break; - } + queueItem.RemovePlayer(*queue, playerGuid, true); + sWorld.GetMessager().AddMessage([playerGuid, bgQueueTypeId, queueSlot, bgTypeId, bgClientInstanceId = queueInfo.clientInstanceId, isRated = queueInfo.isRated, mapId = queueInfo.mapId](World* world) + { + Player* player = sObjectMgr.GetPlayer(playerGuid); + if (!player) + return; + player->RemoveBattleGroundQueueId(bgQueueTypeId); + WorldPacket data; + sBattleGroundMgr.BuildBattleGroundStatusPacket(data, true, bgTypeId, bgClientInstanceId, isRated, mapId, queueSlot, STATUS_NONE, 0, 0, ARENA_TYPE_NONE, TEAM_NONE); + player->GetSession()->SendPacket(data); + }); + + if (queueInfo.arenaType == ARENA_TYPE_NONE) + queue->ScheduleQueueUpdate(queueInfo.arenaTeamRating, queueInfo.arenaType, bgQueueTypeId, bgTypeId, queueInfo.bgBracketId); + break; + default: + sLog.outError("Battleground port: unknown action %u", action); + break; + } + }); } // Sent by client when leaving the battleground void WorldSession::HandleLeaveBattlefieldOpcode(WorldPacket& recv_data) { DEBUG_LOG("WORLD: Received opcode CMSG_LEAVE_BATTLEFIELD"); + uint64 guid; + + // Essentially a BG guid - uint64 + recv_data >> guid; - recv_data.read_skip(); // unk1 - recv_data.read_skip(); // unk2 - recv_data.read_skip(); // BattleGroundTypeId - recv_data.read_skip(); // unk3 + uint32 bgTypeId = (guid & 0x0000FFFFFFFF0000) >> 16; // TODO: Test - // if(bgTypeId >= MAX_BATTLEGROUND_TYPES) // cheating? but not important in this case - // return; + if (bgTypeId >= MAX_BATTLEGROUND_TYPE_ID) // cheating - but not important in this case + return; // not allow leave battleground in combat if (_player->IsInCombat()) @@ -542,6 +594,7 @@ void WorldSession::HandleBattlefieldStatusOpcode(WorldPacket& /*recv_data*/) WorldPacket data; // we must update all queues here BattleGround* bg; + std::vector> idsToCheck; for (uint8 i = 0; i < PLAYER_MAX_BATTLEGROUND_QUEUES; ++i) { BattleGroundQueueTypeId bgQueueTypeId = _player->GetBattleGroundQueueTypeId(i); @@ -561,41 +614,48 @@ void WorldSession::HandleBattlefieldStatusOpcode(WorldPacket& /*recv_data*/) { // this line is checked, i only don't know if GetStartTime is changing itself after bg end! // send status in BattleGround - sBattleGroundMgr.BuildBattleGroundStatusPacket(data, bg, i, STATUS_IN_PROGRESS, bg->GetEndTime(), bg->GetStartTime(), arenaType, _player->GetBGTeam()); + sBattleGroundMgr.BuildBattleGroundStatusPacket(data, bg, bg->GetTypeId(), bg->GetClientInstanceId(), bg->IsRated(), bg->GetMapId(), i, STATUS_IN_PROGRESS, bg->GetEndTime(), bg->GetStartTime(), arenaType, _player->GetBGTeam()); SendPacket(data); continue; } } + + idsToCheck.push_back({i, bgQueueTypeId}); // we are sending update to player about queue - he can be invited there! - // get GroupQueueInfo for queue status - BattleGroundQueue& bgQueue = sBattleGroundMgr.m_battleGroundQueues[bgQueueTypeId]; - GroupQueueInfo queueInfo; - if (!bgQueue.GetPlayerGroupInfoData(_player->GetObjectGuid(), &queueInfo)) - continue; + } - if (queueInfo.isInvitedToBgInstanceGuid) + sWorld.GetBGQueue().GetMessager().AddMessage([idsToCheck, playerGuid = _player->GetObjectGuid(), playerLevel = _player->GetLevel()](BattleGroundQueue* queue) + { + for (auto [queueSlot, bgQueueTypeId] : idsToCheck) { - bg = sBattleGroundMgr.GetBattleGround(queueInfo.isInvitedToBgInstanceGuid, bgTypeId); - if (!bg) + BattleGroundQueueItem& queueItem = queue->GetBattleGroundQueue(bgQueueTypeId); + GroupQueueInfo queueInfo; + if (!queueItem.GetPlayerGroupInfoData(playerGuid, &queueInfo)) continue; - uint32 remainingTime = WorldTimer::getMSTimeDiff(WorldTimer::getMSTime(), queueInfo.removeInviteTime); - // send status invited to BattleGround - sBattleGroundMgr.BuildBattleGroundStatusPacket(data, bg, i, STATUS_WAIT_JOIN, remainingTime, 0, arenaType, TEAM_NONE); - SendPacket(data); + WorldPacket data; + if (queueInfo.isInvitedToBgInstanceGuid) + { + uint32 remainingTime = WorldTimer::getMSTimeDiff(WorldTimer::getMSTime(), queueInfo.removeInviteTime); + // send status invited to BattleGround + sBattleGroundMgr.BuildBattleGroundStatusPacket(data, true, queueInfo.bgTypeId, queueInfo.clientInstanceId, queueInfo.isRated, queueInfo.mapId, queueSlot, STATUS_WAIT_JOIN, remainingTime, 0, queueInfo.arenaType, TEAM_NONE); + } + else + { + uint32 avgTime = queueItem.GetAverageQueueWaitTime(&queueInfo, sBattleGroundMgr.GetBattleGroundBracketIdFromLevel(queueInfo.bgTypeId, playerLevel)); + // send status in BattleGround Queue + sBattleGroundMgr.BuildBattleGroundStatusPacket(data, true, queueInfo.bgTypeId, queueInfo.clientInstanceId, queueInfo.isRated, queueInfo.mapId, queueSlot, STATUS_WAIT_QUEUE, avgTime, WorldTimer::getMSTimeDiff(queueInfo.joinTime, WorldTimer::getMSTime()), queueInfo.arenaType, TEAM_NONE); + } + sWorld.GetMessager().AddMessage([playerGuid, data](World* world) + { + if (Player* player = sObjectMgr.GetPlayer(playerGuid)) + { + player->GetSession()->SendPacket(data); + } + }); } - else - { - bg = sBattleGroundMgr.GetBattleGroundTemplate(bgTypeId); - if (!bg) - continue; + }); - uint32 avgTime = bgQueue.GetAverageQueueWaitTime(&queueInfo, _player->GetBattleGroundBracketIdFromLevel(bgTypeId)); - // send status in BattleGround Queue - sBattleGroundMgr.BuildBattleGroundStatusPacket(data, bg, i, STATUS_WAIT_QUEUE, avgTime, WorldTimer::getMSTimeDiff(queueInfo.joinTime, WorldTimer::getMSTime()), arenaType, TEAM_NONE); - SendPacket(data); - } - } } // Sent by client when requesting the spirit healer @@ -695,7 +755,7 @@ void WorldSession::HandleBattlemasterJoinArena(WorldPacket& recv_data) BattleGroundTypeId bgTypeId = bg->GetTypeId(); BattleGroundQueueTypeId bgQueueTypeId = BattleGroundMgr::BgQueueTypeId(bgTypeId, arenatype); - BattleGroundBracketId bgBracketId = _player->GetBattleGroundBracketIdFromLevel(bgTypeId); + BattleGroundBracketId bgBracketId = sBattleGroundMgr.GetBattleGroundBracketIdFromLevel(bgTypeId, _player->GetLevel()); Group* group = nullptr; @@ -788,56 +848,79 @@ void WorldSession::HandleBattlemasterJoinArena(WorldPacket& recv_data) arenaRating = avg_pers_rating; } - BattleGroundQueue& bgQueue = sBattleGroundMgr.m_battleGroundQueues[bgQueueTypeId]; + AddGroupToQueueInfo info; + info.team = _player->GetTeam(); + info.clientInstanceId = 0; + info.mapId = bg->GetMapId(); if (asGroup) { - DEBUG_LOG("Battleground: arena join as group start"); - if (isRated) - DEBUG_LOG("Battleground: arena team id %u, leader %s queued with rating %u for type %u", _player->GetArenaTeamId(arenaslot), _player->GetName(), arenaRating, arenatype); - - // set arena rated type to show correct minimap arena icon - bg->SetRated(isRated != 0); - - GroupQueueInfo* ginfo = bgQueue.AddGroup(_player, group, bgTypeId, bgBracketId, arenatype, isRated != 0, false, 0, arenaRating, ateamId); - uint32 avgTime = bgQueue.GetAverageQueueWaitTime(ginfo, _player->GetBattleGroundBracketIdFromLevel(bgTypeId)); for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) - { - Player* member = itr->getSource(); - if (!member) - continue; + if (Player* member = itr->getSource()) + info.members.push_back(member->GetObjectGuid()); + } - // add to queue - uint32 queueSlot = member->AddBattleGroundQueueId(bgQueueTypeId); + sWorld.GetBGQueue().GetMessager().AddMessage([playerGuid = _player->GetObjectGuid(), bgQueueTypeId, bgTypeId, bgBracketId, ateamId, asGroup, isRated, arenaTeamId = _player->GetArenaTeamId(arenaslot), playerName = _player->GetName(), info, arenaRating, arenatype](BattleGroundQueue* queue) + { + BattleGroundQueueItem& bgQueue = queue->GetBattleGroundQueue(bgQueueTypeId); + if (asGroup) + { + DEBUG_LOG("Battleground: arena join as group start"); + if (isRated) + DEBUG_LOG("Battleground: arena team id %u, leader %s queued with rating %u for type %u", arenaTeamId, playerName, arenaRating, arenatype); - // store entry point coords (same as leader entry point) - member->SetBattleGroundEntryPoint(_player); + GroupQueueInfo* groupInfo = bgQueue.AddGroup(playerGuid, info, bgTypeId, bgBracketId, arenatype, isRated != 0, false, 0, arenaRating, ateamId); + uint32 avgTime = bgQueue.GetAverageQueueWaitTime(groupInfo, groupInfo->bgBracketId); + sWorld.GetMessager().AddMessage([playerGuid, members = info.members, bgQueueTypeId, bgTypeId, bgClientInstanceId = groupInfo->clientInstanceId, mapId = groupInfo->mapId, avgTime, arenaType = groupInfo->arenaType, isRated = groupInfo->isRated](World* world) + { + Player* leader = sObjectMgr.GetPlayer(playerGuid); + for (ObjectGuid guid : members) + { + Player* member = sObjectMgr.GetPlayer(guid); + if (!member) + continue; + + // add to queue + uint32 queueSlot = member->AddBattleGroundQueueId(bgQueueTypeId); + + // store entry point coords (same as leader entry point) + member->SetBattleGroundEntryPoint(leader); + + WorldPacket data; + // send status packet (in queue) + sBattleGroundMgr.BuildBattleGroundStatusPacket(data, true, bgTypeId, bgClientInstanceId, isRated, mapId, queueSlot, STATUS_WAIT_QUEUE, avgTime, 0, arenaType, TEAM_NONE); + member->GetSession()->SendPacket(data); + sBattleGroundMgr.BuildGroupJoinedBattlegroundPacket(data, bgTypeId, BG_GROUP_JOIN_STATUS_SUCCESS); + member->GetSession()->SendPacket(data); + DEBUG_LOG("Battleground: player joined queue for arena as group bg queue type %u bg type %u: GUID %u, NAME %s", bgQueueTypeId, bgTypeId, member->GetGUIDLow(), member->GetName()); + } - WorldPacket data; - // send status packet (in queue) - sBattleGroundMgr.BuildBattleGroundStatusPacket(data, bg, queueSlot, STATUS_WAIT_QUEUE, avgTime, 0, arenatype, TEAM_NONE); - member->GetSession()->SendPacket(data); - sBattleGroundMgr.BuildGroupJoinedBattlegroundPacket(data, bgTypeId, BG_GROUP_JOIN_STATUS_SUCCESS); - member->GetSession()->SendPacket(data); - DEBUG_LOG("Battleground: player joined queue for arena as group bg queue type %u bg type %u: GUID %u, NAME %s", bgQueueTypeId, bgTypeId, member->GetGUIDLow(), member->GetName()); + DEBUG_LOG("Battleground: arena join as group end"); + }); } - DEBUG_LOG("Battleground: arena join as group end"); - } - else - { - GroupQueueInfo* ginfo = bgQueue.AddGroup(_player, nullptr, bgTypeId, bgBracketId, arenatype, isRated != 0, false, 0, arenaRating, ateamId); - uint32 avgTime = bgQueue.GetAverageQueueWaitTime(ginfo, _player->GetBattleGroundBracketIdFromLevel(bgTypeId)); - uint32 queueSlot = _player->AddBattleGroundQueueId(bgQueueTypeId); + else + { + GroupQueueInfo* groupInfo = bgQueue.AddGroup(playerGuid, info, bgTypeId, bgBracketId, arenatype, isRated != 0, false, 0, arenaRating, ateamId); + uint32 avgTime = bgQueue.GetAverageQueueWaitTime(groupInfo, bgBracketId); - // store entry point coords - _player->SetBattleGroundEntryPoint(); + sWorld.GetMessager().AddMessage([playerGuid, bgQueueTypeId, bgTypeId, bgClientInstanceId = groupInfo->clientInstanceId, avgTime, arenaType = groupInfo->arenaType, isRated = groupInfo->isRated, mapId = groupInfo->mapId](World* world) + { + if (Player* player = sObjectMgr.GetPlayer(playerGuid)) + { + uint32 queueSlot = player->AddBattleGroundQueueId(bgQueueTypeId); - WorldPacket data; - // send status packet (in queue) - sBattleGroundMgr.BuildBattleGroundStatusPacket(data, bg, queueSlot, STATUS_WAIT_QUEUE, avgTime, 0, arenatype, TEAM_NONE); - SendPacket(data); - DEBUG_LOG("Battleground: player joined queue for arena, skirmish, bg queue type %u bg type %u: GUID %u, NAME %s", bgQueueTypeId, bgTypeId, _player->GetGUIDLow(), _player->GetName()); - } - sBattleGroundMgr.ScheduleQueueUpdate(arenaRating, arenatype, bgQueueTypeId, bgTypeId, _player->GetBattleGroundBracketIdFromLevel(bgTypeId)); + // store entry point coords + player->SetBattleGroundEntryPoint(); + + WorldPacket data; + // send status packet (in queue) + sBattleGroundMgr.BuildBattleGroundStatusPacket(data, true, bgTypeId, bgClientInstanceId, isRated, mapId, queueSlot, STATUS_WAIT_QUEUE, avgTime, 0, arenaType, TEAM_NONE); + player->GetSession()->SendPacket(data); + DEBUG_LOG("Battleground: player joined queue for arena, skirmish, bg queue type %u bg type %u: GUID %u, NAME %s", bgQueueTypeId, bgTypeId, playerGuid.GetCounter(), player->GetName()); + } + }); + } + queue->ScheduleQueueUpdate(arenaRating, arenatype, bgQueueTypeId, bgTypeId, sBattleGroundMgr.GetBattleGroundBracketIdFromLevel(bgTypeId, bgBracketId)); + }); } // Sent by client when reporting AFK diff --git a/src/game/BattleGround/BattleGroundMgr.cpp b/src/game/BattleGround/BattleGroundMgr.cpp index 97931e6255e..f1caa0113f5 100644 --- a/src/game/BattleGround/BattleGroundMgr.cpp +++ b/src/game/BattleGround/BattleGroundMgr.cpp @@ -43,1225 +43,14 @@ INSTANTIATE_SINGLETON_1(BattleGroundMgr); -/*********************************************************/ -/*** BATTLEGROUND QUEUE SYSTEM ***/ -/*********************************************************/ - -BattleGroundQueue::BattleGroundQueue() -{ - for (uint8 i = 0; i < PVP_TEAM_COUNT; ++i) - { - for (uint8 j = 0; j < MAX_BATTLEGROUND_BRACKETS; ++j) - { - m_sumOfWaitTimes[i][j] = 0; - m_waitTimeLastPlayer[i][j] = 0; - - for (uint8 k = 0; k < COUNT_OF_PLAYERS_TO_AVERAGE_WAIT_TIME; ++k) - m_waitTimes[i][j][k] = 0; - } - } -} - -BattleGroundQueue::~BattleGroundQueue() -{ - m_queuedPlayers.clear(); - for (auto& m_queuedGroup : m_queuedGroups) - { - for (uint8 j = 0; j < BG_QUEUE_GROUP_TYPES_COUNT; ++j) - { - for (GroupsQueueType::iterator itr = m_queuedGroup[j].begin(); itr != m_queuedGroup[j].end(); ++itr) - delete (*itr); - - m_queuedGroup[j].clear(); - } - } -} - -/*********************************************************/ -/*** BATTLEGROUND QUEUE SELECTION POOLS ***/ -/*********************************************************/ - -// selection pool initialization, used to clean up from prev selection -void BattleGroundQueue::SelectionPool::Init() -{ - selectedGroups.clear(); - playerCount = 0; -} - -/** - Function that removes group infr from pool selection - - returns true when we need to try to add new group to selection pool - - returns false when selection pool is ok or when we kicked smaller group than we need to kick - - sometimes it can be called on empty selection pool - - @param size -*/ -bool BattleGroundQueue::SelectionPool::KickGroup(uint32 size) -{ - // find maxgroup or LAST group with size == size and kick it - bool found = false; - GroupsQueueType::iterator groupToKick = selectedGroups.begin(); - - for (GroupsQueueType::iterator itr = groupToKick; itr != selectedGroups.end(); ++itr) - { - if (abs((int32)((*itr)->players.size() - size)) <= 1) - { - groupToKick = itr; - found = true; - } - else if (!found && (*itr)->players.size() >= (*groupToKick)->players.size()) - groupToKick = itr; - } - - // if pool is empty, do nothing - if (GetPlayerCount()) - { - // update player count - GroupQueueInfo* queueInfo = (*groupToKick); - selectedGroups.erase(groupToKick); - playerCount -= queueInfo->players.size(); - - // return false if we kicked smaller group or there are enough players in selection pool - if (queueInfo->players.size() <= size + 1) - return false; - } - return true; -} - -/** - Function that adds group to selection pool - - returns true if we can invite more players, or when we added group to selection pool - - returns false when selection pool is full - - @param group queue info - @param desired count -*/ -bool BattleGroundQueue::SelectionPool::AddGroup(GroupQueueInfo* queueInfo, uint32 desiredCount, uint32 bgInstanceId) -{ - // if group is larger than desired count - don't allow to add it to pool - if (!queueInfo->isInvitedToBgInstanceGuid && - (!queueInfo->desiredInstanceId || queueInfo->desiredInstanceId == bgInstanceId) && - (desiredCount >= playerCount + queueInfo->players.size())) - { - selectedGroups.push_back(queueInfo); - // increase selected players count - playerCount += queueInfo->players.size(); - - return true; - } - - return playerCount < desiredCount; -} - -/*********************************************************/ -/*** BATTLEGROUND QUEUES ***/ -/*********************************************************/ - -/** - Function that adds group or player (grp == nullptr) to battleground queue with the given leader and specifications - - @param leader player - @param group - @param battleground type id - @param bracket entry - @param arena type - @param isRated - @param isPremade - @param arena rating - @param arena team id -*/ -GroupQueueInfo* BattleGroundQueue::AddGroup(Player* leader, Group* group, BattleGroundTypeId bgTypeId, BattleGroundBracketId bracketId, ArenaType arenaType, bool isRated, bool isPremade, uint32 instanceId, uint32 arenaRating, uint32 arenaTeamId) -{ - // create new ginfo - GroupQueueInfo* queueInfo = new GroupQueueInfo; - queueInfo->bgTypeId = bgTypeId; - queueInfo->arenaType = arenaType; - queueInfo->arenaTeamId = arenaTeamId; - queueInfo->isRated = isRated; - queueInfo->isInvitedToBgInstanceGuid = 0; - queueInfo->joinTime = WorldTimer::getMSTime(); - queueInfo->removeInviteTime = 0; - queueInfo->groupTeam = leader->GetTeam(); - queueInfo->desiredInstanceId = instanceId; - queueInfo->arenaTeamRating = arenaRating; - queueInfo->opponentsTeamRating = 0; - - queueInfo->players.clear(); - - // compute index (if group is premade or joined a rated match) to queues - uint32 index = 0; - if (!isRated && !isPremade) - index += PVP_TEAM_COUNT; // BG_QUEUE_PREMADE_* -> BG_QUEUE_NORMAL_* - - if (queueInfo->groupTeam == HORDE) - ++index; // BG_QUEUE_*_ALLIANCE -> BG_QUEUE_*_HORDE - - DEBUG_LOG("Adding Group to BattleGroundQueue bgTypeId : %u, bracket_id : %u, index : %u", bgTypeId, bracketId, index); - - uint32 lastOnlineTime = WorldTimer::getMSTime(); - - // announce world (this don't need mutex) - if (isRated && sWorld.getConfig(CONFIG_BOOL_ARENA_QUEUE_ANNOUNCER_JOIN)) - { - sWorld.SendWorldText(LANG_ARENA_QUEUE_ANNOUNCE_WORLD_JOIN, queueInfo->arenaType, queueInfo->arenaType, queueInfo->arenaTeamRating); - } - - // add players from group to ginfo - { - // std::lock_guard guard(m_Lock); - if (group) - { - for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) - { - Player* member = itr->getSource(); - if (!member) - continue; // this should never happen - PlayerQueueInfo& playerInfo = m_queuedPlayers[member->GetObjectGuid()]; - playerInfo.lastOnlineTime = lastOnlineTime; - playerInfo.groupInfo = queueInfo; - // add the pinfo to ginfo's list - queueInfo->players[member->GetObjectGuid()] = &playerInfo; - } - } - else - { - PlayerQueueInfo& playerInfo = m_queuedPlayers[leader->GetObjectGuid()]; - playerInfo.lastOnlineTime = lastOnlineTime; - playerInfo.groupInfo = queueInfo; - queueInfo->players[leader->GetObjectGuid()] = &playerInfo; - } - - // add GroupInfo to m_QueuedGroups - m_queuedGroups[bracketId][index].push_back(queueInfo); - - // announce to world, this code needs mutex - if (arenaType == ARENA_TYPE_NONE && !isRated && !isPremade && sWorld.getConfig(CONFIG_UINT32_BATTLEGROUND_QUEUE_ANNOUNCER_JOIN)) - { - if (BattleGround* bg = sBattleGroundMgr.GetBattleGroundTemplate(queueInfo->bgTypeId)) - { - char const* bgName = bg->GetName(); - uint32 minPlayers = bg->GetMinPlayersPerTeam(); - uint32 qHorde = 0; - uint32 qAlliance = 0; - uint32 q_min_level = leader->GetMinLevelForBattleGroundBracketId(bracketId, bgTypeId); - uint32 qMaxLevel = leader->GetMaxLevelForBattleGroundBracketId(bracketId, bgTypeId); - GroupsQueueType::const_iterator itr; - for (itr = m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE].begin(); itr != m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE].end(); ++itr) - if (!(*itr)->isInvitedToBgInstanceGuid) - qAlliance += (*itr)->players.size(); - - for (itr = m_queuedGroups[bracketId][BG_QUEUE_NORMAL_HORDE].begin(); itr != m_queuedGroups[bracketId][BG_QUEUE_NORMAL_HORDE].end(); ++itr) - if (!(*itr)->isInvitedToBgInstanceGuid) - qHorde += (*itr)->players.size(); - - // Show queue status to player only (when joining queue) - if (sWorld.getConfig(CONFIG_UINT32_BATTLEGROUND_QUEUE_ANNOUNCER_JOIN) == 1) - { - ChatHandler(leader).PSendSysMessage(LANG_BG_QUEUE_ANNOUNCE_SELF, bgName, q_min_level, qMaxLevel, - qAlliance, (minPlayers > qAlliance) ? minPlayers - qAlliance : (uint32)0, qHorde, (minPlayers > qHorde) ? minPlayers - qHorde : (uint32)0); - } - // System message - else - { - sWorld.SendWorldText(LANG_BG_QUEUE_ANNOUNCE_WORLD, bgName, q_min_level, qMaxLevel, - qAlliance, (minPlayers > qAlliance) ? minPlayers - qAlliance : (uint32)0, qHorde, (minPlayers > qHorde) ? minPlayers - qHorde : (uint32)0); - } - } - } - // release mutex - } - - return queueInfo; -} - -/** - Method that updates average update wait time - - @param group queue info - @param bracket id -*/ -void BattleGroundQueue::PlayerInvitedToBgUpdateAverageWaitTime(GroupQueueInfo* queueInfo, BattleGroundBracketId bracketId) -{ - uint32 timeInQueue = WorldTimer::getMSTimeDiff(queueInfo->joinTime, WorldTimer::getMSTime()); - uint8 teamIndex = TEAM_INDEX_ALLIANCE; // default set to BG_TEAM_ALLIANCE - or non rated arenas! - - if (queueInfo->arenaType == ARENA_TYPE_NONE) - { - if (queueInfo->groupTeam == HORDE) - teamIndex = TEAM_INDEX_HORDE; - } - else - { - if (queueInfo->isRated) - teamIndex = TEAM_INDEX_HORDE; // for rated arenas use BG_TEAM_HORDE - } - - // store pointer to arrayindex of player that was added first - uint32* lastPlayerAddedPointer = &(m_waitTimeLastPlayer[teamIndex][bracketId]); - - // remove his time from sum - m_sumOfWaitTimes[teamIndex][bracketId] -= m_waitTimes[teamIndex][bracketId][(*lastPlayerAddedPointer)]; - - // set average time to new - m_waitTimes[teamIndex][bracketId][(*lastPlayerAddedPointer)] = timeInQueue; - - // add new time to sum - m_sumOfWaitTimes[teamIndex][bracketId] += timeInQueue; - - // set index of last player added to next one - (*lastPlayerAddedPointer)++; - (*lastPlayerAddedPointer) %= COUNT_OF_PLAYERS_TO_AVERAGE_WAIT_TIME; -} - -/** - Function that returns averate queue wait time - - @param group queue info - @param bracket id -*/ -uint32 BattleGroundQueue::GetAverageQueueWaitTime(GroupQueueInfo* queueInfo, BattleGroundBracketId bracketId) -{ - uint8 teamIndex = TEAM_INDEX_ALLIANCE; // default set to BG_TEAM_ALLIANCE - or non rated arenas! - if (queueInfo->arenaType == ARENA_TYPE_NONE) - { - if (queueInfo->groupTeam == HORDE) - teamIndex = TEAM_INDEX_HORDE; - } - else - { - if (queueInfo->isRated) - teamIndex = TEAM_INDEX_HORDE; // for rated arenas use BG_TEAM_HORDE - } - - // check if there is enought values(we always add values > 0) - if (m_waitTimes[teamIndex][bracketId][COUNT_OF_PLAYERS_TO_AVERAGE_WAIT_TIME - 1]) - return (m_sumOfWaitTimes[teamIndex][bracketId] / COUNT_OF_PLAYERS_TO_AVERAGE_WAIT_TIME); - - // if there aren't enough values return 0 - not available - return 0; -} - -/** - Method that removes player from queue and from group info, if group info is empty then remove it too - - @param guid - @param decrease invite count -*/ -void BattleGroundQueue::RemovePlayer(ObjectGuid guid, bool decreaseInvitedCount) -{ - // Player *plr = sObjectMgr.GetPlayer(guid); - // std::lock_guard guard(m_Lock); - - int32 bracketId = -1; // signed for proper for-loop finish - - // remove player from map, if he's there - QueuedPlayersMap::iterator itr = m_queuedPlayers.find(guid); - if (itr == m_queuedPlayers.end()) - { - sLog.outError("BattleGroundQueue: couldn't find for remove: %s", guid.GetString().c_str()); - return; - } - - GroupQueueInfo* group = itr->second.groupInfo; - GroupsQueueType::iterator group_itr; - // mostly people with the highest levels are in battlegrounds, thats why - // we count from MAX_BATTLEGROUND_QUEUES - 1 to 0 - // variable index removes useless searching in other team's queue - uint32 index = GetTeamIndexByTeamId(group->groupTeam); - - for (int8 bracketIdTmp = MAX_BATTLEGROUND_BRACKETS - 1; bracketIdTmp >= 0 && bracketId == -1; --bracketIdTmp) - { - // we must check premade and normal team's queue - because when players from premade are joining bg, - // they leave groupinfo so we can't use its players size to find out index - for (uint8 j = index; j < BG_QUEUE_GROUP_TYPES_COUNT; j += BG_QUEUE_NORMAL_ALLIANCE) - { - for (GroupsQueueType::iterator group_itr_tmp = m_queuedGroups[bracketIdTmp][j].begin(); group_itr_tmp != m_queuedGroups[bracketIdTmp][j].end(); ++group_itr_tmp) - { - if ((*group_itr_tmp) == group) - { - bracketId = bracketIdTmp; - group_itr = group_itr_tmp; - // we must store index to be able to erase iterator - index = j; - break; - } - } - } - } - - // player can't be in queue without group, but just in case - if (bracketId == -1) - { - sLog.outError("BattleGroundQueue: ERROR Cannot find groupinfo for %s", guid.GetString().c_str()); - return; - } - DEBUG_LOG("BattleGroundQueue: Removing %s, from bracket_id %u", guid.GetString().c_str(), (uint32)bracketId); - - // ALL variables are correctly set - // We can ignore leveling up in queue - it should not cause crash - // remove player from group - // if only one player there, remove group - - // remove player queue info from group queue info - GroupQueueInfoPlayers::iterator pitr = group->players.find(guid); - if (pitr != group->players.end()) - group->players.erase(pitr); - - // if invited to bg, and should decrease invited count, then do it - if (decreaseInvitedCount && group->isInvitedToBgInstanceGuid) - { - BattleGround* bg = sBattleGroundMgr.GetBattleGround(group->isInvitedToBgInstanceGuid, group->bgTypeId); - if (bg) - bg->DecreaseInvitedCount(group->groupTeam); - } - - // remove player queue info - m_queuedPlayers.erase(itr); - - // announce to world if arena team left queue for rated match, show only once - if (group->arenaType != ARENA_TYPE_NONE && group->isRated && group->players.empty() && sWorld.getConfig(CONFIG_BOOL_ARENA_QUEUE_ANNOUNCER_EXIT)) - sWorld.SendWorldText(LANG_ARENA_QUEUE_ANNOUNCE_WORLD_EXIT, group->arenaType, group->arenaType, group->arenaTeamRating); - - // if player leaves queue and he is invited to rated arena match, then he have to loose - if (group->isInvitedToBgInstanceGuid && group->isRated && decreaseInvitedCount) - { - ArenaTeam* at = sObjectMgr.GetArenaTeamById(group->arenaTeamId); - if (at) - { - DEBUG_LOG("UPDATING memberLost's personal arena rating for %s by opponents rating: %u", guid.GetString().c_str(), group->opponentsTeamRating); - Player* plr = sObjectMgr.GetPlayer(guid); - if (plr) - at->MemberLost(plr, group->opponentsTeamRating); - else - at->OfflineMemberLost(guid, group->opponentsTeamRating); - - at->SaveToDB(); - } - } - - // remove group queue info if needed - if (group->players.empty()) - { - m_queuedGroups[bracketId][index].erase(group_itr); - delete group; - } - // if group wasn't empty, so it wasn't deleted, and player have left a rated - // queue -> everyone from the group should leave too - // don't remove recursively if already invited to bg! - else if (!group->isInvitedToBgInstanceGuid && group->isRated) - { - // remove next player, this is recursive - // first send removal information - if (Player* plr2 = sObjectMgr.GetPlayer(group->players.begin()->first)) - { - BattleGround* bg = sBattleGroundMgr.GetBattleGroundTemplate(group->bgTypeId); - BattleGroundQueueTypeId bgQueueTypeId = BattleGroundMgr::BgQueueTypeId(group->bgTypeId, group->arenaType); - - uint32 queueSlot = plr2->GetBattleGroundQueueIndex(bgQueueTypeId); - plr2->RemoveBattleGroundQueueId(bgQueueTypeId); // must be called this way, because if you move this call to - - // queue->removeplayer, it causes bugs - WorldPacket data; - sBattleGroundMgr.BuildBattleGroundStatusPacket(data, bg, queueSlot, STATUS_NONE, 0, 0, ARENA_TYPE_NONE, TEAM_NONE); - plr2->GetSession()->SendPacket(data); - } - - // then actually delete, this may delete the group as well! - RemovePlayer(group->players.begin()->first, decreaseInvitedCount); - } -} - -/** - Function that returns true when player is in queue and is invited to bgInstanceGuid - - @param player guid - @param battleground instance guid - @param remove time -*/ -bool BattleGroundQueue::IsPlayerInvited(ObjectGuid playerGuid, const uint32 bgInstanceGuid, const uint32 removeTime) -{ - // std::lock_guard g(m_Lock); - QueuedPlayersMap::const_iterator qItr = m_queuedPlayers.find(playerGuid); - return (qItr != m_queuedPlayers.end() - && qItr->second.groupInfo->isInvitedToBgInstanceGuid == bgInstanceGuid - && qItr->second.groupInfo->removeInviteTime == removeTime); -} - -/** - Function that returns player group info data - - returns true when the player is found in queue - - @param player guid - @param group queue info -*/ -bool BattleGroundQueue::GetPlayerGroupInfoData(ObjectGuid guid, GroupQueueInfo* queueInfo) -{ - // std::lock_guard g(m_Lock); - QueuedPlayersMap::const_iterator qItr = m_queuedPlayers.find(guid); - if (qItr == m_queuedPlayers.end()) - return false; - - *queueInfo = *(qItr->second.groupInfo); - return true; -} - -/** - Function that invites group to battleground - - @param group queue info - @param battleground - @param team -*/ -bool BattleGroundQueue::InviteGroupToBg(GroupQueueInfo* queueInfo, BattleGround* bg, Team team) -{ - // set side if needed - if (team == ALLIANCE || team == HORDE) - queueInfo->groupTeam = team; - - if (!queueInfo->isInvitedToBgInstanceGuid) - { - // not yet invited - // set invitation - queueInfo->isInvitedToBgInstanceGuid = bg->GetInstanceId(); - BattleGroundTypeId bgTypeId = bg->GetTypeId(); - BattleGroundQueueTypeId bgQueueTypeId = BattleGroundMgr::BgQueueTypeId(bgTypeId, bg->GetArenaType()); - BattleGroundBracketId bracket_id = bg->GetBracketId(); - - // set ArenaTeamId for rated matches - if (bg->IsArena() && bg->IsRated()) - bg->SetArenaTeamIdForTeam(queueInfo->groupTeam, queueInfo->arenaTeamId); - - queueInfo->removeInviteTime = WorldTimer::getMSTime() + INVITE_ACCEPT_WAIT_TIME; - - // loop through the players - for (GroupQueueInfoPlayers::iterator itr = queueInfo->players.begin(); itr != queueInfo->players.end(); ++itr) - { - // get the player - Player* plr = sObjectMgr.GetPlayer(itr->first); - // if offline, skip him, this should not happen - player is removed from queue when he logs out - if (!plr) - continue; - - // invite the player - PlayerInvitedToBgUpdateAverageWaitTime(queueInfo, bracket_id); - // sBattleGroundMgr.InvitePlayer(plr, bg, ginfo->Team); - - // set invited player counters - bg->IncreaseInvitedCount(queueInfo->groupTeam); - - plr->SetInviteForBattleGroundQueueType(bgQueueTypeId, queueInfo->isInvitedToBgInstanceGuid); - - // create remind invite events - BgQueueInviteEvent* inviteEvent = new BgQueueInviteEvent(plr->GetObjectGuid(), queueInfo->isInvitedToBgInstanceGuid, bgTypeId, queueInfo->arenaType, queueInfo->removeInviteTime); - plr->m_events.AddEvent(inviteEvent, plr->m_events.CalculateTime(INVITATION_REMIND_TIME)); - - // create automatic remove events - BgQueueRemoveEvent* removeEvent = new BgQueueRemoveEvent(plr->GetObjectGuid(), queueInfo->isInvitedToBgInstanceGuid, bgTypeId, bgQueueTypeId, queueInfo->removeInviteTime); - plr->m_events.AddEvent(removeEvent, plr->m_events.CalculateTime(INVITE_ACCEPT_WAIT_TIME)); - - WorldPacket data; - - uint32 queueSlot = plr->GetBattleGroundQueueIndex(bgQueueTypeId); - - DEBUG_LOG("Battleground: invited %s to BG instance %u queueindex %u bgtype %u, I can't help it if they don't press the enter battle button.", plr->GetGuidStr().c_str(), bg->GetInstanceId(), queueSlot, bg->GetTypeId()); - - // send status packet - sBattleGroundMgr.BuildBattleGroundStatusPacket(data, bg, queueSlot, STATUS_WAIT_JOIN, INVITE_ACCEPT_WAIT_TIME, 0, queueInfo->arenaType, TEAM_NONE); - - plr->GetSession()->SendPacket(data); - } - return true; - } - - return false; -} - -/** - Method that invites players to an already running battleground - - invitation is based on config file - - large groups are disadvantageous, because they will be kicked first if invitation type = 1 - - @param battleground - @param bracket id -*/ -void BattleGroundQueue::FillPlayersToBg(BattleGround* bg, BattleGroundBracketId bracketId) -{ - int32 hordeFree = bg->GetFreeSlotsForTeam(HORDE); - int32 aliFree = bg->GetFreeSlotsForTeam(ALLIANCE); - - // iterator for iterating through bg queue - GroupsQueueType::const_iterator Ali_itr = m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE].begin(); - // count of groups in queue - used to stop cycles - uint32 aliCount = m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE].size(); - // index to queue which group is current - uint32 aliIndex = 0; - for (; aliIndex < aliCount && m_selectionPools[TEAM_INDEX_ALLIANCE].AddGroup((*Ali_itr), aliFree, bg->GetClientInstanceId()); ++aliIndex) - ++Ali_itr; - - // the same thing for horde - GroupsQueueType::const_iterator Horde_itr = m_queuedGroups[bracketId][BG_QUEUE_NORMAL_HORDE].begin(); - uint32 hordeCount = m_queuedGroups[bracketId][BG_QUEUE_NORMAL_HORDE].size(); - uint32 hordeIndex = 0; - for (; hordeIndex < hordeCount && m_selectionPools[TEAM_INDEX_HORDE].AddGroup((*Horde_itr), hordeFree, bg->GetClientInstanceId()); ++hordeIndex) - ++Horde_itr; - - // if ofc like BG queue invitation is set in config, then we are happy - if (sWorld.getConfig(CONFIG_UINT32_BATTLEGROUND_INVITATION_TYPE) == 0) - return; - - /* - if we reached this code, then we have to solve NP - complete problem called Subset sum problem - So one solution is to check all possible invitation subgroups, or we can use these conditions: - 1. Last time when BattleGroundQueue::Update was executed we invited all possible players - so there is only small possibility - that we will invite now whole queue, because only 1 change has been made to queues from the last BattleGroundQueue::Update call - 2. Other thing we should consider is group order in queue - */ - - // At first we need to compare free space in bg and our selection pool - int32 diffAli = aliFree - int32(m_selectionPools[TEAM_INDEX_ALLIANCE].GetPlayerCount()); - int32 diffHorde = hordeFree - int32(m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount()); - while (abs(diffAli - diffHorde) > 1 && (m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount() > 0 || m_selectionPools[TEAM_INDEX_ALLIANCE].GetPlayerCount() > 0)) - { - // each cycle execution we need to kick at least 1 group - if (diffAli < diffHorde) - { - // kick alliance group, add to pool new group if needed - if (m_selectionPools[TEAM_INDEX_ALLIANCE].KickGroup(diffHorde - diffAli)) - { - for (; aliIndex < aliCount && m_selectionPools[TEAM_INDEX_ALLIANCE].AddGroup((*Ali_itr), (aliFree >= diffHorde) ? aliFree - diffHorde : 0, bg->GetClientInstanceId()); ++aliIndex) - ++Ali_itr; - } - // if ali selection is already empty, then kick horde group, but if there are less horde than ali in bg - break; - if (!m_selectionPools[TEAM_INDEX_ALLIANCE].GetPlayerCount()) - { - if (aliFree <= diffHorde + 1) - break; - m_selectionPools[TEAM_INDEX_HORDE].KickGroup(diffHorde - diffAli); - } - } - else - { - // kick horde group, add to pool new group if needed - if (m_selectionPools[TEAM_INDEX_HORDE].KickGroup(diffAli - diffHorde)) - { - for (; hordeIndex < hordeCount && m_selectionPools[TEAM_INDEX_HORDE].AddGroup((*Horde_itr), (hordeFree >= diffAli) ? hordeFree - diffAli : 0, bg->GetClientInstanceId()); ++hordeIndex) - ++Horde_itr; - } - if (!m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount()) - { - if (hordeFree <= diffAli + 1) - break; - m_selectionPools[TEAM_INDEX_ALLIANCE].KickGroup(diffAli - diffHorde); - } - } - - // count diffs after small update - diffAli = aliFree - int32(m_selectionPools[TEAM_INDEX_ALLIANCE].GetPlayerCount()); - diffHorde = hordeFree - int32(m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount()); - } -} - -/** - Method that checks if (premade vs premade) battlegrouns is possible - - then after 30 mins (default) in queue it moves premade group to normal queue - - it tries to invite as much players as it can - to maxPlayersPerTeam, because premade groups have more than minPlayersPerTeam players - - @param bracket id - @param min players per team - @param max players per team -*/ -bool BattleGroundQueue::CheckPremadeMatch(BattleGroundBracketId bracketId, uint32 minPlayersPerTeam, uint32 maxPlayersPerTeam) -{ - // check match - if (!m_queuedGroups[bracketId][BG_QUEUE_PREMADE_ALLIANCE].empty() && !m_queuedGroups[bracketId][BG_QUEUE_PREMADE_HORDE].empty()) - { - // start premade match - // if groups aren't invited - GroupsQueueType::const_iterator ali_group, horde_group; - for (ali_group = m_queuedGroups[bracketId][BG_QUEUE_PREMADE_ALLIANCE].begin(); ali_group != m_queuedGroups[bracketId][BG_QUEUE_PREMADE_ALLIANCE].end(); ++ali_group) - if (!(*ali_group)->isInvitedToBgInstanceGuid) - break; - - for (horde_group = m_queuedGroups[bracketId][BG_QUEUE_PREMADE_HORDE].begin(); horde_group != m_queuedGroups[bracketId][BG_QUEUE_PREMADE_HORDE].end(); ++horde_group) - if (!(*horde_group)->isInvitedToBgInstanceGuid) - break; - - if (ali_group != m_queuedGroups[bracketId][BG_QUEUE_PREMADE_ALLIANCE].end() && horde_group != m_queuedGroups[bracketId][BG_QUEUE_PREMADE_HORDE].end()) - { - m_selectionPools[TEAM_INDEX_ALLIANCE].AddGroup((*ali_group), maxPlayersPerTeam, 0); - m_selectionPools[TEAM_INDEX_HORDE].AddGroup((*horde_group), maxPlayersPerTeam, 0); - - // add groups/players from normal queue to size of bigger group - uint32 maxPlayers = std::max(m_selectionPools[TEAM_INDEX_ALLIANCE].GetPlayerCount(), m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount()); - for (uint8 i = 0; i < PVP_TEAM_COUNT; ++i) - { - for (GroupsQueueType::const_iterator itr = m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE + i].begin(); itr != m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE + i].end(); ++itr) - { - // if itr can join BG and player count is less that maxPlayers, then add group to selectionpool - if (!(*itr)->isInvitedToBgInstanceGuid && !m_selectionPools[i].AddGroup((*itr), maxPlayers, 0)) - break; - } - } - - // premade selection pools are set - return true; - } - } - // now check if we can move group from Premade queue to normal queue (timer has expired) or group size lowered!! - // this could be 2 cycles but i'm checking only first team in queue - it can cause problem - - // if first is invited to BG and seconds timer expired, but we can ignore it, because players have only 80 seconds to click to enter bg - // and when they click or after 80 seconds the queue info is removed from queue - uint32 time_before = WorldTimer::getMSTime() - sWorld.getConfig(CONFIG_UINT32_BATTLEGROUND_PREMADE_GROUP_WAIT_FOR_MATCH); - for (uint8 i = 0; i < PVP_TEAM_COUNT; ++i) - { - if (!m_queuedGroups[bracketId][BG_QUEUE_PREMADE_ALLIANCE + i].empty()) - { - GroupsQueueType::iterator itr = m_queuedGroups[bracketId][BG_QUEUE_PREMADE_ALLIANCE + i].begin(); - if (!(*itr)->isInvitedToBgInstanceGuid && ((*itr)->joinTime < time_before || (*itr)->players.size() < minPlayersPerTeam)) - { - // we must insert group to normal queue and erase pointer from premade queue - m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE + i].push_front((*itr)); - m_queuedGroups[bracketId][BG_QUEUE_PREMADE_ALLIANCE + i].erase(itr); - } - } - } - - // selection pools are not set - return false; -} - -/** - Method that tries to create battleground or arena with minPlayersPerTeam against maxPlayersPerTeam - - @param battleground - @param bracket id - @param min players - @param max players -*/ -bool BattleGroundQueue::CheckNormalMatch(BattleGround* bgTemplate, BattleGroundBracketId bracketId, uint32 minPlayers, uint32 maxPlayers) -{ - GroupsQueueType::const_iterator itr_team[PVP_TEAM_COUNT]; - for (uint8 i = 0; i < PVP_TEAM_COUNT; ++i) - { - itr_team[i] = m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE + i].begin(); - for (; itr_team[i] != m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE + i].end(); ++(itr_team[i])) - { - if (!(*(itr_team[i]))->isInvitedToBgInstanceGuid) - { - m_selectionPools[i].AddGroup(*(itr_team[i]), maxPlayers, 0); - if (m_selectionPools[i].GetPlayerCount() >= minPlayers) - break; - } - } - } - - // try to invite same number of players - this cycle may cause longer wait time even if there are enough players in queue, but we want ballanced bg - uint32 j = TEAM_INDEX_ALLIANCE; - if (m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount() < m_selectionPools[TEAM_INDEX_ALLIANCE].GetPlayerCount()) - j = TEAM_INDEX_HORDE; - - if (sWorld.getConfig(CONFIG_UINT32_BATTLEGROUND_INVITATION_TYPE) != 0 - && m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount() >= minPlayers && m_selectionPools[TEAM_INDEX_ALLIANCE].GetPlayerCount() >= minPlayers) - { - // we will try to invite more groups to team with less players indexed by j - ++(itr_team[j]); // this will not cause a crash, because for cycle above reached break; - for (; itr_team[j] != m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE + j].end(); ++(itr_team[j])) - { - if (!(*(itr_team[j]))->isInvitedToBgInstanceGuid) - if (!m_selectionPools[j].AddGroup(*(itr_team[j]), m_selectionPools[(j + 1) % PVP_TEAM_COUNT].GetPlayerCount(), 0)) - break; - } - // do not allow to start bg with more than 2 players more on 1 faction - if (abs((int32)(m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount() - m_selectionPools[TEAM_INDEX_ALLIANCE].GetPlayerCount())) > 2) - return false; - } - - // allow 1v0 if debug bg - if (sBattleGroundMgr.IsTesting() && bgTemplate->IsBattleGround() && (m_selectionPools[TEAM_INDEX_ALLIANCE].GetPlayerCount() || m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount())) - return true; - - // return true if there are enough players in selection pools - enable to work .debug bg command correctly - return m_selectionPools[TEAM_INDEX_ALLIANCE].GetPlayerCount() >= minPlayers && m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount() >= minPlayers; -} - -/** - Method that will check if we can invite players to same faction skirmish match - - @param bracket id - @param min players -*/ -bool BattleGroundQueue::CheckSkirmishForSameFaction(BattleGroundBracketId bracketId, uint32 minPlayersPerTeam) -{ - if (m_selectionPools[TEAM_INDEX_ALLIANCE].GetPlayerCount() < minPlayersPerTeam && m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount() < minPlayersPerTeam) - return false; - - PvpTeamIndex teamIdx = TEAM_INDEX_ALLIANCE; - PvpTeamIndex otherTeamIdx = TEAM_INDEX_HORDE; - Team otherTeamId = HORDE; - - if (m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount() == minPlayersPerTeam) - { - teamIdx = TEAM_INDEX_HORDE; - otherTeamIdx = TEAM_INDEX_ALLIANCE; - otherTeamId = ALLIANCE; - } - - // clear other team's selection - m_selectionPools[otherTeamIdx].Init(); - // store last ginfo pointer - GroupQueueInfo* ginfo = m_selectionPools[teamIdx].selectedGroups.back(); - // set itr_team to group that was added to selection pool latest - GroupsQueueType::iterator itr_team = m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE + teamIdx].begin(); - for (; itr_team != m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE + teamIdx].end(); ++itr_team) - if (ginfo == *itr_team) - break; - - if (itr_team == m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE + teamIdx].end()) - return false; - - GroupsQueueType::iterator itr_team2 = itr_team; - ++itr_team2; - // invite players to other selection pool - for (; itr_team2 != m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE + teamIdx].end(); ++itr_team2) - { - // if selection pool is full then break; - if (!(*itr_team2)->isInvitedToBgInstanceGuid && !m_selectionPools[otherTeamIdx].AddGroup(*itr_team2, minPlayersPerTeam, 0)) - break; - } - - if (m_selectionPools[otherTeamIdx].GetPlayerCount() != minPlayersPerTeam) - return false; - - // here we have correct 2 selections and we need to change one teams team and move selection pool teams to other team's queue - for (GroupsQueueType::iterator itr = m_selectionPools[otherTeamIdx].selectedGroups.begin(); itr != m_selectionPools[otherTeamIdx].selectedGroups.end(); ++itr) - { - // set correct team - (*itr)->groupTeam = otherTeamId; - - // add team to other queue - m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE + otherTeamIdx].push_front(*itr); - - // remove team from old queue - GroupsQueueType::iterator itr2 = itr_team; - ++itr2; - for (; itr2 != m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE + teamIdx].end(); ++itr2) - { - if (*itr2 == *itr) - { - m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE + teamIdx].erase(itr2); - break; - } - } - } - return true; -} - -/** - Method that is called when group is inserted, or player / group is removed from BG Queue - there is only one player's status changed, so we don't use while(true) cycles to invite whole queue - - it must be called after fully adding the members of a group to ensure group joining - - should be called from BattleGround::RemovePlayer function in some cases - - @param bg type id - @param bracket id - @param arena type - @param isRated - @param arenaRating -*/ -void BattleGroundQueue::Update(BattleGroundTypeId bgTypeId, BattleGroundBracketId bracketId, ArenaType arenaType, bool isRated, uint32 arenaRating) -{ - // std::lock_guard guard(m_Lock); - // if no players in queue - do nothing - if (m_queuedGroups[bracketId][BG_QUEUE_PREMADE_ALLIANCE].empty() && - m_queuedGroups[bracketId][BG_QUEUE_PREMADE_HORDE].empty() && - m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE].empty() && - m_queuedGroups[bracketId][BG_QUEUE_NORMAL_HORDE].empty()) - return; - - // battleground with free slot for player should be always in the beggining of the queue - // maybe it would be better to create bgfreeslotqueue for each bracket_id - BgFreeSlotQueueType::iterator next; - for (BgFreeSlotQueueType::iterator itr = sBattleGroundMgr.BgFreeSlotQueue[bgTypeId].begin(); itr != sBattleGroundMgr.BgFreeSlotQueue[bgTypeId].end(); itr = next) - { - next = itr; - ++next; - // DO NOT allow queue manager to invite new player to arena - if ((*itr)->IsBattleGround() && (*itr)->GetTypeId() == bgTypeId && (*itr)->GetBracketId() == bracketId && - (*itr)->GetStatus() > STATUS_WAIT_QUEUE && (*itr)->GetStatus() < STATUS_WAIT_LEAVE) - { - BattleGround* bg = *itr; // we have to store battleground pointer here, because when battleground is full, it is removed from free queue (not yet implemented!!) - // and iterator is invalid - - // clear selection pools - m_selectionPools[TEAM_INDEX_ALLIANCE].Init(); - m_selectionPools[TEAM_INDEX_HORDE].Init(); - - // call a function that does the job for us - FillPlayersToBg(bg, bracketId); - - // now everything is set, invite players - for (GroupsQueueType::const_iterator citr = m_selectionPools[TEAM_INDEX_ALLIANCE].selectedGroups.begin(); citr != m_selectionPools[TEAM_INDEX_ALLIANCE].selectedGroups.end(); ++citr) - InviteGroupToBg((*citr), bg, (*citr)->groupTeam); - for (GroupsQueueType::const_iterator citr = m_selectionPools[TEAM_INDEX_HORDE].selectedGroups.begin(); citr != m_selectionPools[TEAM_INDEX_HORDE].selectedGroups.end(); ++citr) - InviteGroupToBg((*citr), bg, (*citr)->groupTeam); - - if (!bg->HasFreeSlots()) - { - // remove BG from BGFreeSlotQueue - bg->RemoveFromBgFreeSlotQueue(); - } - } - } - - // finished iterating through the bgs with free slots, maybe we need to create a new bg - - BattleGround* bgTemplate = sBattleGroundMgr.GetBattleGroundTemplate(bgTypeId); - if (!bgTemplate) - { - sLog.outError("Battleground: Update: bg template not found for %u", bgTypeId); - return; - } - - // get the min. players per team, properly for larger arenas as well. (must have full teams for arena matches!) - uint32 minPlayersPerTeam = bgTemplate->GetMinPlayersPerTeam(); - uint32 maxPlayersPerTeam = bgTemplate->GetMaxPlayersPerTeam(); - - if (sBattleGroundMgr.IsTesting()) - minPlayersPerTeam = 1; - - if (bgTemplate->IsArena()) - { - if (sBattleGroundMgr.IsArenaTesting()) - { - maxPlayersPerTeam = 1; - minPlayersPerTeam = 1; - } - else - { - // this switch can be much shorter - maxPlayersPerTeam = arenaType; - minPlayersPerTeam = arenaType; - /*switch(arenaType) - { - case ARENA_TYPE_2v2: - MaxPlayersPerTeam = 2; - MinPlayersPerTeam = 2; - break; - case ARENA_TYPE_3v3: - MaxPlayersPerTeam = 3; - MinPlayersPerTeam = 3; - break; - case ARENA_TYPE_5v5: - MaxPlayersPerTeam = 5; - MinPlayersPerTeam = 5; - break; - }*/ - } - } - - m_selectionPools[TEAM_INDEX_ALLIANCE].Init(); - m_selectionPools[TEAM_INDEX_HORDE].Init(); - - if (bgTemplate->IsBattleGround()) - { - // check if there is premade against premade match - if (CheckPremadeMatch(bracketId, minPlayersPerTeam, maxPlayersPerTeam)) - { - // create new battleground - BattleGround* bg2 = sBattleGroundMgr.CreateNewBattleGround(bgTypeId, bracketId, ARENA_TYPE_NONE, false); - if (!bg2) - { - sLog.outError("BattleGroundQueue::Update - Cannot create battleground: %u", bgTypeId); - return; - } - - // invite those selection pools - for (uint8 i = 0; i < PVP_TEAM_COUNT; ++i) - for (GroupsQueueType::const_iterator citr = m_selectionPools[TEAM_INDEX_ALLIANCE + i].selectedGroups.begin(); citr != m_selectionPools[TEAM_INDEX_ALLIANCE + i].selectedGroups.end(); ++citr) - InviteGroupToBg((*citr), bg2, (*citr)->groupTeam); - - // start bg - bg2->StartBattleGround(); - // clear structures - m_selectionPools[TEAM_INDEX_ALLIANCE].Init(); - m_selectionPools[TEAM_INDEX_HORDE].Init(); - } - } - - // now check if there are in queues enough players to start new game of (normal battleground, or non-rated arena) - if (!isRated) - { - // if there are enough players in pools, start new battleground or non rated arena - if (CheckNormalMatch(bgTemplate, bracketId, minPlayersPerTeam, maxPlayersPerTeam) - || (bgTemplate->IsArena() && CheckSkirmishForSameFaction(bracketId, minPlayersPerTeam))) - { - // we successfully created a pool - BattleGround* bg2 = sBattleGroundMgr.CreateNewBattleGround(bgTypeId, bracketId, arenaType, false); - if (!bg2) - { - sLog.outError("BattleGroundQueue::Update - Cannot create battleground: %u", bgTypeId); - return; - } - - // invite those selection pools - for (uint8 i = 0; i < PVP_TEAM_COUNT; ++i) - for (GroupsQueueType::const_iterator citr = m_selectionPools[TEAM_INDEX_ALLIANCE + i].selectedGroups.begin(); citr != m_selectionPools[TEAM_INDEX_ALLIANCE + i].selectedGroups.end(); ++citr) - InviteGroupToBg((*citr), bg2, (*citr)->groupTeam); - - // start bg - bg2->StartBattleGround(); - } - } - else if (bgTemplate->IsArena()) - { - // found out the minimum and maximum ratings the newly added team should battle against - // arenaRating is the rating of the latest joined team, or 0 - // 0 is on (automatic update call) and we must set it to team's with longest wait time - if (!arenaRating) - { - GroupQueueInfo* front1 = nullptr; - GroupQueueInfo* front2 = nullptr; - if (!m_queuedGroups[bracketId][BG_QUEUE_PREMADE_ALLIANCE].empty()) - { - front1 = m_queuedGroups[bracketId][BG_QUEUE_PREMADE_ALLIANCE].front(); - arenaRating = front1->arenaTeamRating; - } - - if (!m_queuedGroups[bracketId][BG_QUEUE_PREMADE_HORDE].empty()) - { - front2 = m_queuedGroups[bracketId][BG_QUEUE_PREMADE_HORDE].front(); - arenaRating = front2->arenaTeamRating; - } - - if (front1 && front2) - { - if (front1->joinTime < front2->joinTime) - arenaRating = front1->arenaTeamRating; - } - else if (!front1 && !front2) - return; // queues are empty - } - - // set rating range - uint32 arenaMinRating = (arenaRating <= sBattleGroundMgr.GetMaxRatingDifference()) ? 0 : arenaRating - sBattleGroundMgr.GetMaxRatingDifference(); - uint32 arenaMaxRating = arenaRating + sBattleGroundMgr.GetMaxRatingDifference(); - // if max rating difference is set and the time past since server startup is greater than the rating discard time - // (after what time the ratings aren't taken into account when making teams) then - // the discard time is current_time - time_to_discard, teams that joined after that, will have their ratings taken into account - // else leave the discard time on 0, this way all ratings will be discarded - uint32 discardTime = WorldTimer::getMSTime() - sBattleGroundMgr.GetRatingDiscardTimer(); - - // we need to find 2 teams which will play next game - - GroupsQueueType::iterator itr_team[PVP_TEAM_COUNT]; - - // optimalization : --- we dont need to use selection_pools - each update we select max 2 groups - - for (uint8 i = BG_QUEUE_PREMADE_ALLIANCE; i < BG_QUEUE_NORMAL_ALLIANCE; ++i) - { - // take the group that joined first - itr_team[i] = m_queuedGroups[bracketId][i].begin(); - for (; itr_team[i] != m_queuedGroups[bracketId][i].end(); ++(itr_team[i])) - { - // if group match conditions, then add it to pool - if (!(*itr_team[i])->isInvitedToBgInstanceGuid - && (((*itr_team[i])->arenaTeamRating >= arenaMinRating && (*itr_team[i])->arenaTeamRating <= arenaMaxRating) - || (*itr_team[i])->joinTime < discardTime)) - { - m_selectionPools[i].AddGroup((*itr_team[i]), maxPlayersPerTeam, 0); - // break for cycle to be able to start selecting another group from same faction queue - break; - } - } - } - // now we are done if we have 2 groups - ali vs horde! - // if we don't have, we must try to continue search in same queue - // tmp variables are correctly set - // this code isn't much userfriendly - but it is supposed to continue search for mathing group in HORDE queue - if (m_selectionPools[TEAM_INDEX_ALLIANCE].GetPlayerCount() == 0 && m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount()) - { - itr_team[TEAM_INDEX_ALLIANCE] = itr_team[TEAM_INDEX_HORDE]; - ++itr_team[TEAM_INDEX_ALLIANCE]; - - for (; itr_team[TEAM_INDEX_ALLIANCE] != m_queuedGroups[bracketId][BG_QUEUE_PREMADE_HORDE].end(); ++(itr_team[TEAM_INDEX_ALLIANCE])) - { - if (!(*itr_team[TEAM_INDEX_ALLIANCE])->isInvitedToBgInstanceGuid - && (((*itr_team[TEAM_INDEX_ALLIANCE])->arenaTeamRating >= arenaMinRating && (*itr_team[TEAM_INDEX_ALLIANCE])->arenaTeamRating <= arenaMaxRating) - || (*itr_team[TEAM_INDEX_ALLIANCE])->joinTime < discardTime)) - { - m_selectionPools[TEAM_INDEX_ALLIANCE].AddGroup((*itr_team[TEAM_INDEX_ALLIANCE]), maxPlayersPerTeam, 0); - break; - } - } - } - // this code isn't much userfriendly - but it is supposed to continue search for mathing group in ALLIANCE queue - if (m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount() == 0 && m_selectionPools[TEAM_INDEX_ALLIANCE].GetPlayerCount()) - { - itr_team[TEAM_INDEX_HORDE] = itr_team[TEAM_INDEX_ALLIANCE]; - ++itr_team[TEAM_INDEX_HORDE]; - - for (; itr_team[TEAM_INDEX_HORDE] != m_queuedGroups[bracketId][BG_QUEUE_PREMADE_ALLIANCE].end(); ++(itr_team[TEAM_INDEX_HORDE])) - { - if (!(*itr_team[TEAM_INDEX_HORDE])->isInvitedToBgInstanceGuid - && (((*itr_team[TEAM_INDEX_HORDE])->arenaTeamRating >= arenaMinRating && (*itr_team[TEAM_INDEX_HORDE])->arenaTeamRating <= arenaMaxRating) - || (*itr_team[TEAM_INDEX_HORDE])->joinTime < discardTime)) - { - m_selectionPools[TEAM_INDEX_HORDE].AddGroup((*itr_team[TEAM_INDEX_HORDE]), maxPlayersPerTeam, 0); - break; - } - } - } - - // if we have 2 teams, then start new arena and invite players! - if (m_selectionPools[TEAM_INDEX_ALLIANCE].GetPlayerCount() && m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount()) - { - BattleGround* arena = sBattleGroundMgr.CreateNewBattleGround(bgTypeId, bracketId, arenaType, true); - if (!arena) - { - sLog.outError("BattlegroundQueue::Update couldn't create arena instance for rated arena match!"); - return; - } - - (*(itr_team[TEAM_INDEX_ALLIANCE]))->opponentsTeamRating = (*(itr_team[TEAM_INDEX_HORDE]))->arenaTeamRating; - DEBUG_LOG("setting oposite teamrating for team %u to %u", (*(itr_team[TEAM_INDEX_ALLIANCE]))->arenaTeamId, (*(itr_team[TEAM_INDEX_ALLIANCE]))->opponentsTeamRating); - (*(itr_team[TEAM_INDEX_HORDE]))->opponentsTeamRating = (*(itr_team[TEAM_INDEX_ALLIANCE]))->arenaTeamRating; - DEBUG_LOG("setting oposite teamrating for team %u to %u", (*(itr_team[TEAM_INDEX_HORDE]))->arenaTeamId, (*(itr_team[TEAM_INDEX_HORDE]))->opponentsTeamRating); - - // now we must move team if we changed its faction to another faction queue, because then we will spam log by errors in Queue::RemovePlayer - if ((*(itr_team[TEAM_INDEX_ALLIANCE]))->groupTeam != ALLIANCE) - { - // add to alliance queue - m_queuedGroups[bracketId][BG_QUEUE_PREMADE_ALLIANCE].push_front(*(itr_team[TEAM_INDEX_ALLIANCE])); - // erase from horde queue - m_queuedGroups[bracketId][BG_QUEUE_PREMADE_HORDE].erase(itr_team[TEAM_INDEX_ALLIANCE]); - itr_team[TEAM_INDEX_ALLIANCE] = m_queuedGroups[bracketId][BG_QUEUE_PREMADE_ALLIANCE].begin(); - } - - if ((*(itr_team[TEAM_INDEX_HORDE]))->groupTeam != HORDE) - { - m_queuedGroups[bracketId][BG_QUEUE_PREMADE_HORDE].push_front(*(itr_team[TEAM_INDEX_HORDE])); - m_queuedGroups[bracketId][BG_QUEUE_PREMADE_ALLIANCE].erase(itr_team[TEAM_INDEX_HORDE]); - itr_team[TEAM_INDEX_HORDE] = m_queuedGroups[bracketId][BG_QUEUE_PREMADE_HORDE].begin(); - } - - InviteGroupToBg(*(itr_team[TEAM_INDEX_ALLIANCE]), arena, ALLIANCE); - InviteGroupToBg(*(itr_team[TEAM_INDEX_HORDE]), arena, HORDE); - - DEBUG_LOG("Starting rated arena match!"); - - arena->StartBattleGround(); - } - } -} - -/*********************************************************/ -/*** BATTLEGROUND QUEUE EVENTS ***/ -/*********************************************************/ - -/** - Function that executes battleground queue invite event -*/ -bool BgQueueInviteEvent::Execute(uint64 /*e_time*/, uint32 /*p_time*/) -{ - sWorld.GetMessager().AddMessage([event = *this](World* /*world*/) - { - Player* plr = sObjectMgr.GetPlayer(event.m_playerGuid); - // player logged off (we should do nothing, he is correctly removed from queue in another procedure) - if (!plr) - return; - - BattleGround* bg = sBattleGroundMgr.GetBattleGround(event.m_bgInstanceGuid, event.m_bgTypeId); - // if battleground ended and its instance deleted - do nothing - if (!bg) - return; - - BattleGroundQueueTypeId bgQueueTypeId = BattleGroundMgr::BgQueueTypeId(bg->GetTypeId(), bg->GetArenaType()); - uint32 queueSlot = plr->GetBattleGroundQueueIndex(bgQueueTypeId); - if (queueSlot < PLAYER_MAX_BATTLEGROUND_QUEUES) // player is in queue or in battleground - { - // check if player is invited to this bg - BattleGroundQueue& bgQueue = sBattleGroundMgr.m_battleGroundQueues[bgQueueTypeId]; - if (bgQueue.IsPlayerInvited(event.m_playerGuid, event.m_bgInstanceGuid, event.m_removeTime)) - { - WorldPacket data; - // we must send remaining time in queue - sBattleGroundMgr.BuildBattleGroundStatusPacket(data, bg, queueSlot, STATUS_WAIT_JOIN, INVITE_ACCEPT_WAIT_TIME - INVITATION_REMIND_TIME, 0, event.m_arenaType, TEAM_NONE); - plr->GetSession()->SendPacket(data); - } - } - }); - return true; // event will be deleted -} - -void BgQueueInviteEvent::Abort(uint64 /*e_time*/) -{ - // do nothing -} - -/** - Function that executes battleground queue remove event - this event has many possibilities when it is executed: - 1. player is in battleground ( he clicked enter on invitation window ) - 2. player left battleground queue and he isn't there any more - 3. player left battleground queue and he joined it again and IsInvitedToBGInstanceGUID = 0 - 4. player left queue and he joined again and he has been invited to same battleground again -> we should not remove him from queue yet - 5. player is invited to bg and he didn't choose what to do and timer expired - only in this condition we should call queue::RemovePlayer - we must remove player in the 5. case even if battleground object doesn't exist! -*/ -bool BgQueueRemoveEvent::Execute(uint64 /*e_time*/, uint32 /*p_time*/) -{ - sWorld.GetMessager().AddMessage([event = *this](World* /*world*/) - { - Player* plr = sObjectMgr.GetPlayer(event.m_playerGuid); - if (!plr) - // player logged off (we should do nothing, he is correctly removed from queue in another procedure) - return; - - BattleGround* bg = sBattleGroundMgr.GetBattleGround(event.m_bgInstanceGuid, event.m_bgTypeId); - // battleground can be deleted already when we are removing queue info - // bg pointer can be nullptr! so use it carefully! - - uint32 queueSlot = plr->GetBattleGroundQueueIndex(event.m_bgQueueTypeId); - if (queueSlot < PLAYER_MAX_BATTLEGROUND_QUEUES) // player is in queue, or in Battleground - { - // check if player is in queue for this BG and if we are removing his invite event - BattleGroundQueue& bgQueue = sBattleGroundMgr.m_battleGroundQueues[event.m_bgQueueTypeId]; - if (bgQueue.IsPlayerInvited(event.m_playerGuid, event.m_bgInstanceGuid, event.m_removeTime)) - { - DEBUG_LOG("Battleground: removing player %u from bg queue for instance %u because of not pressing enter battle in time.", plr->GetGUIDLow(), event.m_bgInstanceGuid); - - plr->RemoveBattleGroundQueueId(event.m_bgQueueTypeId); - bgQueue.RemovePlayer(event.m_playerGuid, true); - - // update queues if battleground isn't ended - if (bg && bg->IsBattleGround() && bg->GetStatus() != STATUS_WAIT_LEAVE) - sBattleGroundMgr.ScheduleQueueUpdate(0, ARENA_TYPE_NONE, event.m_bgQueueTypeId, event.m_bgTypeId, bg->GetBracketId()); - - WorldPacket data; - sBattleGroundMgr.BuildBattleGroundStatusPacket(data, bg, queueSlot, STATUS_NONE, 0, 0, ARENA_TYPE_NONE, TEAM_NONE); - plr->GetSession()->SendPacket(data); - } - } - }); - - // event will be deleted - return true; -} - -void BgQueueRemoveEvent::Abort(uint64 /*e_time*/) -{ - // do nothing -} - /*********************************************************/ /*** BATTLEGROUND MANAGER ***/ /*********************************************************/ -BattleGroundMgr::BattleGroundMgr() : m_nextAutoDistributionTime(0), m_autoDistributionTimeChecker(0), m_arenaTesting(false) +BattleGroundMgr::BattleGroundMgr() : m_arenaTesting(false), m_testing(false) { for (uint8 i = BATTLEGROUND_TYPE_NONE; i < MAX_BATTLEGROUND_TYPE_ID; ++i) m_battleGrounds[i].clear(); - m_nextRatingDiscardUpdate = sWorld.getConfig(CONFIG_UINT32_ARENA_RATING_DISCARD_TIMER); - m_testing = false; } BattleGroundMgr::~BattleGroundMgr() @@ -1291,66 +80,9 @@ void BattleGroundMgr::DeleteAllBattleGrounds() @param diff */ -void BattleGroundMgr::Update(uint32 diff) +void BattleGroundMgr::Update(uint32 /*diff*/) { - // update scheduled queues - if (!m_queueUpdateScheduler.empty()) - { - std::vector scheduled; - { - // create mutex - // std::lock_guard guard(SchedulerLock); - // copy vector and clear the other - scheduled = std::vector(m_queueUpdateScheduler); - m_queueUpdateScheduler.clear(); - // release lock - } - - for (unsigned long long i : scheduled) - { - uint32 arenaRating = i >> 32; - ArenaType arenaType = ArenaType(i >> 24 & 255); - BattleGroundQueueTypeId bgQueueTypeId = BattleGroundQueueTypeId(i >> 16 & 255); - BattleGroundTypeId bgTypeId = BattleGroundTypeId((i >> 8) & 255); - BattleGroundBracketId bracket_id = BattleGroundBracketId(i & 255); - - m_battleGroundQueues[bgQueueTypeId].Update(bgTypeId, bracket_id, arenaType, arenaRating > 0, arenaRating); - } - } - - // if rating difference counts, maybe force-update queues - if (sWorld.getConfig(CONFIG_UINT32_ARENA_MAX_RATING_DIFFERENCE) && sWorld.getConfig(CONFIG_UINT32_ARENA_RATING_DISCARD_TIMER)) - { - // it's time to force update - if (m_nextRatingDiscardUpdate < diff) - { - // forced update for level 70 rated arenas - DEBUG_LOG("BattleGroundMgr: UPDATING ARENA QUEUES"); - m_battleGroundQueues[BATTLEGROUND_QUEUE_2v2].Update(BATTLEGROUND_AA, BG_BRACKET_ID_FIRST, ARENA_TYPE_2v2, true, 0); - m_battleGroundQueues[BATTLEGROUND_QUEUE_3v3].Update(BATTLEGROUND_AA, BG_BRACKET_ID_FIRST, ARENA_TYPE_3v3, true, 0); - m_battleGroundQueues[BATTLEGROUND_QUEUE_5v5].Update(BATTLEGROUND_AA, BG_BRACKET_ID_FIRST, ARENA_TYPE_5v5, true, 0); - - m_nextRatingDiscardUpdate = sWorld.getConfig(CONFIG_UINT32_ARENA_RATING_DISCARD_TIMER); - } - else - m_nextRatingDiscardUpdate -= diff; - } - - if (sWorld.getConfig(CONFIG_BOOL_ARENA_AUTO_DISTRIBUTE_POINTS)) - { - if (m_autoDistributionTimeChecker < diff) - { - if (sWorld.GetGameTime() > m_nextAutoDistributionTime) - { - DistributeArenaPoints(); - m_nextAutoDistributionTime = time_t(m_nextAutoDistributionTime + BATTLEGROUND_ARENA_POINT_DISTRIBUTION_DAY * sWorld.getConfig(CONFIG_UINT32_ARENA_AUTO_DISTRIBUTE_INTERVAL_DAYS)); - CharacterDatabase.PExecute("UPDATE saved_variables SET NextArenaPointDistributionTime = '" UI64FMTD "'", uint64(m_nextAutoDistributionTime)); - } - m_autoDistributionTimeChecker = 600000; // check 10 minutes - } - else - m_autoDistributionTimeChecker -= diff; - } + } /** @@ -1365,11 +97,11 @@ void BattleGroundMgr::Update(uint32 diff) @param arena type @param arena team */ -void BattleGroundMgr::BuildBattleGroundStatusPacket(WorldPacket& data, BattleGround* bg, uint8 queueSlot, uint8 statusId, uint32 time1, uint32 time2, ArenaType arenaType, Team arenaTeam) const +void BattleGroundMgr::BuildBattleGroundStatusPacket(WorldPacket& data, bool bgExists, uint32 bgTypeId, uint32 bgClientInstanceId, bool isRated, uint32 mapId, uint8 queueSlot, uint8 statusId, uint32 time1, uint32 time2, ArenaType arenaType, Team arenaTeam) { // we can be in 3 queues in same time... - if (statusId == 0 || !bg) + if (statusId == 0 || !bgExists) { data.Initialize(SMSG_BATTLEFIELD_STATUS, 4 + 8); data << uint32(queueSlot); // queue id (0...2) @@ -1380,25 +112,25 @@ void BattleGroundMgr::BuildBattleGroundStatusPacket(WorldPacket& data, BattleGro data.Initialize(SMSG_BATTLEFIELD_STATUS, (4 + 8 + 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) | (uint64(0x0D) << 8) | (uint64(bg->GetTypeId()) << 16) | (uint64(0x1F90) << 48)); - data << uint32(bg->GetClientInstanceId()); + data << uint64(uint64(arenaType) | (uint64(0x0D) << 8) | (uint64(bgTypeId) << 16) | (uint64(0x1F90) << 48)); + data << uint32(bgClientInstanceId); // alliance/horde for BG and skirmish/rated for Arenas // following displays the minimap-icon 0 = faction icon 1 = arenaicon - data << uint8(bg->IsRated()); + data << uint8(isRated); data << uint32(statusId); // status switch (statusId) { - case STATUS_WAIT_QUEUE: // status_in_queue + 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 + case STATUS_WAIT_JOIN: // status_invite + data << uint32(mapId); // 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 + case STATUS_IN_PROGRESS: // status_in_progress + data << uint32(mapId); // 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(arenaTeam == ALLIANCE ? 1 : 0); // arenaTeam (0 for horde, 1 for alliance) @@ -1415,7 +147,7 @@ void BattleGroundMgr::BuildBattleGroundStatusPacket(WorldPacket& data, BattleGro @param packet @param battleground */ -void BattleGroundMgr::BuildPvpLogDataPacket(WorldPacket& data, BattleGround* bg) const +void BattleGroundMgr::BuildPvpLogDataPacket(WorldPacket& data, BattleGround* bg) { uint8 type = (bg->IsArena() ? 1 : 0); // last check on 2.4.1 @@ -1517,7 +249,7 @@ void BattleGroundMgr::BuildPvpLogDataPacket(WorldPacket& data, BattleGround* bg) @param battleground type id @param battleground group join status */ -void BattleGroundMgr::BuildGroupJoinedBattlegroundPacket(WorldPacket& data, BattleGroundTypeId bgTypeId, BattleGroundGroupJoinStatus status) const +void BattleGroundMgr::BuildGroupJoinedBattlegroundPacket(WorldPacket& data, BattleGroundTypeId bgTypeId, BattleGroundGroupJoinStatus status) { data.Initialize(SMSG_GROUP_JOINED_BATTLEGROUND, 4); switch (status) @@ -1544,7 +276,7 @@ void BattleGroundMgr::BuildGroupJoinedBattlegroundPacket(WorldPacket& data, Batt @param field @param value */ -void BattleGroundMgr::BuildUpdateWorldStatePacket(WorldPacket& data, uint32 field, uint32 value) const +void BattleGroundMgr::BuildUpdateWorldStatePacket(WorldPacket& data, uint32 field, uint32 value) { data.Initialize(SMSG_UPDATE_WORLD_STATE, 4 + 4); data << uint32(field); @@ -1557,7 +289,7 @@ void BattleGroundMgr::BuildUpdateWorldStatePacket(WorldPacket& data, uint32 fiel @param packet @param sound id */ -void BattleGroundMgr::BuildPlaySoundPacket(WorldPacket& data, uint32 soundId) const +void BattleGroundMgr::BuildPlaySoundPacket(WorldPacket& data, uint32 soundId) { data.Initialize(SMSG_PLAY_SOUND, 4); data << uint32(soundId); @@ -1569,7 +301,7 @@ void BattleGroundMgr::BuildPlaySoundPacket(WorldPacket& data, uint32 soundId) co @param packet @param object guid */ -void BattleGroundMgr::BuildPlayerLeftBattleGroundPacket(WorldPacket& data, ObjectGuid guid) const +void BattleGroundMgr::BuildPlayerLeftBattleGroundPacket(WorldPacket& data, ObjectGuid guid) { data.Initialize(SMSG_BATTLEGROUND_PLAYER_LEFT, 8); data << ObjectGuid(guid); @@ -1581,7 +313,7 @@ void BattleGroundMgr::BuildPlayerLeftBattleGroundPacket(WorldPacket& data, Objec @param packet @param player */ -void BattleGroundMgr::BuildPlayerJoinedBattleGroundPacket(WorldPacket& data, Player* player) const +void BattleGroundMgr::BuildPlayerJoinedBattleGroundPacket(WorldPacket& data, Player* player) { data.Initialize(SMSG_BATTLEGROUND_PLAYER_JOINED, 8); data << player->GetObjectGuid(); @@ -1643,42 +375,13 @@ BattleGround* BattleGroundMgr::GetBattleGround(uint32 instanceId, BattleGroundTy @param battleground type id */ -BattleGround* BattleGroundMgr::GetBattleGroundTemplate(BattleGroundTypeId bgTypeId) +BattleGround* BattleGroundMgr::GetBattleGroundTemplate(BattleGroundTypeId bgTypeId) const { // map is sorted and we can be sure that lowest instance id has only BG template return m_battleGrounds[bgTypeId].empty() ? nullptr : m_battleGrounds[bgTypeId].begin()->second; } -/** - Function that returns client instance id from battleground type id and bracket id - - @param battleground type id - @param bracket id -*/ -uint32 BattleGroundMgr::CreateClientVisibleInstanceId(BattleGroundTypeId bgTypeId, BattleGroundBracketId bracketId) -{ - 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; - ClientBattleGroundIdSet& ids = m_clientBattleGroundIds[bgTypeId][bracketId]; - for (ClientBattleGroundIdSet::const_iterator itr = ids.begin(); itr != ids.end();) - { - if ((++lastId) != *itr) // if there is a gap between the ids, we will break.. - break; - lastId = *itr; - } - ids.insert(lastId + 1); - return lastId + 1; -} /** Function that creates a new battleground that is actually used @@ -1688,7 +391,7 @@ uint32 BattleGroundMgr::CreateClientVisibleInstanceId(BattleGroundTypeId bgTypeI @param arena type @param isRated */ -BattleGround* BattleGroundMgr::CreateNewBattleGround(BattleGroundTypeId bgTypeId, BattleGroundBracketId bracketId, ArenaType arenaType, bool isRated) +BattleGround* BattleGroundMgr::CreateNewBattleGround(BattleGroundTypeId bgTypeId, BattleGroundBracketId bracketId, ArenaType arenaType, bool isRated, uint32 instanceId, uint32 clientInstanceId) { // get the template BG BattleGround* bgTemplate = GetBattleGroundTemplate(bgTypeId); @@ -1748,9 +451,9 @@ BattleGround* BattleGroundMgr::CreateNewBattleGround(BattleGroundTypeId bgTypeId bg->SetBracketId(bracketId); // will also set m_bgMap, instanceid - sMapMgr.CreateBgMap(bg->GetMapId(), bg); + sMapMgr.CreateBgMap(bg->GetMapId(), instanceId, bg); - bg->SetClientInstanceId(CreateClientVisibleInstanceId(bgTypeId, bracketId)); + bg->SetClientInstanceId(clientInstanceId); // reset the new bg (set status to status_wait_queue from status_none) bg->Reset(); @@ -1951,61 +654,6 @@ void BattleGroundMgr::CreateInitialBattleGrounds() sLog.outString(); } -/** - Method that initiates the automatic arena points distribution -*/ -void BattleGroundMgr::InitAutomaticArenaPointDistribution() -{ - if (sWorld.getConfig(CONFIG_BOOL_ARENA_AUTO_DISTRIBUTE_POINTS)) - { - auto queryResult = CharacterDatabase.Query("SELECT NextArenaPointDistributionTime FROM saved_variables"); - bool save = false; - bool insert = false; - if (!queryResult) // if not set generate time for next wednesday - insert = true; - else - { - m_nextAutoDistributionTime = time_t((*queryResult)[0].GetUInt64()); - if (m_nextAutoDistributionTime == 0) // uninitialized - save = true; - else // if time already exists - check for config changes - { - tm distribTime = *localtime(&m_nextAutoDistributionTime); - if (distribTime.tm_hour != sWorld.getConfig(CONFIG_UINT32_QUEST_DAILY_RESET_HOUR)) - { - if (time(nullptr) >= m_nextAutoDistributionTime) // if it already expired, do not save and only adjust hour - { - distribTime.tm_hour = sWorld.getConfig(CONFIG_UINT32_QUEST_DAILY_RESET_HOUR); - m_nextAutoDistributionTime = mktime(&distribTime); - } - else - save = true; - } - } - } - - if (save || insert) - { - // generate time by config on first server launch - time_t curTime = time(nullptr); - tm localTm = *localtime(&curTime); - localTm.tm_hour = sWorld.getConfig(CONFIG_UINT32_QUEST_DAILY_RESET_HOUR); - localTm.tm_min = 0; - localTm.tm_sec = 0; - localTm.tm_mday += ((7 - localTm.tm_wday + sWorld.getConfig(CONFIG_UINT32_ARENA_FIRST_RESET_DAY)) % 7); - localTm.tm_isdst = -1; - m_nextAutoDistributionTime = mktime(&localTm); - - if (insert) - CharacterDatabase.PExecute("INSERT INTO saved_variables (NextArenaPointDistributionTime) VALUES ('" UI64FMTD "')", uint64(m_nextAutoDistributionTime)); - if (save) - CharacterDatabase.PExecute("UPDATE saved_variables SET NextArenaPointDistributionTime = '" UI64FMTD "'", uint64(m_nextAutoDistributionTime)); - } - - //uint32 dayofweek = sWorld.getConfig(CONFIG_UINT32_ARENA_AUTO_DISTRIBUTE_INTERVAL_DAYS); - } -} - /** Method that distributes the arena points distribution */ @@ -2280,46 +928,6 @@ void BattleGroundMgr::ResetAllArenaData() } } -/** - Method that builds battleground list data - - @param packet - @param battlemaster guid - @param player - @param battleground type id -*/ -void BattleGroundMgr::BuildBattleGroundListPacket(WorldPacket& data, ObjectGuid guid, Player* player, BattleGroundTypeId bgTypeId) const -{ - if (!player) - return; - - data.Initialize(SMSG_BATTLEFIELD_LIST); - data << 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(0); // number of bg instances - - uint32 bracket_id = player->GetBattleGroundBracketIdFromLevel(bgTypeId); - ClientBattleGroundIdSet const& ids = m_clientBattleGroundIds[bgTypeId][bracket_id]; - for (std::set::const_iterator itr = ids.begin(); itr != ids.end(); ++itr) - { - data << uint32(*itr); - ++count; - } - data.put(count_pos, count); - } -} - /** Method that sends player to battleground @@ -2461,6 +1069,10 @@ void BattleGroundMgr::ToggleTesting() sWorld.SendWorldText(LANG_DEBUG_BG_ON); else sWorld.SendWorldText(LANG_DEBUG_BG_OFF); + sWorld.GetBGQueue().GetMessager().AddMessage([testing = m_testing](BattleGroundQueue* queue) + { + queue->SetTesting(testing); + }); } /** @@ -2473,33 +1085,10 @@ void BattleGroundMgr::ToggleArenaTesting() sWorld.SendWorldText(LANG_DEBUG_ARENA_ON); else sWorld.SendWorldText(LANG_DEBUG_ARENA_OFF); -} - -/** - Method that schedules queue update - - @param arena rating - @param arena type - @param battleground queue type id - @param battleground type id - @param bracket id -*/ -void BattleGroundMgr::ScheduleQueueUpdate(uint32 arenaRating, ArenaType arenaType, BattleGroundQueueTypeId bgQueueTypeId, BattleGroundTypeId bgTypeId, BattleGroundBracketId bracketId) -{ - // std::lock_guard guard(SchedulerLock); - // we will use only 1 number created of bgTypeId and bracket_id - uint64 schedule_id = (uint64(arenaRating) << 32) | (uint64(arenaType) << 24) | (uint64(bgQueueTypeId) << 16) | (uint64(bgTypeId) << 8) | bracketId; - bool found = false; - for (unsigned long long i : m_queueUpdateScheduler) + sWorld.GetBGQueue().GetMessager().AddMessage([arenaTesting = m_arenaTesting](BattleGroundQueue* queue) { - if (i == schedule_id) - { - found = true; - break; - } - } - if (!found) - m_queueUpdateScheduler.push_back(schedule_id); + queue->SetArenaTesting(arenaTesting); + }); } /** @@ -2730,3 +1319,40 @@ void BattleGroundMgr::LoadBattleEventIndexes() sLog.outString(">> Loaded %u battleground eventindexes", count); sLog.outString(); } + +uint32 BattleGroundMgr::GetMinLevelForBattleGroundBracketId(BattleGroundBracketId bracket_id, BattleGroundTypeId bgTypeId) const +{ + if (bracket_id < 1) + return 0; + + if (bracket_id > BG_BRACKET_ID_LAST) + bracket_id = BG_BRACKET_ID_LAST; + + BattleGround* bg = GetBattleGroundTemplate(bgTypeId); + assert(bg); + return 10 * bracket_id + bg->GetMinLevel(); +} + +uint32 BattleGroundMgr::GetMaxLevelForBattleGroundBracketId(BattleGroundBracketId bracket_id, BattleGroundTypeId bgTypeId) const +{ + if (bracket_id >= BG_BRACKET_ID_LAST) + return 255; // hardcoded max level + + // for example EOTS - min level 61 but max level for bracket 69 + return (GetMinLevelForBattleGroundBracketId(bracket_id, bgTypeId) / 10 * 10) + 9; +} + +BattleGroundBracketId BattleGroundMgr::GetBattleGroundBracketIdFromLevel(BattleGroundTypeId bgTypeId, uint32 playerLevel) const +{ + BattleGround* bg = GetBattleGroundTemplate(bgTypeId); + assert(bg); + if (playerLevel < bg->GetMinLevel()) + return BG_BRACKET_ID_FIRST; + + // for example EOTS - min level 61 but max level for bracket 69 + uint32 bracket_id = (playerLevel - (bg->GetMinLevel() / 10 * 10)) / 10; + if (bracket_id > MAX_BATTLEGROUND_BRACKETS) + return BG_BRACKET_ID_LAST; + + return BattleGroundBracketId(bracket_id); +} diff --git a/src/game/BattleGround/BattleGroundMgr.h b/src/game/BattleGround/BattleGroundMgr.h index f8c32c1bfdf..e87d593a29d 100644 --- a/src/game/BattleGround/BattleGroundMgr.h +++ b/src/game/BattleGround/BattleGroundMgr.h @@ -24,182 +24,17 @@ #include "Globals/SharedDefines.h" #include "Server/DBCEnums.h" #include "BattleGround.h" +#include "BattleGround/BattleGroundDefines.h" #include typedef std::map BattleGroundSet; -// this container can't be deque, because deque doesn't like removing the last element - if you remove it, it invalidates next iterator and crash appears -typedef std::list BgFreeSlotQueueType; - typedef std::unordered_map BattleMastersMap; typedef std::unordered_map CreatureBattleEventIndexesMap; typedef std::unordered_map GameObjectBattleEventIndexesMap; -#define BATTLEGROUND_ARENA_POINT_DISTRIBUTION_DAY 86400 // seconds in a day -#define COUNT_OF_PLAYERS_TO_AVERAGE_WAIT_TIME 10 - -struct GroupQueueInfo; // type predefinition -struct PlayerQueueInfo // stores information for players in queue -{ - uint32 lastOnlineTime; // for tracking and removing offline players from queue after 5 minutes - GroupQueueInfo* groupInfo; // pointer to the associated groupqueueinfo -}; - -typedef std::map GroupQueueInfoPlayers; - -struct GroupQueueInfo // stores information about the group in queue (also used when joined as solo!) -{ - GroupQueueInfoPlayers players; // player queue info map - Team groupTeam; // Player team (ALLIANCE/HORDE) - BattleGroundTypeId bgTypeId; // battleground type id - bool isRated; // rated - ArenaType arenaType; // 2v2, 3v3, 5v5 or 0 when BG - uint32 arenaTeamId; // team id if rated match - uint32 joinTime; // time when group was added - uint32 removeInviteTime; // time when we will remove invite for players in group - uint32 isInvitedToBgInstanceGuid; // was invited to certain BG - uint32 desiredInstanceId; // queued for this instance specifically - uint32 arenaTeamRating; // if rated match, inited to the rating of the team - uint32 opponentsTeamRating; // for rated arena matches -}; - -enum BattleGroundQueueGroupTypes -{ - BG_QUEUE_PREMADE_ALLIANCE = 0, - BG_QUEUE_PREMADE_HORDE = 1, - BG_QUEUE_NORMAL_ALLIANCE = 2, - BG_QUEUE_NORMAL_HORDE = 3 -}; - -#define BG_QUEUE_GROUP_TYPES_COUNT 4 - -enum BattleGroundGroupJoinStatus -{ - BG_GROUP_JOIN_STATUS_TEAM_LEFT_QUEUE = -7, - BG_GROUP_JOIN_STATUS_QUEUED_FOR_RATED = -6, - BG_GROUP_JOIN_STATUS_CANNOT_QUEUE_FOR_RATED = -5, - BG_GROUP_JOIN_STATUS_TOO_MANY_QUEUES = -4, - BG_GROUP_JOIN_STATUS_NOT_IN_TEAM = -3, - BG_GROUP_JOIN_STATUS_DESERTERS = -2, - BG_GROUP_JOIN_STATUS_NOT_ELIGIBLE = -1, - BG_GROUP_JOIN_STATUS_SUCCESS = 0, -}; - class BattleGround; -class BattleGroundQueue -{ - public: - BattleGroundQueue(); - ~BattleGroundQueue(); - - void Update(BattleGroundTypeId /*bgTypeId*/, BattleGroundBracketId /*bracketId*/, ArenaType arenaType = ARENA_TYPE_NONE, bool isRated = false, uint32 arenaRating = 0); - - void FillPlayersToBg(BattleGround* /*bg*/, BattleGroundBracketId /*bracketId*/); - bool CheckPremadeMatch(BattleGroundBracketId /*bracketId*/, uint32 /*minPlayersPerTeam*/, uint32 /*maxPlayersPerTeam*/); - bool CheckNormalMatch(BattleGround* /*bgTemplate*/, BattleGroundBracketId /*bracketId*/, uint32 /*minPlayers*/, uint32 /*maxPlayers*/); - bool CheckSkirmishForSameFaction(BattleGroundBracketId /*bracketId*/, uint32 /*minPlayersPerTeam*/); - GroupQueueInfo* AddGroup(Player* /*leader*/, Group* /*group*/, BattleGroundTypeId /*bgTypeId*/, BattleGroundBracketId /*bracketEntry*/, ArenaType /*arenaType*/, bool /*isRated*/, bool /*isPremade*/, uint32 /*instanceId*/, uint32 /*arenaRating*/, uint32 arenaTeamId = 0); - void RemovePlayer(ObjectGuid /*guid*/, bool /*decreaseInvitedCount*/); - bool IsPlayerInvited(ObjectGuid /*playerGuid*/, const uint32 /*bgInstanceGuid*/, const uint32 /*removeTime*/); - bool GetPlayerGroupInfoData(ObjectGuid /*guid*/, GroupQueueInfo* /*groupInfo*/); - void PlayerInvitedToBgUpdateAverageWaitTime(GroupQueueInfo* /*groupInfo*/, BattleGroundBracketId /*bracketId*/); - uint32 GetAverageQueueWaitTime(GroupQueueInfo* /*groupInfo*/, BattleGroundBracketId /*bracketId*/); - - private: - // mutex that should not allow changing private data, nor allowing to update Queue during private data change. - std::recursive_mutex m_lock; - - - typedef std::map QueuedPlayersMap; - QueuedPlayersMap m_queuedPlayers; - - // we need constant add to begin and constant remove / add from the end, therefore deque suits our problem well - typedef std::list GroupsQueueType; - - /* - This two dimensional array is used to store All queued groups - First dimension specifies the bgTypeId - Second dimension specifies the player's group types - - BG_QUEUE_PREMADE_ALLIANCE is used for premade alliance groups and alliance rated arena teams - BG_QUEUE_PREMADE_HORDE is used for premade horde groups and horde rated arena teams - BG_QUEUE_NORMAL_ALLIANCE is used for normal (or small) alliance groups or non-rated arena matches - BG_QUEUE_NORMAL_HORDE is used for normal (or small) horde groups or non-rated arena matches - */ - GroupsQueueType m_queuedGroups[MAX_BATTLEGROUND_BRACKETS][BG_QUEUE_GROUP_TYPES_COUNT]; - - // class to select and invite groups to bg - class SelectionPool - { - public: - SelectionPool() : playerCount(0) {} - void Init(); - bool AddGroup(GroupQueueInfo* ginfo, uint32 desiredCount, uint32 bgInstanceId); - bool KickGroup(uint32 size); - uint32 GetPlayerCount() const {return playerCount;} - GroupsQueueType selectedGroups; - private: - uint32 playerCount; - }; - - // one selection pool for horde, other one for alliance - SelectionPool m_selectionPools[PVP_TEAM_COUNT]; - - bool InviteGroupToBg(GroupQueueInfo* /*groupInfo*/, BattleGround* /*bg*/, Team /*side*/); - - uint32 m_waitTimes[PVP_TEAM_COUNT][MAX_BATTLEGROUND_BRACKETS][COUNT_OF_PLAYERS_TO_AVERAGE_WAIT_TIME]; - uint32 m_waitTimeLastPlayer[PVP_TEAM_COUNT][MAX_BATTLEGROUND_BRACKETS]; - uint32 m_sumOfWaitTimes[PVP_TEAM_COUNT][MAX_BATTLEGROUND_BRACKETS]; -}; - -/* - This class is used to invite player to BG again, when minute lasts from his first invitation - it is capable to solve all possibilities -*/ -class BgQueueInviteEvent : public BasicEvent -{ - public: - BgQueueInviteEvent(ObjectGuid playerGuid, uint32 bgInstanceGuid, BattleGroundTypeId bgTypeId, ArenaType arenaType, uint32 removeTime) : - m_playerGuid(playerGuid), m_bgInstanceGuid(bgInstanceGuid), m_bgTypeId(bgTypeId), m_arenaType(arenaType), m_removeTime(removeTime) - { - }; - virtual ~BgQueueInviteEvent() {}; - - virtual bool Execute(uint64 e_time, uint32 p_time) override; - virtual void Abort(uint64 e_time) override; - - private: - ObjectGuid m_playerGuid; - uint32 m_bgInstanceGuid; - BattleGroundTypeId m_bgTypeId; - ArenaType m_arenaType; - uint32 m_removeTime; -}; - -/* - This class is used to remove player from BG queue after 1 minute 20 seconds from first invitation - We must store removeInvite time in case player left queue and joined and is invited again - We must store bgQueueTypeId, because battleground can be deleted already, when player entered it -*/ -class BgQueueRemoveEvent : public BasicEvent -{ - public: - BgQueueRemoveEvent(ObjectGuid playerGuid, uint32 bgInstanceGuid, BattleGroundTypeId bgTypeId, BattleGroundQueueTypeId bgQueueTypeId, uint32 removeTime) - : m_playerGuid(playerGuid), m_bgInstanceGuid(bgInstanceGuid), m_removeTime(removeTime), m_bgTypeId(bgTypeId), m_bgQueueTypeId(bgQueueTypeId) - {} - - virtual ~BgQueueRemoveEvent() {} - - virtual bool Execute(uint64 e_time, uint32 p_time) override; - virtual void Abort(uint64 e_time) override; - - private: - ObjectGuid m_playerGuid; - uint32 m_bgInstanceGuid; - uint32 m_removeTime; - BattleGroundTypeId m_bgTypeId; - BattleGroundQueueTypeId m_bgQueueTypeId; -}; class BattleGroundMgr { @@ -210,50 +45,36 @@ class BattleGroundMgr void Update(uint32 /*diff*/); /* Packet Building */ - void BuildPlayerJoinedBattleGroundPacket(WorldPacket& /*data*/, Player* /*player*/) const; - void BuildPlayerLeftBattleGroundPacket(WorldPacket& /*data*/, ObjectGuid /*guid*/) const; - void BuildBattleGroundListPacket(WorldPacket& /*data*/, ObjectGuid /*guid*/, Player* /*player*/, BattleGroundTypeId /*bgTypeId*/) const; - void BuildGroupJoinedBattlegroundPacket(WorldPacket& /*data*/, BattleGroundTypeId /*bgTypeId*/, BattleGroundGroupJoinStatus /*status*/) const; - void BuildUpdateWorldStatePacket(WorldPacket& /*data*/, uint32 /*field*/, uint32 /*value*/) const; - void BuildPvpLogDataPacket(WorldPacket& /*data*/, BattleGround* /*bg*/) const; - void BuildBattleGroundStatusPacket(WorldPacket& /*data*/, BattleGround* /*bg*/, uint8 /*queueSlot*/, uint8 /*statusId*/, uint32 /*time1*/, uint32 /*time2*/, ArenaType /*arenaType*/, Team /*arenaTeam*/) const; - void BuildPlaySoundPacket(WorldPacket& /*data*/, uint32 /*soundId*/) const; + static void BuildPlayerJoinedBattleGroundPacket(WorldPacket& /*data*/, Player* /*player*/); + static void BuildPlayerLeftBattleGroundPacket(WorldPacket& /*data*/, ObjectGuid /*guid*/); + static void BuildGroupJoinedBattlegroundPacket(WorldPacket& /*data*/, BattleGroundTypeId /*bgTypeId*/, BattleGroundGroupJoinStatus /*status*/); + static void BuildUpdateWorldStatePacket(WorldPacket& /*data*/, uint32 /*field*/, uint32 /*value*/); + static void BuildPvpLogDataPacket(WorldPacket& /*data*/, BattleGround* /*bg*/); + static void BuildBattleGroundStatusPacket(WorldPacket& data, bool bgExists, uint32 bgTypeId, uint32 bgClientInstanceId, bool isRated, uint32 mapId, uint8 queueSlot, uint8 statusId, uint32 time1, uint32 time2, ArenaType arenaType, Team arenaTeam); + static void BuildPlaySoundPacket(WorldPacket& /*data*/, uint32 /*soundId*/); /* Battlegrounds */ BattleGround* GetBattleGroundThroughClientInstance(uint32 /*instanceId*/, BattleGroundTypeId /*bgTypeId*/); BattleGround* GetBattleGround(uint32 /*instanceId*/, BattleGroundTypeId /*bgTypeId*/); // there must be uint32 because MAX_BATTLEGROUND_TYPE_ID means unknown - BattleGround* GetBattleGroundTemplate(BattleGroundTypeId /*bgTypeId*/); - BattleGround* CreateNewBattleGround(BattleGroundTypeId /*bgTypeId*/, BattleGroundBracketId /*bracketEntry*/, ArenaType /*arenaType*/, bool /*isRated*/); + BattleGround* GetBattleGroundTemplate(BattleGroundTypeId /*bgTypeId*/) const; + BattleGround* CreateNewBattleGround(BattleGroundTypeId bgTypeId, BattleGroundBracketId bracketEntry, ArenaType arenaType, bool isRated, uint32 instanceId, uint32 clientInstanceId); uint32 CreateBattleGround(BattleGroundTypeId /*bgTypeId*/, bool /*isArena*/, uint32 /*minPlayersPerTeam*/, uint32 /*maxPlayersPerTeam*/, uint32 /*levelMin*/, uint32 /*levelMax*/, char const* /*battleGroundName*/, uint32 /*mapId*/, float /*team1StartLocX*/, float /*team1StartLocY*/, float /*team1StartLocZ*/, float /*team1StartLocO*/, float /*team2StartLocX*/, float /*team2StartLocY*/, float /*team2StartLocZ*/, float /*team2StartLocO*/, float /*startMaxDist*/, uint32 /*playerSkinReflootId*/); void AddBattleGround(uint32 instanceId, BattleGroundTypeId bgTypeId, BattleGround* bg) { m_battleGrounds[bgTypeId][instanceId] = bg; }; void RemoveBattleGround(uint32 instanceId, BattleGroundTypeId bgTypeId) { m_battleGrounds[bgTypeId].erase(instanceId); } - uint32 CreateClientVisibleInstanceId(BattleGroundTypeId /*bgTypeId*/, BattleGroundBracketId /*bracketId*/); - void DeleteClientVisibleInstanceId(BattleGroundTypeId bgTypeId, BattleGroundBracketId bracketId, uint32 clientInstanceId) - { - m_clientBattleGroundIds[bgTypeId][bracketId].erase(clientInstanceId); - } - void CreateInitialBattleGrounds(); void DeleteAllBattleGrounds(); void SendToBattleGround(Player* /*player*/, uint32 /*instanceId*/, BattleGroundTypeId /*bgTypeId*/); - /* Battleground queues */ - // these queues are instantiated when creating BattlegroundMrg - BattleGroundQueue m_battleGroundQueues[MAX_BATTLEGROUND_QUEUE_TYPES]; // public, because we need to access them in BG handler code - - BgFreeSlotQueueType BgFreeSlotQueue[MAX_BATTLEGROUND_TYPE_ID]; - - void ScheduleQueueUpdate(uint32 /*arenaRating*/, ArenaType /*arenaType*/, BattleGroundQueueTypeId /*bgQueueTypeId*/, BattleGroundTypeId /*bgTypeId*/, BattleGroundBracketId /*bracketId*/); uint32 GetMaxRatingDifference() const; uint32 GetRatingDiscardTimer() const; uint32 GetPrematureFinishTime() const; - void InitAutomaticArenaPointDistribution(); + void DistributeArenaPoints() const; void RewardArenaSeason(uint32 /*seasonId*/); void ResetAllArenaData(); @@ -303,6 +124,10 @@ class BattleGroundMgr static bool IsBgWeekend(BattleGroundTypeId /*bgTypeId*/); std::set const& GetUsedRefLootIds() const { return m_usedRefloot; } + + uint32 GetMinLevelForBattleGroundBracketId(BattleGroundBracketId bracket_id, BattleGroundTypeId bgTypeId) const; + uint32 GetMaxLevelForBattleGroundBracketId(BattleGroundBracketId bracket_id, BattleGroundTypeId bgTypeId) const; + BattleGroundBracketId GetBattleGroundBracketIdFromLevel(BattleGroundTypeId bgTypeId, uint32 playerLevel) const; private: std::mutex schedulerLock; BattleMastersMap m_battleMastersMap; @@ -311,14 +136,8 @@ class BattleGroundMgr /* Battlegrounds */ BattleGroundSet m_battleGrounds[MAX_BATTLEGROUND_TYPE_ID]; - std::vector m_queueUpdateScheduler; - typedef std::set ClientBattleGroundIdSet; - ClientBattleGroundIdSet m_clientBattleGroundIds[MAX_BATTLEGROUND_TYPE_ID][MAX_BATTLEGROUND_BRACKETS]; // the instanceids just visible for the client - uint32 m_nextRatingDiscardUpdate; - time_t m_nextAutoDistributionTime; - uint32 m_autoDistributionTimeChecker; - bool m_arenaTesting; - bool m_testing; + bool m_arenaTesting; + bool m_testing; std::set m_usedRefloot; }; diff --git a/src/game/BattleGround/BattleGroundQueue.cpp b/src/game/BattleGround/BattleGroundQueue.cpp new file mode 100644 index 00000000000..335c98053c9 --- /dev/null +++ b/src/game/BattleGround/BattleGroundQueue.cpp @@ -0,0 +1,1638 @@ +/* + * This file is part of the CMaNGOS Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "BattleGround/BattleGroundQueue.h" +#include "Tools/Language.h" +#include "World/World.h" +#include "BattleGround/BattleGroundMgr.h" +#include "Arena/ArenaTeam.h" + + /*********************************************************/ + /*** BATTLEGROUND QUEUE SYSTEM ***/ + /*********************************************************/ + +BattleGroundQueueItem::BattleGroundQueueItem() +{ + for (uint8 i = 0; i < PVP_TEAM_COUNT; ++i) + { + for (uint8 j = 0; j < MAX_BATTLEGROUND_BRACKETS; ++j) + { + m_sumOfWaitTimes[i][j] = 0; + m_waitTimeLastPlayer[i][j] = 0; + + for (uint8 k = 0; k < COUNT_OF_PLAYERS_TO_AVERAGE_WAIT_TIME; ++k) + m_waitTimes[i][j][k] = 0; + } + } +} + +BattleGroundQueueItem::~BattleGroundQueueItem() +{ + m_queuedPlayers.clear(); + for (auto& m_queuedGroup : m_queuedGroups) + { + for (uint8 j = 0; j < BG_QUEUE_GROUP_TYPES_COUNT; ++j) + { + for (GroupsQueueType::iterator itr = m_queuedGroup[j].begin(); itr != m_queuedGroup[j].end(); ++itr) + delete (*itr); + + m_queuedGroup[j].clear(); + } + } +} + +/*********************************************************/ +/*** BATTLEGROUND QUEUE SELECTION POOLS ***/ +/*********************************************************/ + +// selection pool initialization, used to clean up from prev selection +void BattleGroundQueueItem::SelectionPool::Init() +{ + selectedGroups.clear(); + playerCount = 0; +} + +/** + Function that removes group infr from pool selection + - returns true when we need to try to add new group to selection pool + - returns false when selection pool is ok or when we kicked smaller group than we need to kick + - sometimes it can be called on empty selection pool + + @param size +*/ +bool BattleGroundQueueItem::SelectionPool::KickGroup(uint32 size) +{ + // find maxgroup or LAST group with size == size and kick it + bool found = false; + GroupsQueueType::iterator groupToKick = selectedGroups.begin(); + + for (GroupsQueueType::iterator itr = groupToKick; itr != selectedGroups.end(); ++itr) + { + if (abs((int32)((*itr)->players.size() - size)) <= 1) + { + groupToKick = itr; + found = true; + } + else if (!found && (*itr)->players.size() >= (*groupToKick)->players.size()) + groupToKick = itr; + } + + // if pool is empty, do nothing + if (GetPlayerCount()) + { + // update player count + GroupQueueInfo* queueInfo = (*groupToKick); + selectedGroups.erase(groupToKick); + playerCount -= queueInfo->players.size(); + + // return false if we kicked smaller group or there are enough players in selection pool + if (queueInfo->players.size() <= size + 1) + return false; + } + return true; +} + +/** + Function that adds group to selection pool + - returns true if we can invite more players, or when we added group to selection pool + - returns false when selection pool is full + + @param group queue info + @param desired count +*/ +bool BattleGroundQueueItem::SelectionPool::AddGroup(GroupQueueInfo* queueInfo, uint32 desiredCount, uint32 bgInstanceId) +{ + // if group is larger than desired count - don't allow to add it to pool + if (!queueInfo->isInvitedToBgInstanceGuid && + (!queueInfo->desiredInstanceId || queueInfo->desiredInstanceId == bgInstanceId) && + (desiredCount >= playerCount + queueInfo->players.size())) + { + selectedGroups.push_back(queueInfo); + // increase selected players count + playerCount += queueInfo->players.size(); + + return true; + } + + return playerCount < desiredCount; +} + +/*********************************************************/ +/*** BATTLEGROUND QUEUES ***/ +/*********************************************************/ + +/** + Function that adds group or player (grp == nullptr) to battleground queue with the given leader and specifications + + @param leader player + @param group + @param battleground type id + @param bracket entry + @param arena type + @param isRated + @param isPremade + @param arena rating + @param arena team id +*/ +GroupQueueInfo* BattleGroundQueueItem::AddGroup(ObjectGuid leader, AddGroupToQueueInfo const& groupInfo, BattleGroundTypeId bgTypeId, BattleGroundBracketId bracketId, ArenaType arenaType, bool isRated, bool isPremade, uint32 instanceId, uint32 arenaRating, uint32 arenaTeamId) +{ + // create new ginfo + GroupQueueInfo* queueInfo = new GroupQueueInfo; + queueInfo->bgTypeId = bgTypeId; + queueInfo->bgBracketId = bracketId; + queueInfo->arenaType = arenaType; + queueInfo->arenaTeamId = arenaTeamId; + queueInfo->isRated = isRated; + queueInfo->isInvitedToBgInstanceGuid = 0; + queueInfo->mapId = groupInfo.mapId; + queueInfo->clientInstanceId = groupInfo.clientInstanceId; + queueInfo->joinTime = WorldTimer::getMSTime(); + queueInfo->removeInviteTime = 0; + queueInfo->groupTeam = groupInfo.team; + queueInfo->desiredInstanceId = instanceId; + queueInfo->arenaTeamRating = arenaRating; + queueInfo->opponentsTeamRating = 0; + + queueInfo->players.clear(); + + // compute index (if group is premade or joined a rated match) to queues + uint32 index = 0; + if (!isRated && !isPremade) + index += PVP_TEAM_COUNT; // BG_QUEUE_PREMADE_* -> BG_QUEUE_NORMAL_* + + if (queueInfo->groupTeam == HORDE) + ++index; // BG_QUEUE_*_ALLIANCE -> BG_QUEUE_*_HORDE + + DEBUG_LOG("Adding Group to BattleGroundQueueItem bgTypeId : %u, bracket_id : %u, index : %u", bgTypeId, bracketId, index); + + uint32 lastOnlineTime = WorldTimer::getMSTime(); + + // announce world (this don't need mutex) + if (isRated && sWorld.getConfig(CONFIG_BOOL_ARENA_QUEUE_ANNOUNCER_JOIN)) + { + sWorld.SendWorldText(LANG_ARENA_QUEUE_ANNOUNCE_WORLD_JOIN, queueInfo->arenaType, queueInfo->arenaType, queueInfo->arenaTeamRating); + } + + // add players from group to ginfo + { + if (!groupInfo.members.empty()) + { + for (ObjectGuid member : groupInfo.members) + { + PlayerQueueInfo& playerInfo = m_queuedPlayers[member]; + playerInfo.lastOnlineTime = lastOnlineTime; + playerInfo.groupInfo = queueInfo; + // add the pinfo to ginfo's list + queueInfo->players[member] = &playerInfo; + } + } + else + { + PlayerQueueInfo& playerInfo = m_queuedPlayers[leader]; + playerInfo.lastOnlineTime = lastOnlineTime; + playerInfo.groupInfo = queueInfo; + queueInfo->players[leader] = &playerInfo; + } + + // add GroupInfo to m_QueuedGroups + m_queuedGroups[bracketId][index].push_back(queueInfo); + + // announce to world, this code needs mutex + if (arenaType == ARENA_TYPE_NONE && !isRated && !isPremade && sWorld.getConfig(CONFIG_UINT32_BATTLEGROUND_QUEUE_ANNOUNCER_JOIN)) + { + if (BattleGround* bg = sBattleGroundMgr.GetBattleGroundTemplate(queueInfo->bgTypeId)) + { + char const* bgName = bg->GetName(); + uint32 minPlayers = bg->GetMinPlayersPerTeam(); + uint32 qHorde = 0; + uint32 qAlliance = 0; + uint32 q_min_level = sBattleGroundMgr.GetMinLevelForBattleGroundBracketId(bracketId, bgTypeId); + uint32 qMaxLevel = sBattleGroundMgr.GetMaxLevelForBattleGroundBracketId(bracketId, bgTypeId); + GroupsQueueType::const_iterator itr; + for (itr = m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE].begin(); itr != m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE].end(); ++itr) + if (!(*itr)->isInvitedToBgInstanceGuid) + qAlliance += (*itr)->players.size(); + + for (itr = m_queuedGroups[bracketId][BG_QUEUE_NORMAL_HORDE].begin(); itr != m_queuedGroups[bracketId][BG_QUEUE_NORMAL_HORDE].end(); ++itr) + if (!(*itr)->isInvitedToBgInstanceGuid) + qHorde += (*itr)->players.size(); + + sWorld.GetMessager().AddMessage([playerGuid = leader, bgName, q_min_level, qMaxLevel, qAlliance, minPlayers, qHorde](World* world) + { + // Show queue status to player only (when joining queue) + if (sWorld.getConfig(CONFIG_UINT32_BATTLEGROUND_QUEUE_ANNOUNCER_JOIN) == 1) + { + if (Player* plr = sObjectMgr.GetPlayer(playerGuid)) + ChatHandler(plr).PSendSysMessage(LANG_BG_QUEUE_ANNOUNCE_SELF, bgName, q_min_level, qMaxLevel, + qAlliance, (minPlayers > qAlliance) ? minPlayers - qAlliance : (uint32)0, qHorde, (minPlayers > qHorde) ? minPlayers - qHorde : (uint32)0); + } + // System message + else + { + sWorld.SendWorldText(LANG_BG_QUEUE_ANNOUNCE_WORLD, bgName, q_min_level, qMaxLevel, + qAlliance, (minPlayers > qAlliance) ? minPlayers - qAlliance : (uint32)0, qHorde, (minPlayers > qHorde) ? minPlayers - qHorde : (uint32)0); + } + }); + } + } + // release mutex + } + + return queueInfo; +} + +/** + Method that updates average update wait time + + @param group queue info + @param bracket id +*/ +void BattleGroundQueueItem::PlayerInvitedToBgUpdateAverageWaitTime(GroupQueueInfo* queueInfo, BattleGroundBracketId bracketId) +{ + uint32 timeInQueue = WorldTimer::getMSTimeDiff(queueInfo->joinTime, WorldTimer::getMSTime()); + uint8 teamIndex = TEAM_INDEX_ALLIANCE; // default set to BG_TEAM_ALLIANCE - or non rated arenas! + + if (queueInfo->arenaType == ARENA_TYPE_NONE) + { + if (queueInfo->groupTeam == HORDE) + teamIndex = TEAM_INDEX_HORDE; + } + else + { + if (queueInfo->isRated) + teamIndex = TEAM_INDEX_HORDE; // for rated arenas use BG_TEAM_HORDE + } + + // store pointer to arrayindex of player that was added first + uint32* lastPlayerAddedPointer = &(m_waitTimeLastPlayer[teamIndex][bracketId]); + + // remove his time from sum + m_sumOfWaitTimes[teamIndex][bracketId] -= m_waitTimes[teamIndex][bracketId][(*lastPlayerAddedPointer)]; + + // set average time to new + m_waitTimes[teamIndex][bracketId][(*lastPlayerAddedPointer)] = timeInQueue; + + // add new time to sum + m_sumOfWaitTimes[teamIndex][bracketId] += timeInQueue; + + // set index of last player added to next one + (*lastPlayerAddedPointer)++; + (*lastPlayerAddedPointer) %= COUNT_OF_PLAYERS_TO_AVERAGE_WAIT_TIME; +} + +/** + Function that returns averate queue wait time + + @param group queue info + @param bracket id +*/ +uint32 BattleGroundQueueItem::GetAverageQueueWaitTime(GroupQueueInfo* queueInfo, BattleGroundBracketId bracketId) +{ + uint8 teamIndex = TEAM_INDEX_ALLIANCE; // default set to BG_TEAM_ALLIANCE - or non rated arenas! + if (queueInfo->arenaType == ARENA_TYPE_NONE) + { + if (queueInfo->groupTeam == HORDE) + teamIndex = TEAM_INDEX_HORDE; + } + else + { + if (queueInfo->isRated) + teamIndex = TEAM_INDEX_HORDE; // for rated arenas use BG_TEAM_HORDE + } + + // check if there is enought values(we always add values > 0) + if (m_waitTimes[teamIndex][bracketId][COUNT_OF_PLAYERS_TO_AVERAGE_WAIT_TIME - 1]) + return (m_sumOfWaitTimes[teamIndex][bracketId] / COUNT_OF_PLAYERS_TO_AVERAGE_WAIT_TIME); + + // if there aren't enough values return 0 - not available + return 0; +} + +/** + Method that removes player from queue and from group info, if group info is empty then remove it too + + @param guid + @param decrease invite count +*/ +void BattleGroundQueueItem::RemovePlayer(BattleGroundQueue& queue, ObjectGuid guid, bool decreaseInvitedCount) +{ + int32 bracketId = -1; // signed for proper for-loop finish + + // remove player from map, if he's there + QueuedPlayersMap::iterator itr = m_queuedPlayers.find(guid); + if (itr == m_queuedPlayers.end()) + { + sLog.outError("BattleGroundQueueItem: couldn't find for remove: %s", guid.GetString().c_str()); + return; + } + + GroupQueueInfo* group = itr->second.groupInfo; + GroupsQueueType::iterator group_itr; + // mostly people with the highest levels are in battlegrounds, thats why + // we count from MAX_BATTLEGROUND_QUEUES - 1 to 0 + // variable index removes useless searching in other team's queue + uint32 index = GetTeamIndexByTeamId(group->groupTeam); + + for (int8 bracketIdTmp = MAX_BATTLEGROUND_BRACKETS - 1; bracketIdTmp >= 0 && bracketId == -1; --bracketIdTmp) + { + // we must check premade and normal team's queue - because when players from premade are joining bg, + // they leave groupinfo so we can't use its players size to find out index + for (uint8 j = index; j < BG_QUEUE_GROUP_TYPES_COUNT; j += BG_QUEUE_NORMAL_ALLIANCE) + { + for (GroupsQueueType::iterator group_itr_tmp = m_queuedGroups[bracketIdTmp][j].begin(); group_itr_tmp != m_queuedGroups[bracketIdTmp][j].end(); ++group_itr_tmp) + { + if ((*group_itr_tmp) == group) + { + bracketId = bracketIdTmp; + group_itr = group_itr_tmp; + // we must store index to be able to erase iterator + index = j; + break; + } + } + } + } + + // player can't be in queue without group, but just in case + if (bracketId == -1) + { + sLog.outError("BattleGroundQueueItem: ERROR Cannot find groupinfo for %s", guid.GetString().c_str()); + return; + } + DEBUG_LOG("BattleGroundQueueItem: Removing %s, from bracket_id %u", guid.GetString().c_str(), (uint32)bracketId); + + // ALL variables are correctly set + // We can ignore leveling up in queue - it should not cause crash + // remove player from group + // if only one player there, remove group + + // remove player queue info from group queue info + GroupQueueInfoPlayers::iterator pitr = group->players.find(guid); + if (pitr != group->players.end()) + group->players.erase(pitr); + + // if invited to bg, and should decrease invited count, then do it + if (decreaseInvitedCount && group->isInvitedToBgInstanceGuid) + { + if (BattleGroundInQueueInfo* bgInstance = queue.GetFreeSlotInstance(group->bgTypeId, group->isInvitedToBgInstanceGuid)) + bgInstance->DecreaseInvitedCount(group->groupTeam); + } + + // remove player queue info + m_queuedPlayers.erase(itr); + + // announce to world if arena team left queue for rated match, show only once + if (group->arenaType != ARENA_TYPE_NONE && group->isRated && group->players.empty() && sWorld.getConfig(CONFIG_BOOL_ARENA_QUEUE_ANNOUNCER_EXIT)) + sWorld.SendWorldText(LANG_ARENA_QUEUE_ANNOUNCE_WORLD_EXIT, group->arenaType, group->arenaType, group->arenaTeamRating); + + // if player leaves queue and he is invited to rated arena match, then he have to loose + if (group->isInvitedToBgInstanceGuid && group->isRated && decreaseInvitedCount) + { + sWorld.GetMessager().AddMessage([arenaTeamId = group->arenaTeamId, guid, opponentRating = group->opponentsTeamRating](World* world) + { + ArenaTeam* at = sObjectMgr.GetArenaTeamById(arenaTeamId); // TODO: One day do all arenateam alterations in bg thread + if (at) + { + DEBUG_LOG("UPDATING memberLost's personal arena rating for %s by opponents rating: %u", guid.GetString().c_str(), opponentRating); + Player* plr = sObjectMgr.GetPlayer(guid); + if (plr) + at->MemberLost(plr, opponentRating); + else + at->OfflineMemberLost(guid, opponentRating); + + at->SaveToDB(); + } + }); + } + + // remove group queue info if needed + if (group->players.empty()) + { + m_queuedGroups[bracketId][index].erase(group_itr); + delete group; + } + // if group wasn't empty, so it wasn't deleted, and player have left a rated + // queue -> everyone from the group should leave too + // don't remove recursively if already invited to bg! + else if (!group->isInvitedToBgInstanceGuid && group->isRated) + { + sWorld.GetMessager().AddMessage([playerGuid = group->players.begin()->first, bgTypeId = group->bgTypeId, arenaType = group->arenaType](World* world) + { + // remove next player, this is recursive + // first send removal information + if (Player* plr2 = sObjectMgr.GetPlayer(playerGuid)) + { + BattleGround* bg = sBattleGroundMgr.GetBattleGroundTemplate(bgTypeId); + BattleGroundQueueTypeId bgQueueTypeId = BattleGroundMgr::BgQueueTypeId(bgTypeId, 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, bg->GetTypeId(), bg->GetClientInstanceId(), bg->IsRated(), bg->GetMapId(), queueSlot, STATUS_NONE, 0, 0, ARENA_TYPE_NONE, TEAM_NONE); + plr2->GetSession()->SendPacket(data); + } + }); + + // then actually delete, this may delete the group as well! + RemovePlayer(queue, group->players.begin()->first, decreaseInvitedCount); + } +} + +/** + Function that returns true when player is in queue and is invited to bgInstanceGuid + + @param player guid + @param battleground instance guid + @param remove time +*/ +bool BattleGroundQueueItem::IsPlayerInvited(ObjectGuid playerGuid, const uint32 bgInstanceGuid, const uint32 removeTime) +{ + QueuedPlayersMap::const_iterator qItr = m_queuedPlayers.find(playerGuid); + return (qItr != m_queuedPlayers.end() + && qItr->second.groupInfo->isInvitedToBgInstanceGuid == bgInstanceGuid + && qItr->second.groupInfo->removeInviteTime == removeTime); +} + +/** + Function that returns player group info data + - returns true when the player is found in queue + + @param player guid + @param group queue info +*/ +bool BattleGroundQueueItem::GetPlayerGroupInfoData(ObjectGuid guid, GroupQueueInfo* queueInfo) +{ + // std::lock_guard g(m_Lock); + QueuedPlayersMap::const_iterator qItr = m_queuedPlayers.find(guid); + if (qItr == m_queuedPlayers.end()) + return false; + + *queueInfo = *(qItr->second.groupInfo); + return true; +} + +/** + Function that invites group to battleground + + @param group queue info + @param battleground + @param team +*/ +bool BattleGroundQueueItem::InviteGroupToBg(GroupQueueInfo* groupInfo, BattleGroundInQueueInfo& queueInfo, Team team) +{ + // set side if needed + if (team == ALLIANCE || team == HORDE) + groupInfo->groupTeam = team; + + if (!groupInfo->isInvitedToBgInstanceGuid) + { + // not yet invited + // set invitation + groupInfo->isInvitedToBgInstanceGuid = queueInfo.GetInstanceId(); + groupInfo->mapId = queueInfo.GetMapId(); + groupInfo->clientInstanceId = queueInfo.GetClientInstanceId(); + BattleGroundTypeId bgTypeId = queueInfo.GetTypeId(); + BattleGroundQueueTypeId bgQueueTypeId = BattleGroundMgr::BgQueueTypeId(bgTypeId, queueInfo.GetArenaType()); + BattleGroundBracketId bracket_id = queueInfo.GetBracketId(); + + groupInfo->removeInviteTime = WorldTimer::getMSTime() + INVITE_ACCEPT_WAIT_TIME; + + // loop through the players + for (auto itr = groupInfo->players.begin(); itr != groupInfo->players.end(); ++itr) + { + sWorld.GetMessager().AddMessage([playerGuid = itr->first, bgQueueTypeId, bgTypeId, isInvited = groupInfo->isInvitedToBgInstanceGuid, clientInstanceId = queueInfo.GetClientInstanceId(), isRated = queueInfo.IsRated(), mapId = queueInfo.GetMapId(), arenaType = groupInfo->arenaType, removeInviteTime = groupInfo->removeInviteTime, instanceId = queueInfo.GetInstanceId(), isBg = queueInfo.IsBattleGround()](World* world) + { + Player* plr = sObjectMgr.GetPlayer(playerGuid); + // if offline, skip him, can happen due to asynchronicity now + if (!plr) + return; + + plr->SetInviteForBattleGroundQueueType(bgQueueTypeId, isInvited); + + // create remind invite events + BgQueueInviteEvent* inviteEvent = new BgQueueInviteEvent(plr->GetObjectGuid(), isInvited, bgTypeId, arenaType, removeInviteTime); + plr->m_events.AddEvent(inviteEvent, plr->m_events.CalculateTime(INVITATION_REMIND_TIME)); + + // create automatic remove events + BgQueueRemoveEvent* removeEvent = new BgQueueRemoveEvent(plr->GetObjectGuid(), isInvited, bgTypeId, bgQueueTypeId, removeInviteTime); + plr->m_events.AddEvent(removeEvent, plr->m_events.CalculateTime(INVITE_ACCEPT_WAIT_TIME)); + + WorldPacket data; + + uint32 queueSlot = plr->GetBattleGroundQueueIndex(bgQueueTypeId); + + DEBUG_LOG("Battleground: invited %s to BG instance %u queueindex %u bgtype %u, I can't help it if they don't press the enter battle button.", plr->GetGuidStr().c_str(), instanceId, queueSlot, bgTypeId); + + // send status packet + sBattleGroundMgr.BuildBattleGroundStatusPacket(data, isBg, bgTypeId, clientInstanceId, isRated, mapId, queueSlot, STATUS_WAIT_JOIN, INVITE_ACCEPT_WAIT_TIME, 0, arenaType, TEAM_NONE); + + plr->GetSession()->SendPacket(data); + }); + + PlayerInvitedToBgUpdateAverageWaitTime(groupInfo, bracket_id); + + // set invited player counters + queueInfo.IncreaseInvitedCount(groupInfo->groupTeam); + // if issues arise due to async state, need to add pending and confirmation + } + return true; + } + + return false; +} + +/** + Method that invites players to an already running battleground + - invitation is based on config file + - large groups are disadvantageous, because they will be kicked first if invitation type = 1 + + @param battleground + @param bracket id +*/ +void BattleGroundQueueItem::FillPlayersToBg(BattleGroundInQueueInfo& queueInfo, BattleGroundBracketId bracketId) +{ + int32 hordeFree = queueInfo.GetFreeSlotsForTeam(HORDE); + int32 aliFree = queueInfo.GetFreeSlotsForTeam(ALLIANCE); + + // iterator for iterating through bg queue + GroupsQueueType::const_iterator Ali_itr = m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE].begin(); + // count of groups in queue - used to stop cycles + uint32 aliCount = m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE].size(); + // index to queue which group is current + uint32 aliIndex = 0; + for (; aliIndex < aliCount && m_selectionPools[TEAM_INDEX_ALLIANCE].AddGroup((*Ali_itr), aliFree, queueInfo.GetClientInstanceId()); ++aliIndex) + ++Ali_itr; + + // the same thing for horde + GroupsQueueType::const_iterator Horde_itr = m_queuedGroups[bracketId][BG_QUEUE_NORMAL_HORDE].begin(); + uint32 hordeCount = m_queuedGroups[bracketId][BG_QUEUE_NORMAL_HORDE].size(); + uint32 hordeIndex = 0; + for (; hordeIndex < hordeCount && m_selectionPools[TEAM_INDEX_HORDE].AddGroup((*Horde_itr), hordeFree, queueInfo.GetClientInstanceId()); ++hordeIndex) + ++Horde_itr; + + // if ofc like BG queue invitation is set in config, then we are happy + if (sWorld.getConfig(CONFIG_UINT32_BATTLEGROUND_INVITATION_TYPE) == 0) + return; + + /* + if we reached this code, then we have to solve NP - complete problem called Subset sum problem + So one solution is to check all possible invitation subgroups, or we can use these conditions: + 1. Last time when BattleGroundQueueItem::Update was executed we invited all possible players - so there is only small possibility + that we will invite now whole queue, because only 1 change has been made to queues from the last BattleGroundQueueItem::Update call + 2. Other thing we should consider is group order in queue + */ + + // At first we need to compare free space in bg and our selection pool + int32 diffAli = aliFree - int32(m_selectionPools[TEAM_INDEX_ALLIANCE].GetPlayerCount()); + int32 diffHorde = hordeFree - int32(m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount()); + while (abs(diffAli - diffHorde) > 1 && (m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount() > 0 || m_selectionPools[TEAM_INDEX_ALLIANCE].GetPlayerCount() > 0)) + { + // each cycle execution we need to kick at least 1 group + if (diffAli < diffHorde) + { + // kick alliance group, add to pool new group if needed + if (m_selectionPools[TEAM_INDEX_ALLIANCE].KickGroup(diffHorde - diffAli)) + { + for (; aliIndex < aliCount && m_selectionPools[TEAM_INDEX_ALLIANCE].AddGroup((*Ali_itr), (aliFree >= diffHorde) ? aliFree - diffHorde : 0, queueInfo.GetClientInstanceId()); ++aliIndex) + ++Ali_itr; + } + // if ali selection is already empty, then kick horde group, but if there are less horde than ali in bg - break; + if (!m_selectionPools[TEAM_INDEX_ALLIANCE].GetPlayerCount()) + { + if (aliFree <= diffHorde + 1) + break; + m_selectionPools[TEAM_INDEX_HORDE].KickGroup(diffHorde - diffAli); + } + } + else + { + // kick horde group, add to pool new group if needed + if (m_selectionPools[TEAM_INDEX_HORDE].KickGroup(diffAli - diffHorde)) + { + for (; hordeIndex < hordeCount && m_selectionPools[TEAM_INDEX_HORDE].AddGroup((*Horde_itr), (hordeFree >= diffAli) ? hordeFree - diffAli : 0, queueInfo.GetClientInstanceId()); ++hordeIndex) + ++Horde_itr; + } + if (!m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount()) + { + if (hordeFree <= diffAli + 1) + break; + m_selectionPools[TEAM_INDEX_ALLIANCE].KickGroup(diffAli - diffHorde); + } + } + + // count diffs after small update + diffAli = aliFree - int32(m_selectionPools[TEAM_INDEX_ALLIANCE].GetPlayerCount()); + diffHorde = hordeFree - int32(m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount()); + } +} + +/** + Method that checks if (premade vs premade) battlegrouns is possible + - then after 30 mins (default) in queue it moves premade group to normal queue + - it tries to invite as much players as it can - to maxPlayersPerTeam, because premade groups have more than minPlayersPerTeam players + + @param bracket id + @param min players per team + @param max players per team +*/ +bool BattleGroundQueueItem::CheckPremadeMatch(BattleGroundBracketId bracketId, uint32 minPlayersPerTeam, uint32 maxPlayersPerTeam) +{ + // check match + if (!m_queuedGroups[bracketId][BG_QUEUE_PREMADE_ALLIANCE].empty() && !m_queuedGroups[bracketId][BG_QUEUE_PREMADE_HORDE].empty()) + { + // start premade match + // if groups aren't invited + GroupsQueueType::const_iterator ali_group, horde_group; + for (ali_group = m_queuedGroups[bracketId][BG_QUEUE_PREMADE_ALLIANCE].begin(); ali_group != m_queuedGroups[bracketId][BG_QUEUE_PREMADE_ALLIANCE].end(); ++ali_group) + if (!(*ali_group)->isInvitedToBgInstanceGuid) + break; + + for (horde_group = m_queuedGroups[bracketId][BG_QUEUE_PREMADE_HORDE].begin(); horde_group != m_queuedGroups[bracketId][BG_QUEUE_PREMADE_HORDE].end(); ++horde_group) + if (!(*horde_group)->isInvitedToBgInstanceGuid) + break; + + if (ali_group != m_queuedGroups[bracketId][BG_QUEUE_PREMADE_ALLIANCE].end() && horde_group != m_queuedGroups[bracketId][BG_QUEUE_PREMADE_HORDE].end()) + { + m_selectionPools[TEAM_INDEX_ALLIANCE].AddGroup((*ali_group), maxPlayersPerTeam, 0); + m_selectionPools[TEAM_INDEX_HORDE].AddGroup((*horde_group), maxPlayersPerTeam, 0); + + // add groups/players from normal queue to size of bigger group + uint32 maxPlayers = std::max(m_selectionPools[TEAM_INDEX_ALLIANCE].GetPlayerCount(), m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount()); + for (uint8 i = 0; i < PVP_TEAM_COUNT; ++i) + { + for (GroupsQueueType::const_iterator itr = m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE + i].begin(); itr != m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE + i].end(); ++itr) + { + // if itr can join BG and player count is less that maxPlayers, then add group to selectionpool + if (!(*itr)->isInvitedToBgInstanceGuid && !m_selectionPools[i].AddGroup((*itr), maxPlayers, 0)) + break; + } + } + + // premade selection pools are set + return true; + } + } + // now check if we can move group from Premade queue to normal queue (timer has expired) or group size lowered!! + // this could be 2 cycles but i'm checking only first team in queue - it can cause problem - + // if first is invited to BG and seconds timer expired, but we can ignore it, because players have only 80 seconds to click to enter bg + // and when they click or after 80 seconds the queue info is removed from queue + uint32 time_before = WorldTimer::getMSTime() - sWorld.getConfig(CONFIG_UINT32_BATTLEGROUND_PREMADE_GROUP_WAIT_FOR_MATCH); + for (uint8 i = 0; i < PVP_TEAM_COUNT; ++i) + { + if (!m_queuedGroups[bracketId][BG_QUEUE_PREMADE_ALLIANCE + i].empty()) + { + GroupsQueueType::iterator itr = m_queuedGroups[bracketId][BG_QUEUE_PREMADE_ALLIANCE + i].begin(); + if (!(*itr)->isInvitedToBgInstanceGuid && ((*itr)->joinTime < time_before || (*itr)->players.size() < minPlayersPerTeam)) + { + // we must insert group to normal queue and erase pointer from premade queue + m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE + i].push_front((*itr)); + m_queuedGroups[bracketId][BG_QUEUE_PREMADE_ALLIANCE + i].erase(itr); + } + } + } + + // selection pools are not set + return false; +} + +/** + Method that tries to create battleground or arena with minPlayersPerTeam against maxPlayersPerTeam + + @param battleground + @param bracket id + @param min players + @param max players +*/ +bool BattleGroundQueueItem::CheckNormalMatch(BattleGroundQueue& queue, BattleGround* bgTemplate, BattleGroundBracketId bracketId, uint32 minPlayers, uint32 maxPlayers) +{ + GroupsQueueType::const_iterator itr_team[PVP_TEAM_COUNT]; + for (uint8 i = 0; i < PVP_TEAM_COUNT; ++i) + { + itr_team[i] = m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE + i].begin(); + for (; itr_team[i] != m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE + i].end(); ++(itr_team[i])) + { + if (!(*(itr_team[i]))->isInvitedToBgInstanceGuid) + { + m_selectionPools[i].AddGroup(*(itr_team[i]), maxPlayers, 0); + if (m_selectionPools[i].GetPlayerCount() >= minPlayers) + break; + } + } + } + + // try to invite same number of players - this cycle may cause longer wait time even if there are enough players in queue, but we want ballanced bg + uint32 j = TEAM_INDEX_ALLIANCE; + if (m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount() < m_selectionPools[TEAM_INDEX_ALLIANCE].GetPlayerCount()) + j = TEAM_INDEX_HORDE; + + if (sWorld.getConfig(CONFIG_UINT32_BATTLEGROUND_INVITATION_TYPE) != 0 + && m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount() >= minPlayers && m_selectionPools[TEAM_INDEX_ALLIANCE].GetPlayerCount() >= minPlayers) + { + // we will try to invite more groups to team with less players indexed by j + ++(itr_team[j]); // this will not cause a crash, because for cycle above reached break; + for (; itr_team[j] != m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE + j].end(); ++(itr_team[j])) + { + if (!(*(itr_team[j]))->isInvitedToBgInstanceGuid) + if (!m_selectionPools[j].AddGroup(*(itr_team[j]), m_selectionPools[(j + 1) % PVP_TEAM_COUNT].GetPlayerCount(), 0)) + break; + } + // do not allow to start bg with more than 2 players more on 1 faction + if (abs((int32)(m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount() - m_selectionPools[TEAM_INDEX_ALLIANCE].GetPlayerCount())) > 2) + return false; + } + + // allow 1v0 if debug bg + if (queue.IsTesting() && bgTemplate->IsBattleGround() && (m_selectionPools[TEAM_INDEX_ALLIANCE].GetPlayerCount() || m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount())) + return true; + + // return true if there are enough players in selection pools - enable to work .debug bg command correctly + return m_selectionPools[TEAM_INDEX_ALLIANCE].GetPlayerCount() >= minPlayers && m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount() >= minPlayers; +} + +/** + Method that will check if we can invite players to same faction skirmish match + + @param bracket id + @param min players +*/ +bool BattleGroundQueueItem::CheckSkirmishForSameFaction(BattleGroundBracketId bracketId, uint32 minPlayersPerTeam) +{ + if (m_selectionPools[TEAM_INDEX_ALLIANCE].GetPlayerCount() < minPlayersPerTeam && m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount() < minPlayersPerTeam) + return false; + + PvpTeamIndex teamIdx = TEAM_INDEX_ALLIANCE; + PvpTeamIndex otherTeamIdx = TEAM_INDEX_HORDE; + Team otherTeamId = HORDE; + + if (m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount() == minPlayersPerTeam) + { + teamIdx = TEAM_INDEX_HORDE; + otherTeamIdx = TEAM_INDEX_ALLIANCE; + otherTeamId = ALLIANCE; + } + + // clear other team's selection + m_selectionPools[otherTeamIdx].Init(); + // store last ginfo pointer + GroupQueueInfo* ginfo = m_selectionPools[teamIdx].selectedGroups.back(); + // set itr_team to group that was added to selection pool latest + GroupsQueueType::iterator itr_team = m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE + teamIdx].begin(); + for (; itr_team != m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE + teamIdx].end(); ++itr_team) + if (ginfo == *itr_team) + break; + + if (itr_team == m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE + teamIdx].end()) + return false; + + GroupsQueueType::iterator itr_team2 = itr_team; + ++itr_team2; + // invite players to other selection pool + for (; itr_team2 != m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE + teamIdx].end(); ++itr_team2) + { + // if selection pool is full then break; + if (!(*itr_team2)->isInvitedToBgInstanceGuid && !m_selectionPools[otherTeamIdx].AddGroup(*itr_team2, minPlayersPerTeam, 0)) + break; + } + + if (m_selectionPools[otherTeamIdx].GetPlayerCount() != minPlayersPerTeam) + return false; + + // here we have correct 2 selections and we need to change one teams team and move selection pool teams to other team's queue + for (GroupsQueueType::iterator itr = m_selectionPools[otherTeamIdx].selectedGroups.begin(); itr != m_selectionPools[otherTeamIdx].selectedGroups.end(); ++itr) + { + // set correct team + (*itr)->groupTeam = otherTeamId; + + // add team to other queue + m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE + otherTeamIdx].push_front(*itr); + + // remove team from old queue + GroupsQueueType::iterator itr2 = itr_team; + ++itr2; + for (; itr2 != m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE + teamIdx].end(); ++itr2) + { + if (*itr2 == *itr) + { + m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE + teamIdx].erase(itr2); + break; + } + } + } + return true; +} + +/** + Method that is called when group is inserted, or player / group is removed from BG Queue - there is only one player's status changed, so we don't use while(true) cycles to invite whole queue + - it must be called after fully adding the members of a group to ensure group joining + - should be called from BattleGround::RemovePlayer function in some cases + + @param bg type id + @param bracket id + @param arena type + @param isRated + @param arenaRating +*/ +void BattleGroundQueueItem::Update(BattleGroundQueue& queue, BattleGroundTypeId bgTypeId, BattleGroundBracketId bracketId, ArenaType arenaType, bool isRated, uint32 arenaRating) +{ + // if no players in queue - do nothing + if (m_queuedGroups[bracketId][BG_QUEUE_PREMADE_ALLIANCE].empty() && + m_queuedGroups[bracketId][BG_QUEUE_PREMADE_HORDE].empty() && + m_queuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE].empty() && + m_queuedGroups[bracketId][BG_QUEUE_NORMAL_HORDE].empty()) + return; + + // battleground with free slot for player should be always in the beggining of the queue + // maybe it would be better to create bgfreeslotqueue for each bracket_id + BgFreeSlotQueueType::iterator next; + auto queueItems = queue.GetFreeSlotQueueItem(bgTypeId); + for (BgFreeSlotQueueType::iterator itr = queueItems.begin(); itr != queueItems.end(); itr = next) + { + BattleGroundInQueueInfo& queueInfo = *itr; + next = itr; + ++next; + // DO NOT allow queue manager to invite new player to arena + if (queueInfo.IsBattleGround() && queueInfo.GetTypeId() == bgTypeId && queueInfo.GetBracketId() == bracketId && + queueInfo.GetStatus() > STATUS_WAIT_QUEUE && queueInfo.GetStatus() < STATUS_WAIT_LEAVE) + { + // and iterator is invalid + + // clear selection pools + m_selectionPools[TEAM_INDEX_ALLIANCE].Init(); + m_selectionPools[TEAM_INDEX_HORDE].Init(); + + // call a function that does the job for us + FillPlayersToBg(queueInfo, bracketId); + + // now everything is set, invite players + for (GroupsQueueType::const_iterator citr = m_selectionPools[TEAM_INDEX_ALLIANCE].selectedGroups.begin(); citr != m_selectionPools[TEAM_INDEX_ALLIANCE].selectedGroups.end(); ++citr) + InviteGroupToBg((*citr), queueInfo, (*citr)->groupTeam); + for (GroupsQueueType::const_iterator citr = m_selectionPools[TEAM_INDEX_HORDE].selectedGroups.begin(); citr != m_selectionPools[TEAM_INDEX_HORDE].selectedGroups.end(); ++citr) + InviteGroupToBg((*citr), queueInfo, (*citr)->groupTeam); + + if (!queueInfo.HasFreeSlots()) + { + // remove BG from BGFreeSlotQueue + queueItems.erase(itr); + sWorld.GetMessager().AddMessage([instanceId = queueInfo.instanceId, typeId = queueInfo.bgTypeId](World* world) + { + if (BattleGround* bg = sBattleGroundMgr.GetBattleGround(instanceId, typeId)) + { + bg->RemovedFromBgFreeSlotQueue(false); + } + }); + } + } + } + + // finished iterating through the bgs with free slots, maybe we need to create a new bg + + BattleGround* bgTemplate = sBattleGroundMgr.GetBattleGroundTemplate(bgTypeId); + if (!bgTemplate) + { + sLog.outError("Battleground: Update: bg template not found for %u", bgTypeId); + return; + } + + // get the min. players per team, properly for larger arenas as well. (must have full teams for arena matches!) + uint32 minPlayersPerTeam = bgTemplate->GetMinPlayersPerTeam(); + uint32 maxPlayersPerTeam = bgTemplate->GetMaxPlayersPerTeam(); + + if (queue.IsTesting()) + minPlayersPerTeam = 1; + + if (bgTemplate->IsArena()) + { + if (queue.IsArenaTesting()) + { + maxPlayersPerTeam = 1; + minPlayersPerTeam = 1; + } + else + { + // this switch can be much shorter + maxPlayersPerTeam = arenaType; + minPlayersPerTeam = arenaType; + /*switch(arenaType) + { + case ARENA_TYPE_2v2: + MaxPlayersPerTeam = 2; + MinPlayersPerTeam = 2; + break; + case ARENA_TYPE_3v3: + MaxPlayersPerTeam = 3; + MinPlayersPerTeam = 3; + break; + case ARENA_TYPE_5v5: + MaxPlayersPerTeam = 5; + MinPlayersPerTeam = 5; + break; + }*/ + } + } + + m_selectionPools[TEAM_INDEX_ALLIANCE].Init(); + m_selectionPools[TEAM_INDEX_HORDE].Init(); + + if (bgTemplate->IsBattleGround()) + { + // check if there is premade against premade match + if (CheckPremadeMatch(bracketId, minPlayersPerTeam, maxPlayersPerTeam)) + { + BattleGround* bgTemplate = sBattleGroundMgr.GetBattleGroundTemplate(bgTypeId); + if (!bgTemplate) + { + sLog.outError("BattleGround: CreateNewBattleGround - bg template not found for %u", bgTypeId); + return; + } + + BattleGroundInQueueInfo bgInfo; + bgInfo.Fill(bgTemplate); + bgInfo.instanceId = sMapMgr.GenerateInstanceId(); + bgInfo.m_clientInstanceId = queue.CreateClientVisibleInstanceId(bgTypeId, bracketId); + + queue.AddBgToFreeSlots(bgInfo); + + // invite those selection pools + for (uint8 i = 0; i < PVP_TEAM_COUNT; ++i) + for (GroupsQueueType::const_iterator citr = m_selectionPools[TEAM_INDEX_ALLIANCE + i].selectedGroups.begin(); citr != m_selectionPools[TEAM_INDEX_ALLIANCE + i].selectedGroups.end(); ++citr) + InviteGroupToBg((*citr), bgInfo, (*citr)->groupTeam); + + // clear structures + m_selectionPools[TEAM_INDEX_ALLIANCE].Init(); + m_selectionPools[TEAM_INDEX_HORDE].Init(); + + sWorld.GetMessager().AddMessage([instanceId = bgInfo.instanceId, clientInstanceId = bgInfo.m_clientInstanceId, bgTypeId, bracketId, allianceCount = bgInfo.GetInvitedCount(ALLIANCE), hordeCount = bgInfo.GetInvitedCount(HORDE)](World* world) + { + // create new battleground + BattleGround* bg2 = sBattleGroundMgr.CreateNewBattleGround(bgTypeId, bracketId, ARENA_TYPE_NONE, false, instanceId, clientInstanceId); + MANGOS_ASSERT(bg2); + bg2->SetInvitedCount(ALLIANCE, allianceCount); + bg2->SetInvitedCount(HORDE, hordeCount); + // start bg + bg2->StartBattleGround(); + }); + } + } + + // 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(queue, bgTemplate, bracketId, minPlayersPerTeam, maxPlayersPerTeam) + || (bgTemplate->IsArena() && CheckSkirmishForSameFaction(bracketId, minPlayersPerTeam))) + { + BattleGround* bgTemplate = sBattleGroundMgr.GetBattleGroundTemplate(bgTypeId); + if (!bgTemplate) + { + sLog.outError("BattleGround: CreateNewBattleGround - bg template not found for %u", bgTypeId); + return; + } + + BattleGroundInQueueInfo bgInfo; + bgInfo.Fill(bgTemplate); + bgInfo.instanceId = sMapMgr.GenerateInstanceId(); + bgInfo.m_clientInstanceId = queue.CreateClientVisibleInstanceId(bgTypeId, bracketId); + + queue.AddBgToFreeSlots(bgInfo); + + // invite those selection pools + for (uint8 i = 0; i < PVP_TEAM_COUNT; ++i) + for (GroupsQueueType::const_iterator citr = m_selectionPools[TEAM_INDEX_ALLIANCE + i].selectedGroups.begin(); citr != m_selectionPools[TEAM_INDEX_ALLIANCE + i].selectedGroups.end(); ++citr) + InviteGroupToBg((*citr), bgInfo, (*citr)->groupTeam); + + sWorld.GetMessager().AddMessage([instanceId = bgInfo.instanceId, clientInstanceId = bgInfo.m_clientInstanceId, bgTypeId, bracketId, allianceCount = bgInfo.GetInvitedCount(ALLIANCE), hordeCount = bgInfo.GetInvitedCount(HORDE)](World* world) + { + // create new battleground + BattleGround* bg2 = sBattleGroundMgr.CreateNewBattleGround(bgTypeId, bracketId, ARENA_TYPE_NONE, false, instanceId, clientInstanceId); + MANGOS_ASSERT(bg2); + bg2->SetInvitedCount(ALLIANCE, allianceCount); + bg2->SetInvitedCount(HORDE, hordeCount); + // start bg + bg2->StartBattleGround(); + }); + } + } + else if (bgTemplate->IsArena()) + { + // found out the minimum and maximum ratings the newly added team should battle against + // arenaRating is the rating of the latest joined team, or 0 + // 0 is on (automatic update call) and we must set it to team's with longest wait time + if (!arenaRating) + { + GroupQueueInfo* front1 = nullptr; + GroupQueueInfo* front2 = nullptr; + if (!m_queuedGroups[bracketId][BG_QUEUE_PREMADE_ALLIANCE].empty()) + { + front1 = m_queuedGroups[bracketId][BG_QUEUE_PREMADE_ALLIANCE].front(); + arenaRating = front1->arenaTeamRating; + } + + if (!m_queuedGroups[bracketId][BG_QUEUE_PREMADE_HORDE].empty()) + { + front2 = m_queuedGroups[bracketId][BG_QUEUE_PREMADE_HORDE].front(); + arenaRating = front2->arenaTeamRating; + } + + if (front1 && front2) + { + if (front1->joinTime < front2->joinTime) + arenaRating = front1->arenaTeamRating; + } + else if (!front1 && !front2) + return; // queues are empty + } + + // set rating range + uint32 arenaMinRating = (arenaRating <= sBattleGroundMgr.GetMaxRatingDifference()) ? 0 : arenaRating - sBattleGroundMgr.GetMaxRatingDifference(); + uint32 arenaMaxRating = arenaRating + sBattleGroundMgr.GetMaxRatingDifference(); + // if max rating difference is set and the time past since server startup is greater than the rating discard time + // (after what time the ratings aren't taken into account when making teams) then + // the discard time is current_time - time_to_discard, teams that joined after that, will have their ratings taken into account + // else leave the discard time on 0, this way all ratings will be discarded + uint32 discardTime = WorldTimer::getMSTime() - sBattleGroundMgr.GetRatingDiscardTimer(); + + // we need to find 2 teams which will play next game + + GroupsQueueType::iterator itr_team[PVP_TEAM_COUNT]; + + // optimalization : --- we dont need to use selection_pools - each update we select max 2 groups + + for (uint8 i = BG_QUEUE_PREMADE_ALLIANCE; i < BG_QUEUE_NORMAL_ALLIANCE; ++i) + { + // take the group that joined first + itr_team[i] = m_queuedGroups[bracketId][i].begin(); + for (; itr_team[i] != m_queuedGroups[bracketId][i].end(); ++(itr_team[i])) + { + // if group match conditions, then add it to pool + if (!(*itr_team[i])->isInvitedToBgInstanceGuid + && (((*itr_team[i])->arenaTeamRating >= arenaMinRating && (*itr_team[i])->arenaTeamRating <= arenaMaxRating) + || (*itr_team[i])->joinTime < discardTime)) + { + m_selectionPools[i].AddGroup((*itr_team[i]), maxPlayersPerTeam, 0); + // break for cycle to be able to start selecting another group from same faction queue + break; + } + } + } + // now we are done if we have 2 groups - ali vs horde! + // if we don't have, we must try to continue search in same queue + // tmp variables are correctly set + // this code isn't much userfriendly - but it is supposed to continue search for mathing group in HORDE queue + if (m_selectionPools[TEAM_INDEX_ALLIANCE].GetPlayerCount() == 0 && m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount()) + { + itr_team[TEAM_INDEX_ALLIANCE] = itr_team[TEAM_INDEX_HORDE]; + ++itr_team[TEAM_INDEX_ALLIANCE]; + + for (; itr_team[TEAM_INDEX_ALLIANCE] != m_queuedGroups[bracketId][BG_QUEUE_PREMADE_HORDE].end(); ++(itr_team[TEAM_INDEX_ALLIANCE])) + { + if (!(*itr_team[TEAM_INDEX_ALLIANCE])->isInvitedToBgInstanceGuid + && (((*itr_team[TEAM_INDEX_ALLIANCE])->arenaTeamRating >= arenaMinRating && (*itr_team[TEAM_INDEX_ALLIANCE])->arenaTeamRating <= arenaMaxRating) + || (*itr_team[TEAM_INDEX_ALLIANCE])->joinTime < discardTime)) + { + m_selectionPools[TEAM_INDEX_ALLIANCE].AddGroup((*itr_team[TEAM_INDEX_ALLIANCE]), maxPlayersPerTeam, 0); + break; + } + } + } + // this code isn't much userfriendly - but it is supposed to continue search for mathing group in ALLIANCE queue + if (m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount() == 0 && m_selectionPools[TEAM_INDEX_ALLIANCE].GetPlayerCount()) + { + itr_team[TEAM_INDEX_HORDE] = itr_team[TEAM_INDEX_ALLIANCE]; + ++itr_team[TEAM_INDEX_HORDE]; + + for (; itr_team[TEAM_INDEX_HORDE] != m_queuedGroups[bracketId][BG_QUEUE_PREMADE_ALLIANCE].end(); ++(itr_team[TEAM_INDEX_HORDE])) + { + if (!(*itr_team[TEAM_INDEX_HORDE])->isInvitedToBgInstanceGuid + && (((*itr_team[TEAM_INDEX_HORDE])->arenaTeamRating >= arenaMinRating && (*itr_team[TEAM_INDEX_HORDE])->arenaTeamRating <= arenaMaxRating) + || (*itr_team[TEAM_INDEX_HORDE])->joinTime < discardTime)) + { + m_selectionPools[TEAM_INDEX_HORDE].AddGroup((*itr_team[TEAM_INDEX_HORDE]), maxPlayersPerTeam, 0); + break; + } + } + } + + // if we have 2 teams, then start new arena and invite players! + if (m_selectionPools[TEAM_INDEX_ALLIANCE].GetPlayerCount() && m_selectionPools[TEAM_INDEX_HORDE].GetPlayerCount()) + { + BattleGround* bgTemplate = sBattleGroundMgr.GetBattleGroundTemplate(bgTypeId); + if (!bgTemplate) + { + sLog.outError("BattleGround: CreateNewBattleGround - bg template not found for %u", bgTypeId); + return; + } + + BattleGroundInQueueInfo bgInfo; + bgInfo.Fill(bgTemplate); + bgInfo.instanceId = sMapMgr.GenerateInstanceId(); + bgInfo.m_clientInstanceId = queue.CreateClientVisibleInstanceId(bgTypeId, bracketId); + bgInfo.isRated = true; + bgInfo.arenaType = arenaType; + + queue.AddBgToFreeSlots(bgInfo); + + GroupQueueInfo* firstGroup = *(itr_team[TEAM_INDEX_ALLIANCE]); + GroupQueueInfo* secondGroup = *(itr_team[TEAM_INDEX_HORDE]); + + firstGroup->opponentsTeamRating = secondGroup->arenaTeamRating; + DEBUG_LOG("Setting oposite teamrating for team %u to %u", firstGroup->arenaTeamId, firstGroup->opponentsTeamRating); + secondGroup->opponentsTeamRating = firstGroup->arenaTeamRating; + DEBUG_LOG("Setting oposite teamrating for team %u to %u", secondGroup->arenaTeamId, secondGroup->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 (firstGroup->groupTeam != ALLIANCE) + { + // add to alliance queue + m_queuedGroups[bracketId][BG_QUEUE_PREMADE_ALLIANCE].push_front(firstGroup); + // erase from horde queue + m_queuedGroups[bracketId][BG_QUEUE_PREMADE_HORDE].erase(itr_team[TEAM_INDEX_ALLIANCE]); + itr_team[TEAM_INDEX_ALLIANCE] = m_queuedGroups[bracketId][BG_QUEUE_PREMADE_ALLIANCE].begin(); + } + + if (secondGroup->groupTeam != HORDE) + { + m_queuedGroups[bracketId][BG_QUEUE_PREMADE_HORDE].push_front(secondGroup); + m_queuedGroups[bracketId][BG_QUEUE_PREMADE_ALLIANCE].erase(itr_team[TEAM_INDEX_HORDE]); + itr_team[TEAM_INDEX_HORDE] = m_queuedGroups[bracketId][BG_QUEUE_PREMADE_HORDE].begin(); + } + + InviteGroupToBg(firstGroup, bgInfo, ALLIANCE); + InviteGroupToBg(secondGroup, bgInfo, HORDE); + + DEBUG_LOG("Starting rated arena match!"); + + sWorld.GetMessager().AddMessage([instanceId = bgInfo.instanceId, clientInstanceId = bgInfo.m_clientInstanceId, arenaType, bgTypeId, bracketId, allianceCount = bgInfo.GetInvitedCount(ALLIANCE), hordeCount = bgInfo.GetInvitedCount(HORDE), firstTeam = firstGroup->groupTeam, firstTeamId = firstGroup->arenaTeamId, secondTeam = secondGroup->groupTeam, secondTeamId = secondGroup->arenaTeamId](World* world) + { + // create new battleground + BattleGround* arena = sBattleGroundMgr.CreateNewBattleGround(bgTypeId, bracketId, arenaType, true, instanceId, clientInstanceId); + MANGOS_ASSERT(arena); + arena->SetInvitedCount(ALLIANCE, allianceCount); + arena->SetInvitedCount(HORDE, hordeCount); + arena->SetArenaTeamIdForTeam(firstTeam, firstTeamId); + arena->SetArenaTeamIdForTeam(secondTeam, secondTeamId); + // start bg + arena->StartBattleGround(); + }); + } + } +} + +/*********************************************************/ +/*** BATTLEGROUND QUEUE EVENTS ***/ +/*********************************************************/ + +/** + Function that executes battleground queue invite event +*/ +bool BgQueueInviteEvent::Execute(uint64 /*e_time*/, uint32 /*p_time*/) +{ + sWorld.GetMessager().AddMessage([event = *this](World* /*world*/) + { + Player* plr = sObjectMgr.GetPlayer(event.m_playerGuid); + // player logged off (we should do nothing, he is correctly removed from queue in another procedure) + if (!plr) + return; + + BattleGround* bg = sBattleGroundMgr.GetBattleGround(event.m_bgInstanceGuid, event.m_bgTypeId); + // if battleground ended and its instance deleted - do nothing + if (!bg) + return; + + bool bgExists = bg; + BattleGroundTypeId bgTypeId = event.m_bgTypeId; + uint32 bgInstanceId = bg->GetClientInstanceId(); + bool isRated = bg->IsRated(); + uint32 mapId = bg->GetMapId(); + + 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 + { + sWorld.GetBGQueue().GetMessager().AddMessage([playerGuid = event.m_playerGuid, bgTypeId, bgInstanceId, isRated, mapId, removeTime = event.m_removeTime, bgQueueTypeId, queueSlot, bgExists, arenaType = event.m_arenaType](BattleGroundQueue* queue) + { + // check if player is invited to this bg + BattleGroundQueueItem& bgQueue = queue->m_battleGroundQueues[bgQueueTypeId]; + if (bgQueue.IsPlayerInvited(playerGuid, bgInstanceId, removeTime)) + { + WorldPacket data; + // we must send remaining time in queue + BattleGroundMgr::BuildBattleGroundStatusPacket(data, bgExists, bgTypeId, bgInstanceId, isRated, mapId, queueSlot, STATUS_WAIT_JOIN, INVITE_ACCEPT_WAIT_TIME - INVITATION_REMIND_TIME, 0, arenaType, TEAM_NONE); + + sWorld.GetMessager().AddMessage([playerGuid, bgQueueTypeId, data](World* /*world*/) + { + if (Player* plr = sObjectMgr.GetPlayer(playerGuid)) + { + plr->GetSession()->SendPacket(data); + } + }); + } + }); + } + }); + return true; // event will be deleted +} + +void BgQueueInviteEvent::Abort(uint64 /*e_time*/) +{ + // do nothing +} + +/** + Function that executes battleground queue remove event + this event has many possibilities when it is executed: + 1. player is in battleground ( he clicked enter on invitation window ) + 2. player left battleground queue and he isn't there any more + 3. player left battleground queue and he joined it again and IsInvitedToBGInstanceGUID = 0 + 4. player left queue and he joined again and he has been invited to same battleground again -> we should not remove him from queue yet + 5. player is invited to bg and he didn't choose what to do and timer expired - only in this condition we should call queue::RemovePlayer + we must remove player in the 5. case even if battleground object doesn't exist! +*/ +bool BgQueueRemoveEvent::Execute(uint64 /*e_time*/, uint32 /*p_time*/) +{ + sWorld.GetMessager().AddMessage([event = *this](World* /*world*/) + { + Player* plr = sObjectMgr.GetPlayer(event.m_playerGuid); + if (!plr) + // player logged off (we should do nothing, he is correctly removed from queue in another procedure) + return; + + BattleGround* bg = sBattleGroundMgr.GetBattleGround(event.m_bgInstanceGuid, event.m_bgTypeId); + // battleground can be deleted already when we are removing queue info + // bg pointer can be nullptr! so use it carefully! + + bool isBattleGround = bg && bg->IsBattleGround(); + bool bgExists = bg; + BattleGroundTypeId bgTypeId = event.m_bgTypeId; + uint32 bgInstanceId = bg ? bg->GetClientInstanceId() : 0; + bool isRated = bg ? bg->IsRated() : false; + uint32 mapId = bg ? bg->GetMapId() : 0; + BattleGroundStatus bgStatus = bg ? bg->GetStatus() : STATUS_NONE; + BattleGroundBracketId bracketId = bg ? bg->GetBracketId() : BG_BRACKET_ID_TEMPLATE; + + uint32 queueSlot = plr->GetBattleGroundQueueIndex(event.m_bgQueueTypeId); + if (queueSlot < PLAYER_MAX_BATTLEGROUND_QUEUES) // player is in queue, or in Battleground + { + sWorld.GetBGQueue().GetMessager().AddMessage([bgQueueTypeId = event.m_bgQueueTypeId, bgTypeId, bgStatus, playerGuid = event.m_playerGuid, instanceGuid = event.m_bgInstanceGuid, removeTime = event.m_removeTime, isBattleGround, queueSlot, bracketId, bgExists, bgInstanceId, isRated, mapId](BattleGroundQueue* queue) + { + // check if player is in queue for this BG and if we are removing his invite event + BattleGroundQueueItem& bgQueue = queue->m_battleGroundQueues[bgQueueTypeId]; + if (bgQueue.IsPlayerInvited(playerGuid, instanceGuid, removeTime)) + { + DEBUG_LOG("Battleground: removing player %u from bg queue for instance %u because of not pressing enter battle in time.", playerGuid.GetCounter(), instanceGuid); + + bgQueue.RemovePlayer(*queue, playerGuid, true); + + // update queues if battleground isn't ended + if (isBattleGround && bgStatus != STATUS_WAIT_LEAVE) + queue->ScheduleQueueUpdate(0, ARENA_TYPE_NONE, bgQueueTypeId, bgTypeId, bracketId); + + WorldPacket data; + BattleGroundMgr::BuildBattleGroundStatusPacket(data, bgExists, bgTypeId, bgInstanceId, isRated, mapId, queueSlot, STATUS_NONE, 0, 0, ARENA_TYPE_NONE, TEAM_NONE); + + sWorld.GetMessager().AddMessage([playerGuid, bgQueueTypeId, data](World* /*world*/) + { + if (Player* plr = sObjectMgr.GetPlayer(playerGuid)) + { + plr->RemoveBattleGroundQueueId(bgQueueTypeId); + plr->GetSession()->SendPacket(data); + } + }); + } + }); + } + }); + + // event will be deleted + return true; +} + +void BgQueueRemoveEvent::Abort(uint64 /*e_time*/) +{ + // do nothing +} + +BattleGroundQueue::BattleGroundQueue() : m_testing(false), m_arenaTesting(false) +{ + +} + +void BattleGroundQueue::Update() +{ + TimePoint previously = sWorld.GetCurrentClockTime(); + InitAutomaticArenaPointDistribution(); + while (!World::IsStopped()) + { + TimePoint now = std::chrono::time_point_cast(Clock::now()); + GetMessager().Execute(this); + + // update scheduled queues + if (!m_queueUpdateScheduler.empty()) + { + std::vector scheduled; + { + // copy vector and clear the other + scheduled = std::vector(m_queueUpdateScheduler); + m_queueUpdateScheduler.clear(); + } + + for (unsigned long long i : scheduled) + { + uint32 arenaRating = i >> 32; + ArenaType arenaType = ArenaType(i >> 24 & 255); + BattleGroundQueueTypeId bgQueueTypeId = BattleGroundQueueTypeId(i >> 16 & 255); + BattleGroundTypeId bgTypeId = BattleGroundTypeId((i >> 8) & 255); + BattleGroundBracketId bracket_id = BattleGroundBracketId(i & 255); + + m_battleGroundQueues[bgQueueTypeId].Update(*this, bgTypeId, bracket_id, arenaType, arenaRating > 0, arenaRating); + } + } + + // if rating difference counts, maybe force-update queues + if (sWorld.getConfig(CONFIG_UINT32_ARENA_MAX_RATING_DIFFERENCE) && sWorld.getConfig(CONFIG_UINT32_ARENA_RATING_DISCARD_TIMER)) + { + // it's time to force update + if (m_nextRatingDiscardUpdate <= now) + { + // forced update for level 70 rated arenas + DEBUG_LOG("BattleGroundMgr: UPDATING ARENA QUEUES"); + m_battleGroundQueues[BATTLEGROUND_QUEUE_2v2].Update(*this, BATTLEGROUND_AA, BG_BRACKET_ID_FIRST, ARENA_TYPE_2v2, true, 0); + m_battleGroundQueues[BATTLEGROUND_QUEUE_3v3].Update(*this, BATTLEGROUND_AA, BG_BRACKET_ID_FIRST, ARENA_TYPE_3v3, true, 0); + m_battleGroundQueues[BATTLEGROUND_QUEUE_5v5].Update(*this, BATTLEGROUND_AA, BG_BRACKET_ID_FIRST, ARENA_TYPE_5v5, true, 0); + + m_nextRatingDiscardUpdate = now + std::chrono::milliseconds(sWorld.getConfig(CONFIG_UINT32_ARENA_RATING_DISCARD_TIMER)); + } + } + + if (sWorld.getConfig(CONFIG_BOOL_ARENA_AUTO_DISTRIBUTE_POINTS)) + { + if (m_nextAutoDistributionTime <= now) + { + sWorld.GetMessager().AddMessage([](World* world) + { + sBattleGroundMgr.DistributeArenaPoints(); // TODO: Is meant to be done as battlegroup, not world + }); + + m_nextAutoDistributionTime = now + std::chrono::seconds(BATTLEGROUND_ARENA_POINT_DISTRIBUTION_DAY * sWorld.getConfig(CONFIG_UINT32_ARENA_AUTO_DISTRIBUTE_INTERVAL_DAYS)); + CharacterDatabase.PExecute("UPDATE saved_variables SET NextArenaPointDistributionTime = '" UI64FMTD "'", uint64(std::chrono::time_point_cast(m_nextAutoDistributionTime).time_since_epoch().count())); + } + } + + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + }; +} + +void BattleGroundQueue::InitAutomaticArenaPointDistribution() +{ + if (sWorld.getConfig(CONFIG_BOOL_ARENA_AUTO_DISTRIBUTE_POINTS)) + { + auto queryResult = CharacterDatabase.Query("SELECT NextArenaPointDistributionTime FROM saved_variables"); + bool save = false; + bool insert = false; + if (!queryResult) // if not set generate time for next wednesday + insert = true; + else + { + uint64 dbTimePoint = (*queryResult)[0].GetUInt64(); + m_nextAutoDistributionTime = TimePoint(std::chrono::seconds(dbTimePoint));; + if (dbTimePoint == 0) // uninitialized + save = true; + else // if time already exists - check for config changes + { + time_t distrTime = std::chrono::system_clock::to_time_t(m_nextAutoDistributionTime); + tm distribTime = *localtime(&distrTime); + if (distribTime.tm_hour != sWorld.getConfig(CONFIG_UINT32_QUEST_DAILY_RESET_HOUR)) + { + TimePoint now = std::chrono::time_point_cast(Clock::now()); + if (now >= m_nextAutoDistributionTime) // if it already expired, do not save and only adjust hour + { + distribTime.tm_hour = sWorld.getConfig(CONFIG_UINT32_QUEST_DAILY_RESET_HOUR); + m_nextAutoDistributionTime = TimePoint(std::chrono::seconds(mktime(&distribTime))); + } + else + save = true; + } + } + } + + if (save || insert) + { + // generate time by config on first server launch + time_t curTime = time(nullptr); + tm localTm = *localtime(&curTime); + localTm.tm_hour = sWorld.getConfig(CONFIG_UINT32_QUEST_DAILY_RESET_HOUR); + localTm.tm_min = 0; + localTm.tm_sec = 0; + localTm.tm_mday += ((7 - localTm.tm_wday + sWorld.getConfig(CONFIG_UINT32_ARENA_FIRST_RESET_DAY)) % 7); + localTm.tm_isdst = -1; + m_nextAutoDistributionTime = TimePoint(std::chrono::seconds(mktime(&localTm))); + + uint64 distributionTime = uint64(std::chrono::time_point_cast(m_nextAutoDistributionTime).time_since_epoch().count()); + if (insert) + CharacterDatabase.PExecute("INSERT INTO saved_variables (NextArenaPointDistributionTime) VALUES ('" UI64FMTD "')", distributionTime); + if (save) + CharacterDatabase.PExecute("UPDATE saved_variables SET NextArenaPointDistributionTime = '" UI64FMTD "'", distributionTime); + } + } +} + +/** + Method that schedules queue update + + @param arena rating + @param arena type + @param battleground queue type id + @param battleground type id + @param bracket id +*/ +void BattleGroundQueue::ScheduleQueueUpdate(uint32 arenaRating, ArenaType arenaType, BattleGroundQueueTypeId bgQueueTypeId, BattleGroundTypeId bgTypeId, BattleGroundBracketId bracketId) +{ + // we will use only 1 number created of bgTypeId and bracket_id + uint64 schedule_id = (uint64(arenaRating) << 32) | (uint64(arenaType) << 24) | (uint64(bgQueueTypeId) << 16) | (uint64(bgTypeId) << 8) | bracketId; + bool found = false; + for (unsigned long long i : m_queueUpdateScheduler) + { + if (i == schedule_id) + { + found = true; + break; + } + } + if (!found) + m_queueUpdateScheduler.push_back(schedule_id); +} + +void BattleGroundQueue::AddBgToFreeSlots(BattleGroundInQueueInfo const& info) +{ + auto& typeIdQueue = m_bgFreeSlotQueue[info.GetTypeId()]; + typeIdQueue.emplace_front(info); +} + +void BattleGroundQueue::RemoveBgFromFreeSlots(BattleGroundTypeId typeId, uint32 instanceId) +{ + auto& typeIdQueue = m_bgFreeSlotQueue[typeId]; + for (auto itr = typeIdQueue.begin(); itr != typeIdQueue.end(); ++itr) + { + if (itr->GetInstanceId() == instanceId) + { + typeIdQueue.erase(itr); + return; + } + } +} + +BgFreeSlotQueueType& BattleGroundQueue::GetFreeSlotQueueItem(BattleGroundTypeId bgTypeId) +{ + return m_bgFreeSlotQueue[bgTypeId]; +} + +BattleGroundInQueueInfo* BattleGroundQueue::GetFreeSlotInstance(BattleGroundTypeId bgTypeId, uint32 instanceId) +{ + auto& queueItem = GetFreeSlotQueueItem(bgTypeId); + auto itr = std::find_if(queueItem.begin(), queueItem.end(), [instanceId](BattleGroundInQueueInfo const& bgInQueue) + { + return bgInQueue.instanceId == instanceId; + }); + if (itr == queueItem.end()) + return nullptr; + return &(*itr); +} + +BattleGroundQueueItem& BattleGroundQueue::GetBattleGroundQueue(BattleGroundQueueTypeId bgQueueTypeId) +{ + return m_battleGroundQueues[bgQueueTypeId]; +} + +void BattleGroundQueue::SetNextRatingDiscardUpdate(std::chrono::milliseconds timePoint) +{ + TimePoint now = std::chrono::time_point_cast(Clock::now());; + m_nextRatingDiscardUpdate = now + timePoint; +} + +/** + Method that builds battleground list data + + @param packet + @param battlemaster guid + @param player + @param battleground type id +*/ +void BattleGroundQueue::BuildBattleGroundListPacket(WorldPacket& data, ObjectGuid guid, uint32 playerLevel, BattleGroundTypeId bgTypeId) const +{ + data.Initialize(SMSG_BATTLEFIELD_LIST); + data << 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(0); // number of bg instances + + uint32 bracket_id = sBattleGroundMgr.GetBattleGroundBracketIdFromLevel(bgTypeId, playerLevel); + ClientBattleGroundIdSet const& ids = m_clientBattleGroundIds[bgTypeId][bracket_id]; + for (std::set::const_iterator itr = ids.begin(); itr != ids.end(); ++itr) + { + data << uint32(*itr); + ++count; + } + data.put(count_pos, count); + } +} + +/** + Function that returns client instance id from battleground type id and bracket id + + @param battleground type id + @param bracket id +*/ +uint32 BattleGroundQueue::CreateClientVisibleInstanceId(BattleGroundTypeId bgTypeId, BattleGroundBracketId bracketId) +{ + if (BattleGroundMgr::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; + ClientBattleGroundIdSet& ids = m_clientBattleGroundIds[bgTypeId][bracketId]; + for (ClientBattleGroundIdSet::const_iterator itr = ids.begin(); itr != ids.end();) + { + if ((++lastId) != *itr) // if there is a gap between the ids, we will break.. + break; + lastId = *itr; + } + ids.insert(lastId + 1); + + return lastId + 1; +} + +void BattleGroundQueue::RemovePlayer(BattleGroundQueueTypeId bgQueueTypeId, ObjectGuid player, bool decreaseInvitedCount) +{ + m_battleGroundQueues[bgQueueTypeId].RemovePlayer(*this, player, decreaseInvitedCount); +} + +void BattleGroundInQueueInfo::DecreaseInvitedCount(Team team) +{ + uint32 count = (team == ALLIANCE) ? --m_invitedAlliance : --m_invitedHorde; + sWorld.GetMessager().AddMessage([bgTypeId = GetTypeId(), instanceId = GetInstanceId(), team, count](World* world) + { + if (BattleGround* bg = sBattleGroundMgr.GetBattleGround(instanceId, bgTypeId)) + bg->SetInvitedCount(team, count); + }); +} + +void BattleGroundInQueueInfo::IncreaseInvitedCount(Team team) +{ + uint32 count = (team == ALLIANCE) ? ++m_invitedAlliance : ++m_invitedHorde; + sWorld.GetMessager().AddMessage([bgTypeId = GetTypeId(), instanceId = GetInstanceId(), team, count](World* world) + { + if (BattleGround* bg = sBattleGroundMgr.GetBattleGround(instanceId, bgTypeId)) + bg->SetInvitedCount(team, count); + }); +} + +void BattleGroundInQueueInfo::Fill(BattleGround* bg) +{ + bgTypeId = bg->GetTypeId(); + instanceId = bg->GetInstanceId(); + isBattleGround = bg->IsBattleGround(); + bracketId = bg->GetBracketId(); + status = bg->GetStatus(); + m_clientInstanceId = bg->GetClientInstanceId(); + mapId = bg->GetMapId(); + arenaType = bg->GetArenaType(); + isRated = bg->IsRated(); + playersInside = bg->GetPlayersSize(); + maxPlayers = bg->GetMaxPlayers(); + m_invitedAlliance = bg->GetInvitedCount(ALLIANCE); + m_invitedHorde = bg->GetInvitedCount(HORDE); + m_maxPlayersPerTeam = bg->GetMaxPlayersPerTeam(); + m_minPlayersPerTeam = bg->GetMinPlayersPerTeam(); +} diff --git a/src/game/BattleGround/BattleGroundQueue.h b/src/game/BattleGround/BattleGroundQueue.h new file mode 100644 index 00000000000..61d71919109 --- /dev/null +++ b/src/game/BattleGround/BattleGroundQueue.h @@ -0,0 +1,294 @@ +/* + * This file is part of the CMaNGOS Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __BATTLEGROUNDQUEUE_H +#define __BATTLEGROUNDQUEUE_H + +#include "Common.h" +#include "BattleGround/BattleGround.h" + +struct GroupQueueInfo; // type predefinition +struct PlayerQueueInfo // stores information for players in queue +{ + uint32 lastOnlineTime; // for tracking and removing offline players from queue after 5 minutes + GroupQueueInfo* groupInfo; // pointer to the associated groupqueueinfo +}; + +typedef std::map GroupQueueInfoPlayers; + +struct AddGroupToQueueInfo +{ + Team team; + std::vector members; // empty when not in group + uint32 clientInstanceId; + uint32 mapId; +}; + +struct GroupQueueInfo // stores information about the group in queue (also used when joined as solo!) +{ + GroupQueueInfoPlayers players; // player queue info map + Team groupTeam; // Player team (ALLIANCE/HORDE) + BattleGroundTypeId bgTypeId; // battleground type id + BattleGroundBracketId bgBracketId; // battleground bracked id + bool isRated; // rated + uint32 mapId; // invited to mapId + uint32 clientInstanceId; // invited to client instance id + ArenaType arenaType; // 2v2, 3v3, 5v5 or 0 when BG + uint32 arenaTeamId; // team id if rated match + uint32 joinTime; // time when group was added + uint32 removeInviteTime; // time when we will remove invite for players in group + uint32 isInvitedToBgInstanceGuid; // was invited to certain BG + uint32 desiredInstanceId; // queued for this instance specifically + uint32 arenaTeamRating; // if rated match, inited to the rating of the team + uint32 opponentsTeamRating; // for rated arena matches +}; + +struct BattleGroundInQueueInfo +{ + BattleGroundTypeId bgTypeId; + uint32 instanceId; + bool isBattleGround; + BattleGroundBracketId bracketId; + BattleGroundStatus status; // only altered in bg and synched here + uint32 m_clientInstanceId; + uint32 mapId; + + ArenaType arenaType; + bool isRated; + + uint32 playersInside; + uint32 maxPlayers; + uint32 m_maxPlayersPerTeam; + uint32 m_minPlayersPerTeam; + + uint32 GetInstanceId() const { return instanceId; } + bool IsBattleGround() const { return isBattleGround; } + bool IsArena() const { return !isBattleGround; } + BattleGroundTypeId GetTypeId() const { return bgTypeId; } + BattleGroundBracketId GetBracketId() const { return bracketId; } + BattleGroundStatus GetStatus() const { return status; } + uint32 GetClientInstanceId() const { return m_clientInstanceId; } + uint32 GetMapId() const { return mapId; } + + ArenaType GetArenaType() const { return arenaType; } + bool IsRated() const { return isRated; } + + uint32 GetPlayersSize() const { return playersInside; } + uint32 GetMaxPlayers() const { return maxPlayers; } + bool HasFreeSlots() const { return GetPlayersSize() < GetMaxPlayers(); } + uint32 GetMaxPlayersPerTeam() const { return m_maxPlayersPerTeam; } + uint32 GetMinPlayersPerTeam() const { return m_minPlayersPerTeam; } + + void DecreaseInvitedCount(Team team); + void IncreaseInvitedCount(Team team); + uint32 GetInvitedCount(Team team) const + { + if (team == ALLIANCE) + return m_invitedAlliance; + return m_invitedHorde; + } + + uint32 GetFreeSlotsForTeam(Team team) const + { + // return free slot count to MaxPlayerPerTeam + if (GetStatus() == STATUS_WAIT_JOIN || GetStatus() == STATUS_IN_PROGRESS) + return (GetInvitedCount(team) < GetMaxPlayersPerTeam()) ? GetMaxPlayersPerTeam() - GetInvitedCount(team) : 0; + + return 0; + } + + void Fill(BattleGround* bg); + + private: + uint32 m_invitedAlliance; + uint32 m_invitedHorde; +}; + +// this container can't be deque, because deque doesn't like removing the last element - if you remove it, it invalidates next iterator and crash appears +typedef std::list BgFreeSlotQueueType; + +class BattleGroundQueue; + +class BattleGroundQueueItem +{ + public: + BattleGroundQueueItem(); + ~BattleGroundQueueItem(); + + void Update(BattleGroundQueue& queue, BattleGroundTypeId /*bgTypeId*/, BattleGroundBracketId /*bracketId*/, ArenaType arenaType = ARENA_TYPE_NONE, bool isRated = false, uint32 arenaRating = 0); + + void FillPlayersToBg(BattleGroundInQueueInfo& queueInfo, BattleGroundBracketId /*bracketId*/); + bool CheckPremadeMatch(BattleGroundBracketId /*bracketId*/, uint32 /*minPlayersPerTeam*/, uint32 /*maxPlayersPerTeam*/); + bool CheckNormalMatch(BattleGroundQueue& queue, BattleGround* /*bgTemplate*/, BattleGroundBracketId /*bracketId*/, uint32 /*minPlayers*/, uint32 /*maxPlayers*/); + bool CheckSkirmishForSameFaction(BattleGroundBracketId /*bracketId*/, uint32 /*minPlayersPerTeam*/); + GroupQueueInfo* AddGroup(ObjectGuid leader, AddGroupToQueueInfo const& /*groupInfo*/, BattleGroundTypeId /*bgTypeId*/, BattleGroundBracketId /*bracketEntry*/, ArenaType /*arenaType*/, bool /*isRated*/, bool /*isPremade*/, uint32 /*instanceId*/, uint32 /*arenaRating*/, uint32 arenaTeamId = 0); + void RemovePlayer(BattleGroundQueue& queue, ObjectGuid guid, bool decreaseInvitedCount); + bool IsPlayerInvited(ObjectGuid /*playerGuid*/, const uint32 /*bgInstanceGuid*/, const uint32 /*removeTime*/); + bool GetPlayerGroupInfoData(ObjectGuid /*guid*/, GroupQueueInfo* /*groupInfo*/); + void PlayerInvitedToBgUpdateAverageWaitTime(GroupQueueInfo* /*groupInfo*/, BattleGroundBracketId /*bracketId*/); + uint32 GetAverageQueueWaitTime(GroupQueueInfo* /*groupInfo*/, BattleGroundBracketId /*bracketId*/); + + private: + typedef std::map QueuedPlayersMap; + QueuedPlayersMap m_queuedPlayers; + + // we need constant add to begin and constant remove / add from the end, therefore deque suits our problem well + typedef std::list GroupsQueueType; + + /* + This two dimensional array is used to store All queued groups + First dimension specifies the bgTypeId + Second dimension specifies the player's group types - + BG_QUEUE_PREMADE_ALLIANCE is used for premade alliance groups and alliance rated arena teams + BG_QUEUE_PREMADE_HORDE is used for premade horde groups and horde rated arena teams + BG_QUEUE_NORMAL_ALLIANCE is used for normal (or small) alliance groups or non-rated arena matches + BG_QUEUE_NORMAL_HORDE is used for normal (or small) horde groups or non-rated arena matches + */ + GroupsQueueType m_queuedGroups[MAX_BATTLEGROUND_BRACKETS][BG_QUEUE_GROUP_TYPES_COUNT]; + + // class to select and invite groups to bg + class SelectionPool + { + public: + SelectionPool() : playerCount(0) {} + void Init(); + bool AddGroup(GroupQueueInfo* ginfo, uint32 desiredCount, uint32 bgInstanceId); + bool KickGroup(uint32 size); + uint32 GetPlayerCount() const { return playerCount; } + GroupsQueueType selectedGroups; + private: + uint32 playerCount; + }; + + // one selection pool for horde, other one for alliance + SelectionPool m_selectionPools[PVP_TEAM_COUNT]; + + bool InviteGroupToBg(GroupQueueInfo* groupInfo, BattleGroundInQueueInfo& queueInfo, Team side); + + uint32 m_waitTimes[PVP_TEAM_COUNT][MAX_BATTLEGROUND_BRACKETS][COUNT_OF_PLAYERS_TO_AVERAGE_WAIT_TIME]; + uint32 m_waitTimeLastPlayer[PVP_TEAM_COUNT][MAX_BATTLEGROUND_BRACKETS]; + uint32 m_sumOfWaitTimes[PVP_TEAM_COUNT][MAX_BATTLEGROUND_BRACKETS]; +}; + +/* + This class is used to invite player to BG again, when minute lasts from his first invitation + it is capable to solve all possibilities +*/ +class BgQueueInviteEvent : public BasicEvent +{ + public: + BgQueueInviteEvent(ObjectGuid playerGuid, uint32 bgInstanceGuid, BattleGroundTypeId bgTypeId, ArenaType arenaType, uint32 removeTime) : + m_playerGuid(playerGuid), m_bgInstanceGuid(bgInstanceGuid), m_bgTypeId(bgTypeId), m_arenaType(arenaType), m_removeTime(removeTime) + { + }; + virtual ~BgQueueInviteEvent() {}; + + virtual bool Execute(uint64 e_time, uint32 p_time) override; + virtual void Abort(uint64 e_time) override; + + private: + ObjectGuid m_playerGuid; + uint32 m_bgInstanceGuid; + BattleGroundTypeId m_bgTypeId; + ArenaType m_arenaType; + uint32 m_removeTime; +}; + +/* + This class is used to remove player from BG queue after 1 minute 20 seconds from first invitation + We must store removeInvite time in case player left queue and joined and is invited again + We must store bgQueueTypeId, because battleground can be deleted already, when player entered it +*/ +class BgQueueRemoveEvent : public BasicEvent +{ + public: + BgQueueRemoveEvent(ObjectGuid playerGuid, uint32 bgInstanceGuid, BattleGroundTypeId bgTypeId, BattleGroundQueueTypeId bgQueueTypeId, uint32 removeTime) + : m_playerGuid(playerGuid), m_bgInstanceGuid(bgInstanceGuid), m_removeTime(removeTime), m_bgTypeId(bgTypeId), m_bgQueueTypeId(bgQueueTypeId) + {} + + virtual ~BgQueueRemoveEvent() {} + + virtual bool Execute(uint64 e_time, uint32 p_time) override; + virtual void Abort(uint64 e_time) override; + + private: + ObjectGuid m_playerGuid; + uint32 m_bgInstanceGuid; + uint32 m_removeTime; + BattleGroundTypeId m_bgTypeId; + BattleGroundQueueTypeId m_bgQueueTypeId; +}; + +class BattleGroundQueue +{ + friend class BgQueueInviteEvent; + friend class BgQueueRemoveEvent; + public: + BattleGroundQueue(); + + void Update(); + + Messager& GetMessager() { return m_messager; } + + void InitAutomaticArenaPointDistribution(); + + void ScheduleQueueUpdate(uint32 /*arenaRating*/, ArenaType /*arenaType*/, BattleGroundQueueTypeId /*bgQueueTypeId*/, BattleGroundTypeId /*bgTypeId*/, BattleGroundBracketId /*bracketId*/); + + void AddBgToFreeSlots(BattleGroundInQueueInfo const& info); + void RemoveBgFromFreeSlots(BattleGroundTypeId typeId, uint32 instanceId); + + void RemovePlayer(BattleGroundQueueTypeId bgQueueTypeId, ObjectGuid player, bool decreaseInvitedCount); + + BgFreeSlotQueueType& GetFreeSlotQueueItem(BattleGroundTypeId bgTypeId); + BattleGroundInQueueInfo* GetFreeSlotInstance(BattleGroundTypeId bgTypeId, uint32 instanceId); + BattleGroundQueueItem& GetBattleGroundQueue(BattleGroundQueueTypeId bgQueueTypeId); + + void SetNextRatingDiscardUpdate(std::chrono::milliseconds timePoint); + + bool IsTesting() const { return m_testing; } + void SetTesting(bool state) { m_testing = state; } + bool IsArenaTesting() const { return m_arenaTesting; } + void SetArenaTesting(bool state) { m_arenaTesting = state; } + + uint32 CreateClientVisibleInstanceId(BattleGroundTypeId /*bgTypeId*/, BattleGroundBracketId /*bracketId*/); + void DeleteClientVisibleInstanceId(BattleGroundTypeId bgTypeId, BattleGroundBracketId bracketId, uint32 clientInstanceId) + { + m_clientBattleGroundIds[bgTypeId][bracketId].erase(clientInstanceId); + } + + void BuildBattleGroundListPacket(WorldPacket& data, ObjectGuid guid, uint32 playerLevel, BattleGroundTypeId bgTypeId) const; + private: + BattleGroundQueueItem m_battleGroundQueues[MAX_BATTLEGROUND_QUEUE_TYPES]; + + BgFreeSlotQueueType m_bgFreeSlotQueue[MAX_BATTLEGROUND_TYPE_ID]; + + std::vector m_queueUpdateScheduler; + TimePoint m_nextRatingDiscardUpdate; + TimePoint m_nextAutoDistributionTime; + TimePoint m_autoDistributionTimeChecker; + + Messager m_messager; + + bool m_testing; + bool m_arenaTesting; + + typedef std::set ClientBattleGroundIdSet; + ClientBattleGroundIdSet m_clientBattleGroundIds[MAX_BATTLEGROUND_TYPE_ID][MAX_BATTLEGROUND_BRACKETS]; // the instanceids just visible for the client +}; + +#endif \ No newline at end of file diff --git a/src/game/Entities/Creature.cpp b/src/game/Entities/Creature.cpp index 647c835a302..f9e77f68d4a 100644 --- a/src/game/Entities/Creature.cpp +++ b/src/game/Entities/Creature.cpp @@ -685,7 +685,7 @@ uint32 Creature::ChooseDisplayId(const CreatureInfo* cinfo, const CreatureData* { if (cinfo->DisplayId[i]) { - if (roll < cinfo->DisplayIdProbability[i]) + if (roll < int32(cinfo->DisplayIdProbability[i])) { display_id = cinfo->DisplayId[i]; break; diff --git a/src/game/Entities/Player.cpp b/src/game/Entities/Player.cpp index d8efbf3daaa..83b3cf87e4a 100644 --- a/src/game/Entities/Player.cpp +++ b/src/game/Entities/Player.cpp @@ -20044,43 +20044,6 @@ bool Player::GetBGAccessByLevel(BattleGroundTypeId bgTypeId) const return true; } -uint32 Player::GetMinLevelForBattleGroundBracketId(BattleGroundBracketId bracket_id, BattleGroundTypeId bgTypeId) -{ - if (bracket_id < 1) - return 0; - - if (bracket_id > BG_BRACKET_ID_LAST) - bracket_id = BG_BRACKET_ID_LAST; - - BattleGround* bg = sBattleGroundMgr.GetBattleGroundTemplate(bgTypeId); - assert(bg); - return 10 * bracket_id + bg->GetMinLevel(); -} - -uint32 Player::GetMaxLevelForBattleGroundBracketId(BattleGroundBracketId bracket_id, BattleGroundTypeId bgTypeId) -{ - if (bracket_id >= BG_BRACKET_ID_LAST) - return 255; // hardcoded max level - - // for example EOTS - min level 61 but max level for bracket 69 - return (GetMinLevelForBattleGroundBracketId(bracket_id, bgTypeId) / 10 * 10) + 9; -} - -BattleGroundBracketId Player::GetBattleGroundBracketIdFromLevel(BattleGroundTypeId bgTypeId) const -{ - BattleGround* bg = sBattleGroundMgr.GetBattleGroundTemplate(bgTypeId); - assert(bg); - if (GetLevel() < bg->GetMinLevel()) - return BG_BRACKET_ID_FIRST; - - // for example EOTS - min level 61 but max level for bracket 69 - uint32 bracket_id = (GetLevel() - (bg->GetMinLevel() / 10 * 10)) / 10; - if (bracket_id > MAX_BATTLEGROUND_BRACKETS) - return BG_BRACKET_ID_LAST; - - return BattleGroundBracketId(bracket_id); -} - float Player::GetReputationPriceDiscount(Creature const* creature) const { return GetReputationPriceDiscount(creature->GetFactionTemplateEntry()); diff --git a/src/game/Entities/Player.h b/src/game/Entities/Player.h index 13ffafe0852..6bd0ecea12f 100644 --- a/src/game/Entities/Player.h +++ b/src/game/Entities/Player.h @@ -41,6 +41,7 @@ #include "Server/SQLStorages.h" #include "Loot/LootMgr.h" #include "Cinematics/CinematicMgr.h" +#include "BattleGround/BattleGroundDefines.h" #include #include @@ -1916,10 +1917,6 @@ class Player : public Unit BattleGroundTypeId GetBattleGroundTypeId() const { return m_bgData.bgTypeID; } BattleGround* GetBattleGround() const; - static uint32 GetMinLevelForBattleGroundBracketId(BattleGroundBracketId bracket_id, BattleGroundTypeId bgTypeId); - static uint32 GetMaxLevelForBattleGroundBracketId(BattleGroundBracketId bracket_id, BattleGroundTypeId bgTypeId); - BattleGroundBracketId GetBattleGroundBracketIdFromLevel(BattleGroundTypeId bgTypeId) const; - bool InBattleGroundQueue() const { for (auto i : m_bgBattleGroundQueueID) diff --git a/src/game/Globals/SharedDefines.h b/src/game/Globals/SharedDefines.h index c41dbf562fe..35a3683a060 100644 --- a/src/game/Globals/SharedDefines.h +++ b/src/game/Globals/SharedDefines.h @@ -1889,31 +1889,6 @@ enum BanReturn BAN_NOTFOUND }; -// indexes of BattlemasterList.dbc -enum BattleGroundTypeId -{ - BATTLEGROUND_TYPE_NONE = 0, - BATTLEGROUND_AV = 1, - BATTLEGROUND_WS = 2, - BATTLEGROUND_AB = 3, - BATTLEGROUND_NA = 4, - BATTLEGROUND_BE = 5, - BATTLEGROUND_AA = 6, // all arenas - BATTLEGROUND_EY = 7, - BATTLEGROUND_RL = 8 -}; -#define MAX_BATTLEGROUND_TYPE_ID 9 - -enum ArenaType -{ - ARENA_TYPE_NONE = 0, // used for mark non-arenas or problematic cases - ARENA_TYPE_2v2 = 2, - ARENA_TYPE_3v3 = 3, - ARENA_TYPE_5v5 = 5 -}; - -inline bool IsArenaTypeValid(ArenaType type) { return type == ARENA_TYPE_2v2 || type == ARENA_TYPE_3v3 || type == ARENA_TYPE_5v5; } - enum MailResponseType { MAIL_SEND = 0, diff --git a/src/game/Groups/Group.cpp b/src/game/Groups/Group.cpp index 09cf5f63628..1c402b90121 100644 --- a/src/game/Groups/Group.cpp +++ b/src/game/Groups/Group.cpp @@ -32,6 +32,7 @@ #include "Maps/MapManager.h" #include "Maps/MapPersistentStateMgr.h" #include "Spells/SpellAuras.h" +#include "BattleGround/BattleGroundMgr.h" #ifdef BUILD_DEPRECATED_PLAYERBOT #include "PlayerBot/Base/PlayerbotMgr.h" #endif @@ -1232,7 +1233,7 @@ uint32 Group::CanJoinBattleGroundQueue(BattleGroundTypeId bgTypeId, BattleGround if (!reference) return BG_JOIN_ERR_OFFLINE_MEMBER; - BattleGroundBracketId bracket_id = reference->GetBattleGroundBracketIdFromLevel(bgTypeId); + BattleGroundBracketId bracket_id = sBattleGroundMgr.GetBattleGroundBracketIdFromLevel(bgTypeId, reference->GetLevel()); uint32 arenaTeamId = reference->GetArenaTeamId(arenaSlot); Team team = reference->GetTeam(); @@ -1247,7 +1248,7 @@ uint32 Group::CanJoinBattleGroundQueue(BattleGroundTypeId bgTypeId, BattleGround if (member->GetTeam() != team) return BG_JOIN_ERR_MIXED_FACTION; // not in the same battleground level bracket, don't let join - if (member->GetBattleGroundBracketIdFromLevel(bgTypeId) != bracket_id) + if (sBattleGroundMgr.GetBattleGroundBracketIdFromLevel(bgTypeId, member->GetLevel()) != bracket_id) return BG_JOIN_ERR_MIXED_LEVELS; // don't let join rated matches if the arena team id doesn't match if (isRated && member->GetArenaTeamId(arenaSlot) != arenaTeamId) diff --git a/src/game/Maps/MapManager.cpp b/src/game/Maps/MapManager.cpp index d6c561ed266..c05f07a1b71 100644 --- a/src/game/Maps/MapManager.cpp +++ b/src/game/Maps/MapManager.cpp @@ -27,6 +27,7 @@ #include "Grids/CellImpl.h" #include "Globals/ObjectMgr.h" #include "Maps/MapWorkers.h" +#include "BattleGround/BattleGroundMgr.h" #include #define CLASS_LOCK MaNGOS::ClassLevelLockable @@ -144,12 +145,12 @@ Map* MapManager::CreateMap(uint32 id, const WorldObject* obj) return m; } -Map* MapManager::CreateBgMap(uint32 mapid, BattleGround* bg) +Map* MapManager::CreateBgMap(uint32 mapid, uint32 instanceId, BattleGround* bg) { sTerrainMgr.LoadTerrain(mapid); Guard _guard(*this); - return CreateBattleGroundMap(mapid, sMapMgr.GenerateInstanceId(), bg); + return CreateBattleGroundMap(mapid, instanceId, bg); } Map* MapManager::FindMap(uint32 mapid, uint32 instanceId) const @@ -384,7 +385,7 @@ BattleGroundMap* MapManager::CreateBattleGroundMap(uint32 id, uint32 InstanceId, DEBUG_LOG("MapInstanced::CreateBattleGroundMap: instance:%d for map:%d and bgType:%d created.", InstanceId, id, bg->GetTypeId()); uint8 spawnMode = uint8(DUNGEON_DIFFICULTY_NORMAL); - if (bg->GetTypeId() == BATTLEGROUND_AV && Player::GetMinLevelForBattleGroundBracketId(bg->GetBracketId(), bg->GetTypeId()) >= 61) + if (bg->GetTypeId() == BATTLEGROUND_AV && sBattleGroundMgr.GetMinLevelForBattleGroundBracketId(bg->GetBracketId(), bg->GetTypeId()) >= 61) spawnMode = uint8(DUNGEON_DIFFICULTY_HEROIC); BattleGroundMap* map = new BattleGroundMap(id, i_gridCleanUpDelay, InstanceId, spawnMode); diff --git a/src/game/Maps/MapManager.h b/src/game/Maps/MapManager.h index 5b91268a7c5..f671be33fae 100644 --- a/src/game/Maps/MapManager.h +++ b/src/game/Maps/MapManager.h @@ -65,7 +65,7 @@ class MapManager : public MaNGOS::Singleton i_MaxInstanceId; MapUpdater m_updater; }; diff --git a/src/game/Server/WorldSession.cpp b/src/game/Server/WorldSession.cpp index 929efc755cb..eb8ecdf959f 100644 --- a/src/game/Server/WorldSession.cpp +++ b/src/game/Server/WorldSession.cpp @@ -702,7 +702,10 @@ void WorldSession::LogoutPlayer() if (BattleGroundQueueTypeId bgQueueTypeId = _player->GetBattleGroundQueueTypeId(i)) { _player->RemoveBattleGroundQueueId(bgQueueTypeId); - sBattleGroundMgr.m_battleGroundQueues[ bgQueueTypeId ].RemovePlayer(_player->GetObjectGuid(), true); + sWorld.GetBGQueue().GetMessager().AddMessage([bgQueueTypeId, playerGuid = _player->GetObjectGuid()](BattleGroundQueue* queue) + { + queue->RemovePlayer(bgQueueTypeId, playerGuid, true); + }); } } diff --git a/src/game/Server/WorldSession.h b/src/game/Server/WorldSession.h index 61d37bd0ce7..503ce3c9bc6 100644 --- a/src/game/Server/WorldSession.h +++ b/src/game/Server/WorldSession.h @@ -32,6 +32,7 @@ #include "WorldSocket.h" #include "Multithreading/Messager.h" #include "LFG/LFGDefines.h" +#include "BattleGround/BattleGroundDefines.h" #include #include diff --git a/src/game/World/World.cpp b/src/game/World/World.cpp index 0a40071ea72..aa8c1d2a349 100644 --- a/src/game/World/World.cpp +++ b/src/game/World/World.cpp @@ -160,6 +160,7 @@ World::~World() MMAP::MMapFactory::clear(); m_lfgQueueThread.join(); + m_bgQueueThread.join(); } /// Cleanups before world stop @@ -924,6 +925,8 @@ void World::SetInitialWorldSettings() ///- Remove the bones (they should not exist in DB though) and old corpses after a restart CharacterDatabase.PExecute("DELETE FROM corpse WHERE corpse_type = '0' OR time < (" _UNIXTIME_ "-'%u')", 3 * DAY); + m_bgQueue.SetNextRatingDiscardUpdate(std::chrono::milliseconds(sWorld.getConfig(CONFIG_UINT32_ARENA_RATING_DISCARD_TIMER))); + /// load spell_dbc first! dbc's need them sLog.outString("Loading spell_template..."); sObjectMgr.LoadSpellTemplate(); @@ -1416,7 +1419,7 @@ void World::SetInitialWorldSettings() ///- Initialize Battlegrounds sLog.outString("Starting BattleGround System"); sBattleGroundMgr.CreateInitialBattleGrounds(); - sBattleGroundMgr.InitAutomaticArenaPointDistribution(); + m_bgQueue.InitAutomaticArenaPointDistribution(); CheckLootTemplates_Reference(ids_set); sLog.outString("Deleting expired bans..."); @@ -2891,3 +2894,11 @@ void World::StartLFGQueueThread() m_lfgQueue.Update(); }); } + +void World::StartBGQueueThread() +{ + m_bgQueueThread = std::thread([&]() + { + m_bgQueue.Update(); + }); +} diff --git a/src/game/World/World.h b/src/game/World/World.h index 57fc46e5109..7a903d30cca 100644 --- a/src/game/World/World.h +++ b/src/game/World/World.h @@ -31,6 +31,7 @@ #include "Multithreading/Messager.h" #include "Globals/GraveyardManager.h" #include "LFG/LFGQueue.h" +#include "BattleGround/BattleGroundQueue.h" #include #include @@ -682,7 +683,9 @@ class World void SendGMTextFlags(uint32 accountFlag, int32 stringId, std::string type, const char* message); LFGQueue& GetLFGQueue() { return m_lfgQueue; } + BattleGroundQueue& GetBGQueue() { return m_bgQueue; } void StartLFGQueueThread(); + void StartBGQueueThread(); protected: void _UpdateGameTime(); // callback for UpdateRealmCharacters @@ -811,9 +814,11 @@ class World GraveyardManager m_graveyardManager; - // Housing this here but logically it is completely asynchronous - TODO: Separate this and unify with BG queue + // Housing this here but logically it is completely asynchronous LFGQueue m_lfgQueue; std::thread m_lfgQueueThread; + BattleGroundQueue m_bgQueue; + std::thread m_bgQueueThread; }; extern uint32 realmID; diff --git a/src/mangosd/Master.cpp b/src/mangosd/Master.cpp index 69b4cf7dd75..a8334d7b2c0 100644 --- a/src/mangosd/Master.cpp +++ b/src/mangosd/Master.cpp @@ -150,6 +150,7 @@ int Master::Run() } sWorld.StartLFGQueueThread(); + sWorld.StartBGQueueThread(); MaNGOS::Thread* cliThread = nullptr;