aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--sql/updates/world/2016_04_10_09_world_2016_01_19_07_world.sql13
-rw-r--r--src/server/scripts/Northrend/Naxxramas/boss_four_horsemen.cpp835
-rw-r--r--src/server/scripts/Northrend/Naxxramas/instance_naxxramas.cpp52
-rw-r--r--src/server/scripts/Northrend/Naxxramas/naxxramas.h5
4 files changed, 591 insertions, 314 deletions
diff --git a/sql/updates/world/2016_04_10_09_world_2016_01_19_07_world.sql b/sql/updates/world/2016_04_10_09_world_2016_01_19_07_world.sql
new file mode 100644
index 00000000000..3cb7abc618e
--- /dev/null
+++ b/sql/updates/world/2016_04_10_09_world_2016_01_19_07_world.sql
@@ -0,0 +1,13 @@
+-- four horsemen rewrite
+UPDATE `creature_template` SET `ScriptName`='boss_four_horsemen_baron' WHERE `entry`=30549;
+UPDATE `creature_template` SET `ScriptName`='boss_four_horsemen_thane' WHERE `entry`=16064;
+UPDATE `creature_template` SET `ScriptName`='boss_four_horsemen_lady' WHERE `entry`=16065;
+UPDATE `creature_template` SET `ScriptName`='boss_four_horsemen_sir' WHERE `entry`=16063;
+
+-- add ragecast texts
+DELETE FROM `creature_text` WHERE `entry` IN (16064,16065,16063,30549) AND `groupid`=7;
+INSERT INTO `creature_text` (`entry`,`groupid`,`id`,`text`,`type`,`probability`,`BroadcastTextId`,`TextRange`,`comment`) VALUES
+(16063,7,0,"%s casts Condemnation on everyone!",41,100,33088,3,"zeliek EMOTE_RAGECAST"),
+(16065,7,0,"%s casts Unyielding Pain on everyone!",41,100,33087,3,"blaumeux EMOTE_RAGECAST");
+-- slay text probability to DB
+UPDATE `creature_text` SET `probability`=30 WHERE `entry` IN (16064,16065,16063,30549) AND `groupid`=3;
diff --git a/src/server/scripts/Northrend/Naxxramas/boss_four_horsemen.cpp b/src/server/scripts/Northrend/Naxxramas/boss_four_horsemen.cpp
index 3e7de89edd9..372e00a1b7d 100644
--- a/src/server/scripts/Northrend/Naxxramas/boss_four_horsemen.cpp
+++ b/src/server/scripts/Northrend/Naxxramas/boss_four_horsemen.cpp
@@ -15,382 +15,688 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+#include "Player.h"
#include "ScriptMgr.h"
#include "ScriptedCreature.h"
#include "SpellScript.h"
#include "SpellAuraEffects.h"
#include "naxxramas.h"
-enum Horsemen
+enum Horseman
{
- HORSEMEN_THANE,
- HORSEMEN_LADY,
- HORSEMEN_BARON,
- HORSEMEN_SIR,
+ THANE = DATA_THANE,
+ LADY = DATA_LADY,
+ BARON = DATA_BARON,
+ SIR = DATA_SIR,
};
+static const std::vector<Horseman> horsemen = { THANE, LADY, BARON, SIR }; // for iterating
enum Spells
{
- SPELL_MARK_DAMAGE = 28836
+ /* all */
+ SPELL_MARK_DAMAGE = 28836,
+ SPELL_BERSERK = 26662,
+ SPELL_ENCOUNTER_CREDIT = 59450,
+
+ /* baron */
+ SPELL_BARON_MARK = 28834,
+ SPELL_UNHOLY_SHADOW = 28882,
+
+ /* thane */
+ SPELL_THANE_MARK = 28832,
+ SPELL_METEOR = 28884,
+
+ /* lady */
+ SPELL_SHADOW_BOLT = 57374,
+ SPELL_LADY_MARK = 28833,
+ SPELL_VOID_ZONE = 28863,
+ SPELL_UNYIELDING_PAIN = 57381,
+
+ /* sir */
+ SPELL_HOLY_BOLT = 57376,
+ SPELL_SIR_MARK = 28835,
+ SPELL_HOLY_WRATH = 28883,
+ SPELL_CONDEMNATION = 57377
};
-enum Events
+#define SPELL_HELPER_UNHOLY_SHADOW RAID_MODE<uint32>(28882, 57369) // Rivendare: Unholy Shadow
+#define SPELL_HELPER_METEOR RAID_MODE<uint32>(28884, 57467) // Korth'azz: Meteor
+#define SPELL_HELPER_SHADOW_BOLT RAID_MODE<uint32>(57374, 57464) // Blaumeux : Shadow Bolt
+#define SPELL_HELPER_VOID_ZONE RAID_MODE<uint32>(28863, 57463) // Blaumeux : Void Zone
+#define SPELL_HELPER_HOLY_BOLT RAID_MODE<uint32>(57376, 57465) // Zeliek : Holy Bolt
+#define SPELL_HELPER_HOLY_WRATH RAID_MODE<uint32>(28883, 57466) // Zeliek: Holy Wrath
+
+enum Actions
{
- EVENT_NONE,
- EVENT_MARK,
- EVENT_CAST,
- EVENT_BERSERK,
+ ACTION_BEGIN_MOVEMENT = 1,
+ ACTION_BEGIN_FIGHTING
+};
+
+enum HorsemenData
+{
+ DATA_HORSEMEN_IS_TIMED_KILL = Data::DATA_HORSEMEN_CHECK_ACHIEVEMENT_CREDIT, // inherit from naxxramas.h - this needs to be the first entry to ensure that there are no conflicts
+ DATA_MOVEMENT_FINISHED,
+ DATA_DEATH_TIME
};
-const Position WaypointPositions[12] =
+enum Events
{
- // Thane waypoints
- {2542.3f, -2984.1f, 241.49f, 5.362f},
- {2547.6f, -2999.4f, 241.34f, 5.049f},
- {2542.9f, -3015.0f, 241.35f, 4.654f},
- // Lady waypoints
- {2498.3f, -2961.8f, 241.28f, 3.267f},
- {2487.7f, -2959.2f, 241.28f, 2.890f},
- {2469.4f, -2947.6f, 241.28f, 2.576f},
- // Baron waypoints
- {2553.8f, -2968.4f, 241.33f, 5.757f},
- {2564.3f, -2972.5f, 241.33f, 5.890f},
- {2583.9f, -2971.67f, 241.35f, 0.008f},
- // Sir waypoints
- {2534.5f, -2921.7f, 241.53f, 1.363f},
- {2523.5f, -2902.8f, 241.28f, 2.095f},
- {2517.8f, -2896.6f, 241.28f, 2.315f},
+ /* all */
+ EVENT_BERSERK = 1,
+ EVENT_MARK,
+
+ /* rivendare */
+ EVENT_UNHOLYSHADOW,
+
+ /* thane */
+ EVENT_METEOR,
+
+ /* lady */
+ EVENT_VOIDZONE,
+
+ /* sir */
+ EVENT_HOLYWRATH
};
-const uint32 NPC_HORSEMEN[] = {16064, 16065, 30549, 16063};
-const uint32 SPELL_MARK[] = {28832, 28833, 28834, 28835};
-#define SPELL_PRIMARY(i) RAID_MODE(SPELL_PRIMARY_N[i], SPELL_PRIMARY_H[i])
-const uint32 SPELL_PRIMARY_N[] = {28884, 28863, 28882, 28883};
-const uint32 SPELL_PRIMARY_H[] = {57467, 57463, 57369, 57466};
-#define SPELL_SECONDARY(i) RAID_MODE(SPELL_SECONDARY_N[i], SPELL_SECONDARY_H[i])
-const uint32 SPELL_SECONDARY_N[]= {0, 57374, 0, 57376};
-const uint32 SPELL_SECONDARY_H[]= {0, 57464, 0, 57465};
-const uint32 SPELL_PUNISH[] = {0, 57381, 0, 57377};
-#define SPELL_BERSERK 26662
-
-enum FourHorsemen
+enum Yells
{
SAY_AGGRO = 0,
- SAY_TAUNT = 1,
SAY_SPECIAL = 2,
SAY_SLAY = 3,
- SAY_DEATH = 4
-};
+ SAY_DEATH = 4,
-class boss_four_horsemen : public CreatureScript
-{
-public:
- boss_four_horsemen() : CreatureScript("boss_four_horsemen") { }
+ EMOTE_RAGECAST = 7
+};
- CreatureAI* GetAI(Creature* creature) const override
- {
- return GetInstanceAI<boss_four_horsemenAI>(creature);
- }
+static const Position baronPath[3] = { { 2552.427f, -2969.737f, 241.3021f },{ 2566.759f, -2972.535f, 241.3217f },{ 2584.32f, -2971.96f, 241.3489f } };
+static const Position thanePath[3] = { { 2540.095f, -2983.192f, 241.3344f },{ 2546.005f, -2999.826f, 241.3665f },{ 2542.697f, -3014.055f, 241.3371f } };
+static const Position ladyPath[3] = { { 2507.94f, -2961.444f, 242.4557f },{ 2488.763f, -2960.007f, 241.2757f },{ 2468.26f, -2947.499f, 241.2753f } };
+static const Position sirPath[3] = { { 2533.141f, -2922.14f, 241.2764f },{ 2525.254f, -2905.907f, 241.2761f },{ 2517.636f, -2897.253f, 241.2758f } };
- struct boss_four_horsemenAI : public BossAI
- {
- boss_four_horsemenAI(Creature* creature) : BossAI(creature, BOSS_HORSEMEN)
+struct boss_four_horsemen_baseAI : public BossAI
+{
+ public:
+ Creature* getHorsemanHandle(Horseman who) const
{
- Initialize();
- id = Horsemen(0);
- for (uint8 i = 0; i < 4; ++i)
- if (me->GetEntry() == NPC_HORSEMEN[i])
- id = Horsemen(i);
- caster = (id == HORSEMEN_LADY || id == HORSEMEN_SIR);
+ if (_which == who)
+ return me;
+ else
+ return ObjectAccessor::GetCreature(*me, instance->GetGuidData(who));
}
-
- void Initialize()
+ boss_four_horsemen_baseAI(Creature* creature, Horseman which, Position const* initialPath) :
+ BossAI(creature, BOSS_HORSEMEN), _which(which), _initialPath(initialPath), _myMovementFinished(false), _nextMovement(0), _timeDied(0), _ourMovementFinished(false)
{
- uiEventStarterGUID.Clear();
- nextWP = 0;
- punishTimer = 2000;
- nextMovementStarted = false;
- movementCompleted = false;
- movementStarted = false;
- encounterActionAttack = false;
- encounterActionReset = false;
- doDelayPunish = false;
+ if (!me->IsAlive() && instance->GetBossState(BOSS_HORSEMEN) != DONE)
+ me->SetRespawnTime(10);
}
- Horsemen id;
- ObjectGuid uiEventStarterGUID;
- uint8 nextWP;
- uint32 punishTimer;
- bool caster;
- bool nextMovementStarted;
- bool movementCompleted;
- bool movementStarted;
- bool encounterActionAttack;
- bool encounterActionReset;
- bool doDelayPunish;
-
- void Reset() override
+ uint32 GetData(uint32 type) const override
{
- if (!encounterActionReset)
- DoEncounteraction(NULL, false, true, false);
-
- instance->SetData(DATA_HORSEMEN0 + id, NOT_STARTED);
-
- me->SetReactState(REACT_AGGRESSIVE);
- Initialize();
- _Reset();
+ switch (type)
+ {
+ case DATA_MOVEMENT_FINISHED:
+ return _myMovementFinished ? 1 : 0;
+ case DATA_DEATH_TIME:
+ return _timeDied;
+ case DATA_HORSEMEN_IS_TIMED_KILL:
+ {
+ uint32 minTime = 0, maxTime = 0;
+ for (Horseman boss : horsemen)
+ if (Creature* cBoss = getHorsemanHandle(boss))
+ {
+ uint32 deathTime = cBoss->AI()->GetData(DATA_DEATH_TIME);
+ if (!deathTime)
+ {
+ TC_LOG_WARN("scripts", "FourHorsemenAI: Checking for achievement credit but horseman %s is reporting not dead", cBoss->GetName().c_str());
+ return 0;
+ }
+ if (!minTime || deathTime < minTime)
+ minTime = deathTime;
+ if (!maxTime || deathTime > maxTime)
+ maxTime = deathTime;
+ }
+ else
+ {
+ TC_LOG_WARN("scripts", "FourHorsemenAI: Checking for achievement credit but horseman with id %u is not present", uint32(boss));
+ return 0;
+ }
+ return (getMSTimeDiff(minTime, maxTime) <= 15 * IN_MILLISECONDS) ? 1 : 0;
+ }
+ default:
+ return 0;
+ }
}
- bool DoEncounteraction(Unit* who, bool attack, bool reset, bool checkAllDead)
+ void DoAction(int32 action) override
{
- Creature* Thane = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_THANE));
- Creature* Lady = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_LADY));
- Creature* Baron = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_BARON));
- Creature* Sir = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_SIR));
+ switch (action)
+ {
+ case ACTION_BEGIN_MOVEMENT:
+ me->GetMotionMaster()->MovePoint(1, _initialPath[0], true);
+ break;
+ case ACTION_BEGIN_FIGHTING:
+ if (_ourMovementFinished)
+ break;
+ me->SetCombatPulseDelay(5);
+ BeginFighting();
+ _ourMovementFinished = true;
+ break;
+ }
+ }
- if (Thane && Lady && Baron && Sir)
+ void CheckIsMovementFinished()
+ {
+ for (Horseman boss : horsemen)
{
- if (attack && who)
+ if (Creature* cBoss = getHorsemanHandle(boss))
{
- ENSURE_AI(boss_four_horsemen::boss_four_horsemenAI, Thane->AI())->encounterActionAttack = true;
- ENSURE_AI(boss_four_horsemen::boss_four_horsemenAI, Lady->AI())->encounterActionAttack = true;
- ENSURE_AI(boss_four_horsemen::boss_four_horsemenAI, Baron->AI())->encounterActionAttack = true;
- ENSURE_AI(boss_four_horsemen::boss_four_horsemenAI, Sir->AI())->encounterActionAttack = true;
-
- ENSURE_AI(boss_four_horsemen::boss_four_horsemenAI, Thane->AI())->AttackStart(who);
- ENSURE_AI(boss_four_horsemen::boss_four_horsemenAI, Lady->AI())->AttackStart(who);
- ENSURE_AI(boss_four_horsemen::boss_four_horsemenAI, Baron->AI())->AttackStart(who);
- ENSURE_AI(boss_four_horsemen::boss_four_horsemenAI, Sir->AI())->AttackStart(who);
+ if (cBoss->IsAlive() && !cBoss->AI()->GetData(DATA_MOVEMENT_FINISHED))
+ return;
}
-
- if (reset)
+ else
{
- if (instance->GetBossState(BOSS_HORSEMEN) != NOT_STARTED)
- {
- if (!Thane->IsAlive())
- Thane->Respawn();
-
- if (!Lady->IsAlive())
- Lady->Respawn();
+ TC_LOG_WARN("scripts", "FourHorsemenAI: Checking if movement is finished but horseman with id %u is not present", uint32(boss));
+ ResetEncounter();
+ return;
+ }
+ }
- if (!Baron->IsAlive())
- Baron->Respawn();
+ for (Horseman boss : horsemen)
+ if (Creature* cBoss = getHorsemanHandle(boss))
+ cBoss->AI()->DoAction(ACTION_BEGIN_FIGHTING);
+ }
- if (!Sir->IsAlive())
- Sir->Respawn();
+ void BeginEncounter()
+ {
+ if (instance->GetBossState(BOSS_HORSEMEN) == IN_PROGRESS)
+ return;
+ if (!instance->CheckRequiredBosses(BOSS_HORSEMEN))
+ {
+ ResetEncounter();
+ return;
+ }
+ instance->SetBossState(BOSS_HORSEMEN, IN_PROGRESS);
+ Map::PlayerList const &players = me->GetMap()->GetPlayers();
+ if (players.isEmpty()) // sanity check
+ ResetEncounter();
- ENSURE_AI(boss_four_horsemen::boss_four_horsemenAI, Thane->AI())->encounterActionReset = true;
- ENSURE_AI(boss_four_horsemen::boss_four_horsemenAI, Lady->AI())->encounterActionReset = true;
- ENSURE_AI(boss_four_horsemen::boss_four_horsemenAI, Baron->AI())->encounterActionReset = true;
- ENSURE_AI(boss_four_horsemen::boss_four_horsemenAI, Sir->AI())->encounterActionReset = true;
+ for (Horseman boss : horsemen)
+ {
+ if (Creature* cBoss = getHorsemanHandle(boss))
+ {
+ if (!cBoss->IsAlive())
+ {
+ ResetEncounter();
+ return;
+ }
+ cBoss->SetReactState(REACT_PASSIVE);
+ cBoss->AttackStop(); // clear initial target that was set on enter combat
+ cBoss->setActive(true);
- ENSURE_AI(boss_four_horsemen::boss_four_horsemenAI, Thane->AI())->EnterEvadeMode();
- ENSURE_AI(boss_four_horsemen::boss_four_horsemenAI, Lady->AI())->EnterEvadeMode();
- ENSURE_AI(boss_four_horsemen::boss_four_horsemenAI, Baron->AI())->EnterEvadeMode();
- ENSURE_AI(boss_four_horsemen::boss_four_horsemenAI, Sir->AI())->EnterEvadeMode();
+ for (Map::PlayerList::const_iterator it = players.begin(); it != players.end(); ++it)
+ {
+ if (Player* player = it->GetSource())
+ {
+ if (player->IsGameMaster())
+ continue;
+
+ if (player->IsAlive())
+ {
+ cBoss->AddThreat(player, 0.0f);
+ cBoss->SetInCombatWith(player);
+ player->SetInCombatWith(cBoss);
+ }
+ }
}
- }
- if (checkAllDead)
- return !Thane->IsAlive() && !Lady->IsAlive() && !Baron->IsAlive() && !Sir->IsAlive();
+ /* Why do the Four Horsemen run to opposite corners of the room when engaged? *
+ * They saw all the mobs leading up to them being AoE'd down and made a judgment call. */
+ cBoss->AI()->DoAction(ACTION_BEGIN_MOVEMENT);
+ }
+ else
+ {
+ TC_LOG_WARN("scripts", "FourHorsemenAI: Encounter starting but horseman with id %u is not present", uint32(boss));
+ ResetEncounter();
+ return;
+ }
}
- return false;
}
- void BeginFourHorsemenMovement()
+ void ResetEncounter()
{
- movementStarted = true;
- me->SetReactState(REACT_PASSIVE);
- me->SetWalk(false);
- me->SetSpeed(MOVE_RUN, me->GetSpeedRate(MOVE_RUN), true);
-
- switch (id)
+ if (instance->GetBossState(BOSS_HORSEMEN) == NOT_STARTED || instance->GetBossState(BOSS_HORSEMEN) == DONE)
+ return;
+ instance->SetBossState(BOSS_HORSEMEN, NOT_STARTED);
+ for (Horseman boss : horsemen)
{
- case HORSEMEN_THANE:
- me->GetMotionMaster()->MovePoint(0, WaypointPositions[0]);
- break;
- case HORSEMEN_LADY:
- me->GetMotionMaster()->MovePoint(3, WaypointPositions[3]);
- break;
- case HORSEMEN_BARON:
- me->GetMotionMaster()->MovePoint(6, WaypointPositions[6]);
- break;
- case HORSEMEN_SIR:
- me->GetMotionMaster()->MovePoint(9, WaypointPositions[9]);
- break;
+ if (Creature* cBoss = getHorsemanHandle(boss))
+ {
+ cBoss->DespawnOrUnsummon(0);
+ cBoss->SetRespawnTime(15);
+ }
+ else
+ {
+ TC_LOG_WARN("scripts", "FourHorsemenAI: Encounter resetting but horseman with id %u is not present", uint32(boss));
+ }
}
}
- void MovementInform(uint32 type, uint32 point) override
+ void EncounterCleared()
{
- if (type != POINT_MOTION_TYPE)
+ if (instance->GetBossState(BOSS_HORSEMEN) == DONE)
return;
+ instance->SetBossState(BOSS_HORSEMEN, DONE);
+ //instance->DoUpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_BE_SPELL_TARGET, SPELL_ENCOUNTER_CREDIT);
+ DoCastAOE(SPELL_ENCOUNTER_CREDIT, true);
+ }
- if (point == 2 || point == 5 || point == 8 || point == 11)
- {
- movementCompleted = true;
- me->SetReactState(REACT_AGGRESSIVE);
+ void EnterCombat(Unit* /*who*/) override
+ {
+ if (instance->GetBossState(BOSS_HORSEMEN) != NOT_STARTED) // another horseman already did it
+ return;
+ Talk(SAY_AGGRO);
+ BeginEncounter();
+ }
+
+ void EnterEvadeMode(EvadeReason /*why*/) override
+ {
+ ResetEncounter();
+ }
+
+ void Reset() override
+ {
+ if (!me->IsAlive())
+ return;
+ _myMovementFinished = false;
+ _nextMovement = 0;
+ _timeDied = 0;
+ _ourMovementFinished = false;
+ me->SetReactState(REACT_AGGRESSIVE);
+ SetCombatMovement(false);
+ me->SetCombatPulseDelay(0);
+ me->ResetLootMode();
+ events.Reset();
+ summons.DespawnAll();
+ }
- Unit* eventStarter = ObjectAccessor::GetUnit(*me, uiEventStarterGUID);
+ void KilledUnit(Unit* victim) override
+ {
+ if (victim->GetTypeId() == TYPEID_PLAYER)
+ Talk(SAY_SLAY, victim);
+ }
+
+ void JustDied(Unit* /*killer*/) override
+ {
+ if (instance->GetBossState(BOSS_HORSEMEN) != IN_PROGRESS) // necessary in case a horseman gets one-shot
+ {
+ BeginEncounter();
+ return;
+ }
- if (eventStarter && me->IsValidAttackTarget(eventStarter))
- AttackStart(eventStarter);
- else if (!UpdateVictim())
+ Talk(SAY_DEATH);
+ _timeDied = getMSTime();
+ for (Horseman boss : horsemen)
+ {
+ if (Creature* cBoss = getHorsemanHandle(boss))
{
- EnterEvadeMode();
- return;
+ if (cBoss->IsAlive())
+ {
+ // in case a horseman dies while moving (unlikely but possible especially in non-335 branch)
+ CheckIsMovementFinished();
+ return;
+ }
}
-
- if (caster)
+ else
{
- me->GetMotionMaster()->Clear();
- me->GetMotionMaster()->MoveIdle();
+ TC_LOG_WARN("scripts", "FourHorsemenAI: %s died but horseman with id %u is not present", me->GetName().c_str(), uint32(boss));
+ ResetEncounter();
}
-
- return;
}
- nextMovementStarted = false;
- nextWP = point + 1;
+ EncounterCleared();
}
- // switch to "who" if nearer than current target.
- void SelectNearestTarget(Unit* who)
+ void MovementInform(uint32 type, uint32 i) override
{
- if (me->GetVictim() && me->GetDistanceOrder(who, me->GetVictim()) && me->IsValidAttackTarget(who))
+ if (type != POINT_MOTION_TYPE)
+ return;
+ if (i < 3)
+ _nextMovement = i; // delay to next updateai to prevent it from instantly expiring
+ else
{
- me->getThreatManager().modifyThreatPercent(me->GetVictim(), -100);
- me->AddThreat(who, 1000000.0f);
+ _myMovementFinished = true;
+ CheckIsMovementFinished();
}
}
- void MoveInLineOfSight(Unit* who) override
-
+ void UpdateAI(uint32 diff) override
{
- BossAI::MoveInLineOfSight(who);
- if (caster)
- SelectNearestTarget(who);
+ if (_nextMovement)
+ {
+ me->GetMotionMaster()->MovePoint(_nextMovement + 1, _initialPath[_nextMovement], true);
+ _nextMovement = 0;
+ }
+ _UpdateAI(diff);
}
- void AttackStart(Unit* who) override
+ virtual void BeginFighting() = 0;
+ virtual void _UpdateAI(uint32 /*diff*/) = 0;
+
+ private:
+ const Horseman _which;
+ const Position* _initialPath;
+ bool _myMovementFinished;
+ uint8 _nextMovement;
+ uint32 _timeDied;
+ protected:
+ bool _ourMovementFinished;
+};
+
+class boss_four_horsemen_baron : public CreatureScript
+{
+ public:
+ boss_four_horsemen_baron() : CreatureScript("boss_four_horsemen_baron") { }
+
+ struct boss_four_horsemen_baronAI : public boss_four_horsemen_baseAI
{
- if (!movementCompleted && !movementStarted)
+ boss_four_horsemen_baronAI(Creature* creature) : boss_four_horsemen_baseAI(creature, BARON, baronPath) { }
+ void BeginFighting() override
+ {
+ SetCombatMovement(true);
+ me->SetReactState(REACT_AGGRESSIVE);
+ ThreatManager& threat = me->getThreatManager();
+ if (threat.isThreatListEmpty())
+ {
+ if (Unit* nearest = me->SelectNearestPlayer(5000.0f))
+ {
+ me->AddThreat(nearest, 1.0f);
+ AttackStart(nearest);
+ }
+ else
+ ResetEncounter();
+ }
+ else
+ AttackStart(threat.getHostilTarget());
+
+ events.ScheduleEvent(EVENT_BERSERK, 10 * MINUTE * IN_MILLISECONDS);
+ events.ScheduleEvent(EVENT_MARK, 24 * IN_MILLISECONDS);
+ events.ScheduleEvent(EVENT_UNHOLYSHADOW, urandms(3,7));
+ }
+
+ void _UpdateAI(uint32 diff) override
{
- uiEventStarterGUID = who->GetGUID();
- BeginFourHorsemenMovement();
+ if (!_ourMovementFinished || !UpdateVictim())
+ return;
+ events.Update(diff);
+
+ while (uint32 eventId = events.ExecuteEvent())
+ {
+ switch (eventId)
+ {
+ case EVENT_BERSERK:
+ DoCastAOE(SPELL_BERSERK, true);
+ break;
+ case EVENT_MARK:
+ DoCastAOE(SPELL_BARON_MARK, true);
+ events.ScheduleEvent(EVENT_MARK, 12 * IN_MILLISECONDS);
+ break;
+ case EVENT_UNHOLYSHADOW:
+ DoCastVictim(SPELL_HELPER_UNHOLY_SHADOW);
+ events.ScheduleEvent(EVENT_UNHOLYSHADOW, urandms(10,30));
+ break;
+ }
+ }
- if (!encounterActionAttack)
- DoEncounteraction(who, true, false, false);
+ if (me->HasUnitState(UNIT_STATE_CASTING))
+ return;
+ DoMeleeAttackIfReady();
}
- else if (movementCompleted && movementStarted)
+
+ void SpellHitTarget(Unit* /*target*/, SpellInfo const* spell) override
{
- if (caster)
- me->Attack(who, false);
- else
- BossAI::AttackStart(who);
+ if (spell->Id == SPELL_HELPER_UNHOLY_SHADOW)
+ Talk(SAY_SPECIAL);
}
- }
+ };
- void KilledUnit(Unit* /*victim*/) override
+ CreatureAI* GetAI(Creature* creature) const override
{
- if (!(rand32() % 5))
- Talk(SAY_SLAY);
+ return GetInstanceAI<boss_four_horsemen_baronAI>(creature);
}
+};
- void JustDied(Unit* /*killer*/) override
- {
- events.Reset();
- summons.DespawnAll();
+class boss_four_horsemen_thane : public CreatureScript
+{
+ public:
+ boss_four_horsemen_thane() : CreatureScript("boss_four_horsemen_thane") { }
- instance->SetData(DATA_HORSEMEN0 + id, DONE);
+ struct boss_four_horsemen_thaneAI : public boss_four_horsemen_baseAI
+ {
+ boss_four_horsemen_thaneAI(Creature* creature) : boss_four_horsemen_baseAI(creature, THANE, thanePath), _shouldSay(true) { }
+ void BeginFighting() override
+ {
+ SetCombatMovement(true);
+ me->SetReactState(REACT_AGGRESSIVE);
+ ThreatManager& threat = me->getThreatManager();
+ if (threat.isThreatListEmpty())
+ {
+ if (Unit* nearest = me->SelectNearestPlayer(5000.0f))
+ {
+ me->AddThreat(nearest, 1.0f);
+ AttackStart(nearest);
+ }
+ else
+ ResetEncounter();
+ }
+ else
+ AttackStart(threat.getHostilTarget());
- if (DoEncounteraction(NULL, false, false, true))
+ events.ScheduleEvent(EVENT_BERSERK, 10 * MINUTE * IN_MILLISECONDS);
+ events.ScheduleEvent(EVENT_MARK, 24 * IN_MILLISECONDS);
+ events.ScheduleEvent(EVENT_METEOR, urandms(10,15));
+ }
+ void _UpdateAI(uint32 diff) override
{
- instance->SetBossState(BOSS_HORSEMEN, DONE);
- instance->SaveToDB();
+ if (!_ourMovementFinished || !UpdateVictim())
+ return;
+ events.Update(diff);
+
+ while (uint32 eventId = events.ExecuteEvent())
+ {
+ switch (eventId)
+ {
+ case EVENT_BERSERK:
+ DoCastAOE(SPELL_BERSERK, true);
+ break;
+ case EVENT_MARK:
+ DoCastAOE(SPELL_THANE_MARK, true);
+ events.ScheduleEvent(EVENT_MARK, 12 * IN_MILLISECONDS);
+ break;
+ case EVENT_METEOR:
+ if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, 20.0f, true))
+ {
+ DoCast(target, SPELL_HELPER_METEOR);
+ _shouldSay = true;
+ }
+ events.ScheduleEvent(EVENT_METEOR, urandms(13,17));
+ break;
+ }
+ }
+
+ if (me->HasUnitState(UNIT_STATE_CASTING))
+ return;
+ DoMeleeAttackIfReady();
+ }
- // Achievements related to the 4-horsemen are given through spell 59450 which does not exist.
- // There is thus no way it can be given by casting the spell on the players.
- instance->DoUpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_BE_SPELL_TARGET, 59450);
+ void SpellHitTarget(Unit* /*target*/, SpellInfo const* spell) override
+ {
+ if (_shouldSay && spell->Id == SPELL_HELPER_METEOR)
+ {
+ Talk(SAY_SPECIAL);
+ _shouldSay = false;
+ }
}
- Talk(SAY_DEATH);
- }
+ private:
+ bool _shouldSay; // throttle to make sure we only talk on first target hit by meteor
+ };
- void EnterCombat(Unit* /*who*/) override
+ CreatureAI* GetAI(Creature* creature) const override
{
- _EnterCombat();
- Talk(SAY_AGGRO);
-
- events.ScheduleEvent(EVENT_MARK, 15000);
- events.ScheduleEvent(EVENT_CAST, 20000 + rand32() % 5000);
- events.ScheduleEvent(EVENT_BERSERK, 15*100*1000);
+ return GetInstanceAI<boss_four_horsemen_thaneAI>(creature);
}
+};
- void UpdateAI(uint32 diff) override
+class boss_four_horsemen_lady : public CreatureScript
+{
+ public:
+ boss_four_horsemen_lady() : CreatureScript("boss_four_horsemen_lady") { }
+
+ struct boss_four_horsemen_ladyAI : public boss_four_horsemen_baseAI
{
- if (nextWP && movementStarted && !movementCompleted && !nextMovementStarted)
+ boss_four_horsemen_ladyAI(Creature* creature) : boss_four_horsemen_baseAI(creature, LADY, ladyPath) { }
+ void BeginFighting() override
{
- nextMovementStarted = true;
- me->GetMotionMaster()->MovePoint(nextWP, WaypointPositions[nextWP]);
+ events.ScheduleEvent(EVENT_BERSERK, 10 * MINUTE * IN_MILLISECONDS);
+ events.ScheduleEvent(EVENT_MARK, 24 * IN_MILLISECONDS);
+ events.ScheduleEvent(EVENT_VOIDZONE, urandms(5,10));
}
- if (!UpdateVictim() || !CheckInRoom() || !movementCompleted)
- return;
+ void _UpdateAI(uint32 diff) override
+ {
+ if (!me->IsInCombat())
+ return;
+ if (!_ourMovementFinished)
+ return;
+ if (me->getThreatManager().isThreatListEmpty())
+ {
+ EnterEvadeMode(EVADE_REASON_NO_HOSTILES);
+ return;
+ }
- events.Update(diff);
+ events.Update(diff);
- if (me->HasUnitState(UNIT_STATE_CASTING))
- return;
+ while (uint32 eventId = events.ExecuteEvent())
+ {
+ switch (eventId)
+ {
+ case EVENT_BERSERK:
+ DoCastAOE(SPELL_BERSERK, true);
+ break;
+ case EVENT_MARK:
+ DoCastAOE(SPELL_LADY_MARK, true);
+ events.ScheduleEvent(EVENT_MARK, 15 * IN_MILLISECONDS);
+ break;
+ case EVENT_VOIDZONE:
+ if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, 45.0f, true))
+ {
+ DoCast(target, SPELL_HELPER_VOID_ZONE, true);
+ Talk(SAY_SPECIAL);
+ }
+ events.ScheduleEvent(EVENT_VOIDZONE, urandms(12, 18));
+ break;
+ }
+ }
+
+ if (me->HasUnitState(UNIT_STATE_CASTING))
+ return;
+
+ if (Unit* target = SelectTarget(SELECT_TARGET_NEAREST, 0, 45.0f, true))
+ DoCast(target, SPELL_HELPER_SHADOW_BOLT);
+ else
+ {
+ DoCastAOE(SPELL_UNYIELDING_PAIN);
+ Talk(EMOTE_RAGECAST);
+ }
+ }
+ };
+
+ CreatureAI* GetAI(Creature* creature) const override
+ {
+ return GetInstanceAI<boss_four_horsemen_ladyAI>(creature);
+ }
+};
+
+class boss_four_horsemen_sir : public CreatureScript
+{
+ public:
+ boss_four_horsemen_sir() : CreatureScript("boss_four_horsemen_sir") { }
+
+ struct boss_four_horsemen_sirAI : public boss_four_horsemen_baseAI
+ {
+ boss_four_horsemen_sirAI(Creature* creature) : boss_four_horsemen_baseAI(creature, SIR, sirPath), _shouldSay(true) { }
+ void BeginFighting() override
+ {
+ events.ScheduleEvent(EVENT_BERSERK, 10 * MINUTE * IN_MILLISECONDS);
+ events.ScheduleEvent(EVENT_MARK, 24 * IN_MILLISECONDS);
+ events.ScheduleEvent(EVENT_HOLYWRATH, urandms(13,18));
+ }
- while (uint32 eventId = events.ExecuteEvent())
+ void _UpdateAI(uint32 diff) override
{
- switch (eventId)
+ if (!me->IsInCombat())
+ return;
+ if (!_ourMovementFinished)
+ return;
+ if (me->getThreatManager().isThreatListEmpty())
{
- case EVENT_MARK:
- if (!(rand32() % 5))
- Talk(SAY_SPECIAL);
- DoCastAOE(SPELL_MARK[id]);
- events.ScheduleEvent(EVENT_MARK, 15000);
- break;
- case EVENT_CAST:
- if (!(rand32() % 5))
- Talk(SAY_TAUNT);
+ EnterEvadeMode(EVADE_REASON_NO_HOSTILES);
+ return;
+ }
- if (caster)
- {
- if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, 45.0f, true))
- DoCast(target, SPELL_PRIMARY(id));
- }
- else
- DoCastVictim(SPELL_PRIMARY(id));
+ events.Update(diff);
- events.ScheduleEvent(EVENT_CAST, 15000);
- break;
- case EVENT_BERSERK:
- Talk(SAY_SPECIAL);
- DoCast(me, EVENT_BERSERK);
- break;
+ while (uint32 eventId = events.ExecuteEvent())
+ {
+ switch (eventId)
+ {
+ case EVENT_BERSERK:
+ DoCastAOE(SPELL_BERSERK, true);
+ break;
+ case EVENT_MARK:
+ DoCastAOE(SPELL_SIR_MARK, true);
+ events.ScheduleEvent(EVENT_MARK, 15 * IN_MILLISECONDS);
+ break;
+ case EVENT_HOLYWRATH:
+ if (Unit* target = SelectTarget(SELECT_TARGET_NEAREST, 0, 45.0f, true))
+ {
+ DoCast(target, SPELL_HELPER_HOLY_WRATH, true);
+ _shouldSay = true;
+ }
+ events.ScheduleEvent(EVENT_HOLYWRATH, urandms(10,18));
+ break;
+ }
+ }
+
+ if (me->HasUnitState(UNIT_STATE_CASTING))
+ return;
+
+ if (Unit* target = SelectTarget(SELECT_TARGET_NEAREST, 0, 45.0f, true))
+ DoCast(target, SPELL_HELPER_HOLY_BOLT);
+ else
+ {
+ DoCastAOE(SPELL_CONDEMNATION);
+ Talk(EMOTE_RAGECAST);
}
}
- if (punishTimer <= diff)
+ void SpellHitTarget(Unit* /*target*/, SpellInfo const* spell) override
{
- if (doDelayPunish)
+ if (_shouldSay && spell->Id == SPELL_HELPER_HOLY_WRATH)
{
- DoCastAOE(SPELL_PUNISH[id], true);
- doDelayPunish = false;
+ Talk(SAY_SPECIAL);
+ _shouldSay = false;
}
- punishTimer = 2000;
- } else punishTimer -= diff;
+ }
- if (!caster)
- DoMeleeAttackIfReady();
- else if ((!DoSpellAttackIfReady(SPELL_SECONDARY(id)) || !me->IsWithinLOSInMap(me->GetVictim())) && movementCompleted && !doDelayPunish)
- doDelayPunish = true;
- }
- };
+ private:
+ bool _shouldSay; // throttle to make sure we only talk on first target hit by holy wrath
+ };
+ CreatureAI* GetAI(Creature* creature) const override
+ {
+ return GetInstanceAI<boss_four_horsemen_sirAI>(creature);
+ }
};
class spell_four_horsemen_mark : public SpellScriptLoader
@@ -450,6 +756,9 @@ class spell_four_horsemen_mark : public SpellScriptLoader
void AddSC_boss_four_horsemen()
{
- new boss_four_horsemen();
+ new boss_four_horsemen_baron();
+ new boss_four_horsemen_thane();
+ new boss_four_horsemen_lady();
+ new boss_four_horsemen_sir();
new spell_four_horsemen_mark();
}
diff --git a/src/server/scripts/Northrend/Naxxramas/instance_naxxramas.cpp b/src/server/scripts/Northrend/Naxxramas/instance_naxxramas.cpp
index 22171c6a1cd..091bb728060 100644
--- a/src/server/scripts/Northrend/Naxxramas/instance_naxxramas.cpp
+++ b/src/server/scripts/Northrend/Naxxramas/instance_naxxramas.cpp
@@ -91,15 +91,6 @@ DoorData const doorData[] =
{ 0, 0, DOOR_TYPE_ROOM }
};
-MinionData const minionData[] =
-{
- { NPC_SIR, BOSS_HORSEMEN },
- { NPC_THANE, BOSS_HORSEMEN },
- { NPC_LADY, BOSS_HORSEMEN },
- { NPC_BARON, BOSS_HORSEMEN },
- { 0, 0, }
-};
-
ObjectData const objectData[] =
{
{ GO_NAXX_PORTAL_ARACHNID, DATA_NAXX_PORTAL_ARACHNID },
@@ -152,11 +143,8 @@ class instance_naxxramas : public InstanceMapScript
SetBossNumber(EncounterCount);
LoadBossBoundaries(boundaries);
LoadDoorData(doorData);
- LoadMinionData(minionData);
LoadObjectData(nullptr, objectData);
- minHorsemenDiedTime = 0;
- maxHorsemenDiedTime = 0;
AbominationCount = 0;
hadAnubRekhanGreet = false;
hadFaerlinaGreet = false;
@@ -218,13 +206,6 @@ class instance_naxxramas : public InstanceMapScript
default:
break;
}
-
- AddMinion(creature, true);
- }
-
- void OnCreatureRemove(Creature* creature) override
- {
- AddMinion(creature, false);
}
void ProcessEvent(WorldObject* /*source*/, uint32 eventId) override
@@ -354,25 +335,6 @@ class instance_naxxramas : public InstanceMapScript
if (GameObject* gate = instance->GetGameObject(GothikGateGUID))
gate->SetGoState(GOState(value));
break;
- case DATA_HORSEMEN0:
- case DATA_HORSEMEN1:
- case DATA_HORSEMEN2:
- case DATA_HORSEMEN3:
- if (value == NOT_STARTED)
- {
- minHorsemenDiedTime = 0;
- maxHorsemenDiedTime = 0;
- }
- else if (value == DONE)
- {
- time_t now = time(NULL);
-
- if (minHorsemenDiedTime == 0)
- minHorsemenDiedTime = now;
-
- maxHorsemenDiedTime = now;
- }
- break;
case DATA_ABOMINATION_KILLED:
AbominationCount = value;
break;
@@ -653,13 +615,11 @@ class instance_naxxramas : public InstanceMapScript
{
switch (criteria_id)
{
- case 7600: // Criteria for achievement 2176: And They Would All Go Down Together 15sec of each other 10-man
- if (Difficulty(instance->GetSpawnMode()) == DIFFICULTY_10_N && (maxHorsemenDiedTime - minHorsemenDiedTime) < 15)
- return true;
- return false;
- case 7601: // Criteria for achievement 2177: And They Would All Go Down Together 15sec of each other 25-man
- if (Difficulty(instance->GetSpawnMode()) == DIFFICULTY_25_N && (maxHorsemenDiedTime - minHorsemenDiedTime) < 15)
- return true;
+ // And They Would All Go Down Together (kill 4HM within 15sec of each other)
+ case 7600: // 25-man
+ case 7601: // 10-man
+ if (Creature* baron = instance->GetCreature(BaronGUID)) // it doesn't matter which one we use, really
+ return (baron->AI()->GetData(DATA_HORSEMEN_CHECK_ACHIEVEMENT_CREDIT) == 1u);
return false;
// Difficulty checks are done on DB.
// Criteria for achievement 2186: The Immortal (25-man)
@@ -706,8 +666,6 @@ class instance_naxxramas : public InstanceMapScript
ObjectGuid BaronGUID;
ObjectGuid SirGUID;
ObjectGuid HorsemenChestGUID;
- time_t minHorsemenDiedTime;
- time_t maxHorsemenDiedTime;
/* The Construct Quarter */
// Thaddius
diff --git a/src/server/scripts/Northrend/Naxxramas/naxxramas.h b/src/server/scripts/Northrend/Naxxramas/naxxramas.h
index d5adf2069cb..c0caa86e93f 100644
--- a/src/server/scripts/Northrend/Naxxramas/naxxramas.h
+++ b/src/server/scripts/Northrend/Naxxramas/naxxramas.h
@@ -50,10 +50,7 @@ enum Data
DATA_HAD_FAERLINA_GREET,
DATA_HAD_THADDIUS_GREET,
- DATA_HORSEMEN0,
- DATA_HORSEMEN1,
- DATA_HORSEMEN2,
- DATA_HORSEMEN3,
+ DATA_HORSEMEN_CHECK_ACHIEVEMENT_CREDIT,
DATA_ABOMINATION_KILLED,
DATA_NAXX_PORTAL_ARACHNID,