fix(Scripts/Ulduar): restore the Clash of Thunder mock fight before Thorim (#27057)

This commit is contained in:
Andrew
2026-08-09 11:19:02 -03:00
committed by GitHub
parent 7eea30d471
commit f027e8b4a5
2 changed files with 137 additions and 13 deletions
@@ -0,0 +1,19 @@
-- Thorim: Clash of Thunder factions (sniffed). The captured mercenaries (1692) and the
-- Jormungar Behemoth (1693) are mutually hostile, driving the pre-encounter mock fight;
-- the arena Dark Rune Acolyte (2119) is friendly to both sides so it can heal across it.
UPDATE `creature_template` SET `faction` = 1693, `VerifiedBuild` = 68887 WHERE `entry` IN (32882, 33154);
UPDATE `creature_template` SET `faction` = 1692, `VerifiedBuild` = 68887 WHERE `entry` IN (32883, 32885, 32907, 32908, 33150, 33151, 33152, 33153);
UPDATE `creature_template` SET `faction` = 2119, `VerifiedBuild` = 68887 WHERE `entry` IN (32886, 33159);
-- Restrict the acolyte's mock fight Circle of Healing (61964) to the pack. It is a
-- TARGET_UNIT_SRC_AREA_ENTRY spell (100 yd, no target cap) and heals anything in the arena
-- without these conditions; the sniff shows it hitting exactly the six pack members.
-- Greater Heal (61965) and Renew (61967) are single-target and script-targeted instead.
DELETE FROM `conditions` WHERE `SourceTypeOrReferenceId` = 13 AND `SourceEntry` IN (61964, 61965, 61967);
INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES
(13, 1, 61964, 0, 0, 31, 0, 3, 32882, 0, 0, 0, 0, '', 'Thorim arena - Circle of Healing targets Jormungar Behemoth'),
(13, 1, 61964, 0, 1, 31, 0, 3, 32883, 0, 0, 0, 0, '', 'Thorim arena - Circle of Healing targets Captured Mercenary Soldier'),
(13, 1, 61964, 0, 2, 31, 0, 3, 32885, 0, 0, 0, 0, '', 'Thorim arena - Circle of Healing targets Captured Mercenary Soldier'),
(13, 1, 61964, 0, 3, 31, 0, 3, 32886, 0, 0, 0, 0, '', 'Thorim arena - Circle of Healing targets Dark Rune Acolyte'),
(13, 1, 61964, 0, 4, 31, 0, 3, 32907, 0, 0, 0, 0, '', 'Thorim arena - Circle of Healing targets Captured Mercenary Captain'),
(13, 1, 61964, 0, 5, 31, 0, 3, 32908, 0, 0, 0, 0, '', 'Thorim arena - Circle of Healing targets Captured Mercenary Captain');
@@ -18,6 +18,7 @@
#include "AchievementCriteriaScript.h" #include "AchievementCriteriaScript.h"
#include "CreatureScript.h" #include "CreatureScript.h"
#include "GameObjectScript.h" #include "GameObjectScript.h"
#include "ObjectAccessor.h"
#include "PassiveAI.h" #include "PassiveAI.h"
#include "Player.h" #include "Player.h"
#include "ScriptedCreature.h" #include "ScriptedCreature.h"
@@ -61,6 +62,10 @@ enum ThorimSpells
SPELL_GREATER_HEAL = 62334, SPELL_GREATER_HEAL = 62334,
SPELL_HOLY_SMITE = 62335, SPELL_HOLY_SMITE = 62335,
SPELL_RENEW = 62333, SPELL_RENEW = 62333,
// NPC variants used while healing the arena mock fight
SPELL_GREATER_HEAL_RP = 61965,
SPELL_RENEW_RP = 61967,
SPELL_CIRCLE_OF_HEALING_RP = 61964,
// CAPTURED MERCENARY SOLDIER // CAPTURED MERCENARY SOLDIER
SPELL_BARBED_SHOT = 62318, SPELL_BARBED_SHOT = 62318,
@@ -70,6 +75,8 @@ enum ThorimSpells
// CAPTURED MERCENARY CAPTAIN // CAPTURED MERCENARY CAPTAIN
SPELL_DEVASTATE = 62317, SPELL_DEVASTATE = 62317,
SPELL_HEROIC_STRIKE = 62444, SPELL_HEROIC_STRIKE = 62444,
SPELL_SUNDER_ARMOR = 57807,
SPELL_THREAT = 34915, // serverside, keeps the behemoth glued to the captain
// JORMUNGAR BEHEMOTH // JORMUNGAR BEHEMOTH
SPELL_ACID_BREATH = 62315, SPELL_ACID_BREATH = 62315,
@@ -178,6 +185,9 @@ enum ThorimEvents
EVENT_DR_ACOLYTE_GH = 20, EVENT_DR_ACOLYTE_GH = 20,
EVENT_DR_ACOLYTE_HS = 21, EVENT_DR_ACOLYTE_HS = 21,
EVENT_DR_ACOLYTE_R = 22, EVENT_DR_ACOLYTE_R = 22,
EVENT_DR_ACOLYTE_RP_GH = 23,
EVENT_DR_ACOLYTE_RP_RENEW = 24,
EVENT_DR_ACOLYTE_RP_COH = 25,
EVENT_CM_SOLDIER_BS = 30, EVENT_CM_SOLDIER_BS = 30,
EVENT_CM_SOLDIER_S = 31, EVENT_CM_SOLDIER_S = 31,
@@ -185,6 +195,8 @@ enum ThorimEvents
EVENT_CM_CAPTAIN_D = 40, EVENT_CM_CAPTAIN_D = 40,
EVENT_CM_CAPTAIN_HC = 41, EVENT_CM_CAPTAIN_HC = 41,
EVENT_CM_CAPTAIN_SA = 42,
EVENT_CM_CAPTAIN_THREAT = 43,
EVENT_JB_ACID_BREATH = 50, EVENT_JB_ACID_BREATH = 50,
EVENT_JB_SWEEP = 51, EVENT_JB_SWEEP = 51,
@@ -288,6 +300,7 @@ enum Misc
ACTION_SIF_START_DOMINION = 5, ACTION_SIF_START_DOMINION = 5,
ACTION_SIF_TRANSFORM = 6, ACTION_SIF_TRANSFORM = 6,
ACTION_IRON_HONOR_DIED = 7, ACTION_IRON_HONOR_DIED = 7,
ACTION_ENGAGE_PLAYERS = 8,
EVENT_PHASE_START = 1, EVENT_PHASE_START = 1,
EVENT_PHASE_RING = 2, EVENT_PHASE_RING = 2,
@@ -343,7 +356,7 @@ struct boss_thorim : public BossAI
void SpawnAllNPCs() void SpawnAllNPCs()
{ {
// Jormungar Behemoth 32882 // Jormungar Behemoth 32882
me->SummonCreature(NPC_JORMUNGAR_BEHEMOT, 2149.68f, -263.477f, 419.679f, 3.12102f, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 5000); me->SummonCreature(NPC_JORMUNGAR_BEHEMOT, 2146.611f, -266.653f, 419.8175f, 2.70526f, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 5000);
// Captured Mercenary Soldier 32885 // Captured Mercenary Soldier 32885
me->SummonCreature(_isAlly ? NPC_CAPTURED_MERCENARY_SOLDIER_ALLY : NPC_CAPTURED_MERCENARY_SOLDIER_HORDE, 2127.24f, -251.309f, 419.793f, 5.89921f, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 5000); me->SummonCreature(_isAlly ? NPC_CAPTURED_MERCENARY_SOLDIER_ALLY : NPC_CAPTURED_MERCENARY_SOLDIER_HORDE, 2127.24f, -251.309f, 419.793f, 5.89921f, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 5000);
@@ -1022,9 +1035,39 @@ struct boss_thorim_start_npcs : public ScriptedAI
events.Reset(); events.Reset();
_isCaster = (me->GetEntry() == NPC_DARK_RUNE_ACOLYTE_I); _isCaster = (me->GetEntry() == NPC_DARK_RUNE_ACOLYTE_I);
_playerAttack = false; _playerAttack = false;
if (me->GetEntry() != NPC_JORMUNGAR_BEHEMOT) StartMockBattle();
if (Creature* cr = me->FindNearestCreature(NPC_JORMUNGAR_BEHEMOT, 30.0f)) }
AttackStart(cr);
void JustReachedHome() override
{
// Reset() runs at evade start, when UNIT_STATE_EVADE still blocks Unit::Attack
StartMockBattle();
}
void StartMockBattle()
{
events.Reset();
// The acolyte is friendly to both sides and heals the fight from its spawn point
if (me->GetEntry() == NPC_DARK_RUNE_ACOLYTE_I)
{
me->SetReactState(REACT_PASSIVE);
events.ScheduleEvent(EVENT_DR_ACOLYTE_RP_GH, 5s);
events.ScheduleEvent(EVENT_DR_ACOLYTE_RP_RENEW, 8s);
events.ScheduleEvent(EVENT_DR_ACOLYTE_RP_COH, 20s);
return;
}
if (me->GetEntry() == NPC_JORMUNGAR_BEHEMOT)
return;
if (Creature* behemoth = me->FindNearestCreature(NPC_JORMUNGAR_BEHEMOT, 60.0f))
{
if (me->GetEntry() == NPC_CAPTURED_MERCENARY_SOLDIER_ALLY || me->GetEntry() == NPC_CAPTURED_MERCENARY_SOLDIER_HORDE)
AttackStartNoMove(behemoth); // soldiers shoot from their spawn point
else
AttackStart(behemoth);
}
} }
void DamageTaken(Unit* who, uint32&, DamageEffectType, SpellSchoolMask) override void DamageTaken(Unit* who, uint32&, DamageEffectType, SpellSchoolMask) override
@@ -1040,16 +1083,31 @@ struct boss_thorim_start_npcs : public ScriptedAI
thorim->AI()->AttackStart(who); thorim->AI()->AttackStart(who);
} }
} }
_playerAttack = true;
me->GetThreatMgr().ResetAllThreat(); // The whole pack turns on the raid at once; CallForHelp cannot cross the mock fight's faction split
me->CallForHelp(40.0f); std::list<Creature*> pack;
AttackStart(who); me->GetCreatureListWithEntryInGrid(pack, { NPC_JORMUNGAR_BEHEMOT, NPC_CAPTURED_MERCENARY_SOLDIER_ALLY, NPC_CAPTURED_MERCENARY_SOLDIER_HORDE,
NPC_CAPTURED_MERCENARY_CAPTAIN_ALLY, NPC_CAPTURED_MERCENARY_CAPTAIN_HORDE, NPC_DARK_RUNE_ACOLYTE_I }, 100.0f);
for (Creature* member : pack)
member->AI()->SetGUID(who->GetGUID(), ACTION_ENGAGE_PLAYERS);
} }
if (!_playerAttack && me->HealthBelowPct(60)) if (!_playerAttack && me->HealthBelowPct(60))
me->SetHealth(me->GetMaxHealth()); me->SetHealth(me->GetMaxHealth());
} }
void SetGUID(ObjectGuid const& guid, int32 id) override
{
if (id != ACTION_ENGAGE_PLAYERS || _playerAttack)
return;
_playerAttack = true;
me->SetReactState(REACT_AGGRESSIVE);
me->GetThreatMgr().ClearAllThreat();
if (Unit* attacker = ObjectAccessor::GetUnit(*me, guid))
AttackStart(attacker);
}
void JustDied(Unit*) override void JustDied(Unit*) override
{ {
if (me->GetInstanceScript()) if (me->GetInstanceScript())
@@ -1061,6 +1119,8 @@ struct boss_thorim_start_npcs : public ScriptedAI
{ {
if (me->GetEntry() == NPC_DARK_RUNE_ACOLYTE_I) if (me->GetEntry() == NPC_DARK_RUNE_ACOLYTE_I)
{ {
// Entering real combat ends the mock fight heal rotation
events.Reset();
events.ScheduleEvent(EVENT_DR_ACOLYTE_GH, 10s); events.ScheduleEvent(EVENT_DR_ACOLYTE_GH, 10s);
events.ScheduleEvent(EVENT_DR_ACOLYTE_HS, 5s); events.ScheduleEvent(EVENT_DR_ACOLYTE_HS, 5s);
events.ScheduleEvent(EVENT_DR_ACOLYTE_R, 7s); events.ScheduleEvent(EVENT_DR_ACOLYTE_R, 7s);
@@ -1075,6 +1135,8 @@ struct boss_thorim_start_npcs : public ScriptedAI
{ {
events.ScheduleEvent(EVENT_CM_CAPTAIN_D, 9s); events.ScheduleEvent(EVENT_CM_CAPTAIN_D, 9s);
events.ScheduleEvent(EVENT_CM_CAPTAIN_HC, 5s); events.ScheduleEvent(EVENT_CM_CAPTAIN_HC, 5s);
events.ScheduleEvent(EVENT_CM_CAPTAIN_SA, 8s);
events.ScheduleEvent(EVENT_CM_CAPTAIN_THREAT, 1200ms);
} }
else if (me->GetEntry() == NPC_JORMUNGAR_BEHEMOT) else if (me->GetEntry() == NPC_JORMUNGAR_BEHEMOT)
{ {
@@ -1088,7 +1150,39 @@ struct boss_thorim_start_npcs : public ScriptedAI
void UpdateAI(uint32 diff) override void UpdateAI(uint32 diff) override
{ {
if (!UpdateVictim()) if (!UpdateVictim())
{
// The acolyte tends both sides of the mock fight from outside of combat
if (_isCaster && !_playerAttack)
{
events.Update(diff);
if (me->HasUnitState(UNIT_STATE_CASTING))
return;
switch (events.ExecuteEvent())
{
case EVENT_DR_ACOLYTE_RP_GH:
if (Creature* behemoth = me->FindNearestCreature(NPC_JORMUNGAR_BEHEMOT, 60.0f))
me->CastSpell(behemoth, SPELL_GREATER_HEAL_RP, false);
events.Repeat(23s);
break;
case EVENT_DR_ACOLYTE_RP_RENEW:
{
Creature* captain = me->FindNearestCreature(NPC_CAPTURED_MERCENARY_CAPTAIN_ALLY, 60.0f);
if (!captain)
captain = me->FindNearestCreature(NPC_CAPTURED_MERCENARY_CAPTAIN_HORDE, 60.0f);
if (captain)
me->CastSpell(captain, SPELL_RENEW_RP, false);
events.Repeat(23s);
break;
}
case EVENT_DR_ACOLYTE_RP_COH:
me->CastSpell(me, SPELL_CIRCLE_OF_HEALING_RP, false);
events.Repeat(23s);
break;
}
}
return; return;
}
events.Update(diff); events.Update(diff);
if (me->HasUnitState(UNIT_STATE_CASTING)) if (me->HasUnitState(UNIT_STATE_CASTING))
@@ -1117,7 +1211,7 @@ struct boss_thorim_start_npcs : public ScriptedAI
break; break;
case EVENT_CM_SOLDIER_BS: case EVENT_CM_SOLDIER_BS:
me->CastSpell(me->GetVictim(), SPELL_BARBED_SHOT, false); me->CastSpell(me->GetVictim(), SPELL_BARBED_SHOT, false);
events.Repeat(9s); events.Repeat(12s);
break; break;
case EVENT_CM_SOLDIER_WC: case EVENT_CM_SOLDIER_WC:
me->CastSpell(me->GetVictim(), SPELL_WING_CLIP, false); me->CastSpell(me->GetVictim(), SPELL_WING_CLIP, false);
@@ -1127,7 +1221,7 @@ struct boss_thorim_start_npcs : public ScriptedAI
if (me->GetDistance(me->GetVictim()) > 8) if (me->GetDistance(me->GetVictim()) > 8)
me->CastSpell(me->GetVictim(), SPELL_SHOOT, false); me->CastSpell(me->GetVictim(), SPELL_SHOOT, false);
events.Repeat(1500ms); events.Repeat(3700ms);
break; break;
case EVENT_CM_CAPTAIN_D: case EVENT_CM_CAPTAIN_D:
me->CastSpell(me->GetVictim(), SPELL_DEVASTATE, false); me->CastSpell(me->GetVictim(), SPELL_DEVASTATE, false);
@@ -1135,7 +1229,18 @@ struct boss_thorim_start_npcs : public ScriptedAI
break; break;
case EVENT_CM_CAPTAIN_HC: case EVENT_CM_CAPTAIN_HC:
me->CastSpell(me->GetVictim(), SPELL_HEROIC_STRIKE, false); me->CastSpell(me->GetVictim(), SPELL_HEROIC_STRIKE, false);
events.Repeat(5s); events.Repeat(12s);
break;
case EVENT_CM_CAPTAIN_SA:
me->CastSpell(me->GetVictim(), SPELL_SUNDER_ARMOR, false);
events.Repeat(12s);
break;
case EVENT_CM_CAPTAIN_THREAT:
if (!_playerAttack)
{
me->CastSpell(me->GetVictim(), SPELL_THREAT, true);
events.Repeat(1200ms);
}
break; break;
case EVENT_JB_ACID_BREATH: case EVENT_JB_ACID_BREATH:
me->CastSpell(me->GetVictim(), SPELL_ACID_BREATH, false); me->CastSpell(me->GetVictim(), SPELL_ACID_BREATH, false);
@@ -1143,7 +1248,7 @@ struct boss_thorim_start_npcs : public ScriptedAI
break; break;
case EVENT_JB_SWEEP: case EVENT_JB_SWEEP:
me->CastSpell(me->GetVictim(), SPELL_SWEEP, false); me->CastSpell(me->GetVictim(), SPELL_SWEEP, false);
events.Repeat(5s); events.Repeat(15s);
break; break;
} }
@@ -1172,7 +1277,7 @@ struct boss_thorim_gauntlet_npcs : public ScriptedAI
events.ScheduleEvent(EVENT_IR_GUARD_IMPALE, 12s); events.ScheduleEvent(EVENT_IR_GUARD_IMPALE, 12s);
events.ScheduleEvent(EVENT_IR_GUARD_WHIRL, 5s); events.ScheduleEvent(EVENT_IR_GUARD_WHIRL, 5s);
} }
else if (me->GetEntry() == NPC_DARK_RUNE_ACOLYTE_I) else if (me->GetEntry() == NPC_DARK_RUNE_ACOLYTE_G)
{ {
events.ScheduleEvent(EVENT_DR_ACOLYTE_GH, 10s); events.ScheduleEvent(EVENT_DR_ACOLYTE_GH, 10s);
events.ScheduleEvent(EVENT_DR_ACOLYTE_HS, 5s); events.ScheduleEvent(EVENT_DR_ACOLYTE_HS, 5s);