TrinityCore
boss_four_horsemen.cpp
Go to the documentation of this file.
1/*
2 * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the
6 * Free Software Foundation; either version 2 of the License, or (at your
7 * option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12 * more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "ScriptMgr.h"
19#include "GameTime.h"
20#include "InstanceScript.h"
21#include "Log.h"
22#include "Map.h"
23#include "MotionMaster.h"
24#include "naxxramas.h"
25#include "ObjectAccessor.h"
26#include "Player.h"
27#include "ScriptedCreature.h"
28#include "SpellInfo.h"
29#include "SpellScript.h"
30
32{
37};
38static const std::vector<Horseman> horsemen = { THANE, LADY, BARON, SIR }; // for iterating
39
41{
42 /* all */
46
47 /* baron */
49
50 /* thane */
52
53 /* lady */
56
57 /* sir */
59 SPELL_CONDEMNATION = 57377
60};
61
62#define SPELL_UNHOLY_SHADOW RAID_MODE<uint32>(28882, 57369) // Rivendare: Unholy Shadow
63#define SPELL_METEOR RAID_MODE<uint32>(28884, 57467) // Korth'azz: Meteor
64#define SPELL_SHADOW_BOLT RAID_MODE<uint32>(57374, 57464) // Blaumeux : Shadow Bolt
65#define SPELL_VOID_ZONE RAID_MODE<uint32>(28863, 57463) // Blaumeux : Void Zone
66#define SPELL_HOLY_BOLT RAID_MODE<uint32>(57376, 57465) // Zeliek : Holy Bolt
67#define SPELL_HOLY_WRATH RAID_MODE<uint32>(28883, 57466) // Zeliek: Holy Wrath
68
70{
73};
74
76{
77 DATA_HORSEMEN_IS_TIMED_KILL = NAXData::DATA_HORSEMEN_CHECK_ACHIEVEMENT_CREDIT, // inherit from naxxramas.h - this needs to be the first entry to ensure that there are no conflicts
80};
81
83{
84 /* all */
87
88 /* rivendare */
90
91 /* thane */
93
94 /* lady */
96
97 /* sir */
99};
100
102{
107
110
111static const Position baronPath[3] = { { 2552.427f, -2969.737f, 241.3021f },{ 2566.759f, -2972.535f, 241.3217f },{ 2584.32f, -2971.96f, 241.3489f } };
112static const Position thanePath[3] = { { 2540.095f, -2983.192f, 241.3344f },{ 2546.005f, -2999.826f, 241.3665f },{ 2542.697f, -3014.055f, 241.3371f } };
113static const Position ladyPath[3] = { { 2507.94f, -2961.444f, 242.4557f },{ 2488.763f, -2960.007f, 241.2757f },{ 2468.26f, -2947.499f, 241.2753f } };
114static const Position sirPath[3] = { { 2533.141f, -2922.14f, 241.2764f },{ 2525.254f, -2905.907f, 241.2761f },{ 2517.636f, -2897.253f, 241.2758f } };
115
117{
118 public:
120 {
121 if (_which == who)
122 return me;
123 else
125 }
126 boss_four_horsemen_baseAI(Creature* creature, Horseman which, Position const* initialPath) :
127 BossAI(creature, BOSS_HORSEMEN), _which(which), _initialPath(initialPath), _myMovementFinished(false), _nextMovement(0), _timeDied(0), _ourMovementFinished(false)
128 {
130 me->SetRespawnTime(10);
131 }
132
133 uint32 GetData(uint32 type) const override
134 {
135 switch (type)
136 {
138 return _myMovementFinished ? 1 : 0;
139 case DATA_DEATH_TIME:
140 return _timeDied;
142 {
143 uint32 minTime = 0, maxTime = 0;
144 for (Horseman boss : horsemen)
145 if (Creature* cBoss = getHorsemanHandle(boss))
146 {
147 uint32 deathTime = cBoss->AI()->GetData(DATA_DEATH_TIME);
148 if (!deathTime)
149 {
150 TC_LOG_WARN("scripts", "FourHorsemenAI: Checking for achievement credit but horseman {} is reporting not dead", cBoss->GetName());
151 return 0;
152 }
153 if (!minTime || deathTime < minTime)
154 minTime = deathTime;
155 if (!maxTime || deathTime > maxTime)
156 maxTime = deathTime;
157 }
158 else
159 {
160 TC_LOG_WARN("scripts", "FourHorsemenAI: Checking for achievement credit but horseman with id {} is not present", uint32(boss));
161 return 0;
162 }
163 return (getMSTimeDiff(minTime, maxTime) <= 15 * IN_MILLISECONDS) ? 1 : 0;
164 }
165 default:
166 return 0;
167 }
168 }
169
170 void DoAction(int32 action) override
171 {
172 switch (action)
173 {
175 me->GetMotionMaster()->MovePoint(1, _initialPath[0], true);
176 break;
179 break;
183 break;
184 }
185 }
186
188 {
189 for (Horseman boss : horsemen)
190 {
191 if (Creature* cBoss = getHorsemanHandle(boss))
192 {
193 if (cBoss->IsAlive() && !cBoss->AI()->GetData(DATA_MOVEMENT_FINISHED))
194 return;
195 }
196 else
197 {
198 TC_LOG_WARN("scripts", "FourHorsemenAI: Checking if movement is finished but horseman with id {} is not present", uint32(boss));
200 return;
201 }
202 }
203
204 for (Horseman boss : horsemen)
205 if (Creature* cBoss = getHorsemanHandle(boss))
206 cBoss->AI()->DoAction(ACTION_BEGIN_FIGHTING);
207 }
208
210 {
212 return;
214 {
216 return;
217 }
219 Map::PlayerList const& players = me->GetMap()->GetPlayers();
220 if (players.isEmpty()) // sanity check
222
223 for (Horseman boss : horsemen)
224 {
225 if (Creature* cBoss = getHorsemanHandle(boss))
226 {
227 if (!cBoss->IsAlive())
228 {
230 return;
231 }
232 cBoss->SetReactState(REACT_PASSIVE);
233 cBoss->AttackStop(); // clear initial target that was set on enter combat
234 cBoss->setActive(true);
235 cBoss->SetFarVisible(true);
236
237 for (Map::PlayerList::const_iterator it = players.begin(); it != players.end(); ++it)
238 {
239 if (Player* player = it->GetSource())
240 {
241 if (player->IsGameMaster())
242 continue;
243
244 if (player->IsAlive())
245 AddThreat(player, 0.0f, cBoss);
246 }
247 }
248
249 /* Why do the Four Horsemen run to opposite corners of the room when engaged? *
250 * They saw all the mobs leading up to them being AoE'd down and made a judgment call. */
251 cBoss->AI()->DoAction(ACTION_BEGIN_MOVEMENT);
252 }
253 else
254 {
255 TC_LOG_WARN("scripts", "FourHorsemenAI: Encounter starting but horseman with id {} is not present", uint32(boss));
257 return;
258 }
259 }
260 }
261
263 {
265 return;
267 for (Horseman boss : horsemen)
268 {
269 if (Creature* cBoss = getHorsemanHandle(boss))
270 cBoss->DespawnOrUnsummon(0s, 15s);
271 else
272 TC_LOG_WARN("scripts", "FourHorsemenAI: Encounter resetting but horseman with id {} is not present", uint32(boss));
273 }
274 }
275
277 {
279 return;
281 //instance->DoUpdateCriteria(CRITERIA_TYPE_BE_SPELL_TARGET, SPELL_ENCOUNTER_CREDIT);
283 }
284
285 void JustEngagedWith(Unit* /*who*/) override
286 {
287 if (instance->GetBossState(BOSS_HORSEMEN) == IN_PROGRESS || instance->GetBossState(BOSS_HORSEMEN) == DONE) // another horseman already did it
288 return;
291 }
292
293 void EnterEvadeMode(EvadeReason /*why*/) override
294 {
296 }
297
298 void Reset() override
299 {
300 if (!me->IsAlive())
301 return;
302 _myMovementFinished = false;
303 _nextMovement = 0;
304 _timeDied = 0;
305 _ourMovementFinished = false;
307 SetCombatMovement(false);
309 me->ResetLootMode();
310 events.Reset();
312 }
313
314 void KilledUnit(Unit* victim) override
315 {
316 if (victim->GetTypeId() == TYPEID_PLAYER)
317 Talk(SAY_SLAY, victim);
318 }
319
320 void JustDied(Unit* /*killer*/) override
321 {
322 if (instance->GetBossState(BOSS_HORSEMEN) != IN_PROGRESS) // necessary in case a horseman gets one-shot
323 {
325 return;
326 }
327
330 for (Horseman boss : horsemen)
331 {
332 if (Creature* cBoss = getHorsemanHandle(boss))
333 {
334 if (cBoss->IsAlive())
335 {
336 // in case a horseman dies while moving (unlikely but possible especially in non-335 branch)
338 return;
339 }
340 }
341 else
342 {
343 TC_LOG_WARN("scripts", "FourHorsemenAI: {} died but horseman with id {} is not present", me->GetName(), uint32(boss));
345 }
346 }
347
349 }
350
351 void MovementInform(uint32 type, uint32 i) override
352 {
353 if (type != POINT_MOTION_TYPE)
354 return;
355 if (i < 3)
356 _nextMovement = i; // delay to next updateai to prevent it from instantly expiring
357 else
358 {
359 _myMovementFinished = true;
361 }
362 }
363
364 void UpdateAI(uint32 diff) override
365 {
366 if (_nextMovement)
367 {
369 _nextMovement = 0;
370 }
371 _UpdateAI(diff);
372 }
373
374 virtual void BeginFighting() = 0;
375 virtual void _UpdateAI(uint32 /*diff*/) = 0;
376
377 private:
383 protected:
385};
386
388{
390 void BeginFighting() override
391 {
392 SetCombatMovement(true);
394 ThreatManager& threat = me->GetThreatManager();
395 if (threat.IsThreatListEmpty())
396 {
397 if (Unit* nearest = me->SelectNearestPlayer(5000.0f))
398 {
399 AddThreat(nearest, 1.0f);
400 AttackStart(nearest);
401 }
402 else
404 }
405 else
407
411 }
412
413 void _UpdateAI(uint32 diff) override
414 {
416 return;
417 events.Update(diff);
418
419 while (uint32 eventId = events.ExecuteEvent())
420 {
421 switch (eventId)
422 {
423 case EVENT_BERSERK:
425 break;
426 case EVENT_MARK:
428 events.Repeat(Seconds(12));
429 break;
433 break;
434 }
435 }
436 }
437
438 void SpellHitTarget(WorldObject* /*target*/, SpellInfo const* spellInfo) override
439 {
440 if (spellInfo->Id == SPELL_UNHOLY_SHADOW)
442 }
443};
444
446{
448 void BeginFighting() override
449 {
450 SetCombatMovement(true);
452 ThreatManager& threat = me->GetThreatManager();
453 if (threat.IsThreatListEmpty())
454 {
455 if (Unit* nearest = me->SelectNearestPlayer(5000.0f))
456 {
457 AddThreat(nearest, 1.0f);
458 AttackStart(nearest);
459 }
460 else
462 }
463 else
465
469 }
470 void _UpdateAI(uint32 diff) override
471 {
473 return;
474 events.Update(diff);
475
476 while (uint32 eventId = events.ExecuteEvent())
477 {
478 switch (eventId)
479 {
480 case EVENT_BERSERK:
482 break;
483 case EVENT_MARK:
485 events.Repeat(Seconds(12));
486 break;
487 case EVENT_METEOR:
488 if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 20.0f, true))
489 {
490 DoCast(target, SPELL_METEOR);
491 _shouldSay = true;
492 }
494 break;
495 }
496 }
497 }
498
499 void SpellHitTarget(WorldObject* /*target*/, SpellInfo const* spellInfo) override
500 {
501 if (_shouldSay && spellInfo->Id == SPELL_METEOR)
502 {
504 _shouldSay = false;
505 }
506 }
507
508 private:
509 bool _shouldSay; // throttle to make sure we only talk on first target hit by meteor
510};
511
513{
515 {
516 me->SetCanMelee(false);
517 }
518
519 void BeginFighting() override
520 {
524 }
525
526 void _UpdateAI(uint32 diff) override
527 {
528 if (!me->IsInCombat())
529 return;
531 return;
533 {
535 return;
536 }
537
538 events.Update(diff);
539
540 while (uint32 eventId = events.ExecuteEvent())
541 {
542 switch (eventId)
543 {
544 case EVENT_BERSERK:
546 break;
547 case EVENT_MARK:
549 events.Repeat(Seconds(15));
550 break;
551 case EVENT_VOIDZONE:
552 if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 45.0f, true))
553 {
554 DoCast(target, SPELL_VOID_ZONE, true);
556 }
558 break;
559 }
560 }
561
563 return;
564
565 if (Unit* target = SelectTarget(SelectTargetMethod::MinDistance, 0, 45.0f, true))
566 DoCast(target, SPELL_SHADOW_BOLT);
567 else
568 {
571 }
572 }
573};
574
576{
578 {
579 me->SetCanMelee(false);
580 }
581
582 void BeginFighting() override
583 {
587 }
588
589 void _UpdateAI(uint32 diff) override
590 {
591 if (!me->IsInCombat())
592 return;
594 return;
596 {
598 return;
599 }
600
601 events.Update(diff);
602
603 while (uint32 eventId = events.ExecuteEvent())
604 {
605 switch (eventId)
606 {
607 case EVENT_BERSERK:
609 break;
610 case EVENT_MARK:
612 events.Repeat(Seconds(15));
613 break;
614 case EVENT_HOLYWRATH:
615 if (Unit* target = SelectTarget(SelectTargetMethod::MinDistance, 0, 45.0f, true))
616 {
617 DoCast(target, SPELL_HOLY_WRATH, true);
618 _shouldSay = true;
619 }
621 break;
622 }
623 }
624
626 return;
627
628 if (Unit* target = SelectTarget(SelectTargetMethod::MinDistance, 0, 45.0f, true))
629 DoCast(target, SPELL_HOLY_BOLT);
630 else
631 {
634 }
635 }
636
637 void SpellHitTarget(WorldObject* /*target*/, SpellInfo const* spellInfo) override
638 {
639 if (_shouldSay && spellInfo->Id == SPELL_HOLY_WRATH)
640 {
642 _shouldSay = false;
643 }
644 }
645
646 private:
647 bool _shouldSay; // throttle to make sure we only talk on first target hit by holy wrath
648};
649
650// 28832 - Mark of Korth'azz
651// 28833 - Mark of Blaumeux
652// 28834 - Mark of Rivendare
653// 28835 - Mark of Zeliek
655{
656 void OnApply(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
657 {
658 if (Unit* caster = GetCaster())
659 {
660 int32 damage;
661 switch (GetStackAmount())
662 {
663 case 1:
664 damage = 0;
665 break;
666 case 2:
667 damage = 500;
668 break;
669 case 3:
670 damage = 1000;
671 break;
672 case 4:
673 damage = 1500;
674 break;
675 case 5:
676 damage = 4000;
677 break;
678 case 6:
679 damage = 12000;
680 break;
681 default:
682 damage = 20000 + 1000 * (GetStackAmount() - 7);
683 break;
684 }
685 if (damage)
686 {
688 args.AddSpellBP0(damage);
689 caster->CastSpell(GetTarget(), SPELL_MARK_DAMAGE, args);
690 }
691 }
692 }
693
694 void Register() override
695 {
697 }
698};
699
701{
707}
Actions
@ IN_MILLISECONDS
Definition: Common.h:35
uint8_t uint8
Definition: Define.h:144
int32_t int32
Definition: Define.h:138
uint32_t uint32
Definition: Define.h:142
std::chrono::seconds Seconds
Seconds shorthand typedef.
Definition: Duration.h:32
@ IN_PROGRESS
@ DONE
@ NOT_STARTED
#define TC_LOG_WARN(filterType__,...)
Definition: Log.h:162
@ POINT_MOTION_TYPE
@ TYPEID_PLAYER
Definition: ObjectGuid.h:41
Spells
Definition: PlayerAI.cpp:32
Milliseconds randtime(Milliseconds min, Milliseconds max)
Definition: Random.cpp:62
#define RegisterSpellScript(spell_script)
Definition: ScriptMgr.h:1369
@ EFFECT_0
Definition: SharedDefines.h:30
AuraEffectHandleModes
@ AURA_EFFECT_HANDLE_REAL_OR_REAPPLY_MASK
@ SPELL_AURA_DUMMY
@ TRIGGERED_FULL_MASK
Used when doing CastSpell with triggered == true.
Definition: SpellDefines.h:266
#define AuraEffectApplyFn(F, I, N, M)
Definition: SpellScript.h:2029
uint32 getMSTimeDiff(uint32 oldMSTime, uint32 newMSTime)
Definition: Timer.h:40
EvadeReason
Definition: UnitAICommon.h:30
@ REACT_PASSIVE
Definition: UnitDefines.h:506
@ REACT_AGGRESSIVE
Definition: UnitDefines.h:508
@ UNIT_STATE_CASTING
Definition: Unit.h:270
#define SPELL_HOLY_BOLT
@ ACTION_BEGIN_MOVEMENT
@ ACTION_BEGIN_FIGHTING
#define SPELL_HOLY_WRATH
static const Position sirPath[3]
@ EMOTE_RAGECAST
@ SAY_SPECIAL
static const Position thanePath[3]
@ SPELL_BARON_MARK
@ SPELL_UNYIELDING_PAIN
@ SPELL_CONDEMNATION
@ SPELL_LADY_MARK
@ SPELL_THANE_MARK
@ SPELL_MARK_DAMAGE
@ SPELL_ENCOUNTER_CREDIT
@ SPELL_SIR_MARK
@ SPELL_BERSERK
@ DATA_HORSEMEN_IS_TIMED_KILL
@ DATA_MOVEMENT_FINISHED
@ DATA_DEATH_TIME
static const std::vector< Horseman > horsemen
#define SPELL_UNHOLY_SHADOW
static const Position ladyPath[3]
static const Position baronPath[3]
#define SPELL_SHADOW_BOLT
#define SPELL_VOID_ZONE
@ EVENT_UNHOLYSHADOW
@ EVENT_METEOR
@ EVENT_MARK
@ EVENT_BERSERK
@ EVENT_VOIDZONE
@ EVENT_HOLYWRATH
#define SPELL_METEOR
void AddSC_boss_four_horsemen()
Yells
HookList< EffectApplyHandler > AfterEffectApply
Definition: SpellScript.h:2028
Unit * GetCaster() const
Unit * GetTarget() const
uint8 GetStackAmount() const
InstanceScript *const instance
SummonList summons
EventMap events
void Talk(uint8 id, WorldObject const *whisperTarget=nullptr)
Definition: CreatureAI.cpp:56
bool UpdateVictim()
Definition: CreatureAI.cpp:245
Creature *const me
Definition: CreatureAI.h:61
void SetCombatPulseDelay(uint32 delay)
Definition: Creature.h:345
void SetCanMelee(bool canMelee, bool fleeFromMelee=false)
Definition: Creature.cpp:2822
void ResetLootMode()
Definition: Creature.h:300
void SetRespawnTime(uint32 respawn)
Definition: Creature.cpp:2877
void SetReactState(ReactStates st)
Definition: Creature.h:160
uint32 ExecuteEvent()
Definition: EventMap.cpp:73
void Update(uint32 time)
Definition: EventMap.h:56
void Repeat(Milliseconds time)
Definition: EventMap.cpp:63
void ScheduleEvent(uint32 eventId, Milliseconds time, uint32 group=0, uint8 phase=0)
Definition: EventMap.cpp:36
void Reset()
Definition: EventMap.cpp:21
virtual bool SetBossState(uint32 id, EncounterState state)
virtual ObjectGuid GetGuidData(uint32 type) const override
EncounterState GetBossState(uint32 id) const
virtual bool CheckRequiredBosses(uint32, Player const *=nullptr) const
bool isEmpty() const
Definition: LinkedList.h:110
iterator end()
Definition: MapRefManager.h:35
iterator begin()
Definition: MapRefManager.h:34
PlayerList const & GetPlayers() const
Definition: Map.h:367
void MovePoint(uint32 id, Position const &pos, bool generatePath=true, Optional< float > finalOrient={}, Optional< float > speed={}, MovementWalkRunSpeedSelectionMode speedSelectionMode=MovementWalkRunSpeedSelectionMode::Default, Optional< float > closeEnoughDistance={})
TypeID GetTypeId() const
Definition: Object.h:173
uint32 const Id
Definition: SpellInfo.h:325
Unit * GetCurrentVictim()
bool IsThreatListEmpty(bool includeOffline=false) const
SpellCastResult DoCastVictim(uint32 spellId, CastSpellExtraArgs const &args={})
Definition: UnitAI.cpp:180
Unit * SelectTarget(SelectTargetMethod targetType, uint32 offset=0, float dist=0.0f, bool playerOnly=false, bool withTank=true, int32 aura=0)
Definition: UnitAI.cpp:79
SpellCastResult DoCastAOE(uint32 spellId, CastSpellExtraArgs const &args={})
Definition: UnitAI.h:161
SpellCastResult DoCast(uint32 spellId)
Definition: UnitAI.cpp:89
Definition: Unit.h:627
ThreatManager & GetThreatManager()
Definition: Unit.h:1063
MotionMaster * GetMotionMaster()
Definition: Unit.h:1652
bool IsAlive() const
Definition: Unit.h:1164
bool HasUnitState(const uint32 f) const
Definition: Unit.h:732
bool IsInCombat() const
Definition: Unit.h:1043
Player * SelectNearestPlayer(float range) const
Definition: Object.cpp:2210
Map * GetMap() const
Definition: Object.h:624
std::string const & GetName() const
Definition: Object.h:555
void OnApply(AuraEffect const *, AuraEffectHandleModes)
uint32 GetGameTimeMS()
Definition: GameTime.cpp:49
TC_GAME_API Creature * GetCreature(WorldObject const &u, ObjectGuid const &guid)
@ BOSS_HORSEMEN
Definition: naxxramas.h:42
@ DATA_HORSEMEN_CHECK_ACHIEVEMENT_CREDIT
Definition: naxxramas.h:52
@ DATA_THANE
Definition: naxxramas.h:67
@ DATA_SIR
Definition: naxxramas.h:70
@ DATA_LADY
Definition: naxxramas.h:68
#define RegisterNaxxramasCreatureAI(ai_name)
Definition: naxxramas.h:221
@ DATA_BARON
Definition: stratholme.h:55
CastSpellExtraArgs & AddSpellBP0(int32 val)
Definition: SpellDefines.h:475
void AttackStart(Unit *) override
== Triggered Actions Requested ==================
void SetCombatMovement(bool allowMovement)
void AddThreat(Unit *victim, float amount, Unit *who=nullptr)
boss_four_horsemen_baron(Creature *creature)
void _UpdateAI(uint32 diff) override
void SpellHitTarget(WorldObject *, SpellInfo const *spellInfo) override
void KilledUnit(Unit *victim) override
uint32 GetData(uint32 type) const override
Creature * getHorsemanHandle(Horseman who) const
virtual void BeginFighting()=0
void DoAction(int32 action) override
void MovementInform(uint32 type, uint32 i) override
boss_four_horsemen_baseAI(Creature *creature, Horseman which, Position const *initialPath)
void UpdateAI(uint32 diff) override
void JustEngagedWith(Unit *) override
virtual void _UpdateAI(uint32)=0
void EnterEvadeMode(EvadeReason) override
void JustDied(Unit *) override
boss_four_horsemen_lady(Creature *creature)
void _UpdateAI(uint32 diff) override
boss_four_horsemen_sir(Creature *creature)
void _UpdateAI(uint32 diff) override
void SpellHitTarget(WorldObject *, SpellInfo const *spellInfo) override
void SpellHitTarget(WorldObject *, SpellInfo const *spellInfo) override
boss_four_horsemen_thane(Creature *creature)
void _UpdateAI(uint32 diff) override