diff options
Diffstat (limited to 'src/server/scripts')
3 files changed, 572 insertions, 314 deletions
diff --git a/src/server/scripts/Northrend/Naxxramas/boss_four_horsemen.cpp b/src/server/scripts/Northrend/Naxxramas/boss_four_horsemen.cpp index 3e7de89edd9..869a3ec3921 100644 --- a/src/server/scripts/Northrend/Naxxramas/boss_four_horsemen.cpp +++ b/src/server/scripts/Northrend/Naxxramas/boss_four_horsemen.cpp @@ -15,382 +15,680 @@   * 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 +enum Actions  { -    EVENT_NONE, -    EVENT_MARK, -    EVENT_CAST, -    EVENT_BERSERK, +    ACTION_BEGIN_MOVEMENT = 1, +    ACTION_BEGIN_FIGHTING +}; + +enum HorsemenData +{ +    DATA_MOVEMENT_FINISHED = DATA_HORSEMEN_CHECK_ACHIEVEMENT_CREDIT + 1, // make sure we don't conflict with the one from naxxramas.h +    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_CHECK_ACHIEVEMENT_CREDIT: +                { +                    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(); +        } + +        void KilledUnit(Unit* victim) override +        { +            if (victim->GetTypeId() == TYPEID_PLAYER) +                Talk(SAY_SLAY, victim); +        } -                Unit* eventStarter = ObjectAccessor::GetUnit(*me, uiEventStarterGUID); +        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              { -                uiEventStarterGUID = who->GetGUID(); -                BeginFourHorsemenMovement(); +                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 (!encounterActionAttack) -                    DoEncounteraction(who, true, false, false); +                events.ScheduleEvent(EVENT_BERSERK, 10 * MINUTE * IN_MILLISECONDS); +                events.ScheduleEvent(EVENT_MARK, 24 * IN_MILLISECONDS); +                events.ScheduleEvent(EVENT_UNHOLYSHADOW, urandms(3,7));              } -            else if (movementCompleted && movementStarted) + +            void _UpdateAI(uint32 diff) override              { -                if (caster) -                    me->Attack(who, false); -                else -                    BossAI::AttackStart(who); +                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_UNHOLY_SHADOW); +                            events.ScheduleEvent(EVENT_UNHOLYSHADOW, urandms(10,30)); +                            break; +                    } +                } + +                if (me->HasUnitState(UNIT_STATE_CASTING)) +                    return; +                DoMeleeAttackIfReady();              } -        } -        void KilledUnit(Unit* /*victim*/) override +            void SpellHitTarget(Unit* /*target*/, SpellInfo const* spell) override +            { +                if (spell->Id == SPELL_UNHOLY_SHADOW) +                    Talk(SAY_SPECIAL); +            } +        }; + +        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_METEOR); +                                _shouldSay = true; +                            } +                            events.ScheduleEvent(EVENT_METEOR, urandms(13,17)); +                            break; +                    } +                } -                // 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); +                if (me->HasUnitState(UNIT_STATE_CASTING)) +                    return; +                DoMeleeAttackIfReady();              } -            Talk(SAY_DEATH); -        } +            void SpellHitTarget(Unit* /*target*/, SpellInfo const* spell) override +            { +                if (_shouldSay && spell->Id == SPELL_METEOR) +                { +                    Talk(SAY_SPECIAL); +                    _shouldSay = false; +                } +            } -        void EnterCombat(Unit* /*who*/) override -        { -            _EnterCombat(); -            Talk(SAY_AGGRO); +            private: +                bool _shouldSay; // throttle to make sure we only talk on first target hit by meteor +        }; -            events.ScheduleEvent(EVENT_MARK, 15000); -            events.ScheduleEvent(EVENT_CAST, 20000 + rand32() % 5000); -            events.ScheduleEvent(EVENT_BERSERK, 15*100*1000); +        CreatureAI* GetAI(Creature* creature) const override +        { +            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_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_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") { } -            while (uint32 eventId = events.ExecuteEvent()) +        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              { -                switch (eventId) +                events.ScheduleEvent(EVENT_BERSERK, 10 * MINUTE * IN_MILLISECONDS); +                events.ScheduleEvent(EVENT_MARK, 24 * IN_MILLISECONDS); +                events.ScheduleEvent(EVENT_HOLYWRATH, urandms(13,18)); +            } + +            void _UpdateAI(uint32 diff) override +            { +                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_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_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_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 +748,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 f9174e5aa94..c8a4eb7fbc8 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,13 @@ 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()) == RAID_DIFFICULTY_10MAN_NORMAL && (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()) == RAID_DIFFICULTY_25MAN_NORMAL && (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 (criteria_id + instance->GetSpawnMode() == 7601) +                            return false; +                        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 +668,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,  | 
