feat(Core/Hooks) Adds hooks for BG and Arena systems. (#23543)

This commit is contained in:
blinkysc
2026-02-15 21:45:03 -06:00
committed by GitHub
parent 27a39dda66
commit 686aafd57b
9 changed files with 178 additions and 23 deletions

View File

@@ -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);

View File

@@ -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);

View File

@@ -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<uint8>(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)

View File

@@ -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<uint16> enabledHooks) :
ScriptObject(name, ALLBATTLEGROUNDHOOK_END)
{

View File

@@ -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 <vector>
@@ -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

View File

@@ -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<uint16> enabledHooks)
: ScriptObject(name, ARENAHOOK_END)
{

View File

@@ -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

View File

@@ -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 */

View File

@@ -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