diff --git a/sql/updates/characters/master/2026_07_19_00_characters.sql b/sql/updates/characters/master/2026_07_19_00_characters.sql new file mode 100644 index 000000000..d81d51e1e --- /dev/null +++ b/sql/updates/characters/master/2026_07_19_00_characters.sql @@ -0,0 +1,2 @@ +ALTER TABLE `character_arena_stats` +ADD COLUMN `rating` smallint unsigned NOT NULL DEFAULT '0' AFTER `matchMakerRating`; \ No newline at end of file diff --git a/src/server/database/Database/Implementation/CharacterDatabase.cpp b/src/server/database/Database/Implementation/CharacterDatabase.cpp index e7e712fdc..b0f4cb504 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.cpp +++ b/src/server/database/Database/Implementation/CharacterDatabase.cpp @@ -449,7 +449,8 @@ void CharacterDatabaseConnection::DoPrepareStatements() PrepareStatement(CHAR_UPD_ARENA_TEAM_STATS, "UPDATE arena_team SET rating = ?, weekGames = ?, weekWins = ?, seasonGames = ?, seasonWins = ?, `rank` = ? WHERE arenaTeamId = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_UPD_ARENA_TEAM_MEMBER, "UPDATE arena_team_member SET personalRating = ?, weekGames = ?, weekWins = ?, seasonGames = ?, seasonWins = ? WHERE arenaTeamId = ? AND guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_CHARACTER_ARENA_STATS, "DELETE FROM character_arena_stats WHERE guid = ?", CONNECTION_ASYNC); - PrepareStatement(CHAR_REP_CHARACTER_ARENA_STATS, "REPLACE INTO character_arena_stats (guid, slot, matchMakerRating) VALUES (?, ?, ?)", CONNECTION_ASYNC); + PrepareStatement(CHAR_REP_CHARACTER_ARENA_STATS, "REPLACE INTO character_arena_stats (guid, slot, matchMakerRating, rating) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC); + PrepareStatement(CHAR_SEL_CHARACTER_ARENA_STATS, "SELECT slot, matchMakerRating, rating FROM character_arena_stats WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_UPD_ARENA_TEAM_NAME, "UPDATE arena_team SET name = ? WHERE arenaTeamId = ?", CONNECTION_ASYNC); // Character battleground data diff --git a/src/server/database/Database/Implementation/CharacterDatabase.h b/src/server/database/Database/Implementation/CharacterDatabase.h index 2acdd1935..d92a9cfa5 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.h +++ b/src/server/database/Database/Implementation/CharacterDatabase.h @@ -317,6 +317,7 @@ enum CharacterDatabaseStatements : uint32 CHAR_UPD_ARENA_TEAM_MEMBER, CHAR_DEL_CHARACTER_ARENA_STATS, CHAR_REP_CHARACTER_ARENA_STATS, + CHAR_SEL_CHARACTER_ARENA_STATS, CHAR_UPD_ARENA_TEAM_NAME, CHAR_SEL_PETITION, diff --git a/src/server/game/Battlegrounds/Arena.cpp b/src/server/game/Battlegrounds/Arena.cpp index 13862f964..91e6f9f5f 100644 --- a/src/server/game/Battlegrounds/Arena.cpp +++ b/src/server/game/Battlegrounds/Arena.cpp @@ -16,6 +16,7 @@ */ #include "Arena.h" +#include "ArenaTeam.h" #include "ArenaTeamMgr.h" #include "BattlegroundMgr.h" #include "BattlegroundPackets.h" @@ -32,14 +33,14 @@ Arena::Arena(BattlegroundTemplate const* battlegroundTemplate) : Battleground(battlegroundTemplate) { - StartDelayTimes[BG_STARTING_EVENT_FIRST] = BG_START_DELAY_1M; + 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_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_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_THIRD] = ARENA_TEXT_START_FIFTEEN_SECONDS; StartMessageIds[BG_STARTING_EVENT_FOURTH] = ARENA_TEXT_START_BATTLE_HAS_BEGUN; } @@ -187,141 +188,218 @@ 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) + if (sBattlegroundMgr->IsFSBOverrideEnabled()) { - // 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); + uint8 slot = ArenaTeam::GetSlotByType(GetArenaType()); + Team loserTeam = GetOtherTeam(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); + // Calculate synthetic bot team ratings for scoreboard display + uint32 botRatingWinner = ScriptHelpers::GetBotTeamRating(GetBgMap(), winner); + uint32 botRatingLoser = ScriptHelpers::GetBotTeamRating(GetBgMap(), loserTeam); + // Process each real player 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()); + Team team = i.second.Team; + uint32 playerRating = player->GetArenaPersonalRating(slot); - // 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()); + // Opponent rating is the bot team rating of the opposing team + uint32 opponentRating = (team == winner) ? botRatingLoser : botRatingWinner; - // 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); + int32 ratingChange; + bool won = (team == winner); - 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); - } + if (won) + ratingChange = ArenaTeam::GetRatingMod(playerRating, opponentRating, true); else - { - if (winner == 0) - winnerArenaTeam->MemberLost(player, loserMatchmakerRating, winnerMatchmakerChange); + ratingChange = ArenaTeam::GetRatingMod(playerRating, opponentRating, false); - loserArenaTeam->MemberLost(player, winnerMatchmakerRating, loserMatchmakerChange); + player->ModifyArenaRating(ratingChange, slot, won); - // Arena lost => reset the win_rated_arena having the "no_lose" condition - player->FailCriteria(CriteriaFailEvent::LoseRankedArenaMatchWithTeamSize, 0); - } + // Set per-player score for scoreboard rating change display + auto scoreItr = PlayerScores.find(player->GetGUID()); + if (scoreItr != PlayerScores.end()) + scoreItr->second->SetPrePostMatchRating(playerRating, playerRating + ratingChange, playerRating, playerRating + ratingChange); + + TC_LOG_DEBUG("bg.arena", "FSB Arena match Type: {} for {} ({}) --- Rating: {} -> {} (change: {}) --- Opponent bot rating: {}", + GetArenaType(), player->GetName(), player->GetGUID().ToString(), playerRating, + playerRating + ratingChange, ratingChange, opponentRating); } - // 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(); + // Populate scoreboard with team ratings + uint8 winnerTeamIdx = winner == ALLIANCE ? PVP_TEAM_ALLIANCE : PVP_TEAM_HORDE; + uint8 loserTeamIdx = winner == ALLIANCE ? PVP_TEAM_HORDE : PVP_TEAM_ALLIANCE; + + // For scoreboard: use player team rating as the team rating, bot team rating for the opponent + uint32 winnerPreRating = 0; + uint32 loserPreRating = 0; + + // Get the first real player's rating for each team for display + for (auto const& i : GetPlayers()) + { + Player* player = _GetPlayer(i.first, i.second.OfflineRemoveTime != 0, "Arena::EndBattleground"); + if (!player) + continue; + + if (i.second.Team == winner) + winnerPreRating = std::max(winnerPreRating, player->GetArenaPersonalRating(slot)); + else + loserPreRating = std::max(loserPreRating, player->GetArenaPersonalRating(slot)); + } + + // If no real players on a team, use bot rating + if (winnerPreRating == 0) + winnerPreRating = botRatingWinner; + if (loserPreRating == 0) + loserPreRating = botRatingLoser; + + int32 winnerChange = ArenaTeam::GetRatingMod(winnerPreRating, loserPreRating, true); + int32 loserChange = ArenaTeam::GetRatingMod(loserPreRating, winnerPreRating, false); + + _arenaTeamScores[winnerTeamIdx].Assign(winnerPreRating, winnerPreRating + winnerChange, winnerPreRating, winnerPreRating); + _arenaTeamScores[loserTeamIdx].Assign(loserPreRating, loserPreRating + loserChange, loserPreRating, loserPreRating); + } + else + { + // Original arena team rating code (dead code - ArenaTeam pointers are never assigned) + ArenaTeam* winnerArenaTeam = nullptr; + ArenaTeam* loserArenaTeam = nullptr; + + if (winnerArenaTeam && loserArenaTeam && winnerArenaTeam != loserArenaTeam) + { + 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: + // 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(); + } } } diff --git a/src/server/game/Battlegrounds/ArenaTeam.h b/src/server/game/Battlegrounds/ArenaTeam.h index ec97c23e1..2f832c7b6 100644 --- a/src/server/game/Battlegrounds/ArenaTeam.h +++ b/src/server/game/Battlegrounds/ArenaTeam.h @@ -164,9 +164,9 @@ class TC_GAME_API ArenaTeam void Inspect(WorldSession* session, ObjectGuid guid); uint32 GetPoints(uint32 MemberRating); - int32 GetMatchmakerRatingMod(uint32 ownRating, uint32 opponentRating, bool won); - int32 GetRatingMod(uint32 ownRating, uint32 opponentRating, bool won); - float GetChanceAgainst(uint32 ownRating, uint32 opponentRating); + static int32 GetMatchmakerRatingMod(uint32 ownRating, uint32 opponentRating, bool won); + static int32 GetRatingMod(uint32 ownRating, uint32 opponentRating, bool won); + static float GetChanceAgainst(uint32 ownRating, uint32 opponentRating); int32 WonAgainst(uint32 Own_MMRating, uint32 Opponent_MMRating, int32& rating_change); void MemberWon(Player* player, uint32 againstMatchmakerRating, int32 MatchmakerRatingChange); int32 LostAgainst(uint32 Own_MMRating, uint32 Opponent_MMRating, int32& rating_change); diff --git a/src/server/game/Battlegrounds/BattlegroundScore.cpp b/src/server/game/Battlegrounds/BattlegroundScore.cpp index c8c35135e..931fae29f 100644 --- a/src/server/game/Battlegrounds/BattlegroundScore.cpp +++ b/src/server/game/Battlegrounds/BattlegroundScore.cpp @@ -129,3 +129,14 @@ std::string BattlegroundScore::ToString() const << ", PostMatchRating: " << PostMatchRating << ", PostMatchMMR: " << PostMatchMMR; return stream.str(); } + +//WowCommunity +void BattlegroundScore::SetPrePostMatchRating(uint32 preMatchRating, uint32 postMatchRating, uint32 preMatchMMR, uint32 postMatchMMR) +{ + PreMatchRating = preMatchRating; + PostMatchRating = postMatchRating; + PreMatchMMR = preMatchMMR; + PostMatchMMR = postMatchMMR; +} + +//WowCommunity diff --git a/src/server/game/Battlegrounds/BattlegroundScore.h b/src/server/game/Battlegrounds/BattlegroundScore.h index 21bccdec6..f30338862 100644 --- a/src/server/game/Battlegrounds/BattlegroundScore.h +++ b/src/server/game/Battlegrounds/BattlegroundScore.h @@ -57,6 +57,9 @@ struct BattlegroundScore uint32 GetAttr(uint8 index) const; void BuildPvPLogPlayerDataPacket(WorldPackets::Battleground::PVPMatchStatistics::PVPMatchPlayerStatistics& playerData) const; + //WowCommunity + void SetPrePostMatchRating(uint32 preMatchRating, uint32 postMatchRating, uint32 preMatchMMR, uint32 postMatchMMR); + //WowCommunity protected: diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 805491f19..73e2b94a4 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -18312,6 +18312,10 @@ bool Player::LoadFromDB(ObjectGuid guid, CharacterDatabaseQueryHolder const& hol _LoadBGData(holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_BG_DATA)); + //WowCommunity + _LoadArenaStats(holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_ARENA_STATS)); + //WowCommunity + GetSession()->SetPlayer(this); MapEntry const* mapEntry = sMapStore.LookupEntry(mapId); Map* map = nullptr; @@ -20817,6 +20821,16 @@ void Player::SaveToDB(LoginDatabaseTransaction loginTransaction, CharacterDataba //WowCommunity if (_mythicPlusData) _mythicPlusData->SaveToDB(trans); + + for (uint32 i = 0; i < m_activePlayerData->PvpInfo.size(); ++i) + { + stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_CHARACTER_ARENA_STATS); + stmt->setUInt64(0, GetGUID().GetCounter()); + stmt->setUInt8(1, *m_activePlayerData->PvpInfo[i].Bracket); + stmt->setUInt16(2, 0); + stmt->setUInt16(3, *m_activePlayerData->PvpInfo[i].Rating); + trans->Append(stmt); + } //WowCommunity // check if stats should only be saved on logout @@ -33147,3 +33161,76 @@ void Player::SetSpectateTarget(ObjectGuid guid) { SetUpdateFieldValue(m_values.ModifyValue(&Player::m_playerData).ModifyValue(&UF::PlayerData::SpectateTarget), guid); } + +void Player::ModifyArenaRating(int32 mod, uint8 slot, bool won) +{ + int32 index = m_activePlayerData->PvpInfo.FindIndexIf([slot](UF::PVPInfo const& pvpInfo) + { + return pvpInfo.Bracket == int8(slot) && !*pvpInfo.Disqualified; + }); + + if (index < 0) + { + AddDynamicUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::PvpInfo)); + index = m_activePlayerData->PvpInfo.size() - 1; + SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::PvpInfo, index).ModifyValue(&UF::PVPInfo::Bracket), int8(slot)); + SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::PvpInfo, index).ModifyValue(&UF::PVPInfo::Rating), 1500u); + } + + uint32 currentRating = m_activePlayerData->PvpInfo[index].Rating; + if (currentRating == 0) + currentRating = 1500; + int32 newRating = int32(currentRating) + mod; + if (newRating < 0) + newRating = 0; + + SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::PvpInfo, index).ModifyValue(&UF::PVPInfo::Rating), uint32(newRating)); + SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::PvpInfo, index).ModifyValue(&UF::PVPInfo::WeeklyPlayed), uint32(*m_activePlayerData->PvpInfo[index].WeeklyPlayed) + 1); + SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::PvpInfo, index).ModifyValue(&UF::PVPInfo::SeasonPlayed), uint32(*m_activePlayerData->PvpInfo[index].SeasonPlayed) + 1); + if (won) + { + SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::PvpInfo, index).ModifyValue(&UF::PVPInfo::WeeklyWon), uint32(*m_activePlayerData->PvpInfo[index].WeeklyWon) + 1); + SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::PvpInfo, index).ModifyValue(&UF::PVPInfo::SeasonWon), uint32(*m_activePlayerData->PvpInfo[index].SeasonWon) + 1); + } +} + +void Player::_LoadArenaStats(PreparedQueryResult result) +{ + // Default ratings for all 3 arena slots (2v2=0, 3v3=1, 5v5=2) + uint32 defaultRating[MAX_ARENA_SLOT] = { 1500, 1500, 1500 }; + + if (result) + { + do + { + Field* fields = result->Fetch(); + uint8 slot = fields[0].GetUInt8(); + uint16 rating = fields[2].GetUInt16(); + + if (slot < MAX_ARENA_SLOT) + defaultRating[slot] = rating; + + AddDynamicUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::PvpInfo)); + int32 idx = m_activePlayerData->PvpInfo.size() - 1; + SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::PvpInfo, idx).ModifyValue(&UF::PVPInfo::Bracket), int8(slot)); + SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::PvpInfo, idx).ModifyValue(&UF::PVPInfo::Rating), uint32(rating)); + } while (result->NextRow()); + } + + // Create default PVPInfo entries for slots that have no saved data + for (uint8 slot = 0; slot < MAX_ARENA_SLOT; ++slot) + { + int32 index = m_activePlayerData->PvpInfo.FindIndexIf([slot](UF::PVPInfo const& pvpInfo) + { + return pvpInfo.Bracket == int8(slot) && !*pvpInfo.Disqualified; + }); + + if (index < 0) + { + AddDynamicUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::PvpInfo)); + index = m_activePlayerData->PvpInfo.size() - 1; + SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::PvpInfo, index).ModifyValue(&UF::PVPInfo::Bracket), int8(slot)); + SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::PvpInfo, index).ModifyValue(&UF::PVPInfo::Rating), defaultRating[slot]); + } + } +} diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index b0c29fe80..db4495a2b 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -1066,6 +1066,7 @@ enum PlayerLoginQueryIndex PLAYER_LOGIN_QUERY_LOAD_SOULBIND_CONDUITS, PLAYER_LOGIN_QUERY_LOAD_SOULBIND_CONDUIT_SOCKETS, PLAYER_LOGIN_QUERY_LOAD_RENOWN_REWARDS, + PLAYER_LOGIN_QUERY_LOAD_ARENA_STATS, MAX_PLAYER_LOGIN_QUERY }; @@ -2276,6 +2277,10 @@ class TC_GAME_API Player final : public Unit, public GridObject uint32 GetRBGPersonalRating() const { return GetArenaPersonalRating(3); } UF::PVPInfo const* GetPvpInfoForBracket(int8 bracket) const; + //WowCommunity + void ModifyArenaRating(int32 mod, uint8 slot, bool won); + //WowCommunity + Difficulty GetDifficultyID(MapEntry const* mapEntry) const; Difficulty GetDungeonDifficultyID() const { return m_dungeonDifficulty; } Difficulty GetRaidDifficultyID() const { return m_raidDifficulty; } @@ -3342,6 +3347,7 @@ class TC_GAME_API Player final : public Unit, public GridObject void _LoadSoulbindConduitSockets(PreparedQueryResult result); void _LoadRenownRewards(PreparedQueryResult result); void GrantRenownReward(RenownRewardsEntry const* reward); + void _LoadArenaStats(PreparedQueryResult result); /*********************************************************/ /*** SAVE SYSTEM ***/ /*********************************************************/ diff --git a/src/server/game/Handlers/CharacterHandler.cpp b/src/server/game/Handlers/CharacterHandler.cpp index b85d8465c..6a7bdce66 100644 --- a/src/server/game/Handlers/CharacterHandler.cpp +++ b/src/server/game/Handlers/CharacterHandler.cpp @@ -397,6 +397,9 @@ bool LoginQueryHolder::Initialize() stmt->setUInt64(0, lowGuid); res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_RENOWN_REWARDS, stmt); + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_ARENA_STATS); + stmt->setUInt64(0, lowGuid); + res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_ARENA_STATS, stmt); //WowCommunity return res; } diff --git a/src/server/game/Scripting/ScriptHelpers.cpp b/src/server/game/Scripting/ScriptHelpers.cpp index ff438fd1c..7c843b93e 100644 --- a/src/server/game/Scripting/ScriptHelpers.cpp +++ b/src/server/game/Scripting/ScriptHelpers.cpp @@ -731,6 +731,28 @@ namespace ScriptHelpers return count; } + uint32 GetBotTeamRating(BattlegroundMap* battlegroundMap, Team team) + { + if (!battlegroundMap) + return 0; + + uint32 totalHealth = 0; + for (auto const& [guid, creature] : battlegroundMap->GetObjectsStore().Data.Head) + { + if (!creature || !creature->IsBot()) + continue; + + if (GetBotTeam(creature) == team) + totalHealth += creature->GetMaxHealth(); + } + + uint32 rating = totalHealth / 100; + if (rating > 2000) + rating = 2000; + + return rating; + } + std::unordered_map HiredBotCounts; void SetHiredBotCount(uint64 playerGuid, uint8 count) diff --git a/src/server/game/Scripting/ScriptHelpers.h b/src/server/game/Scripting/ScriptHelpers.h index 1fb071889..b5d67e66e 100644 --- a/src/server/game/Scripting/ScriptHelpers.h +++ b/src/server/game/Scripting/ScriptHelpers.h @@ -123,6 +123,7 @@ namespace ScriptHelpers TC_GAME_API void AddBotsToPvPLogData(BattlegroundMap* battlegroundMap, WorldPackets::Battleground::PVPMatchStatistics& pvpLogData); TC_GAME_API uint32 GetAliveBotCountByTeam(BattlegroundMap* battlegroundMap, Team team); + TC_GAME_API uint32 GetBotTeamRating(BattlegroundMap* battlegroundMap, Team team); TC_GAME_API void SetHiredBotCount(uint64 playerGuid, uint8 count); TC_GAME_API uint8 GetHiredBotCount(uint64 playerGuid);