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 "SpellAuraEffects.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 */
50
51 /* thane */
53 SPELL_METEOR = 28884,
54
55 /* lady */
60
61 /* sir */
65 SPELL_CONDEMNATION = 57377
66};
67
73
75{
76 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
79};
80
82{
83 /* all */
86
87 /* rivendare */
89
90 /* thane */
92
93 /* lady */
95
96 /* sir */
98};
99
109
110static const Position baronPath[3] = { { 2552.427f, -2969.737f, 241.3021f },{ 2566.759f, -2972.535f, 241.3217f },{ 2584.32f, -2971.96f, 241.3489f } };
111static const Position thanePath[3] = { { 2540.095f, -2983.192f, 241.3344f },{ 2546.005f, -2999.826f, 241.3665f },{ 2542.697f, -3014.055f, 241.3371f } };
112static const Position ladyPath[3] = { { 2507.94f, -2961.444f, 242.4557f },{ 2488.763f, -2960.007f, 241.2757f },{ 2468.26f, -2947.499f, 241.2753f } };
113static const Position sirPath[3] = { { 2533.141f, -2922.14f, 241.2764f },{ 2525.254f, -2905.907f, 241.2761f },{ 2517.636f, -2897.253f, 241.2758f } };
114
116{
117 public:
119 {
120 if (_which == who)
121 return me;
122 else
124 }
125 boss_four_horsemen_baseAI(Creature* creature, Horseman which, Position const* initialPath) :
126 BossAI(creature, BOSS_HORSEMEN), _which(which), _initialPath(initialPath), _myMovementFinished(false), _nextMovement(0), _timeDied(0), _ourMovementFinished(false)
127 {
129 me->SetRespawnTime(10);
130 }
131
132 uint32 GetData(uint32 type) const override
133 {
134 switch (type)
135 {
137 return _myMovementFinished ? 1 : 0;
138 case DATA_DEATH_TIME:
139 return _timeDied;
141 {
142 uint32 minTime = 0, maxTime = 0;
143 for (Horseman boss : horsemen)
144 if (Creature* cBoss = getHorsemanHandle(boss))
145 {
146 uint32 deathTime = cBoss->AI()->GetData(DATA_DEATH_TIME);
147 if (!deathTime)
148 {
149 TC_LOG_WARN("scripts", "FourHorsemenAI: Checking for achievement credit but horseman {} is reporting not dead", cBoss->GetName());
150 return 0;
151 }
152 if (!minTime || deathTime < minTime)
153 minTime = deathTime;
154 if (!maxTime || deathTime > maxTime)
155 maxTime = deathTime;
156 }
157 else
158 {
159 TC_LOG_WARN("scripts", "FourHorsemenAI: Checking for achievement credit but horseman with id {} is not present", uint32(boss));
160 return 0;
161 }
162 return (getMSTimeDiff(minTime, maxTime) <= 15 * IN_MILLISECONDS) ? 1 : 0;
163 }
164 default:
165 return 0;
166 }
167 }
168
169 void DoAction(int32 action) override
170 {
171 switch (action)
172 {
174 me->GetMotionMaster()->MovePoint(1, _initialPath[0], true);
175 break;
178 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.isEmpty()) // 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->DoUpdateAchievementCriteria(ACHIEVEMENT_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);
308 me->ResetLootMode();
309 events.Reset();
311 }
312
313 void KilledUnit(Unit* victim) override
314 {
315 if (victim->GetTypeId() == TYPEID_PLAYER)
316 Talk(SAY_SLAY, victim);
317 }
318
319 void JustDied(Unit* /*killer*/) override
320 {
321 if (instance->GetBossState(BOSS_HORSEMEN) != IN_PROGRESS) // necessary in case a horseman gets one-shot
322 {
324 return;
325 }
326
329 for (Horseman boss : horsemen)
330 {
331 if (Creature* cBoss = getHorsemanHandle(boss))
332 {
333 if (cBoss->IsAlive())
334 {
335 // in case a horseman dies while moving (unlikely but possible especially in non-335 branch)
337 return;
338 }
339 }
340 else
341 {
342 TC_LOG_WARN("scripts", "FourHorsemenAI: {} died but horseman with id {} is not present", me->GetName(), uint32(boss));
344 }
345 }
346
348 }
349
350 void MovementInform(uint32 type, uint32 i) override
351 {
352 if (type != POINT_MOTION_TYPE)
353 return;
354 if (i < 3)
355 _nextMovement = i; // delay to next updateai to prevent it from instantly expiring
356 else
357 {
358 _myMovementFinished = true;
360 }
361 }
362
363 void UpdateAI(uint32 diff) override
364 {
365 if (_nextMovement)
366 {
368 _nextMovement = 0;
369 }
370 _UpdateAI(diff);
371 }
372
373 virtual void BeginFighting() = 0;
374 virtual void _UpdateAI(uint32 /*diff*/) = 0;
375
376 private:
382 protected:
384};
385
387{
389 void BeginFighting() override
390 {
391 SetCombatMovement(true);
393 ThreatManager& threat = me->GetThreatManager();
394 if (threat.IsThreatListEmpty())
395 {
396 if (Unit* nearest = me->SelectNearestPlayer(5000.0f))
397 {
398 AddThreat(nearest, 1.0f);
399 AttackStart(nearest);
400 }
401 else
403 }
404 else
406
410 }
411
412 void _UpdateAI(uint32 diff) override
413 {
415 return;
416 events.Update(diff);
417
418 while (uint32 eventId = events.ExecuteEvent())
419 {
420 switch (eventId)
421 {
422 case EVENT_BERSERK:
424 break;
425 case EVENT_MARK:
427 events.Repeat(Seconds(12));
428 break;
432 break;
433 }
434 }
435
437 return;
439 }
440
441 void SpellHitTarget(WorldObject* /*target*/, SpellInfo const* spellInfo) override
442 {
443 if (spellInfo->Id == SPELL_UNHOLY_SHADOW)
445 }
446};
447
449{
451 void BeginFighting() override
452 {
453 SetCombatMovement(true);
455 ThreatManager& threat = me->GetThreatManager();
456 if (threat.IsThreatListEmpty())
457 {
458 if (Unit* nearest = me->SelectNearestPlayer(5000.0f))
459 {
460 AddThreat(nearest, 1.0f);
461 AttackStart(nearest);
462 }
463 else
465 }
466 else
468
472 }
473 void _UpdateAI(uint32 diff) override
474 {
476 return;
477 events.Update(diff);
478
479 while (uint32 eventId = events.ExecuteEvent())
480 {
481 switch (eventId)
482 {
483 case EVENT_BERSERK:
485 break;
486 case EVENT_MARK:
488 events.Repeat(Seconds(12));
489 break;
490 case EVENT_METEOR:
491 if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 20.0f, true))
492 {
493 DoCast(target, SPELL_METEOR);
494 _shouldSay = true;
495 }
497 break;
498 }
499 }
500
502 return;
504 }
505
506 void SpellHitTarget(WorldObject* /*target*/, SpellInfo const* spellInfo) override
507 {
508 if (_shouldSay && spellInfo->Id == SPELL_METEOR)
509 {
511 _shouldSay = false;
512 }
513 }
514
515 private:
516 bool _shouldSay; // throttle to make sure we only talk on first target hit by meteor
517};
518
520{
528
529 void _UpdateAI(uint32 diff) override
530 {
531 if (!me->IsInCombat())
532 return;
534 return;
536 {
538 return;
539 }
540
541 events.Update(diff);
542
543 while (uint32 eventId = events.ExecuteEvent())
544 {
545 switch (eventId)
546 {
547 case EVENT_BERSERK:
549 break;
550 case EVENT_MARK:
552 events.Repeat(Seconds(15));
553 break;
554 case EVENT_VOIDZONE:
555 if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 45.0f, true))
556 {
557 DoCast(target, SPELL_VOID_ZONE, true);
559 }
561 break;
562 }
563 }
564
566 return;
567
568 if (Unit* target = SelectTarget(SelectTargetMethod::MinDistance, 0, 45.0f, true))
569 DoCast(target, SPELL_SHADOW_BOLT);
570 else
571 {
574 }
575 }
576};
577
579{
587
588 void _UpdateAI(uint32 diff) override
589 {
590 if (!me->IsInCombat())
591 return;
593 return;
595 {
597 return;
598 }
599
600 events.Update(diff);
601
602 while (uint32 eventId = events.ExecuteEvent())
603 {
604 switch (eventId)
605 {
606 case EVENT_BERSERK:
608 break;
609 case EVENT_MARK:
611 events.Repeat(Seconds(15));
612 break;
613 case EVENT_HOLYWRATH:
614 if (Unit* target = SelectTarget(SelectTargetMethod::MinDistance, 0, 45.0f, true))
615 {
616 DoCast(target, SPELL_HOLY_WRATH, true);
617 _shouldSay = true;
618 }
620 break;
621 }
622 }
623
625 return;
626
627 if (Unit* target = SelectTarget(SelectTargetMethod::MinDistance, 0, 45.0f, true))
628 DoCast(target, SPELL_HOLY_BOLT);
629 else
630 {
633 }
634 }
635
636 void SpellHitTarget(WorldObject* /*target*/, SpellInfo const* spellInfo) override
637 {
638 if (_shouldSay && spellInfo->Id == SPELL_HOLY_WRATH)
639 {
641 _shouldSay = false;
642 }
643 }
644
645 private:
646 bool _shouldSay; // throttle to make sure we only talk on first target hit by holy wrath
647};
648
649// 28832 - Mark of Korth'azz
650// 28833 - Mark of Blaumeux
651// 28834 - Mark of Rivendare
652// 28835 - Mark of Zeliek
654{
656
657 void OnApply(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
658 {
659 if (Unit* caster = GetCaster())
660 {
661 int32 damage;
662 switch (GetStackAmount())
663 {
664 case 1:
665 damage = 0;
666 break;
667 case 2:
668 damage = 500;
669 break;
670 case 3:
671 damage = 1000;
672 break;
673 case 4:
674 damage = 1500;
675 break;
676 case 5:
677 damage = 4000;
678 break;
679 case 6:
680 damage = 12000;
681 break;
682 default:
683 damage = 20000 + 1000 * (GetStackAmount() - 7);
684 break;
685 }
686 if (damage)
687 {
689 args.AddSpellBP0(damage);
690 caster->CastSpell(GetTarget(), SPELL_MARK_DAMAGE, args);
691 }
692 }
693 }
694
699};
700
Actions
@ IN_MILLISECONDS
Definition Common.h:35
uint8_t uint8
Definition Define.h:135
int32_t int32
Definition Define.h:129
uint32_t uint32
Definition Define.h:133
std::chrono::seconds Seconds
Seconds shorthand typedef.
Definition Duration.h:27
@ IN_PROGRESS
@ DONE
@ NOT_STARTED
#define TC_LOG_WARN(filterType__,...)
Definition Log.h:162
@ POINT_MOTION_TYPE
@ TYPEID_PLAYER
Definition ObjectGuid.h:39
Spells
Definition PlayerAI.cpp:32
Milliseconds randtime(Milliseconds min, Milliseconds max)
Definition Random.cpp:62
#define RegisterSpellScript(spell_script)
Definition ScriptMgr.h:1128
@ EFFECT_0
AuraEffectHandleModes
@ AURA_EFFECT_HANDLE_REAL_OR_REAPPLY_MASK
@ SPELL_AURA_DUMMY
@ TRIGGERED_FULL_MASK
Will return SPELL_FAILED_DONT_REPORT in CheckCast functions.
#define AuraEffectApplyFn(F, I, N, M)
uint32 getMSTimeDiff(uint32 oldMSTime, uint32 newMSTime)
Definition Timer.h:40
@ REACT_PASSIVE
@ REACT_AGGRESSIVE
@ UNIT_STATE_CASTING
Definition Unit.h:235
@ ACTION_BEGIN_MOVEMENT
@ ACTION_BEGIN_FIGHTING
static const Position sirPath[3]
@ EMOTE_RAGECAST
static const Position thanePath[3]
@ SPELL_HOLY_WRATH
@ SPELL_BARON_MARK
@ SPELL_UNYIELDING_PAIN
@ SPELL_VOID_ZONE
@ SPELL_CONDEMNATION
@ SPELL_LADY_MARK
@ SPELL_THANE_MARK
@ SPELL_MARK_DAMAGE
@ SPELL_SHADOW_BOLT
@ SPELL_ENCOUNTER_CREDIT
@ SPELL_METEOR
@ SPELL_SIR_MARK
@ SPELL_HOLY_BOLT
@ SPELL_BERSERK
@ SPELL_UNHOLY_SHADOW
@ DATA_HORSEMEN_IS_TIMED_KILL
@ DATA_MOVEMENT_FINISHED
@ DATA_DEATH_TIME
static const std::vector< Horseman > horsemen
static const Position ladyPath[3]
static const Position baronPath[3]
@ EVENT_UNHOLYSHADOW
@ EVENT_METEOR
@ EVENT_BERSERK
@ EVENT_VOIDZONE
@ EVENT_HOLYWRATH
void AddSC_boss_four_horsemen()
Yells
HookList< EffectApplyHandler > AfterEffectApply
Unit * GetCaster() const
Unit * GetTarget() const
uint8 GetStackAmount() const
InstanceScript *const instance
SummonList summons
EventMap events
@ EVADE_REASON_NO_HOSTILES
Definition CreatureAI.h:94
bool UpdateVictim()
Creature *const me
Definition CreatureAI.h:82
void SetCombatPulseDelay(uint32 delay)
Definition Creature.h:269
void ResetLootMode()
Definition Creature.h:227
void SetRespawnTime(uint32 respawn)
void SetReactState(ReactStates st)
Definition Creature.h:119
void Update(uint32 time)
Definition EventMap.h:67
void Repeat(Milliseconds time)
Definition EventMap.cpp:63
EventId ExecuteEvent()
Definition EventMap.cpp:73
void ScheduleEvent(EventId eventId, Milliseconds time, GroupIndex group=0u, PhaseIndex phase=0u)
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:108
iterator end()
iterator begin()
PlayerList const & GetPlayers() const
Definition Map.h:448
void MovePoint(uint32 id, Position const &pos, bool generatePath=true, Optional< float > finalOrient={})
TypeID GetTypeId() const
Definition Object.h:93
uint32 Id
Definition SpellInfo.h:289
Unit * GetCurrentVictim()
bool IsThreatListEmpty(bool includeOffline=false) const
void DoMeleeAttackIfReady()
Definition UnitAI.cpp:54
SpellCastResult DoCastVictim(uint32 spellId, CastSpellExtraArgs const &args={})
Definition UnitAI.cpp:166
Unit * SelectTarget(SelectTargetMethod targetType, uint32 offset=0, float dist=0.0f, bool playerOnly=false, bool withTank=true, int32 aura=0)
Definition UnitAI.cpp:96
SpellCastResult DoCastAOE(uint32 spellId, CastSpellExtraArgs const &args={})
Definition UnitAI.h:243
SpellCastResult DoCast(uint32 spellId)
Definition UnitAI.cpp:106
Definition Unit.h:769
ThreatManager & GetThreatManager()
Definition Unit.h:1155
MotionMaster * GetMotionMaster()
Definition Unit.h:1667
bool IsAlive() const
Definition Unit.h:1234
bool HasUnitState(const uint32 f) const
Definition Unit.h:876
bool IsInCombat() const
Definition Unit.h:1144
Map * GetMap() const
Definition Object.h:449
Player * SelectNearestPlayer(float distance) const
Definition Object.cpp:2161
std::string const & GetName() const
Definition Object.h:382
PrepareAuraScript(spell_four_horsemen_mark)
void OnApply(AuraEffect const *, AuraEffectHandleModes)
uint32 GetGameTimeMS()
Definition GameTime.cpp:47
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)
void AttackStart(Unit *) override
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