TrinityCore
Loading...
Searching...
No Matches
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
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
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
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;
182 break;
183 }
184 }
185
187 {
188 for (Horseman boss : horsemen)
189 {
190 if (Creature* cBoss = getHorsemanHandle(boss))
191 {
192 if (cBoss->IsAlive() && !cBoss->AI()->GetData(DATA_MOVEMENT_FINISHED))
193 return;
194 }
195 else
196 {
197 TC_LOG_WARN("scripts", "FourHorsemenAI: Checking if movement is finished but horseman with id {} is not present", uint32(boss));
199 return;
200 }
201 }
202
203 for (Horseman boss : horsemen)
204 if (Creature* cBoss = getHorsemanHandle(boss))
205 cBoss->AI()->DoAction(ACTION_BEGIN_FIGHTING);
206 }
207
209 {
211 return;
213 {
215 return;
216 }
218 Map::PlayerList const& players = me->GetMap()->GetPlayers();
219 if (players.empty()) // sanity check
221
222 for (Horseman boss : horsemen)
223 {
224 if (Creature* cBoss = getHorsemanHandle(boss))
225 {
226 if (!cBoss->IsAlive())
227 {
229 return;
230 }
231 cBoss->SetReactState(REACT_PASSIVE);
232 cBoss->AttackStop(); // clear initial target that was set on enter combat
233 cBoss->setActive(true);
234 cBoss->SetFarVisible(true);
235
236 for (Map::PlayerList::const_iterator it = players.begin(); it != players.end(); ++it)
237 {
238 if (Player* player = it->GetSource())
239 {
240 if (player->IsGameMaster())
241 continue;
242
243 if (player->IsAlive())
244 AddThreat(player, 0.0f, cBoss);
245 }
246 }
247
248 /* Why do the Four Horsemen run to opposite corners of the room when engaged? *
249 * They saw all the mobs leading up to them being AoE'd down and made a judgment call. */
250 cBoss->AI()->DoAction(ACTION_BEGIN_MOVEMENT);
251 }
252 else
253 {
254 TC_LOG_WARN("scripts", "FourHorsemenAI: Encounter starting but horseman with id {} is not present", uint32(boss));
256 return;
257 }
258 }
259 }
260
262 {
264 return;
266 for (Horseman boss : horsemen)
267 {
268 if (Creature* cBoss = getHorsemanHandle(boss))
269 cBoss->DespawnOrUnsummon(0s, 15s);
270 else
271 TC_LOG_WARN("scripts", "FourHorsemenAI: Encounter resetting but horseman with id {} is not present", uint32(boss));
272 }
273 }
274
276 {
278 return;
280 //instance->DoUpdateCriteria(CRITERIA_TYPE_BE_SPELL_TARGET, SPELL_ENCOUNTER_CREDIT);
282 }
283
284 void JustEngagedWith(Unit* /*who*/) override
285 {
286 if (instance->GetBossState(BOSS_HORSEMEN) == IN_PROGRESS || instance->GetBossState(BOSS_HORSEMEN) == DONE) // another horseman already did it
287 return;
290 }
291
292 void EnterEvadeMode(EvadeReason /*why*/) override
293 {
295 }
296
297 void Reset() override
298 {
299 if (!me->IsAlive())
300 return;
301 _myMovementFinished = false;
302 _nextMovement = 0;
303 _timeDied = 0;
304 _ourMovementFinished = false;
306 SetCombatMovement(false);
307 me->ResetLootMode();
308 events.Reset();
310 }
311
312 void KilledUnit(Unit* victim) override
313 {
314 if (victim->GetTypeId() == TYPEID_PLAYER)
315 Talk(SAY_SLAY, victim);
316 }
317
318 void JustDied(Unit* /*killer*/) override
319 {
320 if (instance->GetBossState(BOSS_HORSEMEN) != IN_PROGRESS) // necessary in case a horseman gets one-shot
321 {
323 return;
324 }
325
328 for (Horseman boss : horsemen)
329 {
330 if (Creature* cBoss = getHorsemanHandle(boss))
331 {
332 if (cBoss->IsAlive())
333 {
334 // in case a horseman dies while moving (unlikely but possible especially in non-335 branch)
336 return;
337 }
338 }
339 else
340 {
341 TC_LOG_WARN("scripts", "FourHorsemenAI: {} died but horseman with id {} is not present", me->GetName(), uint32(boss));
343 }
344 }
345
347 }
348
349 void MovementInform(uint32 type, uint32 i) override
350 {
351 if (type != POINT_MOTION_TYPE)
352 return;
353 if (i < 3)
354 _nextMovement = i; // delay to next updateai to prevent it from instantly expiring
355 else
356 {
357 _myMovementFinished = true;
359 }
360 }
361
362 void UpdateAI(uint32 diff) override
363 {
364 if (_nextMovement)
365 {
367 _nextMovement = 0;
368 }
369 _UpdateAI(diff);
370 }
371
372 virtual void BeginFighting() = 0;
373 virtual void _UpdateAI(uint32 /*diff*/) = 0;
374
375 private:
381 protected:
383};
384
386{
388 void BeginFighting() override
389 {
390 SetCombatMovement(true);
392 ThreatManager& threat = me->GetThreatManager();
393 if (threat.IsThreatListEmpty())
394 {
395 if (Unit* nearest = me->SelectNearestPlayer(5000.0f))
396 {
397 AddThreat(nearest, 1.0f);
398 AttackStart(nearest);
399 }
400 else
402 }
403 else
405
409 }
410
411 void _UpdateAI(uint32 diff) override
412 {
414 return;
415 events.Update(diff);
416
417 while (uint32 eventId = events.ExecuteEvent())
418 {
419 switch (eventId)
420 {
421 case EVENT_BERSERK:
423 break;
424 case EVENT_MARK:
426 events.Repeat(Seconds(12));
427 break;
431 break;
432 }
433 }
434 }
435
436 void SpellHitTarget(WorldObject* /*target*/, SpellInfo const* spellInfo) override
437 {
438 if (spellInfo->Id == SPELL_UNHOLY_SHADOW)
440 }
441};
442
444{
446 void BeginFighting() override
447 {
448 SetCombatMovement(true);
450 ThreatManager& threat = me->GetThreatManager();
451 if (threat.IsThreatListEmpty())
452 {
453 if (Unit* nearest = me->SelectNearestPlayer(5000.0f))
454 {
455 AddThreat(nearest, 1.0f);
456 AttackStart(nearest);
457 }
458 else
460 }
461 else
463
467 }
468 void _UpdateAI(uint32 diff) override
469 {
471 return;
472 events.Update(diff);
473
474 while (uint32 eventId = events.ExecuteEvent())
475 {
476 switch (eventId)
477 {
478 case EVENT_BERSERK:
480 break;
481 case EVENT_MARK:
483 events.Repeat(Seconds(12));
484 break;
485 case EVENT_METEOR:
486 if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 20.0f, true))
487 {
488 DoCast(target, SPELL_METEOR);
489 _shouldSay = true;
490 }
492 break;
493 }
494 }
495 }
496
497 void SpellHitTarget(WorldObject* /*target*/, SpellInfo const* spellInfo) override
498 {
499 if (_shouldSay && spellInfo->Id == SPELL_METEOR)
500 {
502 _shouldSay = false;
503 }
504 }
505
506 private:
507 bool _shouldSay; // throttle to make sure we only talk on first target hit by meteor
508};
509
511{
513 {
514 me->SetCanMelee(false);
515 }
516
523
524 void _UpdateAI(uint32 diff) override
525 {
526 if (!me->IsInCombat())
527 return;
529 return;
531 {
533 return;
534 }
535
536 events.Update(diff);
537
538 while (uint32 eventId = events.ExecuteEvent())
539 {
540 switch (eventId)
541 {
542 case EVENT_BERSERK:
544 break;
545 case EVENT_MARK:
547 events.Repeat(Seconds(15));
548 break;
549 case EVENT_VOIDZONE:
550 if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 45.0f, true))
551 {
552 DoCast(target, SPELL_VOID_ZONE, true);
554 }
556 break;
557 }
558 }
559
561 return;
562
563 if (Unit* target = SelectTarget(SelectTargetMethod::MinDistance, 0, 45.0f, true))
564 DoCast(target, SPELL_SHADOW_BOLT);
565 else
566 {
569 }
570 }
571};
572
574{
576 {
577 me->SetCanMelee(false);
578 }
579
586
587 void _UpdateAI(uint32 diff) override
588 {
589 if (!me->IsInCombat())
590 return;
592 return;
594 {
596 return;
597 }
598
599 events.Update(diff);
600
601 while (uint32 eventId = events.ExecuteEvent())
602 {
603 switch (eventId)
604 {
605 case EVENT_BERSERK:
607 break;
608 case EVENT_MARK:
610 events.Repeat(Seconds(15));
611 break;
612 case EVENT_HOLYWRATH:
613 if (Unit* target = SelectTarget(SelectTargetMethod::MinDistance, 0, 45.0f, true))
614 {
615 DoCast(target, SPELL_HOLY_WRATH, true);
616 _shouldSay = true;
617 }
619 break;
620 }
621 }
622
624 return;
625
626 if (Unit* target = SelectTarget(SelectTargetMethod::MinDistance, 0, 45.0f, true))
627 DoCast(target, SPELL_HOLY_BOLT);
628 else
629 {
632 }
633 }
634
635 void SpellHitTarget(WorldObject* /*target*/, SpellInfo const* spellInfo) override
636 {
637 if (_shouldSay && spellInfo->Id == SPELL_HOLY_WRATH)
638 {
640 _shouldSay = false;
641 }
642 }
643
644 private:
645 bool _shouldSay; // throttle to make sure we only talk on first target hit by holy wrath
646};
647
648// 28832 - Mark of Korth'azz
649// 28833 - Mark of Blaumeux
650// 28834 - Mark of Rivendare
651// 28835 - Mark of Zeliek
653{
654 void OnApply(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
655 {
656 if (Unit* caster = GetCaster())
657 {
658 int32 damage;
659 switch (GetStackAmount())
660 {
661 case 1:
662 damage = 0;
663 break;
664 case 2:
665 damage = 500;
666 break;
667 case 3:
668 damage = 1000;
669 break;
670 case 4:
671 damage = 1500;
672 break;
673 case 5:
674 damage = 4000;
675 break;
676 case 6:
677 damage = 12000;
678 break;
679 default:
680 damage = 20000 + 1000 * (GetStackAmount() - 7);
681 break;
682 }
683 if (damage)
684 {
686 args.AddSpellBP0(damage);
687 caster->CastSpell(GetTarget(), SPELL_MARK_DAMAGE, args);
688 }
689 }
690 }
691
696};
697
@ IN_MILLISECONDS
Definition Common.h:38
uint8_t uint8
Definition Define.h:156
int32_t int32
Definition Define.h:150
uint32_t uint32
Definition Define.h:154
std::chrono::seconds Seconds
Seconds shorthand typedef.
Definition Duration.h:28
@ IN_PROGRESS
@ DONE
@ NOT_STARTED
#define TC_LOG_WARN(filterType__, message__,...)
Definition Log.h:187
@ POINT_MOTION_TYPE
@ TYPEID_PLAYER
Definition ObjectGuid.h:44
Spells
Definition PlayerAI.cpp:32
Milliseconds randtime(Milliseconds min, Milliseconds max)
Definition Random.cpp:62
#define RegisterSpellScript(spell_script)
Definition ScriptMgr.h:1383
@ EFFECT_0
AuraEffectHandleModes
@ AURA_EFFECT_HANDLE_REAL_OR_REAPPLY_MASK
@ SPELL_AURA_DUMMY
@ TRIGGERED_FULL_MASK
Used when doing CastSpell with triggered == true.
#define AuraEffectApplyFn(F, I, N, M)
uint32 getMSTimeDiff(uint32 oldMSTime, uint32 newMSTime)
Definition Timer.h:40
EvadeReason
@ REACT_PASSIVE
@ REACT_AGGRESSIVE
@ UNIT_STATE_CASTING
Definition Unit.h:276
#define SPELL_HOLY_BOLT
@ ACTION_BEGIN_MOVEMENT
@ ACTION_BEGIN_FIGHTING
#define SPELL_HOLY_WRATH
static const Position sirPath[3]
@ EMOTE_RAGECAST
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_BERSERK
@ EVENT_VOIDZONE
@ EVENT_HOLYWRATH
#define SPELL_METEOR
void AddSC_boss_four_horsemen()
Yells
HookList< EffectApplyHandler > AfterEffectApply
Unit * GetCaster() const
Unit * GetTarget() const
uint8 GetStackAmount() const
TypeID GetTypeId() const
Definition BaseEntity.h:166
InstanceScript *const instance
SummonList summons
EventMap events
bool UpdateVictim()
Creature *const me
Definition CreatureAI.h:63
void SetCanMelee(bool canMelee, bool fleeFromMelee=false)
void ResetLootMode()
Definition Creature.h:322
void SetRespawnTime(uint32 respawn)
void SetReactState(ReactStates st)
Definition Creature.h:174
uint32 ExecuteEvent()
Definition EventMap.cpp:77
void Update(uint32 time)
Definition EventMap.h:61
void Repeat(Milliseconds time)
Definition EventMap.cpp:67
void ScheduleEvent(uint32 eventId, Milliseconds time, uint32 group=0, uint8 phase=0)
Definition EventMap.cpp:40
void Reset()
Definition EventMap.cpp:25
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 empty() const
Definition LinkedList.h:107
PlayerList const & GetPlayers() const
Definition Map.h:403
void MovePoint(uint32 id, Position const &pos, bool generatePath=true, Optional< float > finalOrient={}, Optional< float > speed={}, MovementWalkRunSpeedSelectionMode speedSelectionMode=MovementWalkRunSpeedSelectionMode::Default, Optional< float > closeEnoughDistance={}, Optional< MovementFadeObject > fadeObject={}, Scripting::v2::ActionResultSetter< MovementStopReason > &&scriptResult={})
iterator end()
Definition RefManager.h:36
iterator begin()
Definition RefManager.h:35
uint32 const Id
Definition SpellInfo.h:328
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:162
SpellCastResult DoCast(uint32 spellId)
Definition UnitAI.cpp:89
Definition Unit.h:635
ThreatManager & GetThreatManager()
Definition Unit.h:1078
MotionMaster * GetMotionMaster()
Definition Unit.h:1723
bool IsAlive() const
Definition Unit.h:1185
bool HasUnitState(const uint32 f) const
Definition Unit.h:743
bool IsInCombat() const
Definition Unit.h:1058
Player * SelectNearestPlayer(float range) const
Definition Object.cpp:1579
Map * GetMap() const
Definition Object.h:411
std::string const & GetName() const
Definition Object.h:342
void OnApply(AuraEffect const *, AuraEffectHandleModes)
uint32 GetGameTimeMS()
Definition GameTime.cpp:57
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(SpellEffectValue val)
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