Compare commits

...

16 Commits

Author SHA1 Message Date
blinkysc
567d36c111
Merge 50965c2c3b1b1d40a73f27c61fc3c11d6dd881e6 into 09e0343491f2d91e18a2aa75f379e8d163386ea4 2025-11-08 03:47:18 -03:00
Andrew
09e0343491
fix(Scripts/Ahnkahet): Clean up Herald Volazj insanity casting and ph… (#23549) 2025-11-07 19:37:42 -03:00
blinkysc
50965c2c3b fix: inverted negation 2025-11-06 21:55:49 +00:00
blinkysc
6fb96fc69f CI happy now? 2025-11-06 21:43:51 +00:00
blinkysc
758b74e805 feat(Scripts): Add hooks to allow modules to override arena rating updates
Add two new ArenaScript hooks to prevent module conflicts:

- OnBeforeArenaTeamMemberUpdate: Allows modules to skip core's
  MemberWon/MemberLost rating calculations by returning true

- CanSaveArenaStatsForMember: Allows modules to prevent core from
  writing to character_arena_stats table by returning false

Hook locations:
- Arena.cpp: Wrap MemberWon/MemberLost calls (lines 358, 366)
- ArenaTeam.cpp: Wrap character_arena_stats DB write (line 969)

Use case: Modules implementing alternative rating systems (like Glicko-2)
can now completely override core arena ratings without database conflicts
or redundant calculations.

When hooks return default values (false/true respectively), core behavior
is unchanged, maintaining backward compatibility.
2025-11-05 14:04:27 -06:00
blinkysc
3ad5e97206 CheckPremadeMatch hook 2025-11-05 09:53:56 -06:00
blinkysc
ca6859a4e3 Revert "forward decordator for gcc14"
This reverts commit 7d51c957f58daa10c007a67c51b54776022e8af8.
2025-11-05 09:14:58 -06:00
blinkysc
7d51c957f5 forward decordator for gcc14 2025-11-05 09:04:51 -06:00
blinkysc
dc2379b89f
Update BattlegroundQueue.cpp 2025-11-05 08:17:45 -06:00
blinkysc
4d445c4798
Update BattlegroundQueue.cpp 2025-11-05 08:17:03 -06:00
blinkysc
cd2fb530e1 feat(Tests): Add module test integration to build system
Implements CMake infrastructure for modules to register tests with the
core test suite using global properties pattern.

Modules can now register test sources and includes by setting:
- ACORE_MODULE_TEST_SOURCES global property
- ACORE_MODULE_TEST_INCLUDES global property

The test system retrieves these properties after unit_tests target is
created and automatically includes them in the build. This solves the
timing issue where module CMake runs before test targets exist.

Benefits:
- Modules can provide their own unit/integration tests
- Tests run as part of standard test suite
- No manual test registration required in core
- Module tests have access to module code via modules library
2025-11-05 07:13:20 -06:00
blinkysc
cf776898fe
Merge branch 'azerothcore:master' into feature/bg-mmr-matchmaking-hooks 2025-11-05 07:11:37 -06:00
blinkysc
459824ade5
Update BattlegroundQueue.cpp 2025-11-05 06:55:45 -06:00
blinkysc
6fd52b5e54 feat(Scripts): Extend matchmaking hooks to cover arena matches
Extended the CanAddGroupToMatchingPool hook to arena matchmaking system,
providing comprehensive coverage for module-based MMR filtering:

- CheckPremadeMatch: Added hooks for rated arena team matching (2v2/3v3/5v5)
- CheckSkirmishForSameFaction: Added hook for unrated arena skirmish matching

These additions complement the existing 6 battleground hook integration points,
bringing total coverage to 9 matchmaking scenarios. Modules implementing
Glicko-2, Elo, or other rating systems can now apply MMR-based filtering
across all PvP content types.
2025-11-04 14:16:05 -06:00
blinkysc
cf20fe23b9
Merge branch 'azerothcore:master' into feature/bg-mmr-matchmaking-hooks 2025-11-04 13:32:04 -06:00
blinkysc
38882cd5a2 feat(Scripts): Add matchmaking hooks for module-based MMR systems
Add two new script hooks to AllBattlegroundScript system enabling
modules to implement custom matchmaking logic:

1. CanAddGroupToMatchingPool - Allows filtering groups before adding
   to battleground selection pool based on custom criteria (e.g., MMR)

2. GetPlayerMatchmakingRating - Allows modules to provide player
   ratings for matchmaking algorithms

Integration points:
- BattlegroundQueue::FillPlayersToBG (6 locations)
  - Initial Alliance/Horde fills
  - Team balance adjustments
- BattlegroundQueue::CheckNormalMatch (2 locations)
  - Normal match creation
  - Team balance

These hooks enable modular MMR systems (Glicko-2, Elo, TrueSkill, etc.)
without modifying core matchmaking code. Hooks are non-intrusive with
no performance impact when unused.

Use cases:
- Battleground MMR-based matchmaking
- Queue time-based MMR relaxation
- Skill-based team balancing
- Custom rating systems
2025-11-04 13:14:24 -06:00
10 changed files with 207 additions and 107 deletions

View File

@ -355,12 +355,18 @@ void Arena::EndBattleground(TeamId winnerTeamId)
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

@ -601,6 +601,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);
@ -658,6 +660,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

@ -77,51 +77,52 @@ enum Misc
DATA_SET_INSANITY_PHASE = 1,
};
enum Events
{
EVENT_HERALD_MIND_FLAY = 1,
EVENT_HERALD_SHADOW,
EVENT_HERALD_SHIVER,
};
const std::array<uint32, MAX_INSANITY_TARGETS> InsanitySpells = { SPELL_INSANITY_PHASING_1, SPELL_INSANITY_PHASING_2, SPELL_INSANITY_PHASING_3, SPELL_INSANITY_PHASING_4, SPELL_INSANITY_PHASING_5 };
struct boss_volazj : public BossAI
{
boss_volazj(Creature* pCreature) : BossAI(pCreature, DATA_HERALD_VOLAZJ),
insanityTimes(0),
insanityPhase(false)
{ }
void InitializeAI() override
{
BossAI::InitializeAI();
// Visible for all players in insanity
me->SetPhaseMask((1 | 16 | 32 | 64 | 128 | 256), true);
}
void Reset() override
{
_Reset();
insanityTimes = 0;
insanityPhase = false;
me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE);
me->SetControlled(false, UNIT_STATE_STUNNED);
ResetPlayersPhaseMask();
instance->DoStopTimedAchievement(ACHIEVEMENT_TIMED_TYPE_EVENT, ACHIEV_QUICK_DEMISE_START_EVENT);
me->SetPhaseMask((1 | 16 | 32 | 64 | 128 | 256), true);
ScheduleHealthCheckEvent({ 66, 33 }, [&]{
scheduler.CancelAll();
DoCastSelf(SPELL_INSANITY);
}, false);
}
void ScheduleTasks() override
{
ScheduleTimedEvent(8s, [&] {
DoCastVictim(SPELL_MIND_FLAY);
}, 20s);
ScheduleTimedEvent(5s, [&] {
DoCastVictim(SPELL_SHADOW_BOLT_VOLLEY);
}, 5s);
ScheduleTimedEvent(15s, [&] {
DoCastRandomTarget(SPELL_SHIVER);
}, 15s);
}
void JustEngagedWith(Unit* /*who*/) override
{
_JustEngagedWith();
events.ScheduleEvent(EVENT_HERALD_MIND_FLAY, 8s);
events.ScheduleEvent(EVENT_HERALD_SHADOW, 5s);
events.ScheduleEvent(EVENT_HERALD_SHIVER, 15s);
Talk(SAY_AGGRO);
DoCastSelf(SPELL_WHISPER_AGGRO);
instance->DoStartTimedAchievement(ACHIEVEMENT_TIMED_TYPE_EVENT, ACHIEV_QUICK_DEMISE_START_EVENT);
me->SetInCombatWithZone();
}
void JustDied(Unit* /*killer*/) override
@ -184,36 +185,13 @@ struct boss_volazj : public BossAI
}
}
void DamageTaken(Unit* /*attacker*/, uint32& damage, DamageEffectType /*damagetype*/, SpellSchoolMask /*damageSchoolMask*/) override
{
// Do not perform insanity recast if boss is casting Insanity already
if (me->FindCurrentSpellBySpellId(SPELL_INSANITY))
{
return;
}
// First insanity
if (insanityTimes == 0 && me->HealthBelowPctDamaged(66, damage))
{
DoCastSelf(SPELL_INSANITY, false);
++insanityTimes;
}
// Second insanity
else if (insanityTimes == 1 && me->HealthBelowPctDamaged(33, damage))
{
me->InterruptNonMeleeSpells(false);
DoCastSelf(SPELL_INSANITY, false);
++insanityTimes;
}
}
void UpdateAI(uint32 diff) override
{
//Return since we have no target
if (!UpdateVictim())
{
return;
}
scheduler.Update(diff);
if (insanityPhase)
{
@ -226,53 +204,13 @@ struct boss_volazj : public BossAI
me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE);
me->SetControlled(false, UNIT_STATE_STUNNED);
me->RemoveAurasDueToSpell(INSANITY_VISUAL);
}
events.Update(diff);
if (me->HasUnitState(UNIT_STATE_CASTING))
{
return;
}
while (uint32 const eventId = events.ExecuteEvent())
{
switch (eventId)
{
case EVENT_HERALD_MIND_FLAY:
{
DoCastVictim(SPELL_MIND_FLAY, false);
events.Repeat(20s);
break;
}
case EVENT_HERALD_SHADOW:
{
DoCastVictim(SPELL_SHADOW_BOLT_VOLLEY, false);
events.Repeat(5s);
break;
}
case EVENT_HERALD_SHIVER:
{
if (Unit* pTarget = SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true))
{
DoCast(pTarget, SPELL_SHIVER, false);
}
events.Repeat(15s);
break;
}
}
if (me->HasUnitState(UNIT_STATE_CASTING))
{
return;
}
ScheduleTasks();
}
DoMeleeAttackIfReady();
}
private:
uint8 insanityTimes;
bool insanityPhase; // Indicates if boss enter to insanity phase
uint32 GetPlrInsanityAuraId(uint32 phaseMask) const
@ -312,6 +250,7 @@ private:
bool CheckPhaseMinions()
{
summons.RemoveNotExisting();
if (summons.empty())
{
ResetPlayersPhaseMask();

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