/*
* 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 "Arena.h"
#include "ArenaTeamMgr.h"
#include "GroupMgr.h"
#include "Log.h"
#include "ObjectAccessor.h"
#include "Pet.h"
#include "Player.h"
#include "ScriptMgr.h"
#include "World.h"
#include "WorldSession.h"
#include "WorldSessionMgr.h"
#include "WorldStateDefines.h"
#include "WorldStatePackets.h"
void ArenaScore::AppendToPacket(WorldPacket& data)
{
data << PlayerGuid;
data << uint32(KillingBlows);
data << uint8(PvPTeamId);
data << uint32(DamageDone);
data << uint32(HealingDone);
BuildObjectivesBlock(data);
}
void ArenaScore::BuildObjectivesBlock(WorldPacket& data)
{
data << uint32(0); // Objectives Count
}
void ArenaTeamScore::BuildRatingInfoBlock(WorldPacket& data)
{
uint32 ratingLost = std::abs(std::min(RatingChange, 0));
uint32 ratingWon = std::max(RatingChange, 0);
// should be old rating, new rating, and client will calculate rating change itself
data << uint32(ratingLost);
data << uint32(ratingWon);
data << uint32(MatchmakerRating);
}
void ArenaTeamScore::BuildTeamInfoBlock(WorldPacket& data)
{
data << TeamName;
}
Arena::Arena()
{
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)
{
Battleground::AddPlayer(player);
PlayerScores.emplace(player->GetGUID().GetCounter(), new ArenaScore(player->GetGUID(), player->GetBgTeamId()));
if (player->GetBgTeamId() == TEAM_ALLIANCE) // gold
{
if (player->GetTeamId() == TEAM_HORDE)
player->CastSpell(player, SPELL_HORDE_GOLD_FLAG, true);
else
player->CastSpell(player, SPELL_ALLIANCE_GOLD_FLAG, true);
}
else // green
{
if (player->GetTeamId() == TEAM_HORDE)
player->CastSpell(player, SPELL_HORDE_GREEN_FLAG, true);
else
player->CastSpell(player, SPELL_ALLIANCE_GREEN_FLAG, true);
}
UpdateArenaWorldState();
Group* group = player->GetGroup();
if (group)
{
// Hackfix for crossfaction arenas, recreate group when joining
// Without this, players in a crossfaction arena group would not be able to cast beneficial spells on their teammates
std::vector members;
bool isCrossfaction = false;
for (Group::member_citerator mitr = group->GetMemberSlots().begin(); mitr != group->GetMemberSlots().end(); ++mitr)
{
Player* member = ObjectAccessor::FindPlayer(mitr->guid);
if (!member || member->GetGUID() == player->GetGUID())
{
continue;
}
members.push_back(member);
if (member->GetTeamId(true) != player->GetTeamId(true))
{
isCrossfaction = true;
}
}
if (isCrossfaction)
{
for (Player* member : members)
{
member->RemoveFromGroup();
}
group->Disband();
group = new Group();
SetBgRaid(player->GetBgTeamId(), group);
group->Create(player);
sGroupMgr->AddGroup(group);
for (Player* member : members)
{
group->AddMember(member);
}
}
}
}
void Arena::RemovePlayer(Player* /*player*/)
{
if (GetStatus() == STATUS_WAIT_LEAVE)
return;
UpdateArenaWorldState();
CheckWinConditions();
}
void Arena::FillInitialWorldStates(WorldPackets::WorldState::InitWorldStates& packet)
{
packet.Worldstates.reserve(2);
packet.Worldstates.emplace_back(WORLD_STATE_ARENA_ALIVE_PLAYERS_GREEN, GetAlivePlayersCountByTeam(TEAM_HORDE));
packet.Worldstates.emplace_back(WORLD_STATE_ARENA_ALIVE_PLAYERS_GOLD, GetAlivePlayersCountByTeam(TEAM_ALLIANCE));
}
void Arena::UpdateArenaWorldState()
{
UpdateWorldState(WORLD_STATE_ARENA_ALIVE_PLAYERS_GREEN, GetAlivePlayersCountByTeam(TEAM_HORDE));
UpdateWorldState(WORLD_STATE_ARENA_ALIVE_PLAYERS_GOLD, GetAlivePlayersCountByTeam(TEAM_ALLIANCE));
}
void Arena::HandleKillPlayer(Player* player, Player* killer)
{
if (GetStatus() != STATUS_IN_PROGRESS)
return;
Battleground::HandleKillPlayer(player, killer);
UpdateArenaWorldState();
CheckWinConditions();
}
void Arena::RemovePlayerAtLeave(Player* player)
{
if (isRated() && GetStatus() == STATUS_IN_PROGRESS)
{
if (auto const& member = Acore::Containers::MapGetValuePtr(m_Players, player->GetGUID()))
{
// if the player was a match participant, calculate rating
auto teamId = member->GetBgTeamId();
ArenaTeam* winnerArenaTeam = sArenaTeamMgr->GetArenaTeamById(GetArenaTeamIdForTeam(GetOtherTeamId(teamId)));
ArenaTeam* loserArenaTeam = sArenaTeamMgr->GetArenaTeamById(GetArenaTeamIdForTeam(teamId));
if (winnerArenaTeam && loserArenaTeam && winnerArenaTeam != loserArenaTeam)
loserArenaTeam->MemberLost(player, GetArenaMatchmakerRating(GetOtherTeamId(teamId)));
}
}
// remove player
Battleground::RemovePlayerAtLeave(player);
}
void Arena::CheckWinConditions()
{
if (!sScriptMgr->OnBeforeArenaCheckWinConditions(this))
return;
if (!GetAlivePlayersCountByTeam(TEAM_ALLIANCE) && GetPlayersCountByTeam(TEAM_HORDE))
EndBattleground(TEAM_HORDE);
else if (GetPlayersCountByTeam(TEAM_ALLIANCE) && !GetAlivePlayersCountByTeam(TEAM_HORDE))
EndBattleground(TEAM_ALLIANCE);
}
void Arena::EndBattleground(TeamId winnerTeamId)
{
if (isRated())
{
uint32 startDelay = GetStartDelayTime();
bool bValidArena = GetStatus() == STATUS_IN_PROGRESS && GetStartTime() >= startDelay + 15000; // pussywizard: only if arena lasted at least 15 secs
uint32 loserTeamRating = 0;
uint32 loserMatchmakerRating = 0;
int32 loserChange = 0;
int32 loserMatchmakerChange = 0;
uint32 winnerTeamRating = 0;
uint32 winnerMatchmakerRating = 0;
int32 winnerChange = 0;
int32 winnerMatchmakerChange = 0;
ArenaTeam* winnerArenaTeam = sArenaTeamMgr->GetArenaTeamById(GetArenaTeamIdForTeam(winnerTeamId == TEAM_NEUTRAL ? TEAM_HORDE : winnerTeamId));
ArenaTeam* loserArenaTeam = sArenaTeamMgr->GetArenaTeamById(GetArenaTeamIdForTeam(winnerTeamId == TEAM_NEUTRAL ? TEAM_ALLIANCE : GetOtherTeamId(winnerTeamId)));
auto SaveArenaLogs = [&]()
{
// pussywizard: arena logs in database
uint32 fightId = sArenaTeamMgr->GetNextArenaLogId();
uint32 currOnline = sWorldSessionMgr->GetActiveSessionCount();
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
CharacterDatabasePreparedStatement* stmt2 = CharacterDatabase.GetPreparedStatement(CHAR_INS_ARENA_LOG_FIGHT);
stmt2->SetData(0, fightId);
stmt2->SetData(1, GetArenaType());
stmt2->SetData(2, ((GetStartTime() <= startDelay ? 0 : GetStartTime() - startDelay) / 1000));
stmt2->SetData(3, winnerArenaTeam->GetId());
stmt2->SetData(4, loserArenaTeam->GetId());
stmt2->SetData(5, (uint16)winnerTeamRating);
stmt2->SetData(6, (uint16)winnerMatchmakerRating);
stmt2->SetData(7, (int16)winnerChange);
stmt2->SetData(8, (uint16)loserTeamRating);
stmt2->SetData(9, (uint16)loserMatchmakerRating);
stmt2->SetData(10, (int16)loserChange);
stmt2->SetData(11, currOnline);
trans->Append(stmt2);
uint8 memberId = 0;
for (auto const& [playerGuid, arenaLogEntryData] : ArenaLogEntries)
{
auto const& score = PlayerScores.find(playerGuid.GetCounter());
stmt2 = CharacterDatabase.GetPreparedStatement(CHAR_INS_ARENA_LOG_MEMBERSTATS);
stmt2->SetData(0, fightId);
stmt2->SetData(1, ++memberId);
stmt2->SetData(2, arenaLogEntryData.Name);
stmt2->SetData(3, arenaLogEntryData.Guid);
stmt2->SetData(4, arenaLogEntryData.ArenaTeamId);
stmt2->SetData(5, arenaLogEntryData.Acc);
stmt2->SetData(6, arenaLogEntryData.IP);
if (score != PlayerScores.end())
{
stmt2->SetData(7, score->second->GetDamageDone());
stmt2->SetData(8, score->second->GetHealingDone());
stmt2->SetData(9, score->second->GetKillingBlows());
}
else
{
stmt2->SetData(7, 0);
stmt2->SetData(8, 0);
stmt2->SetData(9, 0);
}
trans->Append(stmt2);
}
CharacterDatabase.CommitTransaction(trans);
};
if (winnerArenaTeam && loserArenaTeam && winnerArenaTeam != loserArenaTeam)
{
loserTeamRating = loserArenaTeam->GetRating();
loserMatchmakerRating = GetArenaMatchmakerRating(GetOtherTeamId(winnerTeamId));
winnerTeamRating = winnerArenaTeam->GetRating();
winnerMatchmakerRating = GetArenaMatchmakerRating(winnerTeamId);
if (winnerTeamId != TEAM_NEUTRAL)
{
winnerMatchmakerChange = bValidArena ? winnerArenaTeam->WonAgainst(winnerMatchmakerRating, loserMatchmakerRating, winnerChange, GetBgMap()) : 0;
loserMatchmakerChange = loserArenaTeam->LostAgainst(loserMatchmakerRating, winnerMatchmakerRating, loserChange, GetBgMap());
sScriptMgr->OnAfterArenaRatingCalculation(this, winnerMatchmakerChange, loserMatchmakerChange, winnerChange, loserChange);
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(winnerTeamId, winnerMatchmakerRating + winnerMatchmakerChange);
SetArenaMatchmakerRating(GetOtherTeamId(winnerTeamId), loserMatchmakerRating + loserMatchmakerChange);
// bg team that the client expects is different to TeamId
// alliance 1, horde 0
uint8 winnerTeam = winnerTeamId == TEAM_ALLIANCE ? PVP_TEAM_ALLIANCE : PVP_TEAM_HORDE;
uint8 loserTeam = winnerTeamId == TEAM_ALLIANCE ? PVP_TEAM_HORDE : PVP_TEAM_ALLIANCE;
_arenaTeamScores[winnerTeam].Assign(winnerChange, winnerMatchmakerRating, winnerArenaTeam->GetName());
_arenaTeamScores[loserTeam].Assign(loserChange, loserMatchmakerRating, loserArenaTeam->GetName());
LOG_DEBUG("bg.arena", "Arena match Type: {} for Team1Id: {} - Team2Id: {} ended. WinnerTeamId: {}. Winner rating: +{}, Loser rating: {}",
GetArenaType(), GetArenaTeamIdForTeam(TEAM_ALLIANCE), GetArenaTeamIdForTeam(TEAM_HORDE), winnerArenaTeam->GetId(), winnerChange, loserChange);
}
else // Deduct 16 points from each teams arena-rating if there are no winners after 45+2 minutes
{
// pussywizard: in case of a draw, the following is always true:
// winnerArenaTeam => TEAM_HORDE, loserArenaTeam => TEAM_ALLIANCE
winnerTeamRating = winnerArenaTeam->GetRating();
winnerMatchmakerRating = GetArenaMatchmakerRating(TEAM_HORDE);
loserTeamRating = loserArenaTeam->GetRating();
loserMatchmakerRating = GetArenaMatchmakerRating(TEAM_ALLIANCE);
winnerMatchmakerChange = 0;
loserMatchmakerChange = 0;
winnerChange = ARENA_TIMELIMIT_POINTS_LOSS;
loserChange = ARENA_TIMELIMIT_POINTS_LOSS;
_arenaTeamScores[PVP_TEAM_ALLIANCE].Assign(ARENA_TIMELIMIT_POINTS_LOSS, winnerMatchmakerRating, winnerArenaTeam->GetName());
_arenaTeamScores[PVP_TEAM_HORDE].Assign(ARENA_TIMELIMIT_POINTS_LOSS, loserMatchmakerRating, loserArenaTeam->GetName());
winnerArenaTeam->FinishGame(ARENA_TIMELIMIT_POINTS_LOSS, GetBgMap());
loserArenaTeam->FinishGame(ARENA_TIMELIMIT_POINTS_LOSS, GetBgMap());
}
}
SaveArenaLogs();
uint8 aliveWinners = GetAlivePlayersCountByTeam(winnerTeamId);
for (auto const& [playerGuid, player] : GetPlayers())
{
auto const& bgTeamId = player->GetBgTeamId();
// per player calculation
if (winnerArenaTeam && loserArenaTeam && winnerArenaTeam != loserArenaTeam)
{
if (bgTeamId == winnerTeamId)
{
if (bValidArena)
{
// 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());
// 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);
}
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_CONDITION_NO_LOSE, 0);
}
player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_PLAY_ARENA, GetMapId());
}
}
// update previous opponents for arena queue
winnerArenaTeam->SetPreviousOpponents(loserArenaTeam->GetId());
loserArenaTeam->SetPreviousOpponents(winnerArenaTeam->GetId());
// save the stat changes
if (bValidArena)
{
winnerArenaTeam->SaveToDB();
winnerArenaTeam->NotifyStatsChanged();
}
loserArenaTeam->SaveToDB();
loserArenaTeam->NotifyStatsChanged();
}
// end battleground
Battleground::EndBattleground(winnerTeamId);
}