/* * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ #include "Arena.h" #include "ArenaTeamMgr.h" #include "BattlegroundPackets.h" #include "BattlegroundScore.h" #include "GuildMgr.h" #include "Guild.h" #include "Log.h" #include "Map.h" #include "ObjectAccessor.h" #include "Player.h" #include "World.h" #include "WorldSession.h" Arena::Arena(BattlegroundTemplate const* battlegroundTemplate) : Battleground(battlegroundTemplate) { StartDelayTimes[BG_STARTING_EVENT_FIRST] = BG_START_DELAY_1M; StartDelayTimes[BG_STARTING_EVENT_SECOND] = BG_START_DELAY_30S; StartDelayTimes[BG_STARTING_EVENT_THIRD] = BG_START_DELAY_15S; StartDelayTimes[BG_STARTING_EVENT_FOURTH] = BG_START_DELAY_NONE; StartMessageIds[BG_STARTING_EVENT_FIRST] = ARENA_TEXT_START_ONE_MINUTE; StartMessageIds[BG_STARTING_EVENT_SECOND] = ARENA_TEXT_START_THIRTY_SECONDS; StartMessageIds[BG_STARTING_EVENT_THIRD] = ARENA_TEXT_START_FIFTEEN_SECONDS; StartMessageIds[BG_STARTING_EVENT_FOURTH] = ARENA_TEXT_START_BATTLE_HAS_BEGUN; } void Arena::AddPlayer(Player* player, BattlegroundQueueTypeId queueId) { Battleground::AddPlayer(player, queueId); if (player->GetBGTeam() == ALLIANCE) // gold { if (player->GetEffectiveTeam() == HORDE) player->CastSpell(player, SPELL_HORDE_GOLD_FLAG, true); else player->CastSpell(player, SPELL_ALLIANCE_GOLD_FLAG, true); } else // green { if (player->GetEffectiveTeam() == HORDE) player->CastSpell(player, SPELL_HORDE_GREEN_FLAG, true); else player->CastSpell(player, SPELL_ALLIANCE_GREEN_FLAG, true); } UpdateArenaWorldState(); } void Arena::RemovePlayer(Player* /*player*/, ObjectGuid /*guid*/, uint32 /*team*/) { if (GetStatus() == STATUS_WAIT_LEAVE) return; UpdateArenaWorldState(); CheckWinConditions(); } void Arena::UpdateArenaWorldState() { UpdateWorldState(ARENA_WORLD_STATE_ALIVE_PLAYERS_GREEN, GetAlivePlayersCountByTeam(HORDE)); UpdateWorldState(ARENA_WORLD_STATE_ALIVE_PLAYERS_GOLD, GetAlivePlayersCountByTeam(ALLIANCE)); } void Arena::HandleKillPlayer(Player* player, Player* killer) { if (GetStatus() != STATUS_IN_PROGRESS) return; Battleground::HandleKillPlayer(player, killer); UpdateArenaWorldState(); CheckWinConditions(); } void Arena::BuildPvPLogDataPacket(WorldPackets::Battleground::PVPMatchStatistics& pvpLogData) const { Battleground::BuildPvPLogDataPacket(pvpLogData); if (isRated()) { pvpLogData.Ratings.emplace(); for (uint8 i = 0; i < PVP_TEAMS_COUNT; ++i) { pvpLogData.Ratings->Postmatch[i] = _arenaTeamScores[i].PostMatchRating; pvpLogData.Ratings->Prematch[i] = _arenaTeamScores[i].PreMatchRating; pvpLogData.Ratings->PrematchMMR[i] = _arenaTeamScores[i].PreMatchMMR; } } } void Arena::RemovePlayerAtLeave(ObjectGuid guid, bool transport, bool sendPacket) { if (isRated() && GetStatus() == STATUS_IN_PROGRESS) { BattlegroundPlayerMap::const_iterator itr = m_Players.find(guid); if (itr != m_Players.end()) // check if the player was a participant of the match, or only entered through gm command (appear) { // if the player was a match participant, calculate rating Team team = itr->second.Team; ArenaTeam* winnerArenaTeam = nullptr; ArenaTeam* loserArenaTeam = nullptr; // left a rated match while the encounter was in progress, consider as loser if (winnerArenaTeam && loserArenaTeam && winnerArenaTeam != loserArenaTeam) { if (Player* player = _GetPlayer(itr->first, itr->second.OfflineRemoveTime != 0, "Arena::RemovePlayerAtLeave")) loserArenaTeam->MemberLost(player, GetArenaMatchmakerRating(GetOtherTeam(team))); else loserArenaTeam->OfflineMemberLost(guid, GetArenaMatchmakerRating(GetOtherTeam(team))); } } } // remove player Battleground::RemovePlayerAtLeave(guid, transport, sendPacket); } void Arena::CheckWinConditions() { if (!GetAlivePlayersCountByTeam(ALLIANCE) && GetPlayersCountByTeam(HORDE)) EndBattleground(HORDE); else if (GetPlayersCountByTeam(ALLIANCE) && !GetAlivePlayersCountByTeam(HORDE)) EndBattleground(ALLIANCE); } void Arena::EndBattleground(Team winner) { // arena rating calculation if (isRated()) { uint32 loserTeamRating = 0; uint32 loserMatchmakerRating = 0; int32 loserChange = 0; int32 loserMatchmakerChange = 0; uint32 winnerTeamRating = 0; uint32 winnerMatchmakerRating = 0; int32 winnerChange = 0; int32 winnerMatchmakerChange = 0; bool guildAwarded = false; // In case of arena draw, follow this logic: // winnerArenaTeam => ALLIANCE, loserArenaTeam => HORDE ArenaTeam* winnerArenaTeam = nullptr; ArenaTeam* loserArenaTeam = nullptr; if (winnerArenaTeam && loserArenaTeam && winnerArenaTeam != loserArenaTeam) { // In case of arena draw, follow this logic: // winnerMatchmakerRating => ALLIANCE, loserMatchmakerRating => HORDE loserTeamRating = loserArenaTeam->GetRating(); loserMatchmakerRating = GetArenaMatchmakerRating(winner == TEAM_OTHER ? HORDE : GetOtherTeam(winner)); winnerTeamRating = winnerArenaTeam->GetRating(); winnerMatchmakerRating = GetArenaMatchmakerRating(winner == TEAM_OTHER ? ALLIANCE : winner); if (winner != 0) { winnerMatchmakerChange = winnerArenaTeam->WonAgainst(winnerMatchmakerRating, loserMatchmakerRating, winnerChange); loserMatchmakerChange = loserArenaTeam->LostAgainst(loserMatchmakerRating, winnerMatchmakerRating, loserChange); TC_LOG_DEBUG("bg.arena", "match Type: {} --- Winner: old rating: {}, rating gain: {}, old MMR: {}, MMR gain: {} --- Loser: old rating: {}, rating loss: {}, old MMR: {}, MMR loss: {} ---", GetArenaType(), 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 ? PVP_TEAM_ALLIANCE : PVP_TEAM_HORDE; uint8 loserTeam = winner == ALLIANCE ? PVP_TEAM_HORDE : PVP_TEAM_ALLIANCE; _arenaTeamScores[winnerTeam].Assign(winnerTeamRating, winnerTeamRating + winnerChange, winnerMatchmakerRating, GetArenaMatchmakerRating(winner)); _arenaTeamScores[loserTeam].Assign(loserTeamRating, loserTeamRating + loserChange, loserMatchmakerRating, GetArenaMatchmakerRating(GetOtherTeam(winner))); TC_LOG_DEBUG("bg.arena", "Arena match Type: {} ended. WinnerTeamId: {}. Winner rating: +{}, Loser rating: {}", GetArenaType(), winnerArenaTeam->GetId(), winnerChange, loserChange); if (sWorld->getBoolConfig(CONFIG_ARENA_LOG_EXTENDED_INFO)) for (auto const& score : PlayerScores) if (Player* player = ObjectAccessor::FindConnectedPlayer(score.first)) { TC_LOG_DEBUG("bg.arena", "Statistics match Type: {} for {} ({}, Team: {}, IP: {}): {}", GetArenaType(), player->GetName(), score.first.ToString(), player->GetArenaTeamId(GetArenaType() == 5 ? 2 : GetArenaType() == 3), player->GetSession()->GetRemoteAddress(), score.second->ToString()); } } // Deduct 16 points from each teams arena-rating if there are no winners after 45+2 minutes else { _arenaTeamScores[PVP_TEAM_ALLIANCE].Assign(winnerTeamRating, winnerTeamRating + ARENA_TIMELIMIT_POINTS_LOSS, winnerMatchmakerRating, GetArenaMatchmakerRating(ALLIANCE)); _arenaTeamScores[PVP_TEAM_HORDE].Assign(loserTeamRating, loserTeamRating + ARENA_TIMELIMIT_POINTS_LOSS, loserMatchmakerRating, GetArenaMatchmakerRating(HORDE)); winnerArenaTeam->FinishGame(ARENA_TIMELIMIT_POINTS_LOSS); loserArenaTeam->FinishGame(ARENA_TIMELIMIT_POINTS_LOSS); } uint8 aliveWinners = GetAlivePlayersCountByTeam(winner); for (auto const& i : GetPlayers()) { Team team = i.second.Team; if (i.second.OfflineRemoveTime) { // if rated arena match - make member lost! if (team == winner) winnerArenaTeam->OfflineMemberLost(i.first, loserMatchmakerRating, winnerMatchmakerChange); else { if (winner == 0) winnerArenaTeam->OfflineMemberLost(i.first, loserMatchmakerRating, winnerMatchmakerChange); loserArenaTeam->OfflineMemberLost(i.first, winnerMatchmakerRating, loserMatchmakerChange); } continue; } Player* player = _GetPlayer(i.first, i.second.OfflineRemoveTime != 0, "Arena::EndBattleground"); if (!player) continue; player->UpdateCriteria(CriteriaType::ParticipateInArena, GetMapId()); // per player calculation if (team == winner) { // update achievement BEFORE personal rating update uint32 rating = player->GetArenaPersonalRating(winnerArenaTeam->GetSlot()); player->StartCriteria(CriteriaStartEvent::WinRankedArenaMatchWithTeamSize, 0); player->UpdateCriteria(CriteriaType::WinAnyRankedArena, rating ? rating : 1); player->UpdateCriteria(CriteriaType::WinArena, GetMapId()); // Last standing - Rated 5v5 arena & be solely alive player if (GetArenaType() == ARENA_TYPE_5v5 && aliveWinners == 1 && player->IsAlive()) player->CastSpell(player, SPELL_LAST_MAN_STANDING, true); if (!guildAwarded) { guildAwarded = true; if (ObjectGuid::LowType guildId = GetBgMap()->GetOwnerGuildId(player->GetBGTeam())) if (Guild* guild = sGuildMgr->GetGuildById(guildId)) guild->UpdateCriteria(CriteriaType::WinAnyRankedArena, std::max(winnerArenaTeam->GetRating(), 1), 0, 0, nullptr, player); } winnerArenaTeam->MemberWon(player, loserMatchmakerRating, winnerMatchmakerChange); } else { if (winner == 0) winnerArenaTeam->MemberLost(player, loserMatchmakerRating, winnerMatchmakerChange); loserArenaTeam->MemberLost(player, winnerMatchmakerRating, loserMatchmakerChange); // Arena lost => reset the win_rated_arena having the "no_lose" condition player->FailCriteria(CriteriaFailEvent::LoseRankedArenaMatchWithTeamSize, 0); } } // 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(); } } // end battleground Battleground::EndBattleground(winner); }