/*
* This file is part of the AzerothCore 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 "ArenaSpectator.h"
#include "ArenaTeam.h"
#include "BattlegroundBE.h"
#include "BattlegroundDS.h"
#include "BattlegroundMgr.h"
#include "BattlegroundNA.h"
#include "BattlegroundRL.h"
#include "BattlegroundRV.h"
#include "Chat.h"
#include "ChatTextBuilder.h"
#include "Creature.h"
#include "CreatureTextMgr.h"
#include "Formulas.h"
#include "GameEventMgr.h"
#include "GameGraveyard.h"
#include "GridNotifiersImpl.h"
#include "GroupMgr.h"
#include "MapMgr.h"
#include "MiscPackets.h"
#include "Object.h"
#include "ObjectMgr.h"
#include "Pet.h"
#include "Player.h"
#include "ReputationMgr.h"
#include "ScriptMgr.h"
#include "SpellAuras.h"
#include "Transport.h"
#include "Util.h"
#include "World.h"
#include "WorldPacket.h"
#include "WorldStatePackets.h"
namespace Acore
{
class BattlegroundChatBuilder
{
public:
BattlegroundChatBuilder(ChatMsg msgtype, uint32 textId, Player const* source, va_list* args = nullptr)
: _msgtype(msgtype), _textId(textId), _source(source), _args(args) { }
void operator()(WorldPacket& data, LocaleConstant loc_idx)
{
std::string strtext = sObjectMgr->GetAcoreString(_textId, loc_idx);
char const* text = strtext.c_str();
if (_args)
{
// we need copy va_list before use or original va_list will corrupted
va_list ap;
va_copy(ap, *_args);
char str[2048];
vsnprintf(str, 2048, text, ap);
va_end(ap);
do_helper(data, &str[0]);
}
else
do_helper(data, text);
}
private:
void do_helper(WorldPacket& data, char const* text)
{
ChatHandler::BuildChatPacket(data, _msgtype, LANG_UNIVERSAL, _source, _source, text);
}
ChatMsg _msgtype;
uint32 _textId;
Player const* _source;
va_list* _args;
};
class Battleground2ChatBuilder
{
public:
Battleground2ChatBuilder(ChatMsg msgtype, uint32 textId, Player const* source, int32 arg1, int32 arg2)
: _msgtype(msgtype), _textId(textId), _source(source), _arg1(arg1), _arg2(arg2) {}
void operator()(WorldPacket& data, LocaleConstant loc_idx)
{
std::string strtext = sObjectMgr->GetAcoreString(_textId, loc_idx);
char const* text = strtext.c_str();
std::string stragr1str = sObjectMgr->GetAcoreString(_arg1, loc_idx);
char const* arg1str = _arg1 ? stragr1str.c_str() : "";
std::string strarg2str = sObjectMgr->GetAcoreString(_arg2, loc_idx);
char const* arg2str = _arg2 ? strarg2str.c_str() : "";
char str[2048];
snprintf(str, 2048, text, arg1str, arg2str);
ChatHandler::BuildChatPacket(data, _msgtype, LANG_UNIVERSAL, _source, _source, str);
}
private:
ChatMsg _msgtype;
uint32 _textId;
Player const* _source;
uint32 _arg1;
uint32 _arg2;
};
} // namespace Acore
template
void Battleground::BroadcastWorker(Do& _do)
{
for (BattlegroundPlayerMap::const_iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr)
_do(itr->second);
}
void BattlegroundScore::AppendToPacket(WorldPacket& data)
{
data << PlayerGuid;
data << uint32(KillingBlows);
data << uint32(HonorableKills);
data << uint32(Deaths);
data << uint32(BonusHonor);
data << uint32(DamageDone);
data << uint32(HealingDone);
BuildObjectivesBlock(data);
}
Battleground::Battleground()
{
m_RealTypeID = BATTLEGROUND_TYPE_NONE;
m_RandomTypeID = BATTLEGROUND_TYPE_NONE;
m_InstanceID = 0;
m_Status = STATUS_NONE;
m_ClientInstanceID = 0;
m_EndTime = 0;
m_LastResurrectTime = 0;
m_ArenaType = 0;
m_IsArena = false;
m_IsTemplate = true;
m_WinnerId = PVP_TEAM_NEUTRAL;
m_StartTime = 0;
m_ResetStatTimer = 0;
m_ValidStartPositionTimer = 0;
m_Events = 0;
m_StartDelayTime = 0;
m_IsRated = false;
m_BuffChange = false;
m_IsRandom = false;
m_LevelMin = 0;
m_LevelMax = 0;
m_SetDeleteThis = false;
m_MaxPlayersPerTeam = 0;
m_MinPlayersPerTeam = 0;
m_MapId = 0;
m_Map = nullptr;
m_StartMaxDist = 0.0f;
ScriptId = 0;
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_BgInvitedPlayers[TEAM_ALLIANCE] = 0;
m_BgInvitedPlayers[TEAM_HORDE] = 0;
m_TeamScores[TEAM_ALLIANCE] = 0;
m_TeamScores[TEAM_HORDE] = 0;
m_PrematureCountDown = false;
m_PrematureCountDownTimer = 0;
m_HonorMode = BG_NORMAL;
m_SetupCompleted = false;
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;
// pussywizard:
m_UpdateTimer = 0;
}
Battleground::~Battleground()
{
LOG_DEBUG("bg.battleground", "> Remove Battleground {} {} {}", GetName(), GetBgTypeID(), GetInstanceID());
_reviveEvents.KillAllEvents(false);
// remove objects and creatures
// (this is done automatically in mapmanager update, when the instance is reset after the reset time)
uint32 size = uint32(BgCreatures.size());
for (uint32 i = 0; i < size; ++i)
DelCreature(i);
size = uint32(BgObjects.size());
for (uint32 i = 0; i < size; ++i)
DelObject(i);
sScriptMgr->OnBattlegroundDestroy(this);
sBattlegroundMgr->RemoveBattleground(GetBgTypeID(), GetInstanceID());
// unload map
if (m_Map)
{
m_Map->SetUnload();
//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 (auto const& itr : PlayerScores)
delete itr.second;
}
void Battleground::Update(uint32 diff)
{
// pussywizard:
m_UpdateTimer += diff;
if (m_UpdateTimer < BATTLEGROUND_UPDATE_INTERVAL)
return;
diff = BATTLEGROUND_UPDATE_INTERVAL; // just change diff value, no need to replace variable name in many places
m_UpdateTimer -= BATTLEGROUND_UPDATE_INTERVAL;
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(TEAM_HORDE) && !GetInvitedCount(TEAM_ALLIANCE))
// AddToFreeBGObjectsQueue(); // not yet implemented
// should be used instead of current
// ]]
// Battleground Template instance cannot be updated, because it would be deleted
if (!GetInvitedCount(TEAM_HORDE) && !GetInvitedCount(TEAM_ALLIANCE))
{
m_SetDeleteThis = true;
}
return;
}
switch (GetStatus())
{
case STATUS_WAIT_JOIN:
if (GetPlayersSize())
{
_ProcessJoin(diff);
_CheckSafePositions(diff);
}
break;
case STATUS_IN_PROGRESS:
if (isArena())
{
if (GetStartTime() >= 46 * MINUTE * IN_MILLISECONDS) // pussywizard: 1min startup + 45min allowed duration
{
EndBattleground(PVP_TEAM_NEUTRAL);
return;
}
}
else
{
_ProcessResurrect(diff);
if (sBattlegroundMgr->GetPrematureFinishTime() && (GetPlayersCountByTeam(TEAM_ALLIANCE) < GetMinPlayersPerTeam() || GetPlayersCountByTeam(TEAM_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
m_StartTime += diff;
m_ResetStatTimer += diff;
PostUpdateImpl(diff);
sScriptMgr->OnBattlegroundUpdate(this, 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 (auto const& [playerGuid, player] : GetPlayers())
{
Position pos = player->GetPosition();
Position const* startPos = GetTeamStartPosition(player->GetBgTeamId());
if (pos.GetExactDistSq(startPos) > maxDist)
{
LOG_DEBUG("bg.battleground", "BATTLEGROUND: Sending {} back to start location (map: {}) (possible exploit)", player->GetName(), GetMapId());
player->TeleportTo(GetMapId(), startPos->GetPositionX(), startPos->GetPositionY(), startPos->GetPositionZ(), startPos->GetOrientation());
}
}
}
}
inline void Battleground::_ProcessResurrect(uint32 diff)
{
// *********************************************************
// *** BATTLEGROUND RESURRECTION SYSTEM ***
// *********************************************************
// this should be handled by spell system
_reviveEvents.Update(diff);
m_LastResurrectTime += diff;
if (m_LastResurrectTime >= RESURRECTION_INTERVAL)
{
if (GetReviveQueueSize())
{
for (std::map::iterator itr = m_ReviveQueue.begin(); itr != m_ReviveQueue.end(); ++itr)
{
Creature* sh = nullptr;
for (ObjectGuid const& guid : itr->second)
{
Player* player = ObjectAccessor::FindPlayer(guid);
if (!player)
continue;
if (!sh && player->IsInWorld())
{
sh = player->GetMap()->GetCreature(itr->first);
// only for visual effect
if (sh)
// Spirit Heal, effect 117
sh->CastSpell(sh, SPELL_SPIRIT_HEAL, true);
}
// Resurrection visual
player->CastSpell(player, SPELL_RESURRECTION_VISUAL, true);
m_ResurrectQueue.push_back(guid);
}
itr->second.clear();
}
m_ReviveQueue.clear();
m_LastResurrectTime = 0;
}
else
// queue is clear and time passed, just update last resurrection time
m_LastResurrectTime = 0;
}
else if (m_LastResurrectTime > 500) // Resurrect players only half a second later, to see spirit heal effect on NPC
{
for (ObjectGuid const& guid : m_ResurrectQueue)
{
Player* player = ObjectAccessor::FindPlayer(guid);
if (!player)
continue;
player->ResurrectPlayer(1.0f);
player->CastSpell(player, 6962, true);
player->CastSpell(player, SPELL_SPIRIT_HEAL_MANA, true);
player->SpawnCorpseBones(false);
}
m_ResurrectQueue.clear();
}
}
TeamId Battleground::GetPrematureWinner()
{
if (GetPlayersCountByTeam(TEAM_ALLIANCE) >= GetMinPlayersPerTeam())
return TEAM_ALLIANCE;
else if (GetPlayersCountByTeam(TEAM_HORDE) >= GetMinPlayersPerTeam())
return TEAM_HORDE;
return TEAM_NEUTRAL;
}
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))
GetBgMap()->DoForAllPlayers([&](Player* player)
{
ChatHandler(player->GetSession()).PSendSysMessage(LANG_BATTLEGROUND_PREMATURE_FINISH_WARNING, (uint32)(m_PrematureCountDownTimer / (MINUTE * IN_MILLISECONDS)));
});
}
else
{
//announce every 15 seconds
if (newtime / (15 * IN_MILLISECONDS) != m_PrematureCountDownTimer / (15 * IN_MILLISECONDS))
GetBgMap()->DoForAllPlayers([&](Player* player)
{
ChatHandler(player->GetSession()).PSendSysMessage(LANG_BATTLEGROUND_PREMATURE_FINISH_WARNING_SECS, (uint32)(m_PrematureCountDownTimer / IN_MILLISECONDS));
});
}
m_PrematureCountDownTimer = newtime;
}
}
inline void Battleground::_ProcessJoin(uint32 diff)
{
// *********************************************************
// *** BATTLEGROUND STARTING SYSTEM ***
// *********************************************************
ModifyStartDelayTime(diff);
if (m_ResetStatTimer > 5000)
{
m_ResetStatTimer = 0;
for (BattlegroundPlayerMap::const_iterator itr = GetPlayers().begin(); itr != GetPlayers().end(); ++itr)
itr->second->ResetAllPowers();
}
if (!m_SetupCompleted)
{
if (!FindBgMap())
{
LOG_ERROR("bg.battleground", "Battleground::_ProcessJoin: map (map id: {}, instance id: {}) is not created!", m_MapId, m_InstanceID);
EndNow();
return;
}
// Setup here, only when at least one player has ported to the map
if (!SetupBattleground())
{
EndNow();
return;
}
StartingEventCloseDoors();
// Get the configured prep time
uint32 configuredPrepTime;
// Special case for Strand of the Ancients - always use 120 seconds due to boat timing mechanics
if (GetBgTypeID() == BATTLEGROUND_SA)
configuredPrepTime = 120 * IN_MILLISECONDS;
else
configuredPrepTime = isArena() ?
sWorld->getIntConfig(CONFIG_ARENA_PREP_TIME) * IN_MILLISECONDS :
sWorld->getIntConfig(CONFIG_BATTLEGROUND_PREP_TIME) * IN_MILLISECONDS;
SetStartDelayTime(configuredPrepTime);
// Pre-mark events for announcements that should be skipped based on configured prep time
if (configuredPrepTime < StartDelayTimes[BG_STARTING_EVENT_FIRST])
{
// Skip first announcement (120s for BG, 60s for Arena)
m_Events |= BG_STARTING_EVENT_1;
if (configuredPrepTime < StartDelayTimes[BG_STARTING_EVENT_SECOND])
{
// Skip second announcement (60s for BG, 30s for Arena)
m_Events |= BG_STARTING_EVENT_2;
if (configuredPrepTime < StartDelayTimes[BG_STARTING_EVENT_THIRD])
{
// Skip third announcement (30s for BG, 15s for Arena)
m_Events |= BG_STARTING_EVENT_3;
}
}
}
// Mark setup as completed
m_SetupCompleted = true;
}
// First announcement at 120s or 60s (Depending on BG or Arena and configured time)
if (GetStartDelayTime() <= StartDelayTimes[BG_STARTING_EVENT_FIRST] && !(m_Events & BG_STARTING_EVENT_1))
{
m_Events |= BG_STARTING_EVENT_1;
if (StartMessageIds[BG_STARTING_EVENT_FIRST])
SendBroadcastText(StartMessageIds[BG_STARTING_EVENT_FIRST], CHAT_MSG_BG_SYSTEM_NEUTRAL);
}
// Second announcement at 60s or 30s
else if (GetStartDelayTime() <= StartDelayTimes[BG_STARTING_EVENT_SECOND] && !(m_Events & BG_STARTING_EVENT_2))
{
m_Events |= BG_STARTING_EVENT_2;
if (StartMessageIds[BG_STARTING_EVENT_SECOND])
SendBroadcastText(StartMessageIds[BG_STARTING_EVENT_SECOND], CHAT_MSG_BG_SYSTEM_NEUTRAL);
}
// Third announcement at 30s or 15s
else if (GetStartDelayTime() <= StartDelayTimes[BG_STARTING_EVENT_THIRD] && !(m_Events & BG_STARTING_EVENT_3))
{
m_Events |= BG_STARTING_EVENT_3;
if (StartMessageIds[BG_STARTING_EVENT_THIRD])
SendBroadcastText(StartMessageIds[BG_STARTING_EVENT_THIRD], CHAT_MSG_BG_SYSTEM_NEUTRAL);
if (isArena())
switch (GetBgTypeID())
{
case BATTLEGROUND_NA:
DelObject(BG_NA_OBJECT_READY_MARKER_1);
DelObject(BG_NA_OBJECT_READY_MARKER_2);
break;
case BATTLEGROUND_BE:
DelObject(BG_BE_OBJECT_READY_MARKER_1);
DelObject(BG_BE_OBJECT_READY_MARKER_2);
break;
case BATTLEGROUND_RL:
DelObject(BG_RL_OBJECT_READY_MARKER_1);
DelObject(BG_RL_OBJECT_READY_MARKER_2);
break;
case BATTLEGROUND_DS:
DelObject(BG_DS_OBJECT_READY_MARKER_1);
DelObject(BG_DS_OBJECT_READY_MARKER_2);
break;
case BATTLEGROUND_RV:
DelObject(BG_RV_OBJECT_READY_MARKER_1);
DelObject(BG_RV_OBJECT_READY_MARKER_2);
break;
default:
break;
}
}
// Delay expired (after configured prep time)
else if (GetStartDelayTime() <= 0 && !(m_Events & BG_STARTING_EVENT_4))
{
m_Events |= BG_STARTING_EVENT_4;
// Start the battle
StartingEventOpenDoors();
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]);
// Remove preparation
if (isArena())
{
// pussywizard: initial visibility range is 30yd, set it to a proper value now:
if (BattlegroundMap* map = GetBgMap())
map->SetVisibilityRange(World::GetMaxVisibleDistanceInBGArenas());
for (BattlegroundPlayerMap::const_iterator itr = GetPlayers().begin(); itr != GetPlayers().end(); ++itr)
if (Player* player = itr->second)
{
WorldPacket status;
sBattlegroundMgr->BuildBattlegroundStatusPacket(&status, this, player->GetCurrentBattlegroundQueueSlot(), STATUS_IN_PROGRESS, 0, GetStartTime(), GetArenaType(), player->GetBgTeamId());
player->SendDirectMessage(&status);
player->RemoveAurasDueToSpell(SPELL_ARENA_PREPARATION);
player->ResetAllPowers();
// remove auras with duration lower than 30s
Unit::AuraApplicationMap& auraMap = player->GetAppliedAuras();
for (Unit::AuraApplicationMap::iterator iter = auraMap.begin(); iter != auraMap.end();)
{
AuraApplication* aurApp = iter->second;
Aura* aura = aurApp->GetBase();
if (!aura->IsPermanent()
&& aura->GetDuration() <= 30 * IN_MILLISECONDS
&& aurApp->IsPositive()
// && (!aura->GetSpellInfo()->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES)) Xinef: condition, ALL buffs should be removed
&& (!aura->HasEffectType(SPELL_AURA_MOD_INVISIBILITY)))
player->RemoveAura(iter);
else
++iter;
}
player->UpdateObjectVisibility(true);
}
for (SpectatorList::const_iterator itr = m_Spectators.begin(); itr != m_Spectators.end(); ++itr)
ArenaSpectator::HandleResetCommand(*itr);
CheckWinConditions();
// pussywizard: arena spectator stuff
if (GetStatus() == STATUS_IN_PROGRESS)
{
for (ToBeTeleportedMap::const_iterator itr = m_ToBeTeleported.begin(); itr != m_ToBeTeleported.end(); ++itr)
if (Player* p = ObjectAccessor::FindConnectedPlayer(itr->first))
if (Player* t = ObjectAccessor::FindPlayer(itr->second))
{
if (!t->FindMap() || t->FindMap() != GetBgMap())
continue;
p->SetSummonPoint(t->GetMapId(), t->GetPositionX(), t->GetPositionY(), t->GetPositionZ(), 15, true);
WorldPacket data(SMSG_SUMMON_REQUEST, 8 + 4 + 4);
data << t->GetGUID();
data << uint32(t->GetZoneId());
data << uint32(15 * IN_MILLISECONDS);
p->SendDirectMessage(&data);
}
m_ToBeTeleported.clear();
}
sScriptMgr->OnArenaStart(this);
}
else
{
PlaySoundToAll(SOUND_BG_START);
for (BattlegroundPlayerMap::const_iterator itr = GetPlayers().begin(); itr != GetPlayers().end(); ++itr)
{
itr->second->RemoveAurasDueToSpell(SPELL_PREPARATION);
itr->second->ResetAllPowers();
}
// Announce BG starting
if (sWorld->getBoolConfig(CONFIG_BATTLEGROUND_QUEUE_ANNOUNCER_ENABLE))
ChatHandler(nullptr).SendWorldText(LANG_BG_STARTED_ANNOUNCE_WORLD, GetName(), std::min(GetMinLevel(), (uint32)80), std::min(GetMaxLevel(), (uint32)80));
sScriptMgr->OnBattlegroundStart(this);
}
}
}
inline void Battleground::_ProcessLeave(uint32 diff)
{
// *********************************************************
// *** BATTLEGROUND ENDING SYSTEM ***
// *********************************************************
// remove all players from battleground after 2 minutes
m_EndTime -= diff;
if (m_EndTime <= 0)
{
m_EndTime = TIME_TO_AUTOREMOVE; // pussywizard: 0 -> TIME_TO_AUTOREMOVE
BattlegroundPlayerMap::iterator itr, next;
for (itr = m_Players.begin(); itr != m_Players.end(); itr = next)
{
next = itr;
++next;
itr->second->LeaveBattleground(this); //itr is erased here!
}
}
}
void Battleground::SetTeamStartPosition(TeamId teamId, Position const& pos)
{
ASSERT(teamId < TEAM_NEUTRAL);
_startPosition[teamId] = pos;
}
Position const* Battleground::GetTeamStartPosition(TeamId teamId) const
{
ASSERT(teamId < TEAM_NEUTRAL);
return &_startPosition[teamId];
}
void Battleground::SendPacketToAll(WorldPacket const* packet)
{
for (BattlegroundPlayerMap::const_iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr)
itr->second->SendDirectMessage(packet);
}
void Battleground::SendPacketToTeam(TeamId teamId, WorldPacket const* packet, Player* sender, bool self)
{
for (BattlegroundPlayerMap::const_iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr)
if (itr->second->GetBgTeamId() == teamId && (self || sender != itr->second))
itr->second->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 (!sObjectMgr->GetBroadcastText(id))
{
LOG_ERROR("bg.battleground", "Battleground::SendBroadcastText: `broadcast_text` (ID: {}) was not found", id);
return;
}
Acore::BroadcastTextBuilder builder(nullptr, msgType, id, GENDER_MALE, target);
Acore::LocalizedPacketDo localizer(builder);
BroadcastWorker(localizer);
}
void Battleground::PlaySoundToAll(uint32 soundID)
{
SendPacketToAll(WorldPackets::Misc::Playsound(soundID).Write());
}
void Battleground::CastSpellOnTeam(uint32 spellId, TeamId teamId)
{
for (BattlegroundPlayerMap::const_iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr)
if (itr->second->GetBgTeamId() == teamId)
itr->second->CastSpell(itr->second, spellId, true);
}
void Battleground::RemoveAuraOnTeam(uint32 spellId, TeamId teamId)
{
for (BattlegroundPlayerMap::const_iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr)
if (itr->second->GetBgTeamId() == teamId)
itr->second->RemoveAura(spellId);
}
void Battleground::YellToAll(Creature* creature, char const* text, uint32 language)
{
for (BattlegroundPlayerMap::const_iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr)
{
WorldPacket data;
ChatHandler::BuildChatPacket(data, CHAT_MSG_MONSTER_YELL, Language(language), creature, itr->second, text);
itr->second->SendDirectMessage(&data);
}
}
void Battleground::RewardHonorToTeam(uint32 honor, TeamId teamId)
{
for (BattlegroundPlayerMap::const_iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr)
if (itr->second->GetBgTeamId() == teamId)
UpdatePlayerScore(itr->second, SCORE_BONUS_HONOR, honor);
}
void Battleground::RewardReputationToTeam(uint32 factionId, uint32 reputation, TeamId teamId)
{
for (BattlegroundPlayerMap::const_iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr)
if (itr->second->GetBgTeamId() == teamId)
{
uint32 realFactionId = GetRealRepFactionForPlayer(factionId, itr->second);
float repGain = static_cast(reputation);
AddPct(repGain, itr->second->GetTotalAuraModifier(SPELL_AURA_MOD_REPUTATION_GAIN));
AddPct(repGain, itr->second->GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_FACTION_REPUTATION_GAIN, realFactionId));
if (FactionEntry const* factionEntry = sFactionStore.LookupEntry(realFactionId))
itr->second->GetReputationMgr().ModifyReputation(factionEntry, repGain);
}
}
uint32 Battleground::GetRealRepFactionForPlayer(uint32 factionId, Player* player)
{
if (player)
{
// if the bg team is not the original team, reverse reputation
if (player->GetBgTeamId() != player->GetTeamId(true))
{
switch (factionId)
{
case BG_REP_AB_ALLIANCE:
return BG_REP_AB_HORDE;
case BG_REP_AB_HORDE:
return BG_REP_AB_ALLIANCE;
case BG_REP_AV_ALLIANCE:
return BG_REP_AV_HORDE;
case BG_REP_AV_HORDE:
return BG_REP_AV_ALLIANCE;
case BG_REP_WS_ALLIANCE:
return BG_REP_WS_HORDE;
case BG_REP_WS_HORDE:
return BG_REP_WS_ALLIANCE;
}
}
}
return factionId;
}
void Battleground::UpdateWorldState(uint32 variable, uint32 value)
{
WorldPackets::WorldState::UpdateWorldState worldstate;
worldstate.VariableID = variable;
worldstate.Value = value;
SendPacketToAll(worldstate.Write());
}
void Battleground::EndBattleground(PvPTeamId winnerTeamId)
{
// xinef: if this is true, it means that endbattleground is called second time
// skip to avoid double rating reduce / add
// can bug out due to multithreading ?
// set as fast as possible
if (GetStatus() == STATUS_WAIT_LEAVE)
return;
RemoveFromBGFreeSlotQueue();
SetStatus(STATUS_WAIT_LEAVE);
SetWinner(winnerTeamId);
if (winnerTeamId == PVP_TEAM_ALLIANCE)
{
if (isBattleground())
SendBroadcastText(BG_TEXT_ALLIANCE_WINS, CHAT_MSG_BG_SYSTEM_NEUTRAL);
PlaySoundToAll(SOUND_ALLIANCE_WINS); // alliance wins sound
}
else if (winnerTeamId == PVP_TEAM_HORDE)
{
if (isBattleground())
SendBroadcastText(BG_TEXT_HORDE_WINS, CHAT_MSG_BG_SYSTEM_NEUTRAL);
PlaySoundToAll(SOUND_HORDE_WINS); // horde wins sound
}
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].Get() + 1;
}
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_PVPSTATS_BATTLEGROUND);
stmt->SetData(0, battlegroundId);
stmt->SetData(1, GetWinner());
stmt->SetData(2, GetUniqueBracketId());
stmt->SetData(3, GetBgTypeID(true));
CharacterDatabase.Execute(stmt);
}
//we must set it this way, because end time is sent in packet!
m_EndTime = TIME_TO_AUTOREMOVE;
WorldPacket pvpLogData;
BuildPvPLogDataPacket(pvpLogData);
for (auto const& [playerGuid, player] : m_Players)
{
TeamId bgTeamId = player->GetBgTeamId();
// should remove spirit of redemption
if (player->HasSpiritOfRedemptionAura())
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();
player->getHostileRefMgr().deleteReferences();
}
uint32 winner_kills = player->GetRandomWinner() ? sWorld->getIntConfig(CONFIG_BG_REWARD_WINNER_HONOR_LAST) : sWorld->getIntConfig(CONFIG_BG_REWARD_WINNER_HONOR_FIRST);
uint32 loser_kills = player->GetRandomWinner() ? sWorld->getIntConfig(CONFIG_BG_REWARD_LOSER_HONOR_LAST) : sWorld->getIntConfig(CONFIG_BG_REWARD_LOSER_HONOR_FIRST);
uint32 winner_arena = player->GetRandomWinner() ? sWorld->getIntConfig(CONFIG_BG_REWARD_WINNER_ARENA_LAST) : sWorld->getIntConfig(CONFIG_BG_REWARD_WINNER_ARENA_FIRST);
// Reward winner team
if (bgTeamId == GetTeamId(winnerTeamId))
{
if (IsRandom() || BattlegroundMgr::IsBGWeekend(GetBgTypeID(true)))
{
UpdatePlayerScore(player, SCORE_BONUS_HONOR, GetBonusHonorFromKill(winner_kills));
// Xinef: check player level and not bracket level if (CanAwardArenaPoints())
if (player->GetLevel() >= sWorld->getIntConfig(CONFIG_DAILY_RBG_MIN_LEVEL_AP_REWARD))
player->ModifyArenaPoints(winner_arena);
if (!player->GetRandomWinner())
player->SetRandomWinner(true);
// Achievement 908 / 909 "Call to Arms!"
switch (GetBgTypeID(true))
{
case BATTLEGROUND_AB:
// Call to Arms: Arathi Basin
player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUEST, 11335);
player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUEST, 11339);
break;
case BATTLEGROUND_AV:
// Call to Arms: Alterac Valley
player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUEST, 11336);
player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUEST, 11340);
break;
case BATTLEGROUND_EY:
// Call to Arms: Eye of the Storm
player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUEST, 11337);
player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUEST, 11341);
break;
case BATTLEGROUND_WS:
// Call to Arms: Warsong Gulch
player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUEST, 11338);
player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUEST, 11342);
break;
default:
break;
}
}
player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_WIN_BG, player->GetMapId());
}
else
{
if (IsRandom() || BattlegroundMgr::IsBGWeekend(GetBgTypeID(true)))
UpdatePlayerScore(player, SCORE_BONUS_HONOR, GetBonusHonorFromKill(loser_kills));
}
sScriptMgr->OnBattlegroundEndReward(this, player, GetTeamId(winnerTeamId));
player->ResetAllPowers();
player->CombatStopWithPets(true);
BlockMovement(player);
player->SendDirectMessage(&pvpLogData);
if (isBattleground() && sWorld->getBoolConfig(CONFIG_BATTLEGROUND_STORE_STATISTICS_ENABLE))
{
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_PVPSTATS_PLAYER);
auto const& score = PlayerScores.find(player->GetGUID().GetCounter());
stmt->SetData(0, battlegroundId);
stmt->SetData(1, player->GetGUID().GetCounter());
stmt->SetData(2, bgTeamId == GetTeamId(winnerTeamId));
stmt->SetData(3, score->second->GetKillingBlows());
stmt->SetData(4, score->second->GetDeaths());
stmt->SetData(5, score->second->GetHonorableKills());
stmt->SetData(6, score->second->GetBonusHonor());
stmt->SetData(7, score->second->GetDamageDone());
stmt->SetData(8, score->second->GetHealingDone());
stmt->SetData(9, score->second->GetAttr1());
stmt->SetData(10, score->second->GetAttr2());
stmt->SetData(11, score->second->GetAttr3());
stmt->SetData(12, score->second->GetAttr4());
stmt->SetData(13, score->second->GetAttr5());
CharacterDatabase.Execute(stmt);
}
WorldPacket data;
sBattlegroundMgr->BuildBattlegroundStatusPacket(&data, this, player->GetCurrentBattlegroundQueueSlot(), STATUS_IN_PROGRESS, TIME_TO_AUTOREMOVE, GetStartTime(), GetArenaType(), player->GetBgTeamId());
player->SendDirectMessage(&data);
player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_BATTLEGROUND, player->GetMapId());
}
if (IsEventActive(EVENT_SPIRIT_OF_COMPETITION) && isBattleground())
SpiritOfCompetitionEvent(winnerTeamId);
sScriptMgr->OnBattlegroundEnd(this, GetTeamId(winnerTeamId));
}
void Battleground::SpiritOfCompetitionEvent(PvPTeamId winnerTeamId) const
{
bool isDraw = winnerTeamId == PVP_TEAM_NEUTRAL;
std::vector filteredPlayers;
GetBgMap()->DoForAllPlayers([&](Player* player)
{
// Reward all eligible players the participant reward
if (player->GetQuestStatus(QUEST_FLAG_PARTICIPANT) != QUEST_STATUS_REWARDED)
player->CastSpell(player, SPELL_SPIRIT_OF_COMPETITION_PARTICIPANT, true);
// Collect players of the winning team who has yet to recieve the winner reward
if (!isDraw && player->GetBgTeamId() == GetTeamId(winnerTeamId) &&
player->GetQuestStatus(QUEST_FLAG_WINNER) != QUEST_STATUS_REWARDED)
filteredPlayers.push_back(player);
});
// Randomly select one player from winners team to recieve the reward, if any eligible
if (!filteredPlayers.empty())
{
Player* wPlayer = filteredPlayers[rand() % filteredPlayers.size()];
wPlayer->CastSpell(wPlayer, SPELL_SPIRIT_OF_COMPETITION_WINNER, true);
}
}
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 Acore::Honor::hk_honor_at_level(maxLevel, float(kills));
}
void Battleground::BlockMovement(Player* player)
{
player->SetClientControl(player, 0); // 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()
}
void Battleground::RemovePlayerAtLeave(Player* player)
{
TeamId teamId = player->GetBgTeamId();
// check if the player was a participant of the match, or only entered through gm command
bool participant = false;
BattlegroundPlayerMap::iterator itr = m_Players.find(player->GetGUID());
if (itr != m_Players.end())
{
UpdatePlayersCountByTeam(teamId, true); // -1 player
m_Players.erase(itr);
participant = true;
}
// delete player score if exists
auto const& itr2 = PlayerScores.find(player->GetGUID().GetCounter());
if (itr2 != PlayerScores.end())
{
delete itr2->second;
PlayerScores.erase(itr2);
}
RemovePlayerFromResurrectQueue(player);
// resurrect on exit
if (!player->IsAlive())
{
player->ResurrectPlayer(1.0f);
player->SpawnCorpseBones();
}
player->RemoveAurasByType(SPELL_AURA_MOUNTED);
// GetStatus might be changed in RemovePlayer - define it here
BattlegroundStatus status = GetStatus();
// BG subclass specific code
RemovePlayer(player);
// should remove spirit of redemption
if (player->HasSpiritOfRedemptionAura())
player->RemoveAurasByType(SPELL_AURA_MOD_SHAPESHIFT);
// if the player was a match participant
if (participant)
{
player->ClearAfkReports();
WorldPacket data;
sBattlegroundMgr->BuildBattlegroundStatusPacket(&data, this, player->GetCurrentBattlegroundQueueSlot(), STATUS_NONE, 0, 0, 0, TEAM_NEUTRAL);
player->SendDirectMessage(&data);
BattlegroundQueueTypeId bgQueueTypeId = BattlegroundMgr::BGQueueTypeId(GetBgTypeID(), GetArenaType());
// this call is important, because player, when joins to battleground, this method is not called, so it must be called when leaving bg
player->RemoveBattlegroundQueueId(bgQueueTypeId);
// remove from raid group if player is member
if (Group* group = GetBgRaid(teamId))
if (group->IsMember(player->GetGUID()))
if (!group->RemoveMember(player->GetGUID())) // group was disbanded
SetBgRaid(teamId, nullptr);
// let others know
sBattlegroundMgr->BuildPlayerLeftBattlegroundPacket(&data, player->GetGUID());
SendPacketToTeam(teamId, &data, player, false);
// cast deserter
if (isBattleground() && !player->IsGameMaster() && sWorld->getBoolConfig(CONFIG_BATTLEGROUND_CAST_DESERTER))
if (status == STATUS_IN_PROGRESS || status == STATUS_WAIT_JOIN)
player->ScheduleDelayedOperation(DELAYED_SPELL_CAST_DESERTER);
DecreaseInvitedCount(teamId);
//we should update battleground queue, but only if bg isn't ending
if (isBattleground() && GetStatus() < STATUS_WAIT_LEAVE)
{
BattlegroundTypeId bgTypeId = GetBgTypeID();
BattlegroundQueueTypeId bgQueueTypeId = BattlegroundMgr::BGQueueTypeId(GetBgTypeID(), GetArenaType());
// a player has left the battleground, so there are free slots -> add to queue
AddToBGFreeSlotQueue();
sBattlegroundMgr->ScheduleQueueUpdate(0, 0, bgQueueTypeId, bgTypeId, GetBracketId());
}
}
player->SetBattlegroundId(0, BATTLEGROUND_TYPE_NONE, PLAYER_MAX_BATTLEGROUND_QUEUES, false, false, TEAM_NEUTRAL);
// Xinef: remove all criterias on bg leave
player->ResetAchievementCriteria(ACHIEVEMENT_CRITERIA_CONDITION_BG_MAP, GetMapId(), true);
sScriptMgr->OnBattlegroundRemovePlayerAtLeave(this, player);
}
// this method is called when creating bg
void Battleground::Init()
{
SetWinner(PVP_TEAM_NEUTRAL);
SetStatus(STATUS_WAIT_QUEUE);
SetStartTime(0);
SetEndTime(0);
SetLastResurrectTime(0);
m_Events = 0;
if (m_BgInvitedPlayers[TEAM_ALLIANCE] > 0 || m_BgInvitedPlayers[TEAM_HORDE] > 0)
{
LOG_ERROR("bg.battleground", "Battleground::Reset: one of the counters is not 0 (alliance: {}, horde: {}) for BG (map: {}, instance id: {})!", m_BgInvitedPlayers[TEAM_ALLIANCE], m_BgInvitedPlayers[TEAM_HORDE], m_MapId, m_InstanceID);
ABORT();
}
m_BgInvitedPlayers[TEAM_ALLIANCE] = 0;
m_BgInvitedPlayers[TEAM_HORDE] = 0;
_InBGFreeSlotQueue = false;
m_Players.clear();
for (auto const& itr : PlayerScores)
delete itr.second;
PlayerScores.clear();
for (auto& itr : _arenaTeamScores)
itr.Reset();
ResetBGSubclass();
}
void Battleground::StartBattleground()
{
SetStartTime(0);
SetLastResurrectTime(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 Battleground::Update() method is executed
sBattlegroundMgr->AddBattleground(this);
if (m_IsRated)
LOG_DEBUG("bg.arena", "Arena match type: {} for Team1Id: {} - Team2Id: {} started.", m_ArenaType, m_ArenaTeamIds[TEAM_ALLIANCE], m_ArenaTeamIds[TEAM_HORDE]);
}
void Battleground::AddPlayer(Player* player)
{
// remove afk from player
if (player->HasPlayerFlag(PLAYER_FLAGS_AFK))
player->ToggleAFK();
sScriptMgr->OnBattlegroundBeforeAddPlayer(this, player);
// score struct must be created in inherited class
ObjectGuid guid = player->GetGUID();
TeamId teamId = player->GetBgTeamId();
// Add to list/maps
m_Players[guid] = player;
UpdatePlayersCountByTeam(teamId, false); // +1 player
WorldPacket data;
sBattlegroundMgr->BuildPlayerJoinedBattlegroundPacket(&data, player);
SendPacketToTeam(teamId, &data, player, false);
// add arena specific auras
if (isArena())
{
// restore pets health before remove
Pet* pet = player->GetPet();
if (pet)
if (pet->IsAlive())
pet->SetHealth(pet->GetMaxHealth());
player->RemoveArenaAuras();
if (pet)
pet->RemoveArenaAuras();
player->RemoveArenaSpellCooldowns(true);
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.
}
// Xinef: reset all map criterias on map enter
player->ResetAchievementCriteria(ACHIEVEMENT_CRITERIA_CONDITION_BG_MAP, GetMapId(), true);
// setup BG group membership
PlayerAddedToBGCheckIfBGIsRunning(player);
AddOrSetPlayerToCorrectBgGroup(player, teamId);
sScriptMgr->OnBattlegroundAddPlayer(this, player);
// Log
LOG_DEBUG("bg.battleground", "BATTLEGROUND: Player {} joined the battle.", player->GetName());
}
// 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, TeamId teamId)
{
if (player->GetGroup() && (player->GetGroup()->isBGGroup() || player->GetGroup()->isBFGroup()))
{
LOG_INFO("misc", "Battleground::AddOrSetPlayerToCorrectBgGroup - player is already in {} group!", (player->GetGroup()->isBGGroup() ? "BG" : "BF"));
return;
}
ObjectGuid playerGuid = player->GetGUID();
Group* group = GetBgRaid(teamId);
if (!group) // first player joined
{
group = new Group;
SetBgRaid(teamId, group);
group->Create(player);
sGroupMgr->AddGroup(group);
}
else 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 only once ... it adds pointer to queue
void Battleground::AddToBGFreeSlotQueue()
{
if (!_InBGFreeSlotQueue && isBattleground())
{
sBattlegroundMgr->AddToBGFreeSlotQueue(m_RealTypeID, this);
_InBGFreeSlotQueue = true;
}
}
// This method removes this battleground from free queue - it must be called when deleting battleground
void Battleground::RemoveFromBGFreeSlotQueue()
{
if (_InBGFreeSlotQueue)
{
sBattlegroundMgr->RemoveFromBGFreeSlotQueue(m_RealTypeID, m_InstanceID);
_InBGFreeSlotQueue = false;
}
}
uint32 Battleground::GetFreeSlotsForTeam(TeamId teamId) const
{
if (!(GetStatus() == STATUS_IN_PROGRESS || GetStatus() == STATUS_WAIT_JOIN))
return 0;
// if CONFIG_BATTLEGROUND_INVITATION_TYPE == BG_QUEUE_INVITATION_TYPE_NO_BALANCE, invite everyone unless the BG is full
if (sWorld->getIntConfig(CONFIG_BATTLEGROUND_INVITATION_TYPE) == BG_QUEUE_INVITATION_TYPE_NO_BALANCE)
return (GetInvitedCount(teamId) < GetMaxPlayersPerTeam()) ? GetMaxPlayersPerTeam() - GetInvitedCount(teamId) : 0;
// if BG is already started or CONFIG_BATTLEGROUND_INVITATION_TYPE != BG_QUEUE_INVITATION_TYPE_NO_BALANCE, do not allow to join too many players of one faction
uint32 thisTeamInvitedCount = teamId == TEAM_ALLIANCE ? GetInvitedCount(TEAM_ALLIANCE) : GetInvitedCount(TEAM_HORDE);
uint32 thisTeamPlayersCount = teamId == TEAM_ALLIANCE ? GetPlayersCountByTeam(TEAM_ALLIANCE) : GetPlayersCountByTeam(TEAM_HORDE);
uint32 otherTeamInvitedCount = teamId == TEAM_ALLIANCE ? GetInvitedCount(TEAM_HORDE) : GetInvitedCount(TEAM_ALLIANCE);
uint32 otherTeamPlayersCount = teamId == TEAM_ALLIANCE ? GetPlayersCountByTeam(TEAM_HORDE) : GetPlayersCountByTeam(TEAM_ALLIANCE);
// difference based on ppl invited (not necessarily entered battle)
// default: allow 0
uint32 diff = 0;
uint32 maxPlayersPerTeam = GetMaxPlayersPerTeam();
uint32 minPlayersPerTeam = GetMinPlayersPerTeam();
// allow join one person if the sides are equal (to fill up bg to minPlayerPerTeam)
if (otherTeamInvitedCount == thisTeamInvitedCount)
diff = 1;
else if (otherTeamInvitedCount > thisTeamInvitedCount) // allow join more ppl if the other side has more players
diff = otherTeamInvitedCount - thisTeamInvitedCount;
// difference based on max players per team (don't allow inviting more)
uint32 diff2 = (thisTeamInvitedCount < maxPlayersPerTeam) ? maxPlayersPerTeam - 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;
else if (otherTeamPlayersCount > thisTeamPlayersCount) // allow join more ppl if the other side has more players
diff3 = otherTeamPlayersCount - thisTeamPlayersCount;
else if (thisTeamInvitedCount <= minPlayersPerTeam) // or other side has less than minPlayersPerTeam
diff3 = minPlayersPerTeam - thisTeamInvitedCount + 1;
// return the minimum of the 3 differences
// min of diff, diff2 and diff3
return std::min({ diff, diff2, diff3 });
}
uint32 Battleground::GetMaxFreeSlots() const
{
return std::max(GetFreeSlotsForTeam(TEAM_ALLIANCE), GetFreeSlotsForTeam(TEAM_HORDE));
}
bool Battleground::HasFreeSlots() const
{
if (GetStatus() != STATUS_WAIT_JOIN && GetStatus() != STATUS_IN_PROGRESS)
return false;
for (uint8 i = 0; i < PVP_TEAMS_COUNT; ++i)
if (GetFreeSlotsForTeam((TeamId)i) > 0)
return true;
return false;
}
void Battleground::SpectatorsSendPacket(WorldPacket& data)
{
for (SpectatorList::const_iterator itr = m_Spectators.begin(); itr != m_Spectators.end(); ++itr)
(*itr)->SendDirectMessage(&data);
}
void Battleground::ReadyMarkerClicked(Player* p)
{
if (!isArena() || GetStatus() >= STATUS_IN_PROGRESS || GetStartDelayTime() <= BG_START_DELAY_15S || (m_Events & BG_STARTING_EVENT_3) || p->IsSpectator())
return;
readyMarkerClickedSet.insert(p->GetGUID());
uint32 count = readyMarkerClickedSet.size();
uint32 req = ArenaTeam::GetReqPlayersForType(GetArenaType());
ChatHandler(p->GetSession()).SendNotification("You are marked as ready {}/{}", count, req);
if (count == req)
{
m_Events |= BG_STARTING_EVENT_2;
m_StartTime += GetStartDelayTime() - BG_START_DELAY_15S;
SetStartDelayTime(BG_START_DELAY_15S);
}
}
void Battleground::BuildPvPLogDataPacket(WorldPacket& data)
{
uint8 type = (isArena() ? 1 : 0);
data.Initialize(MSG_PVP_LOG_DATA, 1 + 1 + 4 + 40 * GetPlayerScores()->size());
data << uint8(type); // type (battleground = 0 / arena = 1)
if (type) // arena
{
for (uint8 i = 0; i < PVP_TEAMS_COUNT; ++i)
_arenaTeamScores[i].BuildRatingInfoBlock(data);
for (uint8 i = 0; i < PVP_TEAMS_COUNT; ++i)
_arenaTeamScores[i].BuildTeamInfoBlock(data);
}
if (GetStatus() == STATUS_WAIT_LEAVE)
{
data << uint8(1); // bg ended
data << uint8(GetWinner()); // who win
}
else
data << uint8(0); // bg not ended
data << uint32(GetPlayerScores()->size());
for (auto const& score : PlayerScores)
score.second->AppendToPacket(data);
}
bool Battleground::UpdatePlayerScore(Player* player, uint32 type, uint32 value, bool doAddHonor)
{
auto const& itr = PlayerScores.find(player->GetGUID().GetCounter());
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::AddPlayerToResurrectQueue(ObjectGuid npc_guid, ObjectGuid player_guid)
{
m_ReviveQueue[npc_guid].push_back(player_guid);
Player* player = ObjectAccessor::FindPlayer(player_guid);
if (!player)
return;
player->CastSpell(player, SPELL_WAITING_FOR_RESURRECT, true);
}
void Battleground::RemovePlayerFromResurrectQueue(Player* player)
{
for (std::map::iterator itr = m_ReviveQueue.begin(); itr != m_ReviveQueue.end(); ++itr)
for (GuidVector::iterator itr2 = itr->second.begin(); itr2 != itr->second.end(); ++itr2)
if (*itr2 == player->GetGUID())
{
itr->second.erase(itr2);
player->RemoveAurasDueToSpell(SPELL_WAITING_FOR_RESURRECT);
return;
}
}
void Battleground::RelocateDeadPlayers(ObjectGuid queueIndex)
{
// Those who are waiting to resurrect at this node are taken to the closest own node's graveyard
GuidVector& ghostList = m_ReviveQueue[queueIndex];
if (!ghostList.empty())
{
GraveyardStruct const* closestGrave = nullptr;
for (ObjectGuid const& guid : ghostList)
{
Player* player = ObjectAccessor::FindPlayer(guid);
if (!player)
continue;
if (!closestGrave)
closestGrave = GetClosestGraveyard(player);
if (closestGrave)
player->TeleportTo(GetMapId(), closestGrave->x, closestGrave->y, closestGrave->z, player->GetOrientation());
}
ghostList.clear();
}
}
bool Battleground::AddObject(uint32 type, uint32 entry, float x, float y, float z, float o, float rotation0, float rotation1, float rotation2, float rotation3, uint32 /*respawnTime*/, GOState goState)
{
// If the assert is called, means that BgObjects must be resized!
ASSERT(type < BgObjects.size());
Map* map = FindBgMap();
if (!map)
return false;
// Must be created this way, adding to godatamap would add it to the base map of the instance
// and when loading it (in go::LoadFromDB()), a new guid would be assigned to the object, and a new object would be created
// So we must create it specific for this instance
GameObject* go = sObjectMgr->IsGameObjectStaticTransport(entry) ? new StaticTransport() : new GameObject();
if (!go->Create(map->GenerateLowGuid(), entry, GetBgMap(),
PHASEMASK_NORMAL, x, y, z, o, G3D::Quat(rotation0, rotation1, rotation2, rotation3), 100, goState))
{
LOG_ERROR("sql.sql", "Battleground::AddObject: cannot create gameobject (entry: {}) for BG (map: {}, instance id: {})!",
entry, m_MapId, m_InstanceID);
LOG_ERROR("bg.battleground", "Battleground::AddObject: cannot create gameobject (entry: {}) for BG (map: {}, instance id: {})!",
entry, m_MapId, m_InstanceID);
delete go;
return false;
}
/*
ObjectGuid::LowType spawnId = go->GetSpawnId();
// without this, UseButtonOrDoor caused the crash, since it tried to get go info from godata
// iirc that was changed, so adding to go data map is no longer required if that was the only function using godata from GameObject without checking if it existed
GameObjectData& data = sObjectMgr->NewGOData(spawnId);
data.id = entry;
data.mapid = GetMapId();
data.posX = x;
data.posY = y;
data.posZ = z;
data.orientation = o;
data.rotation0 = rotation0;
data.rotation1 = rotation1;
data.rotation2 = rotation2;
data.rotation3 = rotation3;
data.spawntimesecs = respawnTime;
data.spawnMask = 1;
data.animprogress = 100;
data.go_state = 1;
*/
// Add to world, so it can be later looked up from HashMapHolder
if (!map->AddToMap(go))
{
delete go;
return false;
}
BgObjects[type] = go->GetGUID();
return true;
}
// Some doors aren't despawned so we cannot handle their closing in gameobject::update()
// It would be nice to correctly implement GO_ACTIVATED state and open/close doors in gameobject code
void Battleground::DoorClose(uint32 type)
{
if (GameObject* obj = GetBgMap()->GetGameObject(BgObjects[type]))
{
// If doors are open, close it
if (obj->getLootState() == GO_ACTIVATED && obj->GetGoState() != GO_STATE_READY)
{
obj->SetLootState(GO_READY);
obj->SetGoState(GO_STATE_READY);
}
}
else
LOG_ERROR("bg.battleground", "Battleground::DoorClose: door gameobject (type: {}, {}) not found for BG (map: {}, instance id: {})!",
type, BgObjects[type].ToString(), m_MapId, m_InstanceID);
}
void Battleground::DoorOpen(uint32 type)
{
if (GameObject* obj = GetBgMap()->GetGameObject(BgObjects[type]))
{
obj->SetLootState(GO_ACTIVATED);
obj->SetGoState(GO_STATE_ACTIVE);
}
else
LOG_ERROR("bg.battleground", "Battleground::DoorOpen: door gameobject (type: {}, {}) not found for BG (map: {}, instance id: {})!",
type, BgObjects[type].ToString(), m_MapId, m_InstanceID);
}
GameObject* Battleground::GetBGObject(uint32 type)
{
GameObject* obj = GetBgMap()->GetGameObject(BgObjects[type]);
if (!obj)
LOG_ERROR("bg.battleground", "Battleground::GetBGObject: gameobject (type: {}, {}) not found for BG (map: {}, instance id: {})!",
type, BgObjects[type].ToString(), m_MapId, m_InstanceID);
return obj;
}
Creature* Battleground::GetBGCreature(uint32 type)
{
Creature* creature = GetBgMap()->GetCreature(BgCreatures[type]);
if (!creature)
LOG_ERROR("bg.battleground", "Battleground::GetBGCreature: creature (type: {}, {}) not found for BG (map: {}, instance id: {})!",
type, BgCreatures[type].ToString(), m_MapId, m_InstanceID);
return creature;
}
void Battleground::SpawnBGObject(uint32 type, uint32 respawntime, uint32 forceRespawnDelay)
{
if (Map* map = FindBgMap())
if (GameObject* obj = map->GetGameObject(BgObjects[type]))
{
if (respawntime)
obj->SetLootState(GO_JUST_DEACTIVATED);
else if (obj->getLootState() == GO_JUST_DEACTIVATED)
// Change state from GO_JUST_DEACTIVATED to GO_READY in case battleground is starting again
obj->SetLootState(GO_READY);
obj->SetRespawnTime(respawntime);
map->AddToMap(obj);
if (forceRespawnDelay)
{
obj->SetRespawnDelay(forceRespawnDelay);
}
}
}
Creature* Battleground::AddCreature(uint32 entry, uint32 type, float x, float y, float z, float o, uint32 respawntime, MotionTransport* transport)
{
// If the assert is called, means that BgCreatures must be resized!
ASSERT(type < BgCreatures.size());
Map* map = FindBgMap();
if (!map)
return nullptr;
if (transport)
{
transport->CalculatePassengerPosition(x, y, z, &o);
if (Creature* creature = transport->SummonCreature(entry, x, y, z, o, TEMPSUMMON_MANUAL_DESPAWN))
{
transport->AddPassenger(creature, true);
BgCreatures[type] = creature->GetGUID();
return creature;
}
return nullptr;
}
Creature* creature = new Creature();
if (!creature->Create(map->GenerateLowGuid(), map, PHASEMASK_NORMAL, entry, 0, x, y, z, o))
{
LOG_ERROR("bg.battleground", "Battleground::AddCreature: cannot create creature (entry: {}) for BG (map: {}, instance id: {})!",
entry, m_MapId, m_InstanceID);
delete creature;
return nullptr;
}
creature->SetHomePosition(x, y, z, o);
CreatureTemplate const* cinfo = sObjectMgr->GetCreatureTemplate(entry);
if (!cinfo)
{
LOG_ERROR("bg.battleground", "Battleground::AddCreature: creature template (entry: {}) does not exist for BG (map: {}, instance id: {})!",
entry, m_MapId, m_InstanceID);
delete creature;
return nullptr;
}
// Force using DB speeds
creature->SetSpeed(MOVE_WALK, cinfo->speed_walk);
creature->SetSpeed(MOVE_RUN, cinfo->speed_run);
if (!map->AddToMap(creature))
{
delete creature;
return nullptr;
}
BgCreatures[type] = creature->GetGUID();
if (respawntime)
creature->SetRespawnDelay(respawntime);
// Xinef: Set PVP state for vehicles, should be for all creatures in bg?
if (creature->IsVehicle())
creature->SetPvP(true);
return creature;
}
bool Battleground::DelCreature(uint32 type)
{
if (!BgCreatures[type])
return true;
if (Creature* creature = GetBgMap()->GetCreature(BgCreatures[type]))
{
creature->AddObjectToRemoveList();
BgCreatures[type].Clear();
return true;
}
LOG_ERROR("bg.battleground", "Battleground::DelCreature: creature (type: {}, {}) not found for BG (map: {}, instance id: {})!",
type, BgCreatures[type].ToString(), m_MapId, m_InstanceID);
BgCreatures[type].Clear();
return false;
}
bool Battleground::DelObject(uint32 type)
{
if (!BgObjects[type])
return true;
if (GameObject* obj = GetBgMap()->GetGameObject(BgObjects[type]))
{
obj->SetRespawnTime(0); // not save respawn time
obj->Delete();
BgObjects[type].Clear();
return true;
}
LOG_ERROR("bg.battleground", "Battleground::DelObject: gameobject (type: {}, {}) not found for BG (map: {}, instance id: {})!",
type, BgObjects[type].ToString(), m_MapId, m_InstanceID);
BgObjects[type].Clear();
return false;
}
bool Battleground::AddSpiritGuide(uint32 type, float x, float y, float z, float o, TeamId teamId)
{
uint32 entry = (teamId == TEAM_ALLIANCE) ? BG_CREATURE_ENTRY_A_SPIRITGUIDE : BG_CREATURE_ENTRY_H_SPIRITGUIDE;
if (Creature* creature = AddCreature(entry, type, x, y, z, o))
{
creature->setDeathState(DeathState::Dead);
creature->SetGuidValue(UNIT_FIELD_CHANNEL_OBJECT, creature->GetGUID());
// aura
/// @todo: Fix display here
// creature->SetVisibleAura(0, SPELL_SPIRIT_HEAL_CHANNEL);
// casting visual effect
creature->SetUInt32Value(UNIT_CHANNEL_SPELL, SPELL_SPIRIT_HEAL_CHANNEL);
// correct cast speed
creature->SetFloatValue(UNIT_MOD_CAST_SPEED, 1.0f);
//creature->CastSpell(creature, SPELL_SPIRIT_HEAL_CHANNEL, true);
return true;
}
LOG_ERROR("bg.battleground", "Battleground::AddSpiritGuide: cannot create spirit guide (type: {}, entry: {}) for BG (map: {}, instance id: {})!",
type, entry, m_MapId, m_InstanceID);
EndNow();
return false;
}
void Battleground::EndNow()
{
RemoveFromBGFreeSlotQueue();
SetStatus(STATUS_WAIT_LEAVE);
SetEndTime(0);
}
void Battleground::HandleTriggerBuff(GameObject* gameObject)
{
// Xinef: crash fix?
if (GetStatus() != STATUS_IN_PROGRESS || !GetPlayersSize() || BgObjects.empty())
return;
uint32 index = 0;
for (; index < BgObjects.size() && BgObjects[index] != gameObject->GetGUID(); ++index);
if (BgObjects[index] != gameObject->GetGUID())
{
return;
}
if (m_BuffChange)
{
uint8 buff = urand(0, 2);
if (gameObject->GetEntry() != Buff_Entries[buff])
{
SpawnBGObject(index, RESPAWN_ONE_DAY);
for (uint8 currBuffTypeIndex = 0; currBuffTypeIndex < 3; ++currBuffTypeIndex)
if (gameObject->GetEntry() == Buff_Entries[currBuffTypeIndex])
{
index -= currBuffTypeIndex;
index += buff;
}
}
}
uint32 respawnTime = SPEED_BUFF_RESPAWN_TIME;
if (Map* map = FindBgMap())
{
if (GameObject* obj = map->GetGameObject(BgObjects[index]))
{
switch (obj->GetEntry())
{
case BG_OBJECTID_REGENBUFF_ENTRY:
respawnTime = RESTORATION_BUFF_RESPAWN_TIME;
break;
case BG_OBJECTID_BERSERKERBUFF_ENTRY:
respawnTime = BERSERKING_BUFF_RESPAWN_TIME;
break;
default:
break;
}
}
}
SpawnBGObject(index, respawnTime);
}
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;
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 = itr->second;
if (creditedPlayer == killer)
continue;
if (creditedPlayer->GetBgTeamId() == killer->GetBgTeamId() && (creditedPlayer == killer || 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);
}
}
TeamId Battleground::GetOtherTeamId(TeamId teamId)
{
return teamId != TEAM_NEUTRAL ? (teamId == TEAM_ALLIANCE ? TEAM_HORDE : TEAM_ALLIANCE) : TEAM_NEUTRAL;
}
bool Battleground::IsPlayerInBattleground(ObjectGuid guid) const
{
BattlegroundPlayerMap::const_iterator itr = m_Players.find(guid);
if (itr != m_Players.end())
return true;
return false;
}
void Battleground::PlayerAddedToBGCheckIfBGIsRunning(Player* player)
{
if (GetStatus() != STATUS_WAIT_LEAVE)
return;
WorldPacket data;
BlockMovement(player);
BuildPvPLogDataPacket(data);
player->SendDirectMessage(&data);
sBattlegroundMgr->BuildBattlegroundStatusPacket(&data, this, player->GetCurrentBattlegroundQueueSlot(), STATUS_IN_PROGRESS, GetEndTime(), GetStartTime(), GetArenaType(), player->GetBgTeamId());
player->SendDirectMessage(&data);
}
uint32 Battleground::GetAlivePlayersCountByTeam(TeamId teamId) const
{
uint32 count = 0;
for (BattlegroundPlayerMap::const_iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr)
if (itr->second->IsAlive() && !itr->second->HasByteFlag(UNIT_FIELD_BYTES_2, 3, FORM_SPIRITOFREDEMPTION) && itr->second->GetBgTeamId() == teamId)
++count;
return count;
}
void Battleground::SetHoliday(bool is_holiday)
{
m_HonorMode = is_holiday ? BG_HOLIDAY : BG_NORMAL;
}
int32 Battleground::GetObjectType(ObjectGuid guid)
{
for (uint32 i = 0; i < BgObjects.size(); ++i)
if (BgObjects[i] == guid)
return i;
LOG_ERROR("bg.battleground", "Battleground::GetObjectType: player used gameobject ({}) which is not in internal data for BG (map: {}, instance id: {}), cheating?",
guid.ToString(), m_MapId, m_InstanceID);
return -1;
}
void Battleground::SetBgRaid(TeamId teamId, Group* bg_raid)
{
Group*& old_raid = m_BgRaids[teamId];
if (old_raid)
old_raid->SetBattlegroundGroup(nullptr);
if (bg_raid)
bg_raid->SetBattlegroundGroup(this);
old_raid = bg_raid;
}
GraveyardStruct const* Battleground::GetClosestGraveyard(Player* player)
{
return sGraveyard->GetClosestGraveyard(player, player->GetBgTeamId());
}
void Battleground::SetBracket(PvPDifficultyEntry const* bracketEntry)
{
m_IsTemplate = false;
m_BracketId = bracketEntry->GetBracketId();
SetLevelRange(bracketEntry->minLevel, bracketEntry->maxLevel);
}
void Battleground::StartTimedAchievement(AchievementCriteriaTimedTypes type, uint32 entry)
{
for (BattlegroundPlayerMap::const_iterator itr = GetPlayers().begin(); itr != GetPlayers().end(); ++itr)
itr->second->StartTimedAchievement(type, entry);
}
uint32 Battleground::GetTeamScore(TeamId teamId) const
{
if (teamId == TEAM_ALLIANCE || teamId == TEAM_HORDE)
return m_TeamScores[teamId];
LOG_ERROR("bg.battleground", "GetTeamScore with wrong Team {} for BG {}", teamId, GetBgTypeID());
return 0;
}
void Battleground::RewardXPAtKill(Player* killer, Player* victim)
{
if (sWorld->getBoolConfig(CONFIG_BG_XP_FOR_KILL) && killer && victim)
killer->RewardPlayerAndGroupAtKill(victim, true);
}
uint8 Battleground::GetUniqueBracketId() const
{
return GetMaxLevel() / 10;
}