/* * Copyright (C) 2008-2014 TrinityCore * Copyright (C) 2005-2009 MaNGOS * * 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 "ArenaScore.h" #include "ArenaTeam.h" #include "ArenaTeamMgr.h" #include "Battleground.h" #include "BattlegroundMgr.h" #include "BattlegroundScore.h" #include "Creature.h" #include "CreatureTextMgr.h" #include "Chat.h" #include "Formulas.h" #include "GridNotifiersImpl.h" #include "Group.h" #include "MapManager.h" #include "Object.h" #include "ObjectMgr.h" #include "Player.h" #include "ReputationMgr.h" #include "SpellAuraEffects.h" #include "SpellAuras.h" #include "Util.h" #include "World.h" #include "WorldPacket.h" namespace Trinity { class BattlegroundChatBuilder { public: BattlegroundChatBuilder(ChatMsg msgtype, int32 textId, Player const* source, va_list* args = NULL) : _msgtype(msgtype), _textId(textId), _source(source), _args(args) { } void operator()(WorldPacket& data, LocaleConstant loc_idx) { char const* text = sObjectMgr->GetTrinityString(_textId, loc_idx); 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; int32 _textId; Player const* _source; va_list* _args; }; class Battleground2ChatBuilder { public: Battleground2ChatBuilder(ChatMsg msgtype, int32 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) { char const* text = sObjectMgr->GetTrinityString(_textId, loc_idx); char const* arg1str = _arg1 ? sObjectMgr->GetTrinityString(_arg1, loc_idx) : ""; char const* arg2str = _arg2 ? sObjectMgr->GetTrinityString(_arg2, loc_idx) : ""; char str[2048]; snprintf(str, 2048, text, arg1str, arg2str); ChatHandler::BuildChatPacket(data, _msgtype, LANG_UNIVERSAL, _source, _source, str); } private: ChatMsg _msgtype; int32 _textId; Player const* _source; int32 _arg1; int32 _arg2; }; } // namespace Trinity 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() { m_TypeID = 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_BracketId = BG_BRACKET_ID_FIRST; m_InvitedAlliance = 0; m_InvitedHorde = 0; m_ArenaType = 0; m_IsArena = false; m_Winner = 2; 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_Name = ""; m_LevelMin = 0; m_LevelMax = 0; m_InBGFreeSlotQueue = false; m_SetDeleteThis = false; m_MaxPlayersPerTeam = 0; m_MaxPlayers = 0; m_MinPlayersPerTeam = 0; m_MinPlayers = 0; m_MapId = 0; m_Map = NULL; 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] = NULL; m_BgRaids[TEAM_HORDE] = NULL; 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_HonorMode = BG_NORMAL; 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; //we must set to some default existing values StartMessageIds[BG_STARTING_EVENT_FIRST] = LANG_BG_WS_START_TWO_MINUTES; StartMessageIds[BG_STARTING_EVENT_SECOND] = LANG_BG_WS_START_ONE_MINUTE; StartMessageIds[BG_STARTING_EVENT_THIRD] = LANG_BG_WS_START_HALF_MINUTE; StartMessageIds[BG_STARTING_EVENT_FOURTH] = LANG_BG_WS_HAS_BEGUN; } Battleground::~Battleground() { // 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); sBattlegroundMgr->RemoveBattleground(GetTypeID(), GetInstanceID()); // unload map if (m_Map) { m_Map->SetUnload(); //unlink to prevent crash, always unlink all pointer reference before destruction m_Map->SetBG(NULL); m_Map = NULL; } // remove from bg free slot queue RemoveFromBGFreeSlotQueue(); for (BattlegroundScoreMap::const_iterator itr = PlayerScores.begin(); itr != PlayerScores.end(); ++itr) delete itr->second; } 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)) // this->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(); // after 47 minutes without one team losing, the arena closes with no winner and no rating change if (isArena()) { if (GetStartTime() >= 47 * MINUTE*IN_MILLISECONDS) { UpdateArenaWorldState(); CheckArenaAfterTimerConditions(); return; } } else { _ProcessResurrect(diff); 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 m_StartTime += diff; 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)) { Position pos = player->GetPosition(); Position const* startPos = GetTeamStartPosition(Battleground::GetTeamIndexByTeamId(player->GetBGTeam())); if (pos.GetExactDistSq(startPos) > maxDist) { TC_LOG_DEBUG("bg.battleground", "BATTLEGROUND: Sending %s back to start location (map: %u) (possible exploit)", player->GetName().c_str(), GetMapId()); player->TeleportTo(GetMapId(), startPos->GetPositionX(), startPos->GetPositionY(), startPos->GetPositionZ(), startPos->GetOrientation()); } } } } 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 <= sWorld->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() } } } } inline void Battleground::_ProcessResurrect(uint32 diff) { // ********************************************************* // *** BATTLEGROUND RESURRECTION SYSTEM *** // ********************************************************* // this should be handled by spell system 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 = NULL; for (std::vector::const_iterator itr2 = (itr->second).begin(); itr2 != (itr->second).end(); ++itr2) { Player* player = ObjectAccessor::FindPlayer(*itr2); 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(*itr2); } (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 (std::vector::const_iterator itr = m_ResurrectQueue.begin(); itr != m_ResurrectQueue.end(); ++itr) { Player* player = ObjectAccessor::FindPlayer(*itr); if (!player) continue; player->ResurrectPlayer(1.0f); player->CastSpell(player, 6962, true); player->CastSpell(player, SPELL_SPIRIT_HEAL_MANA, true); sObjectAccessor->ConvertCorpseForPlayer(*itr); } m_ResurrectQueue.clear(); } } uint32 Battleground::GetPrematureWinner() { uint32 winner = 0; 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, NULL, (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, NULL, (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) 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: %u, instance id: %u) 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(); SetStartDelayTime(StartDelayTimes[BG_STARTING_EVENT_FIRST]); // First start warning - 2 or 1 minute SendMessageToAll(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; SendMessageToAll(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; SendMessageToAll(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; StartingEventOpenDoors(); SendWarningToAll(StartMessageIds[BG_STARTING_EVENT_FOURTH]); SetStatus(STATUS_IN_PROGRESS); SetStartDelayTime(StartDelayTimes[BG_STARTING_EVENT_FOURTH]); // 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)) { // BG Status packet WorldPacket status; BattlegroundQueueTypeId bgQueueTypeId = sBattlegroundMgr->BGQueueTypeId(m_TypeID, GetArenaType()); uint32 queueSlot = player->GetBattlegroundQueueIndex(bgQueueTypeId); sBattlegroundMgr->BuildBattlegroundStatusPacket(&status, this, queueSlot, STATUS_IN_PROGRESS, 0, GetStartTime(), GetArenaType(), player->GetBGTeam()); player->SendDirectMessage(&status); player->RemoveAurasDueToSpell(SPELL_ARENA_PREPARATION); player->ResetAllPowers(); if (!player->IsGameMaster()) { // 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()->Attributes & SPELL_ATTR0_UNAFFECTED_BY_INVULNERABILITY)) && (!aura->HasEffectType(SPELL_AURA_MOD_INVISIBILITY))) player->RemoveAura(iter); else ++iter; } } } CheckArenaWinConditions(); } 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().c_str(), GetMinLevel(), GetMaxLevel()); } } } 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 = 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 } } } inline Player* Battleground::_GetPlayer(uint64 guid, bool offlineRemove, char const* context) const { Player* player = NULL; if (!offlineRemove) { player = ObjectAccessor::FindPlayer(guid); if (!player) TC_LOG_ERROR("bg.battleground", "Battleground::%s: player (GUID: %u) not found for BG (map: %u, instance id: %u)!", context, GUID_LOPART(guid), m_MapId, m_InstanceID); } return player; } inline Player* Battleground::_GetPlayer(BattlegroundPlayerMap::iterator itr, char const* context) { return _GetPlayer(itr->first, itr->second.OfflineRemoveTime, context); } inline Player* Battleground::_GetPlayer(BattlegroundPlayerMap::const_iterator itr, char const* context) const { return _GetPlayer(itr->first, itr->second.OfflineRemoveTime, context); } inline Player* Battleground::_GetPlayerForTeam(uint32 teamId, BattlegroundPlayerMap::const_iterator itr, char const* context) const { Player* player = _GetPlayer(itr, context); if (player) { uint32 team = itr->second.Team; if (!team) team = player->GetTeam(); if (team != teamId) player = NULL; } return player; } 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* packet) { 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(uint32 TeamID, WorldPacket* packet, Player* sender, bool self) { for (BattlegroundPlayerMap::const_iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr) { if (Player* player = _GetPlayerForTeam(TeamID, itr, "SendPacketToTeam")) { if (self || sender != player) player->SendDirectMessage(packet); } } } void Battleground::SendChatMessage(Creature* source, uint8 textId, WorldObject* target /*= NULL*/) { sCreatureTextMgr->SendChat(source, textId, target, CHAT_MSG_ADDON, LANG_ADDON, TEXT_RANGE_MAP); } void Battleground::PlaySoundToAll(uint32 SoundID) { WorldPacket data; sBattlegroundMgr->BuildPlaySoundPacket(&data, SoundID); SendPacketToAll(&data); } void Battleground::PlaySoundToTeam(uint32 SoundID, uint32 TeamID) { WorldPacket data; for (BattlegroundPlayerMap::const_iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr) if (Player* player = _GetPlayerForTeam(TeamID, itr, "PlaySoundToTeam")) { sBattlegroundMgr->BuildPlaySoundPacket(&data, SoundID); player->SendDirectMessage(&data); } } void Battleground::CastSpellOnTeam(uint32 SpellID, uint32 TeamID) { for (BattlegroundPlayerMap::const_iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr) if (Player* player = _GetPlayerForTeam(TeamID, itr, "CastSpellOnTeam")) player->CastSpell(player, SpellID, true); } void Battleground::RemoveAuraOnTeam(uint32 SpellID, uint32 TeamID) { for (BattlegroundPlayerMap::const_iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr) if (Player* player = _GetPlayerForTeam(TeamID, itr, "RemoveAuraOnTeam")) player->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) if (Player* player = _GetPlayer(itr, "YellToAll")) { WorldPacket data; ChatHandler::BuildChatPacket(data, CHAT_MSG_MONSTER_YELL, Language(language), creature, player, text); player->SendDirectMessage(&data); } } void Battleground::RewardHonorToTeam(uint32 Honor, uint32 TeamID) { for (BattlegroundPlayerMap::const_iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr) if (Player* player = _GetPlayerForTeam(TeamID, itr, "RewardHonorToTeam")) UpdatePlayerScore(player, SCORE_BONUS_HONOR, Honor); } void Battleground::RewardReputationToTeam(uint32 faction_id, uint32 Reputation, uint32 TeamID) { if (FactionEntry const* factionEntry = sFactionStore.LookupEntry(faction_id)) for (BattlegroundPlayerMap::const_iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr) if (Player* player = _GetPlayerForTeam(TeamID, itr, "RewardReputationToTeam")) player->GetReputationMgr().ModifyReputation(factionEntry, Reputation); } void Battleground::UpdateWorldState(uint32 Field, uint32 Value) { WorldPacket data; sBattlegroundMgr->BuildUpdateWorldStatePacket(&data, Field, Value); SendPacketToAll(&data); } void Battleground::UpdateWorldStateForPlayer(uint32 field, uint32 value, Player* player) { WorldPacket data; sBattlegroundMgr->BuildUpdateWorldStatePacket(&data, field, value); player->SendDirectMessage(&data); } void Battleground::EndBattleground(uint32 winner) { RemoveFromBGFreeSlotQueue(); ArenaTeam* winnerArenaTeam = NULL; ArenaTeam* loserArenaTeam = NULL; uint32 loserTeamRating = 0; uint32 loserMatchmakerRating = 0; int32 loserChange = 0; int32 loserMatchmakerChange = 0; uint32 winnerTeamRating = 0; uint32 winnerMatchmakerRating = 0; int32 winnerChange = 0; int32 winnerMatchmakerChange = 0; int32 winmsg_id = 0; if (winner == ALLIANCE) { winmsg_id = isBattleground() ? LANG_BG_A_WINS : LANG_ARENA_GOLD_WINS; PlaySoundToAll(SOUND_ALLIANCE_WINS); // alliance wins sound SetWinner(WINNER_ALLIANCE); } else if (winner == HORDE) { winmsg_id = isBattleground() ? LANG_BG_H_WINS : LANG_ARENA_GREEN_WINS; PlaySoundToAll(SOUND_HORDE_WINS); // horde wins sound SetWinner(WINNER_HORDE); } else { SetWinner(3); // weird } SetStatus(STATUS_WAIT_LEAVE); //we must set it this way, because end time is sent in packet! m_EndTime = TIME_TO_AUTOREMOVE; // arena rating calculation if (isArena() && isRated()) { winnerArenaTeam = sArenaTeamMgr->GetArenaTeamById(GetArenaTeamIdForTeam(winner)); loserArenaTeam = sArenaTeamMgr->GetArenaTeamById(GetArenaTeamIdForTeam(GetOtherTeam(winner))); if (winnerArenaTeam && loserArenaTeam && winnerArenaTeam != loserArenaTeam) { loserTeamRating = loserArenaTeam->GetRating(); loserMatchmakerRating = GetArenaMatchmakerRating(GetOtherTeam(winner)); winnerTeamRating = winnerArenaTeam->GetRating(); winnerMatchmakerRating = GetArenaMatchmakerRating(winner); if (winner != WINNER_NONE) { winnerMatchmakerChange = winnerArenaTeam->WonAgainst(winnerMatchmakerRating, loserMatchmakerRating, winnerChange); loserMatchmakerChange = loserArenaTeam->LostAgainst(loserMatchmakerRating, winnerMatchmakerRating, loserChange); TC_LOG_DEBUG("bg.arena", "match Type: %u --- Winner: old rating: %u, rating gain: %d, old MMR: %u, MMR gain: %d --- Loser: old rating: %u, rating loss: %d, old MMR: %u, MMR loss: %d ---", m_ArenaType, winnerTeamRating, winnerChange, winnerMatchmakerRating, winnerMatchmakerChange, loserTeamRating, loserChange, loserMatchmakerRating, loserMatchmakerChange); SetArenaMatchmakerRating(winner, winnerMatchmakerRating + winnerMatchmakerChange); SetArenaMatchmakerRating(GetOtherTeam(winner), loserMatchmakerRating + loserMatchmakerChange); // bg team that the client expects is different to TeamId // alliance 1, horde 0 uint8 winnerTeam = winner == ALLIANCE ? WINNER_ALLIANCE : WINNER_HORDE; uint8 loserTeam = winner == ALLIANCE ? WINNER_HORDE : WINNER_ALLIANCE; _arenaTeamScores[winnerTeam].Assign(winnerChange, winnerMatchmakerRating, winnerArenaTeam->GetName()); _arenaTeamScores[loserTeam].Assign(loserChange, loserMatchmakerRating, loserArenaTeam->GetName()); TC_LOG_DEBUG("bg.arena", "Arena match Type: %u for Team1Id: %u - Team2Id: %u ended. WinnerTeamId: %u. Winner rating: +%d, Loser rating: %d", m_ArenaType, m_ArenaTeamIds[TEAM_ALLIANCE], m_ArenaTeamIds[TEAM_HORDE], winnerArenaTeam->GetId(), winnerChange, loserChange); if (sWorld->getBoolConfig(CONFIG_ARENA_LOG_EXTENDED_INFO)) for (auto const& score : PlayerScores) if (Player* player = ObjectAccessor::FindPlayer(MAKE_NEW_GUID(score.first, 0, HIGHGUID_PLAYER))) { TC_LOG_DEBUG("bg.arena", "Statistics match Type: %u for %s (GUID: %u, Team: %d, IP: %s): %s", m_ArenaType, player->GetName().c_str(), score.first, player->GetArenaTeamId(m_ArenaType == 5 ? 2 : m_ArenaType == 3), player->GetSession()->GetRemoteAddress().c_str(), score.second->ToString().c_str()); } } // Deduct 16 points from each teams arena-rating if there are no winners after 45+2 minutes else { _arenaTeamScores[WINNER_ALLIANCE].Assign(ARENA_TIMELIMIT_POINTS_LOSS, winnerMatchmakerRating, winnerArenaTeam->GetName()); _arenaTeamScores[WINNER_HORDE].Assign(ARENA_TIMELIMIT_POINTS_LOSS, loserMatchmakerRating, loserArenaTeam->GetName()); winnerArenaTeam->FinishGame(ARENA_TIMELIMIT_POINTS_LOSS); loserArenaTeam->FinishGame(ARENA_TIMELIMIT_POINTS_LOSS); } } } WorldPacket pvpLogData; BuildPvPLogDataPacket(pvpLogData); BattlegroundQueueTypeId bgQueueTypeId = BattlegroundMgr::BGQueueTypeId(GetTypeID(), GetArenaType()); uint8 aliveWinners = GetAlivePlayersCountByTeam(winner); for (BattlegroundPlayerMap::iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr) { uint32 team = itr->second.Team; if (itr->second.OfflineRemoveTime) { //if rated arena match - make member lost! if (isArena() && isRated() && winnerArenaTeam && loserArenaTeam && winnerArenaTeam != loserArenaTeam) { if (team == winner) winnerArenaTeam->OfflineMemberLost(itr->first, loserMatchmakerRating, winnerMatchmakerChange); else loserArenaTeam->OfflineMemberLost(itr->first, winnerMatchmakerRating, loserMatchmakerChange); } continue; } 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); // Last standing - Rated 5v5 arena & be solely alive player if (team == winner && isArena() && isRated() && GetArenaType() == ARENA_TYPE_5v5 && aliveWinners == 1 && player->IsAlive()) player->CastSpell(player, SPELL_THE_LAST_STANDING, true); 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->getHostileRefManager().deleteReferences(); } // per player calculation if (isArena() && isRated() && winnerArenaTeam && loserArenaTeam && winnerArenaTeam != loserArenaTeam) { if (team == winner) { // update achievement BEFORE personal rating update uint32 rating = player->GetArenaPersonalRating(winnerArenaTeam->GetSlot()); player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_WIN_RATED_ARENA, rating ? rating : 1); player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_WIN_ARENA, GetMapId()); winnerArenaTeam->MemberWon(player, loserMatchmakerRating, winnerMatchmakerChange); } else { loserArenaTeam->MemberLost(player, winnerMatchmakerRating, loserMatchmakerChange); // Arena lost => reset the win_rated_arena having the "no_lose" condition player->ResetAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_WIN_RATED_ARENA, ACHIEVEMENT_CRITERIA_CONDITION_NO_LOSE); } } 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 (team == winner) { if (IsRandom() || BattlegroundMgr::IsBGWeekend(GetTypeID())) { UpdatePlayerScore(player, SCORE_BONUS_HONOR, GetBonusHonorFromKill(winner_kills)); if (CanAwardArenaPoints()) player->ModifyArenaPoints(winner_arena); if (!player->GetRandomWinner()) player->SetRandomWinner(true); } player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_WIN_BG, 1); } else { if (IsRandom() || BattlegroundMgr::IsBGWeekend(GetTypeID())) UpdatePlayerScore(player, SCORE_BONUS_HONOR, GetBonusHonorFromKill(loser_kills)); } player->ResetAllPowers(); player->CombatStopWithPets(true); BlockMovement(player); player->SendDirectMessage(&pvpLogData); WorldPacket data; sBattlegroundMgr->BuildBattlegroundStatusPacket(&data, this, player->GetBattlegroundQueueIndex(bgQueueTypeId), STATUS_IN_PROGRESS, TIME_TO_AUTOREMOVE, GetStartTime(), GetArenaType(), player->GetBGTeam()); player->SendDirectMessage(&data); player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_BATTLEGROUND, 1); } if (isArena() && isRated() && winnerArenaTeam && loserArenaTeam && winnerArenaTeam != loserArenaTeam) { // save the stat changes winnerArenaTeam->SaveToDB(); loserArenaTeam->SaveToDB(); // send updated arena team stats to players // this way all arena team members will get notified, not only the ones who participated in this match winnerArenaTeam->NotifyStatsChanged(); loserArenaTeam->NotifyStatsChanged(); } if (winmsg_id) SendMessageToAll(winmsg_id, CHAT_MSG_BG_SYSTEM_NEUTRAL); } 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) { 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(uint64 guid, bool Transport, bool SendPacket) { uint32 team = GetPlayerTeam(guid); bool participant = false; // Remove from lists/maps BattlegroundPlayerMap::iterator itr = m_Players.find(guid); if (itr != m_Players.end()) { 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_LOPART(guid)); if (itr2 != PlayerScores.end()) { delete itr2->second; // delete player's score PlayerScores.erase(itr2); } RemovePlayerFromResurrectQueue(guid); Player* player = ObjectAccessor::FindPlayer(guid); // should remove spirit of redemption if (player) { if (player->HasAuraType(SPELL_AURA_SPIRIT_OF_REDEMPTION)) player->RemoveAurasByType(SPELL_AURA_MOD_SHAPESHIFT); if (!player->IsAlive()) // resurrect on exit { player->ResurrectPlayer(1.0f); player->SpawnCorpseBones(); } } else // try to resurrect the offline player. If he is alive nothing will happen sObjectAccessor->ConvertCorpseForPlayer(guid); RemovePlayer(player, guid, team); // BG subclass specific code if (participant) // if the player was a match participant, remove auras, calc rating, update queue { BattlegroundTypeId bgTypeId = GetTypeID(); BattlegroundQueueTypeId bgQueueTypeId = BattlegroundMgr::BGQueueTypeId(GetTypeID(), GetArenaType()); if (player) { player->ClearAfkReports(); if (!team) team = player->GetTeam(); // if arena, remove the specific arena auras if (isArena()) { bgTypeId=BATTLEGROUND_AA; // set the bg type to all arenas (it will be used for queue refreshing) // unsummon current and summon old pet if there was one and there isn't a current pet player->RemovePet(NULL, PET_SAVE_NOT_IN_SLOT); player->ResummonPetTemporaryUnSummonedIfAny(); if (isRated() && GetStatus() == STATUS_IN_PROGRESS) { //left a rated match while the encounter was in progress, consider as loser ArenaTeam* winnerArenaTeam = sArenaTeamMgr->GetArenaTeamById(GetArenaTeamIdForTeam(GetOtherTeam(team))); ArenaTeam* loserArenaTeam = sArenaTeamMgr->GetArenaTeamById(GetArenaTeamIdForTeam(team)); if (winnerArenaTeam && loserArenaTeam && winnerArenaTeam != loserArenaTeam) loserArenaTeam->MemberLost(player, GetArenaMatchmakerRating(GetOtherTeam(team))); } } if (SendPacket) { WorldPacket data; sBattlegroundMgr->BuildBattlegroundStatusPacket(&data, this, player->GetBattlegroundQueueIndex(bgQueueTypeId), STATUS_NONE, 0, 0, 0, 0); player->SendDirectMessage(&data); } // 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); } else // removing offline participant { if (isRated() && GetStatus() == STATUS_IN_PROGRESS) { //left a rated match while the encounter was in progress, consider as loser ArenaTeam* others_arena_team = sArenaTeamMgr->GetArenaTeamById(GetArenaTeamIdForTeam(GetOtherTeam(team))); ArenaTeam* players_arena_team = sArenaTeamMgr->GetArenaTeamById(GetArenaTeamIdForTeam(team)); if (others_arena_team && players_arena_team) players_arena_team->OfflineMemberLost(guid, GetArenaMatchmakerRating(GetOtherTeam(team))); } } // remove from raid group if player is member if (Group* group = GetBgRaid(team)) { if (!group->RemoveMember(guid)) // group was disbanded { SetBgRaid(team, NULL); } } DecreaseInvitedCount(team); //we should update battleground queue, but only if bg isn't ending if (isBattleground() && GetStatus() < STATUS_WAIT_LEAVE) { // a player has left the battleground, so there are free slots -> add to queue AddToBGFreeSlotQueue(); sBattlegroundMgr->ScheduleQueueUpdate(0, 0, bgQueueTypeId, bgTypeId, GetBracketId()); } // Let others know WorldPacket data; sBattlegroundMgr->BuildPlayerLeftBattlegroundPacket(&data, guid); SendPacketToTeam(team, &data, player, false); } if (player) { // Do next only if found in battleground player->SetBattlegroundId(0, BATTLEGROUND_TYPE_NONE); // We're not in BG. // reset destination bg team player->SetBGTeam(0); if (Transport) player->TeleportToBGEntryPoint(); TC_LOG_DEBUG("bg.battleground", "Removed player %s from Battleground.", player->GetName().c_str()); } //battleground object will be deleted next Battleground::Update() call } // this method is called when no players remains in battleground void Battleground::Reset() { SetWinner(WINNER_NONE); SetStatus(STATUS_WAIT_QUEUE); SetStartTime(0); SetEndTime(0); SetLastResurrectTime(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: %u, horde: %u) for BG (map: %u, instance id: %u)!", m_InvitedAlliance, m_InvitedHorde, m_MapId, 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(); for (uint8 i = 0; i < BG_TEAMS_COUNT; ++i) _arenaTeamScores[i].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 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: %u for Team1Id: %u - Team2Id: %u started.", m_ArenaType, m_ArenaTeamIds[TEAM_ALLIANCE], m_ArenaTeamIds[TEAM_HORDE]); } void Battleground::AddPlayer(Player* player) { // remove afk from player if (player->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_AFK)) player->ToggleAFK(); // score struct must be created in inherited class uint64 guid = player->GetGUID(); uint32 team = player->GetBGTeam(); BattlegroundPlayer bp; bp.OfflineRemoveTime = 0; bp.Team = team; // Add to list/maps m_Players[guid] = bp; UpdatePlayersCountByTeam(team, false); // +1 player WorldPacket data; sBattlegroundMgr->BuildPlayerJoinedBattlegroundPacket(&data, player); SendPacketToTeam(team, &data, player, false); player->RemoveAurasByType(SPELL_AURA_MOUNTED); // add arena specific auras if (isArena()) { player->RemoveArenaEnchantments(TEMP_ENCHANTMENT_SLOT); if (team == ALLIANCE) // gold { if (player->GetTeam() == HORDE) player->CastSpell(player, SPELL_HORDE_GOLD_FLAG, true); else player->CastSpell(player, SPELL_ALLIANCE_GOLD_FLAG, true); } else // green { if (player->GetTeam() == HORDE) player->CastSpell(player, SPELL_HORDE_GREEN_FLAG, true); else player->CastSpell(player, SPELL_ALLIANCE_GREEN_FLAG, true); } 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. } player->ResetAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_KILL_CREATURE, ACHIEVEMENT_CRITERIA_CONDITION_BG_MAP, GetMapId(), true); player->ResetAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_WIN_BG, ACHIEVEMENT_CRITERIA_CONDITION_BG_MAP, GetMapId(), true); player->ResetAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_DAMAGE_DONE, ACHIEVEMENT_CRITERIA_CONDITION_BG_MAP, GetMapId(), true); player->ResetAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_BE_SPELL_TARGET, ACHIEVEMENT_CRITERIA_CONDITION_BG_MAP, GetMapId(), true); player->ResetAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_CAST_SPELL, ACHIEVEMENT_CRITERIA_CONDITION_BG_MAP, GetMapId(), true); player->ResetAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_BG_OBJECTIVE_CAPTURE, ACHIEVEMENT_CRITERIA_CONDITION_BG_MAP, GetMapId(), true); player->ResetAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HONORABLE_KILL_AT_AREA, ACHIEVEMENT_CRITERIA_CONDITION_BG_MAP, GetMapId(), true); player->ResetAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HONORABLE_KILL, ACHIEVEMENT_CRITERIA_CONDITION_BG_MAP, GetMapId(), true); player->ResetAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HEALING_DONE, ACHIEVEMENT_CRITERIA_CONDITION_BG_MAP, GetMapId(), true); player->ResetAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_GET_KILLING_BLOWS, ACHIEVEMENT_CRITERIA_CONDITION_BG_MAP, GetMapId(), true); player->ResetAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_SPECIAL_PVP_KILL, ACHIEVEMENT_CRITERIA_CONDITION_BG_MAP, GetMapId(), true); // setup BG group membership PlayerAddedToBGCheckIfBGIsRunning(player); AddOrSetPlayerToCorrectBgGroup(player, team); } // 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, uint32 team) { uint64 playerGuid = player->GetGUID(); Group* group = GetBgRaid(team); if (!group) // first player joined { group = new Group; SetBgRaid(team, group); group->Create(player); } 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) { uint64 guid = player->GetGUID(); // player is correct pointer for (std::deque::iterator 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) { uint64 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 = sWorld->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, then end arena! if (isArena()) 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(m_TypeID, 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(m_TypeID, 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(uint32 Team) const { // if BG is starting ... invite anyone if (GetStatus() == STATUS_WAIT_JOIN) return (GetInvitedCount(Team) < GetMaxPlayersPerTeam()) ? GetMaxPlayersPerTeam() - GetInvitedCount(Team) : 0; // if BG is already started .. do not allow to join too much players of one faction uint32 otherTeam; uint32 otherIn; if (Team == ALLIANCE) { otherTeam = GetInvitedCount(HORDE); otherIn = GetPlayersCountByTeam(HORDE); } else { otherTeam = GetInvitedCount(ALLIANCE); otherIn = GetPlayersCountByTeam(ALLIANCE); } if (GetStatus() == STATUS_IN_PROGRESS) { // 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 minplayersperteam) if (otherTeam == GetInvitedCount(Team)) diff = 1; // allow join more ppl if the other side has more players else if (otherTeam > GetInvitedCount(Team)) diff = otherTeam - GetInvitedCount(Team); // difference based on max players per team (don't allow inviting more) uint32 diff2 = (GetInvitedCount(Team) < GetMaxPlayersPerTeam()) ? GetMaxPlayersPerTeam() - GetInvitedCount(Team) : 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 minplayersperteam) if (otherIn == GetPlayersCountByTeam(Team)) diff3 = 1; // allow join more ppl if the other side has more players else if (otherIn > GetPlayersCountByTeam(Team)) diff3 = otherIn - GetPlayersCountByTeam(Team); // or other side has less than minPlayersPerTeam else if (GetInvitedCount(Team) <= GetMinPlayersPerTeam()) diff3 = GetMinPlayersPerTeam() - GetInvitedCount(Team) + 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::HasFreeSlots() const { return GetPlayersSize() < GetMaxPlayers(); } void Battleground::BuildPvPLogDataPacket(WorldPacket& data) { uint8 type = (isArena() ? 1 : 0); data.Initialize(MSG_PVP_LOG_DATA, 1 + 1 + 4 + 40 * GetPlayerScoresSize()); data << uint8(type); // type (battleground = 0 / arena = 1) if (type) // arena { for (uint8 i = 0; i < BG_TEAMS_COUNT; ++i) _arenaTeamScores[i].BuildRatingInfoBlock(data); for (uint8 i = 0; i < BG_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(GetPlayerScoresSize()); for (auto const& score : PlayerScores) score.second->AppendToPacket(data); } bool Battleground::UpdatePlayerScore(Player* player, uint32 type, uint32 value, bool doAddHonor) { BattlegroundScoreMap::const_iterator itr = PlayerScores.find(player->GetGUIDLow()); if (itr == PlayerScores.end()) // player not found... return false; itr->second->UpdateScore(type, value); if (type == SCORE_BONUS_HONOR && doAddHonor && isBattleground()) player->RewardHonor(NULL, 1, value); // RewardHonor calls UpdatePlayerScore with doAddHonor = false return true; } void Battleground::AddPlayerToResurrectQueue(uint64 npc_guid, uint64 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(uint64 player_guid) { for (std::map >::iterator itr = m_ReviveQueue.begin(); itr != m_ReviveQueue.end(); ++itr) { for (std::vector::iterator itr2 = (itr->second).begin(); itr2 != (itr->second).end(); ++itr2) { if (*itr2 == player_guid) { (itr->second).erase(itr2); if (Player* player = ObjectAccessor::FindPlayer(player_guid)) player->RemoveAurasDueToSpell(SPELL_WAITING_FOR_RESURRECT); return; } } } } void Battleground::RelocateDeadPlayers(uint64 queueIndex) { // Those who are waiting to resurrect at this node are taken to the closest own node's graveyard std::vector& ghostList = m_ReviveQueue[queueIndex]; if (!ghostList.empty()) { WorldSafeLocsEntry const* closestGrave = NULL; for (std::vector::const_iterator itr = ghostList.begin(); itr != ghostList.end(); ++itr) { Player* player = ObjectAccessor::FindPlayer(*itr); 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*/) { // 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 = new GameObject; if (!go->Create(sObjectMgr->GenerateLowGuid(HIGHGUID_GAMEOBJECT), entry, GetBgMap(), PHASEMASK_NORMAL, x, y, z, o, rotation0, rotation1, rotation2, rotation3, 100, GO_STATE_READY)) { TC_LOG_ERROR("bg.battleground", "Battleground::AddObject: cannot create gameobject (entry: %u) for BG (map: %u, instance id: %u)!", entry, m_MapId, m_InstanceID); delete go; return false; } /* uint32 guid = go->GetGUIDLow(); // 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(guid); 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; } bool Battleground::AddObject(uint32 type, uint32 entry, Position const& pos, float rotation0, float rotation1, float rotation2, float rotation3, uint32 respawnTime /*= 0*/) { return AddObject(type, entry, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), pos.GetOrientation(), rotation0, rotation1, rotation2, rotation3, respawnTime); } // 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 TC_LOG_ERROR("bg.battleground", "Battleground::DoorClose: door gameobject (type: %u, GUID: %u) not found for BG (map: %u, instance id: %u)!", type, GUID_LOPART(BgObjects[type]), 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 TC_LOG_ERROR("bg.battleground", "Battleground::DoorOpen: door gameobject (type: %u, GUID: %u) not found for BG (map: %u, instance id: %u)!", type, GUID_LOPART(BgObjects[type]), m_MapId, m_InstanceID); } GameObject* Battleground::GetBGObject(uint32 type, bool logError) { GameObject* obj = GetBgMap()->GetGameObject(BgObjects[type]); if (!obj) { if (logError) TC_LOG_ERROR("bg.battleground", "Battleground::GetBGObject: gameobject (type: %u, GUID: %u) not found for BG (map: %u, instance id: %u)!", type, GUID_LOPART(BgObjects[type]), m_MapId, m_InstanceID); else TC_LOG_INFO("bg.battleground", "Battleground::GetBGObject: gameobject (type: %u, GUID: %u) not found for BG (map: %u, instance id: %u)!", type, GUID_LOPART(BgObjects[type]), m_MapId, m_InstanceID); } return obj; } Creature* Battleground::GetBGCreature(uint32 type, bool logError) { Creature* creature = GetBgMap()->GetCreature(BgCreatures[type]); if (!creature) { if (logError) TC_LOG_ERROR("bg.battleground", "Battleground::GetBGCreature: creature (type: %u, GUID: %u) not found for BG (map: %u, instance id: %u)!", type, GUID_LOPART(BgCreatures[type]), m_MapId, m_InstanceID); else TC_LOG_INFO("bg.battleground", "Battleground::GetBGCreature: creature (type: %u, GUID: %u) not found for BG (map: %u, instance id: %u)!", type, GUID_LOPART(BgCreatures[type]), m_MapId, m_InstanceID); } return creature; } void Battleground::SpawnBGObject(uint32 type, uint32 respawntime) { 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); } } Creature* Battleground::AddCreature(uint32 entry, uint32 type, float x, float y, float z, float o, TeamId /*teamId = TEAM_NEUTRAL*/, uint32 respawntime /*= 0*/) { // If the assert is called, means that BgCreatures must be resized! ASSERT(type < BgCreatures.size()); Map* map = FindBgMap(); if (!map) return NULL; Creature* creature = new Creature(); if (!creature->Create(sObjectMgr->GenerateLowGuid(HIGHGUID_UNIT), map, PHASEMASK_NORMAL, entry, x, y, z, o)) { TC_LOG_ERROR("bg.battleground", "Battleground::AddCreature: cannot create creature (entry: %u) for BG (map: %u, instance id: %u)!", entry, m_MapId, m_InstanceID); delete creature; return NULL; } creature->SetHomePosition(x, y, z, o); CreatureTemplate const* cinfo = sObjectMgr->GetCreatureTemplate(entry); if (!cinfo) { TC_LOG_ERROR("bg.battleground", "Battleground::AddCreature: creature template (entry: %u) does not exist for BG (map: %u, instance id: %u)!", entry, m_MapId, m_InstanceID); delete creature; return NULL; } if (!map->AddToMap(creature)) { delete creature; return NULL; } BgCreatures[type] = creature->GetGUID(); if (respawntime) creature->SetRespawnDelay(respawntime); return creature; } Creature* Battleground::AddCreature(uint32 entry, uint32 type, Position const& pos, TeamId teamId /*= TEAM_NEUTRAL*/, uint32 respawntime /*= 0*/) { return AddCreature(entry, type, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), pos.GetOrientation(), teamId, respawntime); } bool Battleground::DelCreature(uint32 type) { if (!BgCreatures[type]) return true; if (Creature* creature = GetBgMap()->GetCreature(BgCreatures[type])) { creature->AddObjectToRemoveList(); BgCreatures[type] = 0; return true; } TC_LOG_ERROR("bg.battleground", "Battleground::DelCreature: creature (type: %u, GUID: %u) not found for BG (map: %u, instance id: %u)!", type, GUID_LOPART(BgCreatures[type]), m_MapId, m_InstanceID); BgCreatures[type] = 0; 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] = 0; return true; } TC_LOG_ERROR("bg.battleground", "Battleground::DelObject: gameobject (type: %u, GUID: %u) not found for BG (map: %u, instance id: %u)!", type, GUID_LOPART(BgObjects[type]), m_MapId, m_InstanceID); BgObjects[type] = 0; return false; } bool Battleground::AddSpiritGuide(uint32 type, float x, float y, float z, float o, TeamId teamId /*= TEAM_NEUTRAL*/) { 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, teamId)) { creature->setDeathState(DEAD); creature->SetUInt64Value(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; } TC_LOG_ERROR("bg.battleground", "Battleground::AddSpiritGuide: cannot create spirit guide (type: %u, entry: %u) for BG (map: %u, instance id: %u)!", type, entry, m_MapId, m_InstanceID); EndNow(); return false; } bool Battleground::AddSpiritGuide(uint32 type, Position const& pos, TeamId teamId /*= TEAM_NEUTRAL*/) { return AddSpiritGuide(type, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), pos.GetOrientation(), teamId); } void Battleground::SendMessageToAll(int32 entry, ChatMsg type, Player const* source) { if (!entry) return; Trinity::BattlegroundChatBuilder bg_builder(type, entry, source); Trinity::LocalizedPacketDo bg_do(bg_builder); BroadcastWorker(bg_do); } void Battleground::PSendMessageToAll(int32 entry, ChatMsg type, Player const* source, ...) { if (!entry) return; va_list ap; va_start(ap, source); Trinity::BattlegroundChatBuilder bg_builder(type, entry, source, &ap); Trinity::LocalizedPacketDo bg_do(bg_builder); BroadcastWorker(bg_do); va_end(ap); } void Battleground::SendWarningToAll(int32 entry, ...) { if (!entry) return; std::map localizedPackets; for (BattlegroundPlayerMap::const_iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr) if (Player* player = _GetPlayer(itr, "SendWarningToAll")) { if (localizedPackets.find(player->GetSession()->GetSessionDbLocaleIndex()) == localizedPackets.end()) { char const* format = sObjectMgr->GetTrinityString(entry, player->GetSession()->GetSessionDbLocaleIndex()); char str[1024]; va_list ap; va_start(ap, entry); vsnprintf(str, 1024, format, ap); va_end(ap); ChatHandler::BuildChatPacket(localizedPackets[player->GetSession()->GetSessionDbLocaleIndex()], CHAT_MSG_RAID_BOSS_EMOTE, LANG_UNIVERSAL, NULL, NULL, str); } player->SendDirectMessage(&localizedPackets[player->GetSession()->GetSessionDbLocaleIndex()]); } } void Battleground::SendMessage2ToAll(int32 entry, ChatMsg type, Player const* source, int32 arg1, int32 arg2) { Trinity::Battleground2ChatBuilder bg_builder(type, entry, source, arg1, arg2); Trinity::LocalizedPacketDo bg_do(bg_builder); BroadcastWorker(bg_do); } void Battleground::EndNow() { RemoveFromBGFreeSlotQueue(); SetStatus(STATUS_WAIT_LEAVE); SetEndTime(0); } // To be removed char const* Battleground::GetTrinityString(int32 entry) { // FIXME: now we have different DBC locales and need localized message for each target client return sObjectMgr->GetTrinityStringForDBCLocale(entry); } // IMPORTANT NOTICE: // buffs aren't spawned/despawned when players captures anything // buffs are in their positions when battleground starts void Battleground::HandleTriggerBuff(uint64 go_guid) { GameObject* obj = GetBgMap()->GetGameObject(go_guid); if (!obj || obj->GetGoType() != GAMEOBJECT_TYPE_TRAP || !obj->isSpawned()) return; // Change buff type, when buff is used: int32 index = BgObjects.size() - 1; while (index >= 0 && BgObjects[index] != go_guid) index--; if (index < 0) { TC_LOG_ERROR("bg.battleground", "Battleground::HandleTriggerBuff: cannot find buff gameobject (GUID: %u, entry: %u, type: %u) in internal data for BG (map: %u, instance id: %u)!", GUID_LOPART(go_guid), obj->GetEntry(), obj->GetGoType(), m_MapId, m_InstanceID); return; } // Randomly select new buff uint8 buff = urand(0, 2); uint32 entry = obj->GetEntry(); if (m_BuffChange && entry != Buff_Entries[buff]) { // Despawn current buff SpawnBGObject(index, RESPAWN_ONE_DAY); // Set index for new one for (uint8 currBuffTypeIndex = 0; currBuffTypeIndex < 3; ++currBuffTypeIndex) if (entry == Buff_Entries[currBuffTypeIndex]) { index -= currBuffTypeIndex; index += buff; } } SpawnBGObject(index, BUFF_RESPAWN_TIME); } 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 = ObjectAccessor::FindPlayer(itr->first); if (!creditedPlayer || creditedPlayer == killer) continue; if (creditedPlayer->GetTeam() == killer->GetTeam() && creditedPlayer->IsAtGroupRewardDistance(victim)) UpdatePlayerScore(creditedPlayer, SCORE_HONORABLE_KILLS, 1); } } if (!isArena()) { // To be able to remove insignia -- ONLY IN Battlegrounds victim->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SKINNABLE); RewardXPAtKill(killer, victim); } } // Return the player's team based on battlegroundplayer info // Used in same faction arena matches mainly uint32 Battleground::GetPlayerTeam(uint64 guid) const { BattlegroundPlayerMap::const_iterator itr = m_Players.find(guid); if (itr != m_Players.end()) return itr->second.Team; return 0; } uint32 Battleground::GetOtherTeam(uint32 teamId) const { return teamId ? ((teamId == ALLIANCE) ? HORDE : ALLIANCE) : 0; } bool Battleground::IsPlayerInBattleground(uint64 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; BattlegroundQueueTypeId bgQueueTypeId = BattlegroundMgr::BGQueueTypeId(GetTypeID(), GetArenaType()); BlockMovement(player); BuildPvPLogDataPacket(data); player->SendDirectMessage(&data); sBattlegroundMgr->BuildBattlegroundStatusPacket(&data, this, player->GetBattlegroundQueueIndex(bgQueueTypeId), STATUS_IN_PROGRESS, GetEndTime(), GetStartTime(), GetArenaType(), player->GetBGTeam()); player->SendDirectMessage(&data); } uint32 Battleground::GetAlivePlayersCountByTeam(uint32 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() && !player->HasByteFlag(UNIT_FIELD_BYTES_2, 3, FORM_SPIRITOFREDEMPTION)) ++count; } } return count; } void Battleground::SetHoliday(bool is_holiday) { m_HonorMode = is_holiday ? BG_HOLIDAY : BG_NORMAL; } int32 Battleground::GetObjectType(uint64 guid) { for (uint32 i = 0; i < BgObjects.size(); ++i) if (BgObjects[i] == guid) return i; TC_LOG_ERROR("bg.battleground", "Battleground::GetObjectType: player used gameobject (GUID: %u) which is not in internal data for BG (map: %u, instance id: %u), cheating?", GUID_LOPART(guid), m_MapId, m_InstanceID); return -1; } void Battleground::HandleKillUnit(Creature* /*victim*/, Player* /*killer*/) { } void Battleground::CheckArenaAfterTimerConditions() { EndBattleground(WINNER_NONE); } void Battleground::CheckArenaWinConditions() { if (!GetAlivePlayersCountByTeam(ALLIANCE) && GetPlayersCountByTeam(HORDE)) EndBattleground(HORDE); else if (GetPlayersCountByTeam(ALLIANCE) && !GetAlivePlayersCountByTeam(HORDE)) EndBattleground(ALLIANCE); } void Battleground::UpdateArenaWorldState() { UpdateWorldState(0xe10, GetAlivePlayersCountByTeam(HORDE)); UpdateWorldState(0xe11, GetAlivePlayersCountByTeam(ALLIANCE)); } void Battleground::SetBgRaid(uint32 TeamID, Group* bg_raid) { Group*& old_raid = TeamID == ALLIANCE ? m_BgRaids[TEAM_ALLIANCE] : m_BgRaids[TEAM_HORDE]; if (old_raid) old_raid->SetBattlegroundGroup(NULL); if (bg_raid) bg_raid->SetBattlegroundGroup(this); old_raid = bg_raid; } WorldSafeLocsEntry const* Battleground::GetClosestGraveYard(Player* player) { return sObjectMgr->GetClosestGraveYard(player->GetPositionX(), player->GetPositionY(), player->GetPositionZ(), player->GetMapId(), player->GetTeam()); } void Battleground::StartTimedAchievement(AchievementCriteriaTimedTypes type, uint32 entry) { for (BattlegroundPlayerMap::const_iterator itr = GetPlayers().begin(); itr != GetPlayers().end(); ++itr) if (Player* player = ObjectAccessor::FindPlayer(itr->first)) player->StartTimedAchievement(type, entry); } void Battleground::SetBracket(PvPDifficultyEntry const* bracketEntry) { m_BracketId = bracketEntry->GetBracketId(); SetLevelRange(bracketEntry->minLevel, bracketEntry->maxLevel); } void Battleground::RewardXPAtKill(Player* killer, Player* victim) { if (sWorld->getBoolConfig(CONFIG_BG_XP_FOR_KILL) && killer && victim) killer->RewardPlayerAndGroupAtKill(victim, true); } uint32 Battleground::GetTeamScore(uint32 teamId) const { if (teamId == TEAM_ALLIANCE || teamId == TEAM_HORDE) return m_TeamScores[teamId]; TC_LOG_ERROR("bg.battleground", "GetTeamScore with wrong Team %u for BG %u", teamId, GetTypeID()); return 0; } void Battleground::HandleAreaTrigger(Player* player, uint32 trigger) { TC_LOG_DEBUG("bg.battleground", "Unhandled AreaTrigger %u in Battleground %u. Player coords (x: %f, y: %f, z: %f)", trigger, player->GetMapId(), player->GetPositionX(), player->GetPositionY(), player->GetPositionZ()); } bool Battleground::CheckAchievementCriteriaMeet(uint32 criteriaId, Player const* /*source*/, Unit const* /*target*/, uint32 /*miscvalue1*/) { TC_LOG_ERROR("bg.battleground", "Battleground::CheckAchievementCriteriaMeet: No implementation for criteria %u", criteriaId); return false; }