From 686aafd57be6f1594a99634bade3756643fbf6f3 Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Sun, 15 Feb 2026 21:45:03 -0600 Subject: [PATCH] feat(Core/Hooks) Adds hooks for BG and Arena systems. (#23543) --- src/server/game/Battlegrounds/Arena.cpp | 8 +- src/server/game/Battlegrounds/ArenaTeam.cpp | 15 +-- .../game/Battlegrounds/BattlegroundQueue.cpp | 94 ++++++++++++++++--- .../ScriptDefines/AllBattlegroundScript.cpp | 10 ++ .../ScriptDefines/AllBattlegroundScript.h | 34 +++++++ .../Scripting/ScriptDefines/ArenaScript.cpp | 10 ++ .../Scripting/ScriptDefines/ArenaScript.h | 6 ++ src/server/game/Scripting/ScriptMgr.h | 4 + src/test/CMakeLists.txt | 20 ++++ 9 files changed, 178 insertions(+), 23 deletions(-) diff --git a/src/server/game/Battlegrounds/Arena.cpp b/src/server/game/Battlegrounds/Arena.cpp index 92a97fa5a..cc24d9418 100644 --- a/src/server/game/Battlegrounds/Arena.cpp +++ b/src/server/game/Battlegrounds/Arena.cpp @@ -351,16 +351,16 @@ void Arena::EndBattleground(TeamId winnerTeamId) // 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); + if (!sScriptMgr->OnBeforeArenaTeamMemberUpdate(winnerArenaTeam, player, true, loserMatchmakerRating, winnerMatchmakerChange)) + winnerArenaTeam->MemberWon(player, loserMatchmakerRating, winnerMatchmakerChange); } } else { - loserArenaTeam->MemberLost(player, winnerMatchmakerRating, loserMatchmakerChange); + if (!sScriptMgr->OnBeforeArenaTeamMemberUpdate(loserArenaTeam, player, false, winnerMatchmakerRating, loserMatchmakerChange)) + 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); diff --git a/src/server/game/Battlegrounds/ArenaTeam.cpp b/src/server/game/Battlegrounds/ArenaTeam.cpp index 2b6265e7c..dcd567d67 100644 --- a/src/server/game/Battlegrounds/ArenaTeam.cpp +++ b/src/server/game/Battlegrounds/ArenaTeam.cpp @@ -966,12 +966,15 @@ void ArenaTeam::SaveToDB(bool forceMemberSave) stmt->SetData(6, itr->Guid.GetCounter()); trans->Append(stmt); - stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_CHARACTER_ARENA_STATS); - stmt->SetData(0, itr->Guid.GetCounter()); - stmt->SetData(1, GetSlot()); - stmt->SetData(2, itr->MatchMakerRating); - stmt->SetData(3, itr->MaxMMR); - trans->Append(stmt); + if (sScriptMgr->CanSaveArenaStatsForMember(this, itr->Guid)) + { + stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_CHARACTER_ARENA_STATS); + stmt->SetData(0, itr->Guid.GetCounter()); + stmt->SetData(1, GetSlot()); + stmt->SetData(2, itr->MatchMakerRating); + stmt->SetData(3, itr->MaxMMR); + trans->Append(stmt); + } } CharacterDatabase.CommitTransaction(trans); diff --git a/src/server/game/Battlegrounds/BattlegroundQueue.cpp b/src/server/game/Battlegrounds/BattlegroundQueue.cpp index 60da08d67..97a529327 100644 --- a/src/server/game/Battlegrounds/BattlegroundQueue.cpp +++ b/src/server/game/Battlegrounds/BattlegroundQueue.cpp @@ -434,15 +434,37 @@ void BattlegroundQueue::FillPlayersToBG(Battleground* bg, BattlegroundBracketId //index to queue which group is current uint32 aliIndex = 0; - for (; aliIndex < aliCount && m_SelectionPools[TEAM_ALLIANCE].AddGroup((*Ali_itr), aliFree); aliIndex++) + for (; aliIndex < aliCount; aliIndex++) + { + if (!sScriptMgr->CanAddGroupToMatchingPool(this, (*Ali_itr), m_SelectionPools[TEAM_ALLIANCE].GetPlayerCount(), bg, bracket_id)) + { + ++Ali_itr; + continue; + } + + if (!m_SelectionPools[TEAM_ALLIANCE].AddGroup((*Ali_itr), aliFree)) + break; + ++Ali_itr; + } //the same thing for horde GroupsQueueType::const_iterator Horde_itr = m_QueuedGroups[bracket_id][BG_QUEUE_NORMAL_HORDE].begin(); uint32 hordeIndex = 0; - for (; hordeIndex < hordeCount && m_SelectionPools[TEAM_HORDE].AddGroup((*Horde_itr), hordeFree); hordeIndex++) + for (; hordeIndex < hordeCount; hordeIndex++) + { + if (!sScriptMgr->CanAddGroupToMatchingPool(this, (*Horde_itr), m_SelectionPools[TEAM_HORDE].GetPlayerCount(), bg, bracket_id)) + { + ++Horde_itr; + continue; + } + + if (!m_SelectionPools[TEAM_HORDE].AddGroup((*Horde_itr), hordeFree)) + break; + ++Horde_itr; + } //if ofc like BG queue invitation is set in config, then we are happy if (sWorld->getIntConfig(CONFIG_BATTLEGROUND_INVITATION_TYPE) == BG_QUEUE_INVITATION_TYPE_NO_BALANCE) @@ -468,8 +490,19 @@ void BattlegroundQueue::FillPlayersToBG(Battleground* bg, BattlegroundBracketId //kick alliance group, add to pool new group if needed if (m_SelectionPools[TEAM_ALLIANCE].KickGroup(diffHorde - diffAli)) { - for (; aliIndex < aliCount && m_SelectionPools[TEAM_ALLIANCE].AddGroup((*Ali_itr), (aliFree >= diffHorde) ? aliFree - diffHorde : 0); aliIndex++) + for (; aliIndex < aliCount; aliIndex++) + { + if (!sScriptMgr->CanAddGroupToMatchingPool(this, (*Ali_itr), m_SelectionPools[TEAM_ALLIANCE].GetPlayerCount(), bg, bracket_id)) + { + ++Ali_itr; + continue; + } + + if (!m_SelectionPools[TEAM_ALLIANCE].AddGroup((*Ali_itr), (aliFree >= diffHorde) ? aliFree - diffHorde : 0)) + break; + ++Ali_itr; + } } //if ali selection is already empty, then kick horde group, but if there are less horde than ali in bg - break; @@ -486,8 +519,19 @@ void BattlegroundQueue::FillPlayersToBG(Battleground* bg, BattlegroundBracketId //kick horde group, add to pool new group if needed if (m_SelectionPools[TEAM_HORDE].KickGroup(diffAli - diffHorde)) { - for (; hordeIndex < hordeCount && m_SelectionPools[TEAM_HORDE].AddGroup((*Horde_itr), (hordeFree >= diffAli) ? hordeFree - diffAli : 0); hordeIndex++) + for (; hordeIndex < hordeCount; hordeIndex++) + { + if (!sScriptMgr->CanAddGroupToMatchingPool(this, (*Horde_itr), m_SelectionPools[TEAM_HORDE].GetPlayerCount(), bg, bracket_id)) + { + ++Horde_itr; + continue; + } + + if (!m_SelectionPools[TEAM_HORDE].AddGroup((*Horde_itr), (hordeFree >= diffAli) ? hordeFree - diffAli : 0)) + break; + ++Horde_itr; + } } if (!m_SelectionPools[TEAM_HORDE].GetPlayerCount()) @@ -525,6 +569,12 @@ bool BattlegroundQueue::CheckPremadeMatch(BattlegroundBracketId bracket_id, uint // if found both groups if (ali_group != m_QueuedGroups[bracket_id][BG_QUEUE_PREMADE_ALLIANCE].end() && horde_group != m_QueuedGroups[bracket_id][BG_QUEUE_PREMADE_HORDE].end()) { + if (!sScriptMgr->CanAddGroupToMatchingPool(this, (*ali_group), 0, nullptr, bracket_id)) + return false; + + if (!sScriptMgr->CanAddGroupToMatchingPool(this, (*horde_group), m_SelectionPools[TEAM_ALLIANCE].GetPlayerCount(), nullptr, bracket_id)) + return false; + m_SelectionPools[TEAM_ALLIANCE].AddGroup((*ali_group), MaxPlayersPerTeam); m_SelectionPools[TEAM_HORDE].AddGroup((*horde_group), MaxPlayersPerTeam); @@ -536,9 +586,14 @@ bool BattlegroundQueue::CheckPremadeMatch(BattlegroundBracketId bracket_id, uint { for (itr = m_QueuedGroups[bracket_id][BG_QUEUE_NORMAL_ALLIANCE + i].begin(); itr != m_QueuedGroups[bracket_id][BG_QUEUE_NORMAL_ALLIANCE + i].end(); ++itr) { - //if itr can join BG and player count is less that maxPlayers, then add group to selectionpool - if (!(*itr)->IsInvitedToBGInstanceGUID && !m_SelectionPools[i].AddGroup((*itr), maxPlayers)) - break; + if (!(*itr)->IsInvitedToBGInstanceGUID) + { + if (!sScriptMgr->CanAddGroupToMatchingPool(this, (*itr), m_SelectionPools[i].GetPlayerCount(), nullptr, bracket_id)) + continue; + + if (!m_SelectionPools[i].AddGroup((*itr), maxPlayers)) + break; + } } } @@ -596,9 +651,12 @@ bool BattlegroundQueue::CheckNormalMatch(Battleground* bgTemplate, BattlegroundB { if (!(*(itr_team[i]))->IsInvitedToBGInstanceGUID) { - m_SelectionPools[i].AddGroup(*(itr_team[i]), maxPlayers); - if (m_SelectionPools[i].GetPlayerCount() >= minPlayers) - break; + if (sScriptMgr->CanAddGroupToMatchingPool(this, *(itr_team[i]), m_SelectionPools[i].GetPlayerCount(), bgTemplate, bracket_id)) + { + m_SelectionPools[i].AddGroup(*(itr_team[i]), maxPlayers); + if (m_SelectionPools[i].GetPlayerCount() >= minPlayers) + break; + } } } } @@ -616,8 +674,13 @@ bool BattlegroundQueue::CheckNormalMatch(Battleground* bgTemplate, BattlegroundB for (; itr_team[j] != m_QueuedGroups[bracket_id][BG_QUEUE_NORMAL_ALLIANCE + j].end(); ++(itr_team[j])) { if (!(*(itr_team[j]))->IsInvitedToBGInstanceGUID) + { + if (!sScriptMgr->CanAddGroupToMatchingPool(this, *(itr_team[j]), m_SelectionPools[j].GetPlayerCount(), bgTemplate, bracket_id)) + continue; + if (!m_SelectionPools[j].AddGroup(*(itr_team[j]), m_SelectionPools[(j + 1) % PVP_TEAMS_COUNT].GetPlayerCount())) break; + } } // do not allow to start bg with more than 2 players more on 1 faction @@ -664,9 +727,14 @@ bool BattlegroundQueue::CheckSkirmishForSameFaction(BattlegroundBracketId bracke //invite players to other selection pool for (; itr_team2 != m_QueuedGroups[bracket_id][BG_QUEUE_NORMAL_ALLIANCE + static_cast(teamIndex)].end(); ++itr_team2) { - //if selection pool is full then break; - if (!(*itr_team2)->IsInvitedToBGInstanceGUID && !m_SelectionPools[otherTeam].AddGroup(*itr_team2, minPlayersPerTeam)) - break; + if (!(*itr_team2)->IsInvitedToBGInstanceGUID) + { + if (!sScriptMgr->CanAddGroupToMatchingPool(this, *itr_team2, m_SelectionPools[otherTeam].GetPlayerCount(), nullptr, bracket_id)) + continue; + + if (!m_SelectionPools[otherTeam].AddGroup(*itr_team2, minPlayersPerTeam)) + break; + } } if (m_SelectionPools[otherTeam].GetPlayerCount() != minPlayersPerTeam) diff --git a/src/server/game/Scripting/ScriptDefines/AllBattlegroundScript.cpp b/src/server/game/Scripting/ScriptDefines/AllBattlegroundScript.cpp index 0c9c3c8f4..9881a25f0 100644 --- a/src/server/game/Scripting/ScriptDefines/AllBattlegroundScript.cpp +++ b/src/server/game/Scripting/ScriptDefines/AllBattlegroundScript.cpp @@ -104,6 +104,16 @@ void ScriptMgr::OnBattlegroundCreate(Battleground* bg) CALL_ENABLED_HOOKS(AllBattlegroundScript, ALLBATTLEGROUNDHOOK_ON_BATTLEGROUND_CREATE, script->OnBattlegroundCreate(bg)); } +bool ScriptMgr::CanAddGroupToMatchingPool(BattlegroundQueue* queue, GroupQueueInfo* group, uint32 poolPlayerCount, Battleground* bg, BattlegroundBracketId bracketId) +{ + CALL_ENABLED_BOOLEAN_HOOKS(AllBattlegroundScript, ALLBATTLEGROUNDHOOK_CAN_ADD_GROUP_TO_MATCHING_POOL, !script->CanAddGroupToMatchingPool(queue, group, poolPlayerCount, bg, bracketId)); +} + +bool ScriptMgr::GetPlayerMatchmakingRating(ObjectGuid playerGuid, BattlegroundTypeId bgTypeId, float& outRating) +{ + CALL_ENABLED_BOOLEAN_HOOKS_WITH_DEFAULT_FALSE(AllBattlegroundScript, ALLBATTLEGROUNDHOOK_GET_PLAYER_MATCHMAKING_RATING, script->GetPlayerMatchmakingRating(playerGuid, bgTypeId, outRating)); +} + AllBattlegroundScript::AllBattlegroundScript(char const* name, std::vector enabledHooks) : ScriptObject(name, ALLBATTLEGROUNDHOOK_END) { diff --git a/src/server/game/Scripting/ScriptDefines/AllBattlegroundScript.h b/src/server/game/Scripting/ScriptDefines/AllBattlegroundScript.h index 9ab6eea78..108f3cf7f 100644 --- a/src/server/game/Scripting/ScriptDefines/AllBattlegroundScript.h +++ b/src/server/game/Scripting/ScriptDefines/AllBattlegroundScript.h @@ -18,6 +18,7 @@ #ifndef SCRIPT_OBJECT_ALL_BATTLEGROUND_SCRIPT_H_ #define SCRIPT_OBJECT_ALL_BATTLEGROUND_SCRIPT_H_ +#include "ObjectGuid.h" #include "ScriptObject.h" #include @@ -40,6 +41,8 @@ enum AllBattlegroundHook ALLBATTLEGROUNDHOOK_ON_BATTLEGROUND_END, ALLBATTLEGROUNDHOOK_ON_BATTLEGROUND_DESTROY, ALLBATTLEGROUNDHOOK_ON_BATTLEGROUND_CREATE, + ALLBATTLEGROUNDHOOK_CAN_ADD_GROUP_TO_MATCHING_POOL, + ALLBATTLEGROUNDHOOK_GET_PLAYER_MATCHMAKING_RATING, ALLBATTLEGROUNDHOOK_END }; @@ -47,6 +50,9 @@ enum BattlegroundBracketId : uint8; enum BattlegroundTypeId : uint8; enum TeamId : uint8; +class BattlegroundQueue; +struct GroupQueueInfo; + class AllBattlegroundScript : public ScriptObject { protected: @@ -132,6 +138,34 @@ public: * @param bg Contains information about the Battleground */ virtual void OnBattlegroundCreate(Battleground* /*bg*/) { } + + /** + * @brief This hook runs before adding a group to the battleground matching pool + * + * Allows modules to filter groups based on custom criteria (e.g., MMR matching). + * Called during FillPlayersToBG before each group is added to the SelectionPool. + * + * @param queue The battleground queue + * @param group The group being considered for addition + * @param poolPlayerCount Current number of players already in the selection pool + * @param bg The battleground instance + * @param bracketId The bracket ID + * @return True to allow adding this group, false to skip it + */ + [[nodiscard]] virtual bool CanAddGroupToMatchingPool(BattlegroundQueue* /*queue*/, GroupQueueInfo* /*group*/, uint32 /*poolPlayerCount*/, Battleground* /*bg*/, BattlegroundBracketId /*bracketId*/) { return true; } + + /** + * @brief This hook allows modules to provide matchmaking rating for a player + * + * Modules implementing MMR systems can use this hook to provide player ratings + * for use in matchmaking algorithms. + * + * @param playerGuid The player's GUID + * @param bgTypeId The battleground type + * @param outRating Reference to store the rating value + * @return True if rating was provided, false otherwise + */ + [[nodiscard]] virtual bool GetPlayerMatchmakingRating(ObjectGuid /*playerGuid*/, BattlegroundTypeId /*bgTypeId*/, float& /*outRating*/) { return false; } }; // Compatibility for old scripts diff --git a/src/server/game/Scripting/ScriptDefines/ArenaScript.cpp b/src/server/game/Scripting/ScriptDefines/ArenaScript.cpp index af3baf332..5521e7cbc 100644 --- a/src/server/game/Scripting/ScriptDefines/ArenaScript.cpp +++ b/src/server/game/Scripting/ScriptDefines/ArenaScript.cpp @@ -44,6 +44,16 @@ void ScriptMgr::OnArenaStart(Battleground* bg) CALL_ENABLED_HOOKS(ArenaScript, ARENAHOOK_ON_ARENA_START, script->OnArenaStart(bg)); } +bool ScriptMgr::OnBeforeArenaTeamMemberUpdate(ArenaTeam* team, Player* player, bool won, uint32 opponentMatchmakerRating, int32 matchmakerChange) +{ + CALL_ENABLED_BOOLEAN_HOOKS(ArenaScript, ARENAHOOK_ON_BEFORE_TEAM_MEMBER_UPDATE, !script->OnBeforeArenaTeamMemberUpdate(team, player, won, opponentMatchmakerRating, matchmakerChange)); +} + +bool ScriptMgr::CanSaveArenaStatsForMember(ArenaTeam* team, ObjectGuid playerGuid) +{ + CALL_ENABLED_BOOLEAN_HOOKS(ArenaScript, ARENAHOOK_CAN_SAVE_ARENA_STATS_FOR_MEMBER, !script->CanSaveArenaStatsForMember(team, playerGuid)); +} + ArenaScript::ArenaScript(const char* name, std::vector enabledHooks) : ScriptObject(name, ARENAHOOK_END) { diff --git a/src/server/game/Scripting/ScriptDefines/ArenaScript.h b/src/server/game/Scripting/ScriptDefines/ArenaScript.h index 152dd7584..0529d23ac 100644 --- a/src/server/game/Scripting/ScriptDefines/ArenaScript.h +++ b/src/server/game/Scripting/ScriptDefines/ArenaScript.h @@ -29,6 +29,8 @@ enum ArenaHook ARENAHOOK_CAN_SAVE_TO_DB, ARENAHOOK_ON_BEFORE_CHECK_WIN_CONDITION, ARENAHOOK_ON_ARENA_START, + ARENAHOOK_ON_BEFORE_TEAM_MEMBER_UPDATE, + ARENAHOOK_CAN_SAVE_ARENA_STATS_FOR_MEMBER, ARENAHOOK_END }; @@ -51,6 +53,10 @@ public: [[nodiscard]] virtual bool CanSaveToDB(ArenaTeam* /*team*/) { return true; } virtual void OnArenaStart(Battleground* /* bg */) { }; + + [[nodiscard]] virtual bool OnBeforeArenaTeamMemberUpdate(ArenaTeam* /*team*/, Player* /*player*/, bool /*won*/, uint32 /*opponentMatchmakerRating*/, int32 /*matchmakerChange*/) { return false; } + + [[nodiscard]] virtual bool CanSaveArenaStatsForMember(ArenaTeam* /*team*/, ObjectGuid /*playerGuid*/) { return true; } }; #endif diff --git a/src/server/game/Scripting/ScriptMgr.h b/src/server/game/Scripting/ScriptMgr.h index 222dac69e..3687e455a 100644 --- a/src/server/game/Scripting/ScriptMgr.h +++ b/src/server/game/Scripting/ScriptMgr.h @@ -596,6 +596,8 @@ public: /* BGScript */ void OnBattlegroundEnd(Battleground* bg, TeamId winnerTeamId); void OnBattlegroundDestroy(Battleground* bg); void OnBattlegroundCreate(Battleground* bg); + bool CanAddGroupToMatchingPool(BattlegroundQueue* queue, GroupQueueInfo* group, uint32 poolPlayerCount, Battleground* bg, BattlegroundBracketId bracketId); + bool GetPlayerMatchmakingRating(ObjectGuid playerGuid, BattlegroundTypeId bgTypeId, float& outRating); public: /* Arena Team Script */ void OnGetSlotByType(const uint32 type, uint8& slot); @@ -651,6 +653,8 @@ public: /* ArenaScript */ bool CanSaveToDB(ArenaTeam* team); bool OnBeforeArenaCheckWinConditions(Battleground* const bg); void OnArenaStart(Battleground* const bg); + bool OnBeforeArenaTeamMemberUpdate(ArenaTeam* team, Player* player, bool won, uint32 opponentMatchmakerRating, int32 matchmakerChange); + bool CanSaveArenaStatsForMember(ArenaTeam* team, ObjectGuid playerGuid); public: /* MiscScript */ diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 62ae23578..bf5386d5b 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -31,6 +31,26 @@ target_link_libraries( game-interface ) +# Add module test sources if any modules registered tests +get_property(MODULE_TEST_SOURCES GLOBAL PROPERTY ACORE_MODULE_TEST_SOURCES) +get_property(MODULE_TEST_INCLUDES GLOBAL PROPERTY ACORE_MODULE_TEST_INCLUDES) + +if(MODULE_TEST_SOURCES) + target_sources(unit_tests PRIVATE ${MODULE_TEST_SOURCES}) + message(STATUS "Added module tests to unit_tests target") +endif() + +if(MODULE_TEST_INCLUDES) + list(REMOVE_DUPLICATES MODULE_TEST_INCLUDES) + target_include_directories(unit_tests PRIVATE ${MODULE_TEST_INCLUDES}) + message(STATUS "Added module test includes to unit_tests target") +endif() + +# Link modules library to tests so module code is available +if(TARGET modules) + target_link_libraries(unit_tests modules) +endif() + add_test( NAME unit