/*
* 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 .
*/
#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
template
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_ArenaTeamIds[TEAM_ALLIANCE] = 0;
m_ArenaTeamIds[TEAM_HORDE] = 0;
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 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);
}
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(Milliseconds(std::max(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)))
{
UpdatePlayerScore(player, SCORE_BONUS_HONOR, GetBonusHonorFromKill(winnerKills));
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));
}
}
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(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 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: {} for Team1Id: {} - Team2Id: {} started.", m_ArenaType, m_ArenaTeamIds[TEAM_ALLIANCE], m_ArenaTeamIds[TEAM_HORDE]);
}
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(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 = player->GetPrimaryTalentTree();
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)
{
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); // 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 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 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);
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);
}
}
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();
}