mirror of
https://github.com/TrinityCore/TrinityCore.git
synced 2026-01-15 23:20:36 +01:00
1573 lines
56 KiB
C++
1573 lines
56 KiB
C++
/*
|
|
* This file is part of the TrinityCore 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, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "Battleground.h"
|
|
#include "BattlegroundMgr.h"
|
|
#include "BattlegroundPackets.h"
|
|
#include "BattlegroundScore.h"
|
|
#include "BattlegroundScript.h"
|
|
#include "ChatTextBuilder.h"
|
|
#include "Creature.h"
|
|
#include "CreatureTextMgr.h"
|
|
#include "DatabaseEnv.h"
|
|
#include "DB2Stores.h"
|
|
#include "Formulas.h"
|
|
#include "GameEventSender.h"
|
|
#include "GameTime.h"
|
|
#include "GridNotifiersImpl.h"
|
|
#include "Group.h"
|
|
#include "Guild.h"
|
|
#include "GuildMgr.h"
|
|
#include "KillRewarder.h"
|
|
#include "Language.h"
|
|
#include "Log.h"
|
|
#include "Map.h"
|
|
#include "MapUtils.h"
|
|
#include "MiscPackets.h"
|
|
#include "ObjectAccessor.h"
|
|
#include "ObjectMgr.h"
|
|
#include "Player.h"
|
|
#include "ReputationMgr.h"
|
|
#include "SpellAuras.h"
|
|
#include "TemporarySummon.h"
|
|
#include "Transport.h"
|
|
#include "Util.h"
|
|
#include "WorldStateMgr.h"
|
|
#include <cstdarg>
|
|
|
|
template<class Do>
|
|
void Battleground::BroadcastWorker(Do& _do)
|
|
{
|
|
for (BattlegroundPlayerMap::const_iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr)
|
|
if (Player* player = _GetPlayer(itr, "BroadcastWorker"))
|
|
_do(player);
|
|
}
|
|
|
|
Battleground::Battleground(BattlegroundTemplate const* battlegroundTemplate) : _battlegroundTemplate(battlegroundTemplate), _pvpDifficultyEntry(nullptr), _pvpStatIds(nullptr), _preparationStartTime(0)
|
|
{
|
|
ASSERT(_battlegroundTemplate, "Nonexisting Battleground Template passed to battleground ctor!");
|
|
|
|
m_InstanceID = 0;
|
|
m_Status = STATUS_NONE;
|
|
m_ClientInstanceID = 0;
|
|
m_EndTime = 0;
|
|
m_InvitedAlliance = 0;
|
|
m_InvitedHorde = 0;
|
|
m_ArenaType = 0;
|
|
_winnerTeamId = PVP_TEAM_NEUTRAL;
|
|
m_StartTime = 0;
|
|
m_ResetStatTimer = 0;
|
|
m_ValidStartPositionTimer = 0;
|
|
m_Events = 0;
|
|
m_StartDelayTime = 0;
|
|
m_IsRated = false;
|
|
m_InBGFreeSlotQueue = false;
|
|
m_SetDeleteThis = false;
|
|
|
|
m_Map = nullptr;
|
|
|
|
m_ArenaTeamMMR[TEAM_ALLIANCE] = 0;
|
|
m_ArenaTeamMMR[TEAM_HORDE] = 0;
|
|
|
|
m_BgRaids[TEAM_ALLIANCE] = nullptr;
|
|
m_BgRaids[TEAM_HORDE] = nullptr;
|
|
|
|
m_PlayersCount[TEAM_ALLIANCE] = 0;
|
|
m_PlayersCount[TEAM_HORDE] = 0;
|
|
|
|
m_TeamScores[TEAM_ALLIANCE] = 0;
|
|
m_TeamScores[TEAM_HORDE] = 0;
|
|
|
|
m_PrematureCountDown = false;
|
|
m_PrematureCountDownTimer = 0;
|
|
|
|
m_LastPlayerPositionBroadcast = 0;
|
|
|
|
StartDelayTimes[BG_STARTING_EVENT_FIRST] = BG_START_DELAY_2M;
|
|
StartDelayTimes[BG_STARTING_EVENT_SECOND] = BG_START_DELAY_1M;
|
|
StartDelayTimes[BG_STARTING_EVENT_THIRD] = BG_START_DELAY_30S;
|
|
StartDelayTimes[BG_STARTING_EVENT_FOURTH] = BG_START_DELAY_NONE;
|
|
|
|
StartMessageIds[BG_STARTING_EVENT_FIRST] = BG_TEXT_START_TWO_MINUTES;
|
|
StartMessageIds[BG_STARTING_EVENT_SECOND] = BG_TEXT_START_ONE_MINUTE;
|
|
StartMessageIds[BG_STARTING_EVENT_THIRD] = BG_TEXT_START_HALF_MINUTE;
|
|
StartMessageIds[BG_STARTING_EVENT_FOURTH] = BG_TEXT_BATTLE_HAS_BEGUN;
|
|
}
|
|
|
|
Battleground::Battleground(Battleground const&) = default;
|
|
|
|
Battleground::~Battleground()
|
|
{
|
|
// unload map
|
|
if (m_Map)
|
|
{
|
|
m_Map->UnloadAll(); // unload all objects (they may hold a reference to bg in their ZoneScript pointer)
|
|
m_Map->SetUnload(); // mark for deletion by MapManager
|
|
|
|
//unlink to prevent crash, always unlink all pointer reference before destruction
|
|
m_Map->SetBG(nullptr);
|
|
m_Map = nullptr;
|
|
}
|
|
// remove from bg free slot queue
|
|
RemoveFromBGFreeSlotQueue();
|
|
|
|
for (BattlegroundScoreMap::const_iterator itr = PlayerScores.begin(); itr != PlayerScores.end(); ++itr)
|
|
delete itr->second;
|
|
|
|
_playerPositions.clear();
|
|
}
|
|
|
|
void Battleground::Update(uint32 diff)
|
|
{
|
|
if (!PreUpdateImpl(diff))
|
|
return;
|
|
|
|
if (!GetPlayersSize())
|
|
{
|
|
//BG is empty
|
|
// if there are no players invited, delete BG
|
|
// this will delete arena or bg object, where any player entered
|
|
// [[ but if you use battleground object again (more battles possible to be played on 1 instance)
|
|
// then this condition should be removed and code:
|
|
// if (!GetInvitedCount(HORDE) && !GetInvitedCount(ALLIANCE))
|
|
// AddToFreeBGObjectsQueue(); // not yet implemented
|
|
// should be used instead of current
|
|
// ]]
|
|
// Battleground Template instance cannot be updated, because it would be deleted
|
|
if (!GetInvitedCount(HORDE) && !GetInvitedCount(ALLIANCE))
|
|
m_SetDeleteThis = true;
|
|
return;
|
|
}
|
|
|
|
switch (GetStatus())
|
|
{
|
|
case STATUS_WAIT_JOIN:
|
|
if (GetPlayersSize())
|
|
{
|
|
_ProcessJoin(diff);
|
|
_CheckSafePositions(diff);
|
|
}
|
|
break;
|
|
case STATUS_IN_PROGRESS:
|
|
_ProcessOfflineQueue();
|
|
_ProcessPlayerPositionBroadcast(diff);
|
|
// after 47 minutes without one team losing, the arena closes with no winner and no rating change
|
|
if (isArena())
|
|
{
|
|
if (GetElapsedTime() >= 47 * MINUTE*IN_MILLISECONDS)
|
|
{
|
|
EndBattleground(TEAM_OTHER);
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (sBattlegroundMgr->GetPrematureFinishTime() && (GetPlayersCountByTeam(ALLIANCE) < GetMinPlayersPerTeam() || GetPlayersCountByTeam(HORDE) < GetMinPlayersPerTeam()))
|
|
_ProcessProgress(diff);
|
|
else if (m_PrematureCountDown)
|
|
m_PrematureCountDown = false;
|
|
}
|
|
break;
|
|
case STATUS_WAIT_LEAVE:
|
|
_ProcessLeave(diff);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Update start time and reset stats timer
|
|
SetElapsedTime(GetElapsedTime() + diff);
|
|
if (GetStatus() == STATUS_WAIT_JOIN)
|
|
m_ResetStatTimer += diff;
|
|
|
|
PostUpdateImpl(diff);
|
|
}
|
|
|
|
inline void Battleground::_CheckSafePositions(uint32 diff)
|
|
{
|
|
float maxDist = GetStartMaxDist();
|
|
if (!maxDist)
|
|
return;
|
|
|
|
m_ValidStartPositionTimer += diff;
|
|
if (m_ValidStartPositionTimer >= CHECK_PLAYER_POSITION_INVERVAL)
|
|
{
|
|
m_ValidStartPositionTimer = 0;
|
|
|
|
for (BattlegroundPlayerMap::const_iterator itr = GetPlayers().begin(); itr != GetPlayers().end(); ++itr)
|
|
{
|
|
if (Player* player = ObjectAccessor::FindPlayer(itr->first))
|
|
{
|
|
if (player->IsGameMaster())
|
|
continue;
|
|
|
|
Position pos = player->GetPosition();
|
|
WorldSafeLocsEntry const* startPos = GetTeamStartPosition(Battleground::GetTeamIndexByTeamId(player->GetBGTeam()));
|
|
if (pos.GetExactDistSq(startPos->Loc) > maxDist)
|
|
{
|
|
TC_LOG_DEBUG("bg.battleground", "BATTLEGROUND: Sending {} back to start location (map: {}) (possible exploit)", player->GetName(), GetMapId());
|
|
player->TeleportTo(startPos->Loc);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Battleground::_ProcessPlayerPositionBroadcast(uint32 diff)
|
|
{
|
|
m_LastPlayerPositionBroadcast += diff;
|
|
if (m_LastPlayerPositionBroadcast >= PLAYER_POSITION_UPDATE_INTERVAL)
|
|
{
|
|
m_LastPlayerPositionBroadcast = 0;
|
|
|
|
WorldPackets::Battleground::BattlegroundPlayerPositions playerPositions;
|
|
|
|
for (WorldPackets::Battleground::BattlegroundPlayerPosition& playerPosition : _playerPositions)
|
|
{
|
|
// Update position data if we found player.
|
|
if (Player* player = ObjectAccessor::GetPlayer(GetBgMap(), playerPosition.Guid))
|
|
playerPosition.Pos = player->GetPosition();
|
|
|
|
playerPositions.FlagCarriers.push_back(playerPosition);
|
|
}
|
|
|
|
SendPacketToAll(playerPositions.Write());
|
|
}
|
|
}
|
|
|
|
inline void Battleground::_ProcessOfflineQueue()
|
|
{
|
|
// remove offline players from bg after 5 minutes
|
|
if (!m_OfflineQueue.empty())
|
|
{
|
|
BattlegroundPlayerMap::iterator itr = m_Players.find(*(m_OfflineQueue.begin()));
|
|
if (itr != m_Players.end())
|
|
{
|
|
if (itr->second.OfflineRemoveTime <= GameTime::GetGameTime())
|
|
{
|
|
RemovePlayerAtLeave(itr->first, true, true);// remove player from BG
|
|
m_OfflineQueue.pop_front(); // remove from offline queue
|
|
//do not use itr for anything, because it is erased in RemovePlayerAtLeave()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Team Battleground::GetPrematureWinner()
|
|
{
|
|
Team winner = TEAM_OTHER;
|
|
if (GetPlayersCountByTeam(ALLIANCE) >= GetMinPlayersPerTeam())
|
|
winner = ALLIANCE;
|
|
else if (GetPlayersCountByTeam(HORDE) >= GetMinPlayersPerTeam())
|
|
winner = HORDE;
|
|
|
|
return winner;
|
|
}
|
|
|
|
inline void Battleground::_ProcessProgress(uint32 diff)
|
|
{
|
|
// *********************************************************
|
|
// *** BATTLEGROUND BALLANCE SYSTEM ***
|
|
// *********************************************************
|
|
// if less then minimum players are in on one side, then start premature finish timer
|
|
if (!m_PrematureCountDown)
|
|
{
|
|
m_PrematureCountDown = true;
|
|
m_PrematureCountDownTimer = sBattlegroundMgr->GetPrematureFinishTime();
|
|
}
|
|
else if (m_PrematureCountDownTimer < diff)
|
|
{
|
|
// time's up!
|
|
EndBattleground(GetPrematureWinner());
|
|
m_PrematureCountDown = false;
|
|
}
|
|
else if (!sBattlegroundMgr->isTesting())
|
|
{
|
|
uint32 newtime = m_PrematureCountDownTimer - diff;
|
|
// announce every minute
|
|
if (newtime > (MINUTE * IN_MILLISECONDS))
|
|
{
|
|
if (newtime / (MINUTE * IN_MILLISECONDS) != m_PrematureCountDownTimer / (MINUTE * IN_MILLISECONDS))
|
|
PSendMessageToAll(LANG_BATTLEGROUND_PREMATURE_FINISH_WARNING, CHAT_MSG_SYSTEM, nullptr, (uint32)(m_PrematureCountDownTimer / (MINUTE * IN_MILLISECONDS)));
|
|
}
|
|
else
|
|
{
|
|
//announce every 15 seconds
|
|
if (newtime / (15 * IN_MILLISECONDS) != m_PrematureCountDownTimer / (15 * IN_MILLISECONDS))
|
|
PSendMessageToAll(LANG_BATTLEGROUND_PREMATURE_FINISH_WARNING_SECS, CHAT_MSG_SYSTEM, nullptr, (uint32)(m_PrematureCountDownTimer / IN_MILLISECONDS));
|
|
}
|
|
m_PrematureCountDownTimer = newtime;
|
|
}
|
|
}
|
|
|
|
inline void Battleground::_ProcessJoin(uint32 diff)
|
|
{
|
|
// *********************************************************
|
|
// *** BATTLEGROUND STARTING SYSTEM ***
|
|
// *********************************************************
|
|
ModifyStartDelayTime(diff);
|
|
|
|
if (!isArena())
|
|
SetRemainingTime(300000);
|
|
|
|
if (m_ResetStatTimer > 5000)
|
|
{
|
|
m_ResetStatTimer = 0;
|
|
for (BattlegroundPlayerMap::const_iterator itr = GetPlayers().begin(); itr != GetPlayers().end(); ++itr)
|
|
if (Player* player = ObjectAccessor::FindPlayer(itr->first))
|
|
player->ResetAllPowers();
|
|
}
|
|
|
|
if (!(m_Events & BG_STARTING_EVENT_1))
|
|
{
|
|
m_Events |= BG_STARTING_EVENT_1;
|
|
|
|
if (!FindBgMap())
|
|
{
|
|
TC_LOG_ERROR("bg.battleground", "Battleground::_ProcessJoin: map (map id: {}, instance id: {}) is not created!", GetMapId(), m_InstanceID);
|
|
EndNow();
|
|
return;
|
|
}
|
|
|
|
_preparationStartTime = GameTime::GetGameTime();
|
|
for (Group* group : m_BgRaids)
|
|
if (group)
|
|
group->StartCountdown(CountdownTimerType::Pvp, Seconds(StartDelayTimes[BG_STARTING_EVENT_FIRST] / 1000), _preparationStartTime);
|
|
|
|
GetBgMap()->GetBattlegroundScript()->OnPrepareStage1();
|
|
SetStartDelayTime(StartDelayTimes[BG_STARTING_EVENT_FIRST]);
|
|
// First start warning - 2 or 1 minute
|
|
if (StartMessageIds[BG_STARTING_EVENT_FIRST])
|
|
SendBroadcastText(StartMessageIds[BG_STARTING_EVENT_FIRST], CHAT_MSG_BG_SYSTEM_NEUTRAL);
|
|
}
|
|
// After 1 minute or 30 seconds, warning is signaled
|
|
else if (GetStartDelayTime() <= StartDelayTimes[BG_STARTING_EVENT_SECOND] && !(m_Events & BG_STARTING_EVENT_2))
|
|
{
|
|
m_Events |= BG_STARTING_EVENT_2;
|
|
GetBgMap()->GetBattlegroundScript()->OnPrepareStage2();
|
|
if (StartMessageIds[BG_STARTING_EVENT_SECOND])
|
|
SendBroadcastText(StartMessageIds[BG_STARTING_EVENT_SECOND], CHAT_MSG_BG_SYSTEM_NEUTRAL);
|
|
}
|
|
// After 30 or 15 seconds, warning is signaled
|
|
else if (GetStartDelayTime() <= StartDelayTimes[BG_STARTING_EVENT_THIRD] && !(m_Events & BG_STARTING_EVENT_3))
|
|
{
|
|
m_Events |= BG_STARTING_EVENT_3;
|
|
GetBgMap()->GetBattlegroundScript()->OnPrepareStage3();
|
|
if (StartMessageIds[BG_STARTING_EVENT_THIRD])
|
|
SendBroadcastText(StartMessageIds[BG_STARTING_EVENT_THIRD], CHAT_MSG_BG_SYSTEM_NEUTRAL);
|
|
}
|
|
// Delay expired (after 2 or 1 minute)
|
|
else if (GetStartDelayTime() <= 0 && !(m_Events & BG_STARTING_EVENT_4))
|
|
{
|
|
m_Events |= BG_STARTING_EVENT_4;
|
|
|
|
GetBgMap()->GetBattlegroundScript()->OnStart();
|
|
|
|
if (StartMessageIds[BG_STARTING_EVENT_FOURTH])
|
|
SendBroadcastText(StartMessageIds[BG_STARTING_EVENT_FOURTH], CHAT_MSG_BG_SYSTEM_NEUTRAL);
|
|
SetStatus(STATUS_IN_PROGRESS);
|
|
SetStartDelayTime(StartDelayTimes[BG_STARTING_EVENT_FOURTH]);
|
|
|
|
SendPacketToAll(WorldPackets::Battleground::PVPMatchSetState(WorldPackets::Battleground::PVPMatchState::Engaged).Write());
|
|
|
|
for (auto const& [guid, _] : GetPlayers())
|
|
{
|
|
if (Player* player = ObjectAccessor::GetPlayer(GetBgMap(), guid))
|
|
{
|
|
player->StartCriteria(CriteriaStartEvent::StartBattleground, GetBgMap()->GetId());
|
|
player->AtStartOfEncounter(EncounterType::Battleground);
|
|
}
|
|
}
|
|
|
|
// Remove preparation
|
|
if (isArena())
|
|
{
|
|
/// @todo add arena sound PlaySoundToAll(SOUND_ARENA_START);
|
|
for (BattlegroundPlayerMap::const_iterator itr = GetPlayers().begin(); itr != GetPlayers().end(); ++itr)
|
|
{
|
|
if (Player* player = ObjectAccessor::FindPlayer(itr->first))
|
|
{
|
|
// Correctly display EnemyUnitFrame
|
|
player->SetArenaFaction(player->GetBGTeam());
|
|
|
|
player->RemoveAurasDueToSpell(SPELL_ARENA_PREPARATION);
|
|
player->ResetAllPowers();
|
|
if (!player->IsGameMaster())
|
|
{
|
|
// remove auras with duration lower than 30s
|
|
player->RemoveAppliedAuras([](AuraApplication const* aurApp)
|
|
{
|
|
Aura* aura = aurApp->GetBase();
|
|
return !aura->IsPermanent()
|
|
&& aura->GetDuration() <= 30 * IN_MILLISECONDS
|
|
&& aurApp->IsPositive()
|
|
&& !aura->GetSpellInfo()->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES)
|
|
&& !aura->HasEffectType(SPELL_AURA_MOD_INVISIBILITY);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
CheckWinConditions();
|
|
}
|
|
else
|
|
{
|
|
PlaySoundToAll(SOUND_BG_START);
|
|
|
|
for (BattlegroundPlayerMap::const_iterator itr = GetPlayers().begin(); itr != GetPlayers().end(); ++itr)
|
|
{
|
|
if (Player* player = ObjectAccessor::FindPlayer(itr->first))
|
|
{
|
|
player->RemoveAurasDueToSpell(SPELL_PREPARATION);
|
|
player->ResetAllPowers();
|
|
}
|
|
}
|
|
// Announce BG starting
|
|
if (sWorld->getBoolConfig(CONFIG_BATTLEGROUND_QUEUE_ANNOUNCER_ENABLE))
|
|
sWorld->SendWorldText(LANG_BG_STARTED_ANNOUNCE_WORLD, GetName(), GetMinLevel(), GetMaxLevel());
|
|
}
|
|
}
|
|
|
|
if (GetRemainingTime() > 0 && (m_EndTime -= diff) > 0)
|
|
SetRemainingTime(GetRemainingTime() - diff);
|
|
}
|
|
|
|
inline void Battleground::_ProcessLeave(uint32 diff)
|
|
{
|
|
// *********************************************************
|
|
// *** BATTLEGROUND ENDING SYSTEM ***
|
|
// *********************************************************
|
|
// remove all players from battleground after 2 minutes
|
|
SetRemainingTime(GetRemainingTime() - diff);
|
|
if (GetRemainingTime() <= 0)
|
|
{
|
|
SetRemainingTime(0);
|
|
BattlegroundPlayerMap::iterator itr, next;
|
|
for (itr = m_Players.begin(); itr != m_Players.end(); itr = next)
|
|
{
|
|
next = itr;
|
|
++next;
|
|
//itr is erased here!
|
|
RemovePlayerAtLeave(itr->first, true, true);// remove player from BG
|
|
// do not change any battleground's private variables
|
|
}
|
|
}
|
|
}
|
|
|
|
Player* Battleground::_GetPlayer(ObjectGuid guid, bool offlineRemove, char const* context) const
|
|
{
|
|
Player* player = nullptr;
|
|
if (!offlineRemove)
|
|
{
|
|
// should this be ObjectAccessor::FindConnectedPlayer() to return players teleporting ?
|
|
player = ObjectAccessor::FindPlayer(guid);
|
|
if (!player)
|
|
TC_LOG_ERROR("bg.battleground", "Battleground::{}: player ({}) not found for BG (map: {}, instance id: {})!",
|
|
context, guid.ToString(), GetMapId(), m_InstanceID);
|
|
}
|
|
return player;
|
|
}
|
|
|
|
Player* Battleground::_GetPlayerForTeam(Team team, BattlegroundPlayerMap::const_iterator itr, char const* context) const
|
|
{
|
|
Player* player = _GetPlayer(itr, context);
|
|
if (player)
|
|
{
|
|
Team playerTeam = itr->second.Team;
|
|
if (!playerTeam)
|
|
playerTeam = player->GetEffectiveTeam();
|
|
if (playerTeam != team)
|
|
player = nullptr;
|
|
}
|
|
return player;
|
|
}
|
|
|
|
BattlegroundMap* Battleground::GetBgMap() const
|
|
{
|
|
ASSERT(m_Map);
|
|
return m_Map;
|
|
}
|
|
|
|
WorldSafeLocsEntry const* Battleground::GetTeamStartPosition(TeamId teamId) const
|
|
{
|
|
ASSERT(teamId < TEAM_NEUTRAL);
|
|
return _battlegroundTemplate->StartLocation[teamId];
|
|
}
|
|
|
|
float Battleground::GetStartMaxDist() const
|
|
{
|
|
return _battlegroundTemplate->MaxStartDistSq;
|
|
}
|
|
|
|
void Battleground::SendPacketToAll(WorldPacket const* packet) const
|
|
{
|
|
for (BattlegroundPlayerMap::const_iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr)
|
|
if (Player* player = _GetPlayer(itr, "SendPacketToAll"))
|
|
player->SendDirectMessage(packet);
|
|
}
|
|
|
|
void Battleground::SendPacketToTeam(Team team, WorldPacket const* packet, Player* except /*= nullptr*/) const
|
|
{
|
|
for (BattlegroundPlayerMap::const_iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr)
|
|
{
|
|
if (Player* player = _GetPlayerForTeam(team, itr, "SendPacketToTeam"))
|
|
if (player != except)
|
|
player->SendDirectMessage(packet);
|
|
}
|
|
}
|
|
|
|
void Battleground::SendChatMessage(Creature* source, uint8 textId, WorldObject* target /*= nullptr*/)
|
|
{
|
|
sCreatureTextMgr->SendChat(source, textId, target);
|
|
}
|
|
|
|
void Battleground::SendBroadcastText(uint32 id, ChatMsg msgType, WorldObject const* target)
|
|
{
|
|
if (!sBroadcastTextStore.LookupEntry(id))
|
|
{
|
|
TC_LOG_ERROR("bg.battleground", "Battleground::SendBroadcastText: `broadcast_text` (ID: {}) was not found", id);
|
|
return;
|
|
}
|
|
|
|
Trinity::BroadcastTextBuilder builder(nullptr, msgType, id, GENDER_MALE, target);
|
|
Trinity::LocalizedDo<Trinity::BroadcastTextBuilder> localizer(builder);
|
|
BroadcastWorker(localizer);
|
|
}
|
|
|
|
void Battleground::PlaySoundToAll(uint32 soundID)
|
|
{
|
|
SendPacketToAll(WorldPackets::Misc::PlaySound(ObjectGuid::Empty, soundID, 0).Write());
|
|
}
|
|
|
|
void Battleground::PlaySoundToTeam(uint32 soundID, Team team)
|
|
{
|
|
SendPacketToTeam(team, WorldPackets::Misc::PlaySound(ObjectGuid::Empty, soundID, 0).Write());
|
|
}
|
|
|
|
void Battleground::CastSpellOnTeam(uint32 SpellID, Team team)
|
|
{
|
|
for (BattlegroundPlayerMap::const_iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr)
|
|
if (Player* player = _GetPlayerForTeam(team, itr, "CastSpellOnTeam"))
|
|
player->CastSpell(player, SpellID, true);
|
|
}
|
|
|
|
void Battleground::RemoveAuraOnTeam(uint32 SpellID, Team team)
|
|
{
|
|
for (BattlegroundPlayerMap::const_iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr)
|
|
if (Player* player = _GetPlayerForTeam(team, itr, "RemoveAuraOnTeam"))
|
|
player->RemoveAura(SpellID);
|
|
}
|
|
|
|
void Battleground::RewardHonorToTeam(uint32 Honor, Team team)
|
|
{
|
|
for (BattlegroundPlayerMap::const_iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr)
|
|
if (Player* player = _GetPlayerForTeam(team, itr, "RewardHonorToTeam"))
|
|
UpdatePlayerScore(player, SCORE_BONUS_HONOR, Honor, true, HonorGainSource::TeamContribution);
|
|
}
|
|
|
|
void Battleground::RewardReputationToTeam(uint32 faction_id, uint32 Reputation, Team team)
|
|
{
|
|
FactionEntry const* factionEntry = sFactionStore.LookupEntry(faction_id);
|
|
if (!factionEntry)
|
|
return;
|
|
|
|
for (BattlegroundPlayerMap::const_iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr)
|
|
{
|
|
Player* player = _GetPlayerForTeam(team, itr, "RewardReputationToTeam");
|
|
if (!player)
|
|
continue;
|
|
|
|
if (player->HasPlayerFlagEx(PLAYER_FLAGS_EX_MERCENARY_MODE))
|
|
continue;
|
|
|
|
uint32 repGain = Reputation;
|
|
AddPct(repGain, player->GetTotalAuraModifier(SPELL_AURA_MOD_REPUTATION_GAIN));
|
|
AddPct(repGain, player->GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_FACTION_REPUTATION_GAIN, faction_id));
|
|
player->GetReputationMgr().ModifyReputation(factionEntry, repGain);
|
|
}
|
|
}
|
|
|
|
void Battleground::UpdateWorldState(int32 worldStateId, int32 value, bool hidden /*= false*/)
|
|
{
|
|
sWorldStateMgr->SetValue(worldStateId, value, hidden, GetBgMap());
|
|
}
|
|
|
|
void Battleground::EndBattleground(Team winner)
|
|
{
|
|
RemoveFromBGFreeSlotQueue();
|
|
|
|
bool guildAwarded = false;
|
|
|
|
if (winner == ALLIANCE)
|
|
{
|
|
if (isBattleground())
|
|
SendBroadcastText(BG_TEXT_ALLIANCE_WINS, CHAT_MSG_BG_SYSTEM_NEUTRAL);
|
|
|
|
PlaySoundToAll(SOUND_ALLIANCE_WINS); // alliance wins sound
|
|
|
|
SetWinner(PVP_TEAM_ALLIANCE);
|
|
}
|
|
else if (winner == HORDE)
|
|
{
|
|
if (isBattleground())
|
|
SendBroadcastText(BG_TEXT_HORDE_WINS, CHAT_MSG_BG_SYSTEM_NEUTRAL);
|
|
|
|
PlaySoundToAll(SOUND_HORDE_WINS); // horde wins sound
|
|
|
|
SetWinner(PVP_TEAM_HORDE);
|
|
}
|
|
else
|
|
{
|
|
SetWinner(PVP_TEAM_NEUTRAL);
|
|
}
|
|
|
|
CharacterDatabasePreparedStatement* stmt = nullptr;
|
|
uint64 battlegroundId = 1;
|
|
if (isBattleground() && sWorld->getBoolConfig(CONFIG_BATTLEGROUND_STORE_STATISTICS_ENABLE))
|
|
{
|
|
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PVPSTATS_MAXID);
|
|
PreparedQueryResult result = CharacterDatabase.Query(stmt);
|
|
|
|
if (result)
|
|
{
|
|
Field* fields = result->Fetch();
|
|
battlegroundId = fields[0].GetUInt64() + 1;
|
|
}
|
|
|
|
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_PVPSTATS_BATTLEGROUND);
|
|
stmt->setUInt64(0, battlegroundId);
|
|
stmt->setUInt8(1, GetWinner());
|
|
stmt->setUInt8(2, GetUniqueBracketId());
|
|
stmt->setUInt32(3, GetTypeID());
|
|
CharacterDatabase.Execute(stmt);
|
|
}
|
|
|
|
SetStatus(STATUS_WAIT_LEAVE);
|
|
//we must set it this way, because end time is sent in packet!
|
|
SetRemainingTime(TIME_AUTOCLOSE_BATTLEGROUND);
|
|
|
|
WorldPackets::Battleground::PVPMatchComplete pvpMatchComplete;
|
|
pvpMatchComplete.Winner = GetWinner();
|
|
pvpMatchComplete.Duration = std::chrono::duration_cast<Seconds>(Milliseconds(std::max<int32>(0, (GetElapsedTime() - BG_START_DELAY_2M))));
|
|
pvpMatchComplete.LogData.emplace();
|
|
BuildPvPLogDataPacket(*pvpMatchComplete.LogData);
|
|
pvpMatchComplete.Write();
|
|
|
|
for (BattlegroundPlayerMap::iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr)
|
|
{
|
|
Team team = itr->second.Team;
|
|
|
|
Player* player = _GetPlayer(itr, "EndBattleground");
|
|
if (!player)
|
|
continue;
|
|
|
|
// should remove spirit of redemption
|
|
if (player->HasAuraType(SPELL_AURA_SPIRIT_OF_REDEMPTION))
|
|
player->RemoveAurasByType(SPELL_AURA_MOD_SHAPESHIFT);
|
|
|
|
if (!player->IsAlive())
|
|
{
|
|
player->ResurrectPlayer(1.0f);
|
|
player->SpawnCorpseBones();
|
|
}
|
|
else
|
|
//needed cause else in av some creatures will kill the players at the end
|
|
player->CombatStop();
|
|
|
|
// remove temporary currency bonus auras before rewarding player
|
|
player->RemoveAura(SPELL_HONORABLE_DEFENDER_25Y);
|
|
player->RemoveAura(SPELL_HONORABLE_DEFENDER_60Y);
|
|
|
|
uint32 winnerKills = player->GetRandomWinner() ? sWorld->getIntConfig(CONFIG_BG_REWARD_WINNER_HONOR_LAST) : sWorld->getIntConfig(CONFIG_BG_REWARD_WINNER_HONOR_FIRST);
|
|
uint32 loserKills = player->GetRandomWinner() ? sWorld->getIntConfig(CONFIG_BG_REWARD_LOSER_HONOR_LAST) : sWorld->getIntConfig(CONFIG_BG_REWARD_LOSER_HONOR_FIRST);
|
|
|
|
if (isBattleground() && sWorld->getBoolConfig(CONFIG_BATTLEGROUND_STORE_STATISTICS_ENABLE))
|
|
{
|
|
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_PVPSTATS_PLAYER);
|
|
BattlegroundScoreMap::const_iterator score = PlayerScores.find(player->GetGUID());
|
|
|
|
stmt->setUInt32(0, battlegroundId);
|
|
stmt->setUInt64(1, player->GetGUID().GetCounter());
|
|
stmt->setBool (2, team == winner);
|
|
stmt->setUInt32(3, score->second->GetKillingBlows());
|
|
stmt->setUInt32(4, score->second->GetDeaths());
|
|
stmt->setUInt32(5, score->second->GetHonorableKills());
|
|
stmt->setUInt32(6, score->second->GetBonusHonor());
|
|
stmt->setUInt32(7, score->second->GetDamageDone());
|
|
stmt->setUInt32(8, score->second->GetHealingDone());
|
|
stmt->setUInt32(9, score->second->GetAttr(0));
|
|
stmt->setUInt32(10, score->second->GetAttr(1));
|
|
stmt->setUInt32(11, score->second->GetAttr(2));
|
|
stmt->setUInt32(12, score->second->GetAttr(3));
|
|
stmt->setUInt32(13, score->second->GetAttr(4));
|
|
|
|
CharacterDatabase.Execute(stmt);
|
|
}
|
|
|
|
// Reward winner team
|
|
if (team == winner)
|
|
{
|
|
if (BattlegroundPlayer const* bgPlayer = GetBattlegroundPlayerData(player->GetGUID()))
|
|
{
|
|
if (BattlegroundMgr::IsRandomBattleground(bgPlayer->queueTypeId.BattlemasterListId)
|
|
|| BattlegroundMgr::IsBGWeekend(BattlegroundTypeId(bgPlayer->queueTypeId.BattlemasterListId)))
|
|
{
|
|
HonorGainSource source = HonorGainSource::BGCompletion;
|
|
if (!player->GetRandomWinner())
|
|
source = BattlegroundMgr::IsRandomBattleground(bgPlayer->queueTypeId.BattlemasterListId) ? HonorGainSource::RandomBGCompletion : HonorGainSource::HolidayBGCompletion;
|
|
|
|
UpdatePlayerScore(player, SCORE_BONUS_HONOR, GetBonusHonorFromKill(winnerKills), true, source);
|
|
if (!player->GetRandomWinner())
|
|
{
|
|
player->SetRandomWinner(true);
|
|
// TODO: win honor xp
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// TODO: loss honor xp
|
|
}
|
|
}
|
|
|
|
player->UpdateCriteria(CriteriaType::WinBattleground, player->GetMapId());
|
|
if (!guildAwarded)
|
|
{
|
|
guildAwarded = true;
|
|
if (ObjectGuid::LowType guildId = GetBgMap()->GetOwnerGuildId(player->GetBGTeam()))
|
|
{
|
|
if (Guild* guild = sGuildMgr->GetGuildById(guildId))
|
|
guild->UpdateCriteria(CriteriaType::WinBattleground, player->GetMapId(), 0, 0, nullptr, player);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (BattlegroundPlayer const* bgPlayer = GetBattlegroundPlayerData(player->GetGUID()))
|
|
{
|
|
if (BattlegroundMgr::IsRandomBattleground(bgPlayer->queueTypeId.BattlemasterListId)
|
|
|| BattlegroundMgr::IsBGWeekend(BattlegroundTypeId(bgPlayer->queueTypeId.BattlemasterListId)))
|
|
UpdatePlayerScore(player, SCORE_BONUS_HONOR, GetBonusHonorFromKill(loserKills), true, HonorGainSource::BGCompletion);
|
|
}
|
|
}
|
|
|
|
player->ResetAllPowers();
|
|
player->CombatStopWithPets(true);
|
|
|
|
BlockMovement(player);
|
|
|
|
player->SendDirectMessage(pvpMatchComplete.GetRawPacket());
|
|
|
|
player->UpdateCriteria(CriteriaType::ParticipateInBattleground, player->GetMapId());
|
|
|
|
GetBgMap()->GetBattlegroundScript()->OnEnd(winner);
|
|
}
|
|
}
|
|
|
|
uint32 Battleground::GetScriptId() const
|
|
{
|
|
return _battlegroundTemplate->ScriptId;
|
|
}
|
|
|
|
uint32 Battleground::GetBonusHonorFromKill(uint32 kills) const
|
|
{
|
|
//variable kills means how many honorable kills you scored (so we need kills * honor_for_one_kill)
|
|
uint32 maxLevel = std::min<uint32>(GetMaxLevel(), 80U);
|
|
return Trinity::Honor::hk_honor_at_level(maxLevel, float(kills));
|
|
}
|
|
|
|
void Battleground::BlockMovement(Player* player)
|
|
{
|
|
// movement disabled NOTE: the effect will be automatically removed by client when the player is teleported from the battleground, so no need to send with uint8(1) in RemovePlayerAtLeave()
|
|
player->SetClientControl(player, false);
|
|
}
|
|
|
|
void Battleground::RemovePlayerAtLeave(ObjectGuid guid, bool Transport, bool SendPacket)
|
|
{
|
|
Team team = GetPlayerTeam(guid);
|
|
bool participant = false;
|
|
// Remove from lists/maps
|
|
BattlegroundPlayerMap::iterator itr = m_Players.find(guid);
|
|
Optional<BattlegroundQueueTypeId> bgQueueTypeId;
|
|
if (itr != m_Players.end())
|
|
{
|
|
bgQueueTypeId = itr->second.queueTypeId;
|
|
UpdatePlayersCountByTeam(team, true); // -1 player
|
|
m_Players.erase(itr);
|
|
// check if the player was a participant of the match, or only entered through gm command (goname)
|
|
participant = true;
|
|
}
|
|
|
|
BattlegroundScoreMap::iterator itr2 = PlayerScores.find(guid);
|
|
if (itr2 != PlayerScores.end())
|
|
{
|
|
delete itr2->second; // delete player's score
|
|
PlayerScores.erase(itr2);
|
|
}
|
|
|
|
Player* player = ObjectAccessor::FindPlayer(guid);
|
|
|
|
if (player)
|
|
{
|
|
// should remove spirit of redemption
|
|
if (player->HasAuraType(SPELL_AURA_SPIRIT_OF_REDEMPTION))
|
|
player->RemoveAurasByType(SPELL_AURA_MOD_SHAPESHIFT);
|
|
|
|
player->RemoveAurasByType(SPELL_AURA_MOUNTED);
|
|
player->RemoveAura(SPELL_MERCENARY_HORDE_1);
|
|
player->RemoveAura(SPELL_MERCENARY_HORDE_REACTIONS);
|
|
player->RemoveAura(SPELL_MERCENARY_ALLIANCE_1);
|
|
player->RemoveAura(SPELL_MERCENARY_ALLIANCE_REACTIONS);
|
|
player->RemoveAura(SPELL_MERCENARY_SHAPESHIFT);
|
|
player->RemovePlayerFlagEx(PLAYER_FLAGS_EX_MERCENARY_MODE);
|
|
|
|
player->AtEndOfEncounter(EncounterType::Battleground);
|
|
|
|
player->RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags2::LeaveArenaOrBattleground);
|
|
|
|
if (!player->IsAlive()) // resurrect on exit
|
|
{
|
|
player->ResurrectPlayer(1.0f);
|
|
player->SpawnCorpseBones();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CharacterDatabaseTransaction trans(nullptr);
|
|
Player::OfflineResurrect(guid, trans);
|
|
}
|
|
|
|
RemovePlayer(player, guid, team); // BG subclass specific code
|
|
|
|
if (participant) // if the player was a match participant, remove auras, calc rating, update queue
|
|
{
|
|
if (player)
|
|
{
|
|
player->ClearAfkReports();
|
|
|
|
// if arena, remove the specific arena auras
|
|
if (isArena())
|
|
{
|
|
// unsummon current and summon old pet if there was one and there isn't a current pet
|
|
player->RemovePet(nullptr, PET_SAVE_NOT_IN_SLOT);
|
|
player->ResummonPetTemporaryUnSummonedIfAny();
|
|
}
|
|
|
|
if (SendPacket && bgQueueTypeId)
|
|
{
|
|
WorldPackets::Battleground::BattlefieldStatusNone battlefieldStatus;
|
|
BattlegroundMgr::BuildBattlegroundStatusNone(&battlefieldStatus, player, player->GetBattlegroundQueueIndex(*bgQueueTypeId), player->GetBattlegroundQueueJoinTime(*bgQueueTypeId));
|
|
player->SendDirectMessage(battlefieldStatus.Write());
|
|
}
|
|
|
|
// this call is important, because player, when joins to battleground, this method is not called, so it must be called when leaving bg
|
|
if (bgQueueTypeId)
|
|
player->RemoveBattlegroundQueueId(*bgQueueTypeId);
|
|
}
|
|
|
|
// remove from raid group if player is member
|
|
if (Group* group = GetBgRaid(team))
|
|
{
|
|
if (!group->RemoveMember(guid)) // group was disbanded
|
|
SetBgRaid(team, nullptr);
|
|
}
|
|
DecreaseInvitedCount(team);
|
|
//we should update battleground queue, but only if bg isn't ending
|
|
if (isBattleground() && GetStatus() < STATUS_WAIT_LEAVE && bgQueueTypeId)
|
|
{
|
|
// a player has left the battleground, so there are free slots -> add to queue
|
|
AddToBGFreeSlotQueue();
|
|
sBattlegroundMgr->ScheduleQueueUpdate(0, *bgQueueTypeId, GetBracketId());
|
|
}
|
|
|
|
// Let others know
|
|
WorldPackets::Battleground::BattlegroundPlayerLeft playerLeft;
|
|
playerLeft.Guid = guid;
|
|
SendPacketToTeam(team, playerLeft.Write(), player);
|
|
}
|
|
|
|
if (player)
|
|
{
|
|
// Do next only if found in battleground
|
|
player->SetBattlegroundId(0, BATTLEGROUND_TYPE_NONE, BATTLEGROUND_QUEUE_NONE); // We're not in BG.
|
|
// reset destination bg team
|
|
player->SetBGTeam(TEAM_OTHER);
|
|
|
|
// remove all criterias on bg leave
|
|
player->FailCriteria(CriteriaFailEvent::LeaveBattleground, 0);
|
|
|
|
if (Transport)
|
|
player->TeleportToBGEntryPoint();
|
|
|
|
TC_LOG_DEBUG("bg.battleground", "Removed player {} from Battleground.", player->GetName());
|
|
}
|
|
|
|
//battleground object will be deleted next Battleground::Update() call
|
|
}
|
|
|
|
// this method is called when no players remains in battleground
|
|
void Battleground::Reset()
|
|
{
|
|
SetWinner(PVP_TEAM_NEUTRAL);
|
|
SetStatus(STATUS_WAIT_QUEUE);
|
|
SetElapsedTime(0);
|
|
SetRemainingTime(0);
|
|
m_Events = 0;
|
|
|
|
if (m_InvitedAlliance > 0 || m_InvitedHorde > 0)
|
|
TC_LOG_ERROR("bg.battleground", "Battleground::Reset: one of the counters is not 0 (alliance: {}, horde: {}) for BG (map: {}, instance id: {})!",
|
|
m_InvitedAlliance, m_InvitedHorde, GetMapId(), m_InstanceID);
|
|
|
|
m_InvitedAlliance = 0;
|
|
m_InvitedHorde = 0;
|
|
m_InBGFreeSlotQueue = false;
|
|
|
|
m_Players.clear();
|
|
|
|
for (BattlegroundScoreMap::const_iterator itr = PlayerScores.begin(); itr != PlayerScores.end(); ++itr)
|
|
delete itr->second;
|
|
PlayerScores.clear();
|
|
|
|
_playerPositions.clear();
|
|
}
|
|
|
|
void Battleground::StartBattleground()
|
|
{
|
|
SetElapsedTime(0);
|
|
// add BG to free slot queue
|
|
AddToBGFreeSlotQueue();
|
|
|
|
// 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
|
|
// and it doesn't matter if we call StartBattleground() more times, because m_Battlegrounds is a map and instance id never changes
|
|
sBattlegroundMgr->AddBattleground(this);
|
|
|
|
if (m_IsRated)
|
|
TC_LOG_DEBUG("bg.arena", "Arena match type: {} started.", m_ArenaType);
|
|
}
|
|
|
|
void Battleground::TeleportPlayerToExploitLocation(Player* player)
|
|
{
|
|
if (WorldSafeLocsEntry const* loc = GetExploitTeleportLocation(Team(player->GetBGTeam())))
|
|
player->TeleportTo(loc->Loc);
|
|
}
|
|
|
|
void Battleground::AddPlayer(Player* player, BattlegroundQueueTypeId queueId)
|
|
{
|
|
// remove afk from player
|
|
if (player->isAFK())
|
|
player->ToggleAFK();
|
|
|
|
// score struct must be created in inherited class
|
|
|
|
Team team = player->GetBGTeam();
|
|
|
|
BattlegroundPlayer bp;
|
|
bp.OfflineRemoveTime = 0;
|
|
bp.Team = team;
|
|
bp.Mercenary = player->IsMercenaryForBattlegroundQueueType(queueId);
|
|
bp.queueTypeId = queueId;
|
|
|
|
bool const isInBattleground = IsPlayerInBattleground(player->GetGUID());
|
|
// Add to list/maps
|
|
m_Players[player->GetGUID()] = bp;
|
|
|
|
if (!isInBattleground)
|
|
{
|
|
UpdatePlayersCountByTeam(team, false); // +1 player
|
|
PlayerScores[player->GetGUID()] = new BattlegroundScore(player->GetGUID(), player->GetBGTeam(), _pvpStatIds);
|
|
}
|
|
|
|
WorldPackets::Battleground::BattlegroundPlayerJoined playerJoined;
|
|
playerJoined.Guid = player->GetGUID();
|
|
SendPacketToTeam(team, playerJoined.Write(), player);
|
|
|
|
WorldPackets::Battleground::PVPMatchInitialize pvpMatchInitialize;
|
|
pvpMatchInitialize.MapID = GetMapId();
|
|
switch (GetStatus())
|
|
{
|
|
case STATUS_NONE:
|
|
case STATUS_WAIT_QUEUE:
|
|
pvpMatchInitialize.State = WorldPackets::Battleground::PVPMatchState::Inactive;
|
|
break;
|
|
case STATUS_WAIT_JOIN:
|
|
pvpMatchInitialize.State = WorldPackets::Battleground::PVPMatchState::StartUp;
|
|
break;
|
|
case STATUS_IN_PROGRESS:
|
|
pvpMatchInitialize.State = WorldPackets::Battleground::PVPMatchState::Engaged;
|
|
break;
|
|
case STATUS_WAIT_LEAVE:
|
|
pvpMatchInitialize.State = WorldPackets::Battleground::PVPMatchState::Complete;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (GetElapsedTime() >= BG_START_DELAY_2M)
|
|
{
|
|
Milliseconds duration(GetElapsedTime() - BG_START_DELAY_2M);
|
|
pvpMatchInitialize.Duration = std::chrono::duration_cast<Seconds>(duration);
|
|
pvpMatchInitialize.StartTime = GameTime::GetSystemTime() - duration;
|
|
}
|
|
|
|
pvpMatchInitialize.ArenaFaction = player->GetBGTeam() == HORDE ? PVP_TEAM_HORDE : PVP_TEAM_ALLIANCE;
|
|
pvpMatchInitialize.BattlemasterListID = queueId.BattlemasterListId;
|
|
pvpMatchInitialize.Registered = false;
|
|
pvpMatchInitialize.AffectsRating = isRated();
|
|
|
|
player->SendDirectMessage(pvpMatchInitialize.Write());
|
|
|
|
player->RemoveAurasByType(SPELL_AURA_MOUNTED);
|
|
|
|
// add arena specific auras
|
|
if (isArena())
|
|
{
|
|
player->RemoveArenaEnchantments(TEMP_ENCHANTMENT_SLOT);
|
|
player->DestroyConjuredItems(true);
|
|
player->UnsummonPetTemporaryIfAny();
|
|
|
|
if (GetStatus() == STATUS_WAIT_JOIN) // not started yet
|
|
{
|
|
player->CastSpell(player, SPELL_ARENA_PREPARATION, true);
|
|
player->ResetAllPowers();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (GetStatus() == STATUS_WAIT_JOIN) // not started yet
|
|
player->CastSpell(player, SPELL_PREPARATION, true); // reduces all mana cost of spells.
|
|
|
|
if (bp.Mercenary)
|
|
{
|
|
if (bp.Team == HORDE)
|
|
{
|
|
player->CastSpell(player, SPELL_MERCENARY_HORDE_1);
|
|
player->CastSpell(player, SPELL_MERCENARY_HORDE_REACTIONS);
|
|
}
|
|
else if (bp.Team == ALLIANCE)
|
|
{
|
|
player->CastSpell(player, SPELL_MERCENARY_ALLIANCE_1);
|
|
player->CastSpell(player, SPELL_MERCENARY_ALLIANCE_REACTIONS);
|
|
}
|
|
player->CastSpell(player, SPELL_MERCENARY_SHAPESHIFT);
|
|
player->SetPlayerFlagEx(PLAYER_FLAGS_EX_MERCENARY_MODE);
|
|
}
|
|
}
|
|
|
|
// setup BG group membership
|
|
PlayerAddedToBGCheckIfBGIsRunning(player);
|
|
AddOrSetPlayerToCorrectBgGroup(player, team);
|
|
|
|
GetBgMap()->GetBattlegroundScript()->OnPlayerJoined(player, isInBattleground);
|
|
}
|
|
|
|
// this method adds player to his team's bg group, or sets his correct group if player is already in bg group
|
|
void Battleground::AddOrSetPlayerToCorrectBgGroup(Player* player, Team team)
|
|
{
|
|
ObjectGuid playerGuid = player->GetGUID();
|
|
Group* group = GetBgRaid(team);
|
|
if (!group) // first player joined
|
|
{
|
|
group = new Group;
|
|
SetBgRaid(team, group);
|
|
group->Create(player);
|
|
Seconds countdownMaxForBGType = Seconds(StartDelayTimes[BG_STARTING_EVENT_FIRST] / 1000);
|
|
if (_preparationStartTime)
|
|
group->StartCountdown(CountdownTimerType::Pvp, countdownMaxForBGType, _preparationStartTime);
|
|
else
|
|
group->StartCountdown(CountdownTimerType::Pvp, countdownMaxForBGType);
|
|
}
|
|
else // raid already exist
|
|
{
|
|
if (group->IsMember(playerGuid))
|
|
{
|
|
uint8 subgroup = group->GetMemberGroup(playerGuid);
|
|
player->SetBattlegroundOrBattlefieldRaid(group, subgroup);
|
|
}
|
|
else
|
|
{
|
|
group->AddMember(player);
|
|
if (Group* originalGroup = player->GetOriginalGroup())
|
|
if (originalGroup->IsLeader(playerGuid))
|
|
{
|
|
group->ChangeLeader(playerGuid);
|
|
group->SendUpdate();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// This method should be called when player logs into running battleground
|
|
void Battleground::EventPlayerLoggedIn(Player* player)
|
|
{
|
|
ObjectGuid guid = player->GetGUID();
|
|
// player is correct pointer
|
|
for (auto itr = m_OfflineQueue.begin(); itr != m_OfflineQueue.end(); ++itr)
|
|
{
|
|
if (*itr == guid)
|
|
{
|
|
m_OfflineQueue.erase(itr);
|
|
break;
|
|
}
|
|
}
|
|
m_Players[guid].OfflineRemoveTime = 0;
|
|
PlayerAddedToBGCheckIfBGIsRunning(player);
|
|
// if battleground is starting, then add preparation aura
|
|
// we don't have to do that, because preparation aura isn't removed when player logs out
|
|
}
|
|
|
|
// This method should be called when player logs out from running battleground
|
|
void Battleground::EventPlayerLoggedOut(Player* player)
|
|
{
|
|
ObjectGuid guid = player->GetGUID();
|
|
if (!IsPlayerInBattleground(guid)) // Check if this player really is in battleground (might be a GM who teleported inside)
|
|
return;
|
|
|
|
// player is correct pointer, it is checked in WorldSession::LogoutPlayer()
|
|
m_OfflineQueue.push_back(player->GetGUID());
|
|
m_Players[guid].OfflineRemoveTime = GameTime::GetGameTime() + MAX_OFFLINE_TIME;
|
|
if (GetStatus() == STATUS_IN_PROGRESS)
|
|
{
|
|
// drop flag and handle other cleanups
|
|
RemovePlayer(player, guid, GetPlayerTeam(guid));
|
|
|
|
// 1 player is logging out, if it is the last alive, then end arena!
|
|
if (isArena() && player->IsAlive())
|
|
if (GetAlivePlayersCountByTeam(player->GetBGTeam()) <= 1 && GetPlayersCountByTeam(GetOtherTeam(player->GetBGTeam())))
|
|
EndBattleground(GetOtherTeam(player->GetBGTeam()));
|
|
}
|
|
}
|
|
|
|
// This method should be called only once ... it adds pointer to queue
|
|
void Battleground::AddToBGFreeSlotQueue()
|
|
{
|
|
if (!m_InBGFreeSlotQueue && isBattleground())
|
|
{
|
|
sBattlegroundMgr->AddToBGFreeSlotQueue(this);
|
|
m_InBGFreeSlotQueue = true;
|
|
}
|
|
}
|
|
|
|
// This method removes this battleground from free queue - it must be called when deleting battleground
|
|
void Battleground::RemoveFromBGFreeSlotQueue()
|
|
{
|
|
if (m_InBGFreeSlotQueue)
|
|
{
|
|
sBattlegroundMgr->RemoveFromBGFreeSlotQueue(GetMapId(), m_InstanceID);
|
|
m_InBGFreeSlotQueue = false;
|
|
}
|
|
}
|
|
|
|
// get the number of free slots for team
|
|
// returns the number how many players can join battleground to MaxPlayersPerTeam
|
|
uint32 Battleground::GetFreeSlotsForTeam(Team team) const
|
|
{
|
|
// if BG is starting and CONFIG_BATTLEGROUND_INVITATION_TYPE == BG_QUEUE_INVITATION_TYPE_NO_BALANCE, invite anyone
|
|
if (GetStatus() == STATUS_WAIT_JOIN && sWorld->getIntConfig(CONFIG_BATTLEGROUND_INVITATION_TYPE) == BG_QUEUE_INVITATION_TYPE_NO_BALANCE)
|
|
return (GetInvitedCount(team) < GetMaxPlayersPerTeam()) ? GetMaxPlayersPerTeam() - GetInvitedCount(team) : 0;
|
|
|
|
// if BG is already started or CONFIG_BATTLEGROUND_INVITATION_TYPE != BG_QUEUE_INVITATION_TYPE_NO_BALANCE, do not allow to join too much players of one faction
|
|
uint32 otherTeamInvitedCount;
|
|
uint32 thisTeamInvitedCount;
|
|
uint32 otherTeamPlayersCount;
|
|
uint32 thisTeamPlayersCount;
|
|
|
|
if (team == ALLIANCE)
|
|
{
|
|
thisTeamInvitedCount = GetInvitedCount(ALLIANCE);
|
|
otherTeamInvitedCount = GetInvitedCount(HORDE);
|
|
thisTeamPlayersCount = GetPlayersCountByTeam(ALLIANCE);
|
|
otherTeamPlayersCount = GetPlayersCountByTeam(HORDE);
|
|
}
|
|
else
|
|
{
|
|
thisTeamInvitedCount = GetInvitedCount(HORDE);
|
|
otherTeamInvitedCount = GetInvitedCount(ALLIANCE);
|
|
thisTeamPlayersCount = GetPlayersCountByTeam(HORDE);
|
|
otherTeamPlayersCount = GetPlayersCountByTeam(ALLIANCE);
|
|
}
|
|
if (GetStatus() == STATUS_IN_PROGRESS || GetStatus() == STATUS_WAIT_JOIN)
|
|
{
|
|
// difference based on ppl invited (not necessarily entered battle)
|
|
// default: allow 0
|
|
uint32 diff = 0;
|
|
|
|
// allow join one person if the sides are equal (to fill up bg to minPlayerPerTeam)
|
|
if (otherTeamInvitedCount == thisTeamInvitedCount)
|
|
diff = 1;
|
|
// allow join more ppl if the other side has more players
|
|
else if (otherTeamInvitedCount > thisTeamInvitedCount)
|
|
diff = otherTeamInvitedCount - thisTeamInvitedCount;
|
|
|
|
// difference based on max players per team (don't allow inviting more)
|
|
uint32 diff2 = (thisTeamInvitedCount < GetMaxPlayersPerTeam()) ? GetMaxPlayersPerTeam() - thisTeamInvitedCount : 0;
|
|
|
|
// difference based on players who already entered
|
|
// default: allow 0
|
|
uint32 diff3 = 0;
|
|
// allow join one person if the sides are equal (to fill up bg minPlayerPerTeam)
|
|
if (otherTeamPlayersCount == thisTeamPlayersCount)
|
|
diff3 = 1;
|
|
// allow join more ppl if the other side has more players
|
|
else if (otherTeamPlayersCount > thisTeamPlayersCount)
|
|
diff3 = otherTeamPlayersCount - thisTeamPlayersCount;
|
|
// or other side has less than minPlayersPerTeam
|
|
else if (thisTeamInvitedCount <= GetMinPlayersPerTeam())
|
|
diff3 = GetMinPlayersPerTeam() - thisTeamInvitedCount + 1;
|
|
|
|
// return the minimum of the 3 differences
|
|
|
|
// min of diff and diff 2
|
|
diff = std::min(diff, diff2);
|
|
// min of diff, diff2 and diff3
|
|
return std::min(diff, diff3);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
bool Battleground::isArena() const
|
|
{
|
|
return _battlegroundTemplate->IsArena();
|
|
}
|
|
|
|
bool Battleground::isBattleground() const
|
|
{
|
|
return !isArena();
|
|
}
|
|
|
|
bool Battleground::HasFreeSlots() const
|
|
{
|
|
return GetPlayersSize() < GetMaxPlayers();
|
|
}
|
|
|
|
void Battleground::BuildPvPLogDataPacket(WorldPackets::Battleground::PVPMatchStatistics& pvpLogData) const
|
|
{
|
|
pvpLogData.Statistics.reserve(GetPlayerScoresSize());
|
|
for (auto const& score : PlayerScores)
|
|
{
|
|
if (Player* player = ObjectAccessor::GetPlayer(GetBgMap(), score.first))
|
|
{
|
|
WorldPackets::Battleground::PVPMatchStatistics::PVPMatchPlayerStatistics playerData;
|
|
score.second->BuildPvPLogPlayerDataPacket(playerData);
|
|
|
|
playerData.IsInWorld = true;
|
|
playerData.PrimaryTalentTree = AsUnderlyingType(player->GetPrimarySpecialization());
|
|
playerData.Sex = player->GetGender();
|
|
playerData.Race = player->GetRace();
|
|
playerData.Class = player->GetClass();
|
|
playerData.HonorLevel = player->GetHonorLevel();
|
|
|
|
pvpLogData.Statistics.push_back(playerData);
|
|
}
|
|
}
|
|
|
|
pvpLogData.PlayerCount[PVP_TEAM_HORDE] = int8(GetPlayersCountByTeam(HORDE));
|
|
pvpLogData.PlayerCount[PVP_TEAM_ALLIANCE] = int8(GetPlayersCountByTeam(ALLIANCE));
|
|
}
|
|
|
|
BattlegroundScore const* Battleground::GetBattlegroundScore(Player* player) const
|
|
{
|
|
return Trinity::Containers::MapGetValuePtr(PlayerScores, player->GetGUID());
|
|
}
|
|
|
|
bool Battleground::UpdatePlayerScore(Player* player, uint32 type, uint32 value, bool doAddHonor, Optional<HonorGainSource> source)
|
|
{
|
|
BattlegroundScoreMap::const_iterator itr = PlayerScores.find(player->GetGUID());
|
|
if (itr == PlayerScores.end()) // player not found...
|
|
return false;
|
|
|
|
if (type == SCORE_BONUS_HONOR && doAddHonor && isBattleground())
|
|
player->RewardHonor(nullptr, 1, value, source.value_or(HonorGainSource::Kill)); // RewardHonor calls UpdatePlayerScore with doAddHonor = false
|
|
else
|
|
itr->second->UpdateScore(type, value);
|
|
|
|
return true;
|
|
}
|
|
|
|
void Battleground::UpdatePvpStat(Player* player, uint32 pvpStatId, uint32 value)
|
|
{
|
|
if (BattlegroundScore* score = Trinity::Containers::MapGetValuePtr(PlayerScores, player->GetGUID()))
|
|
score->UpdatePvpStat(pvpStatId, value);
|
|
}
|
|
|
|
uint32 Battleground::GetMapId() const
|
|
{
|
|
return _battlegroundTemplate->MapIDs.front();
|
|
}
|
|
|
|
void Battleground::SetBgMap(BattlegroundMap* map)
|
|
{
|
|
m_Map = map;
|
|
if (map)
|
|
_pvpStatIds = sDB2Manager.GetPVPStatIDsForMap(map->GetId());
|
|
else
|
|
_pvpStatIds = nullptr;
|
|
}
|
|
|
|
void Battleground::SendMessageToAll(uint32 entry, ChatMsg msgType, Player const* source)
|
|
{
|
|
if (!entry)
|
|
return;
|
|
|
|
Trinity::TrinityStringChatBuilder builder(nullptr, msgType, entry, source);
|
|
Trinity::LocalizedDo<Trinity::TrinityStringChatBuilder> localizer(builder);
|
|
BroadcastWorker(localizer);
|
|
}
|
|
|
|
void Battleground::PSendMessageToAll(uint32 entry, ChatMsg msgType, Player const* source, ...)
|
|
{
|
|
if (!entry)
|
|
return;
|
|
|
|
va_list ap;
|
|
va_start(ap, source);
|
|
|
|
Trinity::TrinityStringChatBuilder builder(nullptr, msgType, entry, source, &ap);
|
|
Trinity::LocalizedDo<Trinity::TrinityStringChatBuilder> localizer(builder);
|
|
BroadcastWorker(localizer);
|
|
|
|
va_end(ap);
|
|
}
|
|
|
|
void Battleground::AddPlayerPosition(WorldPackets::Battleground::BattlegroundPlayerPosition const& position)
|
|
{
|
|
_playerPositions.push_back(position);
|
|
}
|
|
|
|
void Battleground::RemovePlayerPosition(ObjectGuid guid)
|
|
{
|
|
auto itr = std::remove_if(_playerPositions.begin(), _playerPositions.end(), [guid](WorldPackets::Battleground::BattlegroundPlayerPosition const& playerPosition)
|
|
{
|
|
return playerPosition.Guid == guid;
|
|
});
|
|
|
|
_playerPositions.erase(itr, _playerPositions.end());
|
|
}
|
|
|
|
void Battleground::EndNow()
|
|
{
|
|
RemoveFromBGFreeSlotQueue();
|
|
SetStatus(STATUS_WAIT_LEAVE);
|
|
SetRemainingTime(0);
|
|
}
|
|
|
|
void Battleground::HandleKillPlayer(Player* victim, Player* killer)
|
|
{
|
|
// Keep in mind that for arena this will have to be changed a bit
|
|
|
|
// Add +1 deaths
|
|
UpdatePlayerScore(victim, SCORE_DEATHS, 1);
|
|
// Add +1 kills to group and +1 killing_blows to killer
|
|
if (killer)
|
|
{
|
|
// Don't reward credit for killing ourselves, like fall damage of hellfire (warlock)
|
|
if (killer == victim)
|
|
return;
|
|
|
|
Team killerTeam = GetPlayerTeam(killer->GetGUID());
|
|
|
|
UpdatePlayerScore(killer, SCORE_HONORABLE_KILLS, 1);
|
|
UpdatePlayerScore(killer, SCORE_KILLING_BLOWS, 1);
|
|
killer->UpdateCriteria(CriteriaType::KillPlayer, 1, 0, 0, victim);
|
|
|
|
for (BattlegroundPlayerMap::const_iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr)
|
|
{
|
|
Player* creditedPlayer = ObjectAccessor::FindPlayer(itr->first);
|
|
if (!creditedPlayer || creditedPlayer == killer)
|
|
continue;
|
|
|
|
if (itr->second.Team == killerTeam && creditedPlayer->IsAtGroupRewardDistance(victim))
|
|
{
|
|
UpdatePlayerScore(creditedPlayer, SCORE_HONORABLE_KILLS, 1);
|
|
creditedPlayer->UpdateCriteria(CriteriaType::KillPlayer, 1, 0, 0, victim);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!isArena())
|
|
{
|
|
// To be able to remove insignia -- ONLY IN Battlegrounds
|
|
victim->SetUnitFlag(UNIT_FLAG_SKINNABLE);
|
|
RewardXPAtKill(killer, victim);
|
|
}
|
|
|
|
if (BattlegroundScript* script = GetBgMap()->GetBattlegroundScript())
|
|
script->OnPlayerKilled(victim, killer);
|
|
}
|
|
|
|
void Battleground::HandleKillUnit(Creature* victim, Unit* killer)
|
|
{
|
|
if (BattlegroundScript* script = GetBgMap()->GetBattlegroundScript())
|
|
script->OnUnitKilled(victim, killer);
|
|
}
|
|
|
|
// Return the player's team based on battlegroundplayer info
|
|
// Used in same faction arena matches mainly
|
|
Team Battleground::GetPlayerTeam(ObjectGuid guid) const
|
|
{
|
|
BattlegroundPlayerMap::const_iterator itr = m_Players.find(guid);
|
|
if (itr != m_Players.end())
|
|
return itr->second.Team;
|
|
return TEAM_OTHER;
|
|
}
|
|
|
|
bool Battleground::IsPlayerInBattleground(ObjectGuid guid) const
|
|
{
|
|
BattlegroundPlayerMap::const_iterator itr = m_Players.find(guid);
|
|
if (itr != m_Players.end())
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
bool Battleground::IsPlayerMercenaryInBattleground(ObjectGuid guid) const
|
|
{
|
|
auto itr = m_Players.find(guid);
|
|
if (itr != m_Players.end())
|
|
return itr->second.Mercenary;
|
|
return false;
|
|
}
|
|
|
|
void Battleground::PlayerAddedToBGCheckIfBGIsRunning(Player* player)
|
|
{
|
|
if (GetStatus() != STATUS_WAIT_LEAVE)
|
|
return;
|
|
|
|
BlockMovement(player);
|
|
|
|
WorldPackets::Battleground::PVPMatchStatisticsMessage pvpMatchStatistics;
|
|
BuildPvPLogDataPacket(pvpMatchStatistics.Data);
|
|
player->SendDirectMessage(pvpMatchStatistics.Write());
|
|
}
|
|
|
|
uint32 Battleground::GetAlivePlayersCountByTeam(Team team) const
|
|
{
|
|
int count = 0;
|
|
for (BattlegroundPlayerMap::const_iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr)
|
|
{
|
|
if (itr->second.Team == team)
|
|
{
|
|
Player* player = ObjectAccessor::FindPlayer(itr->first);
|
|
if (player && player->IsAlive())
|
|
++count;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
void Battleground::SetBgRaid(Team team, Group* bg_raid)
|
|
{
|
|
Group*& old_raid = team == ALLIANCE ? m_BgRaids[TEAM_ALLIANCE] : m_BgRaids[TEAM_HORDE];
|
|
if (old_raid)
|
|
old_raid->SetBattlegroundGroup(nullptr);
|
|
if (bg_raid)
|
|
bg_raid->SetBattlegroundGroup(this);
|
|
old_raid = bg_raid;
|
|
}
|
|
|
|
void Battleground::SetBracket(PVPDifficultyEntry const* bracketEntry)
|
|
{
|
|
_pvpDifficultyEntry = bracketEntry;
|
|
}
|
|
|
|
void Battleground::RewardXPAtKill(Player* killer, Player* victim)
|
|
{
|
|
if (sWorld->getBoolConfig(CONFIG_BG_XP_FOR_KILL) && killer && victim)
|
|
{
|
|
Player* killers[] = { killer };
|
|
KillRewarder(Trinity::IteratorPair(std::begin(killers), std::end(killers)), victim, true).Reward();
|
|
}
|
|
}
|
|
|
|
uint32 Battleground::GetTeamScore(TeamId teamId) const
|
|
{
|
|
if (teamId == TEAM_ALLIANCE || teamId == TEAM_HORDE)
|
|
return m_TeamScores[teamId];
|
|
|
|
TC_LOG_ERROR("bg.battleground", "GetTeamScore with wrong Team {} for BG {}", teamId, GetTypeID());
|
|
return 0;
|
|
}
|
|
|
|
char const* Battleground::GetName() const
|
|
{
|
|
return _battlegroundTemplate->BattlemasterEntry->Name[sWorld->GetDefaultDbcLocale()];
|
|
}
|
|
|
|
BattlegroundTypeId Battleground::GetTypeID() const
|
|
{
|
|
return _battlegroundTemplate->Id;
|
|
}
|
|
|
|
BattlegroundBracketId Battleground::GetBracketId() const
|
|
{
|
|
return _pvpDifficultyEntry->GetBracketId();
|
|
}
|
|
|
|
uint8 Battleground::GetUniqueBracketId() const
|
|
{
|
|
return uint8(GetMinLevel() / 5) - 1; // 10 - 1, 15 - 2, 20 - 3, etc.
|
|
}
|
|
|
|
uint32 Battleground::GetMaxPlayers() const
|
|
{
|
|
return GetMaxPlayersPerTeam() * 2;
|
|
}
|
|
|
|
uint32 Battleground::GetMinPlayers() const
|
|
{
|
|
return GetMinPlayersPerTeam() * 2;
|
|
}
|
|
|
|
uint32 Battleground::GetMinLevel() const
|
|
{
|
|
if (_pvpDifficultyEntry)
|
|
return _pvpDifficultyEntry->MinLevel;
|
|
|
|
return _battlegroundTemplate->GetMinLevel();
|
|
}
|
|
|
|
uint32 Battleground::GetMaxLevel() const
|
|
{
|
|
if (_pvpDifficultyEntry)
|
|
return _pvpDifficultyEntry->MaxLevel;
|
|
|
|
return _battlegroundTemplate->GetMaxLevel();
|
|
}
|
|
|
|
uint32 Battleground::GetMaxPlayersPerTeam() const
|
|
{
|
|
if (isArena())
|
|
{
|
|
switch (GetArenaType())
|
|
{
|
|
case ARENA_TYPE_2v2:
|
|
return 2;
|
|
case ARENA_TYPE_3v3:
|
|
return 3;
|
|
case ARENA_TYPE_5v5: // removed
|
|
return 5;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return _battlegroundTemplate->GetMaxPlayersPerTeam();
|
|
}
|
|
|
|
uint32 Battleground::GetMinPlayersPerTeam() const
|
|
{
|
|
return _battlegroundTemplate->GetMinPlayersPerTeam();
|
|
}
|