diff options
Diffstat (limited to 'src/server/scripts')
9 files changed, 849 insertions, 384 deletions
diff --git a/src/server/scripts/EasternKingdoms/AlteracValley/boss_drekthar.cpp b/src/server/scripts/EasternKingdoms/AlteracValley/boss_drekthar.cpp index 269e115189a..d5ed2421efc 100644 --- a/src/server/scripts/EasternKingdoms/AlteracValley/boss_drekthar.cpp +++ b/src/server/scripts/EasternKingdoms/AlteracValley/boss_drekthar.cpp @@ -77,7 +77,7 @@ public:              Talk(SAY_RESPAWN);          } -        bool CheckInRoom() +        bool CheckInRoom() override          {              if (me->GetDistance2d(me->GetHomePosition().GetPositionX(), me->GetHomePosition().GetPositionY()) > 50)              { diff --git a/src/server/scripts/EasternKingdoms/AlteracValley/boss_galvangar.cpp b/src/server/scripts/EasternKingdoms/AlteracValley/boss_galvangar.cpp index 93583364ef6..238942c15f6 100644 --- a/src/server/scripts/EasternKingdoms/AlteracValley/boss_galvangar.cpp +++ b/src/server/scripts/EasternKingdoms/AlteracValley/boss_galvangar.cpp @@ -78,7 +78,7 @@ public:                  Talk(SAY_BUFF);          } -        bool CheckInRoom() +        bool CheckInRoom() override          {              if (me->GetDistance2d(me->GetHomePosition().GetPositionX(), me->GetHomePosition().GetPositionY()) > 50)              { diff --git a/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/instance_trial_of_the_crusader.cpp b/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/instance_trial_of_the_crusader.cpp index e5ac2676ad0..b1a0f0217c4 100644 --- a/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/instance_trial_of_the_crusader.cpp +++ b/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/instance_trial_of_the_crusader.cpp @@ -22,12 +22,11 @@  #include "Player.h"  #include "TemporarySummon.h" -AreaBoundary const* const mainBoundary = new CircleBoundary(Position(563.26f, 139.6f), 75.0);  BossBoundaryData const boundaries = { -    { BOSS_BEASTS, mainBoundary }, -    { BOSS_JARAXXUS, mainBoundary }, -    { BOSS_CRUSADERS, mainBoundary }, -    { BOSS_VALKIRIES, mainBoundary }, +    { BOSS_BEASTS, new CircleBoundary(Position(563.26f, 139.6f), 75.0) }, +    { BOSS_JARAXXUS, new CircleBoundary(Position(563.26f, 139.6f), 75.0) }, +    { BOSS_CRUSADERS, new CircleBoundary(Position(563.26f, 139.6f), 75.0) }, +    { BOSS_VALKIRIES, new CircleBoundary(Position(563.26f, 139.6f), 75.0) },      { BOSS_ANUBARAK, new EllipseBoundary(Position(746.0f, 135.0f), 100.0, 75.0) }  }; diff --git a/src/server/scripts/Northrend/IcecrownCitadel/boss_sindragosa.cpp b/src/server/scripts/Northrend/IcecrownCitadel/boss_sindragosa.cpp index 9bdbce81dbf..ac094588d35 100644 --- a/src/server/scripts/Northrend/IcecrownCitadel/boss_sindragosa.cpp +++ b/src/server/scripts/Northrend/IcecrownCitadel/boss_sindragosa.cpp @@ -265,7 +265,7 @@ class boss_sindragosa : public CreatureScript              {                  if (!instance->CheckRequiredBosses(DATA_SINDRAGOSA, victim->ToPlayer()))                  { -                    EnterEvadeMode(); +                    EnterEvadeMode(EVADE_REASON_SEQUENCE_BREAK);                      instance->DoCastSpellOnPlayers(LIGHT_S_HAMMER_TELEPORT);                      return;                  } @@ -276,6 +276,13 @@ class boss_sindragosa : public CreatureScript                  Talk(SAY_AGGRO);              } +            void EnterEvadeMode(EvadeReason why) override +            { +                if (_isInAirPhase && why == EVADE_REASON_BOUNDARY) +                    return; +                BossAI::EnterEvadeMode(why); +            } +              void JustReachedHome() override              {                  BossAI::JustReachedHome(); diff --git a/src/server/scripts/Northrend/Naxxramas/boss_gothik.cpp b/src/server/scripts/Northrend/Naxxramas/boss_gothik.cpp index c9684cf10cf..c875a336aaf 100644 --- a/src/server/scripts/Northrend/Naxxramas/boss_gothik.cpp +++ b/src/server/scripts/Northrend/Naxxramas/boss_gothik.cpp @@ -20,30 +20,70 @@  #include "SpellScript.h"  #include "GridNotifiers.h"  #include "CombatAI.h" +#include "AreaBoundary.h"  #include "naxxramas.h" +/* Constants */  enum Yells  { -    SAY_SPEECH                  = 0, -    SAY_KILL                    = 1, -    SAY_DEATH                   = 2, -    SAY_TELEPORT                = 3 +    SAY_INTRO_1                 = 0, +    SAY_INTRO_2                 = 1, +    SAY_INTRO_3                 = 2, +    SAY_INTRO_4                 = 3, +    SAY_PHASE_TWO               = 4, +    SAY_DEATH                   = 5, +    SAY_KILL                    = 6, + +    EMOTE_PHASE_TWO             = 7, +    EMOTE_GATE_OPENED           = 8  }; -//Gothik  enum Spells  { +    /* living trainee spells */ +    SPELL_DEATH_PLAGUE          = 55604, + +    /* living knight spells */ +    SPELL_SHADOW_MARK           = 27825, + +    /* living rider spells */ +    SPELL_SHADOW_BOLT_VOLLEY    = 27831, + +    /* spectral trainee spells */ +    SPELL_ARCANE_EXPLOSION      = 27989, + +    /* spectral knight spells */ +    SPELL_WHIRLWIND             = 56408, + +    /* spectral rider spells */ +    SPELL_DRAIN_LIFE            = 27994, +    SPELL_UNHOLY_FRENZY         = 55648, + +    /* spectral horse spells */ +    SPELL_STOMP                 = 27993, + +    /* gothik phase two spells */      SPELL_HARVEST_SOUL          = 28679,      SPELL_SHADOW_BOLT           = 29317, -    SPELL_INFORM_LIVE_TRAINEE   = 27892, -    SPELL_INFORM_LIVE_KNIGHT    = 27928, -    SPELL_INFORM_LIVE_RIDER     = 27935, -    SPELL_INFORM_DEAD_TRAINEE   = 27915, -    SPELL_INFORM_DEAD_KNIGHT    = 27931, -    SPELL_INFORM_DEAD_RIDER     = 27937, - -    SPELL_SHADOW_MARK           = 27825 +     +    /* visual spells */ +    SPELL_ANCHOR_1_TRAINEE      = 27892, +    SPELL_ANCHOR_1_DK           = 27928, +    SPELL_ANCHOR_1_RIDER        = 27935, + +    SPELL_ANCHOR_2_TRAINEE      = 27893, +    SPELL_ANCHOR_2_DK           = 27929, +    SPELL_ANCHOR_2_RIDER        = 27936, + +    SPELL_SKULLS_TRAINEE        = 27915, +    SPELL_SKULLS_DK             = 27931, +    SPELL_SKULLS_RIDER          = 27937, + +    /* teleport spells */ +    SPELL_TELEPORT_DEAD         = 28025, +    SPELL_TELEPORT_LIVE         = 28026  }; +#define SPELLHELPER_UNHOLY_FRENZY RAID_MODE<uint32>(SPELL_UNHOLY_FRENZY,27995)  enum Creatures  { @@ -53,108 +93,205 @@ enum Creatures      NPC_DEAD_TRAINEE    = 16127,      NPC_DEAD_KNIGHT     = 16148,      NPC_DEAD_RIDER      = 16150, -    NPC_DEAD_HORSE      = 16149 +    NPC_DEAD_HORSE      = 16149, + +    NPC_TRIGGER         = 16137  }; -struct Waves { uint32 entry, time, mode; }; -// wave setups are not the same in heroic and normal difficulty, -// mode is 0 only normal, 1 both and 2 only heroic -// but this is handled in DoGothikSummon function -const Waves waves[] = +enum Phases  { -    {NPC_LIVE_TRAINEE, 20000, 1}, -    {NPC_LIVE_TRAINEE, 20000, 1}, -    {NPC_LIVE_TRAINEE, 10000, 1}, -    {NPC_LIVE_KNIGHT, 10000, 1}, -    {NPC_LIVE_TRAINEE, 15000, 1}, -    {NPC_LIVE_KNIGHT, 5000, 1}, -    {NPC_LIVE_TRAINEE, 20000, 1}, -    {NPC_LIVE_TRAINEE, 0, 1}, -    {NPC_LIVE_KNIGHT, 10000, 1}, -    {NPC_LIVE_TRAINEE, 10000, 2}, -    {NPC_LIVE_RIDER, 10000, 0}, -    {NPC_LIVE_RIDER, 5000, 2}, -    {NPC_LIVE_TRAINEE, 5000, 0}, -    {NPC_LIVE_TRAINEE, 15000, 2}, -    {NPC_LIVE_KNIGHT, 15000, 0}, -    {NPC_LIVE_TRAINEE, 0, 0}, -    {NPC_LIVE_RIDER, 10000, 1}, -    {NPC_LIVE_KNIGHT, 10000, 1}, -    {NPC_LIVE_TRAINEE, 10000, 0}, -    {NPC_LIVE_RIDER, 10000, 2}, -    {NPC_LIVE_TRAINEE, 0, 2}, -    {NPC_LIVE_RIDER, 5000, 1}, -    {NPC_LIVE_TRAINEE, 0, 2}, -    {NPC_LIVE_KNIGHT, 5000, 1}, -    {NPC_LIVE_RIDER, 0, 2}, -    {NPC_LIVE_TRAINEE, 20000, 1}, -    {NPC_LIVE_RIDER, 0, 1}, -    {NPC_LIVE_KNIGHT, 0, 1}, -    {NPC_LIVE_TRAINEE, 25000, 2}, -    {NPC_LIVE_TRAINEE, 15000, 0}, -    {NPC_LIVE_TRAINEE, 25000, 0}, -    {0, 0, 1}, +    PHASE_ONE = 1, +    PHASE_TWO = 2  }; -#define POS_Y_GATE  -3360.78f -#define POS_Y_WEST  -3285.0f -#define POS_Y_EAST  -3434.0f -#define POS_X_NORTH  2750.49f -#define POS_X_SOUTH  2633.84f - -#define IN_LIVE_SIDE(who) (who->GetPositionY() < POS_Y_GATE) -  enum Events  { -    EVENT_NONE, +    EVENT_INTRO_2 = 1, +    EVENT_INTRO_3, +    EVENT_INTRO_4, +    EVENT_PHASE_TWO,      EVENT_SUMMON, +    EVENT_DOORS_UNLOCK, +    EVENT_TELEPORT,      EVENT_HARVEST,      EVENT_BOLT, -    EVENT_TELEPORT -}; -enum Pos -{ -   POS_LIVE = 6, -   POS_DEAD = 5 +    EVENT_RESUME_ATTACK  }; -const Position PosSummonLive[POS_LIVE] = +enum Actions  { -    {2669.7f, -3428.76f, 268.56f, 1.6f}, -    {2692.1f, -3428.76f, 268.56f, 1.6f}, -    {2714.4f, -3428.76f, 268.56f, 1.6f}, -    {2669.7f, -3431.67f, 268.56f, 1.6f}, -    {2692.1f, -3431.67f, 268.56f, 1.6f}, -    {2714.4f, -3431.67f, 268.56f, 1.6f}, +    ACTION_GATE_OPENED = 1, +    ACTION_MINION_EVADE, +    ACTION_ACQUIRE_TARGET  }; -const Position PosSummonDead[POS_DEAD] = + +/* Room side checking logic */ +static AreaBoundary* const livingSide = new RectangleBoundary(2633.84f, 2750.49f, -3434.0f, -3360.78f); +static AreaBoundary* const deadSide = new RectangleBoundary(2633.84f, 2750.49f, -3360.78f, -3285.0f); +enum Side  { -    {2725.1f, -3310.0f, 268.85f, 3.4f}, -    {2699.3f, -3322.8f, 268.60f, 3.3f}, -    {2733.1f, -3348.5f, 268.84f, 3.1f}, -    {2682.8f, -3304.2f, 268.85f, 3.9f}, -    {2664.8f, -3340.7f, 268.23f, 3.7f}, +    SIDE_NONE = 0, +    SIDE_LIVING, +    SIDE_DEAD  }; +inline static Side GetSide(Position const* who) +{ +    if (livingSide->IsWithinBoundary(who)) +        return SIDE_LIVING; +    if (deadSide->IsWithinBoundary(who)) +        return SIDE_DEAD; +    return SIDE_NONE; +} +inline static bool IsOnSameSide(Position const* who, Position const* other) +{ +    return (GetSide(who) == GetSide(other)); +} +static Player* FindEligibleTarget(Creature const* me, bool isGateOpen) +{ +    Map::PlayerList const& players = me->GetMap()->GetPlayers(); +    for (Map::PlayerList::const_iterator it = players.begin(); it != players.end(); ++it) +    { +        Player* player = it->GetSource(); +        if (player && (isGateOpen || IsOnSameSide(me, player)) && me->CanSeeOrDetect(player) && me->IsValidAttackTarget(player) && player->isInAccessiblePlaceFor(me)) +        { +            return player; +        } +    } -float const PosGroundLiveSide[4] = {2691.2f, -3387.0f, 267.68f, 1.52f}; -float const PosGroundDeadSide[4] = {2693.5f, -3334.6f, 267.68f, 4.67f}; -float const PosPlatform[4] = {2640.5f, -3360.6f, 285.26f, 0.0f}; +    return nullptr; +} -// Predicate function to check that the r   efzr unit is NOT on the same side as the source. -struct NotOnSameSide : public std::unary_function<Unit*, bool> -{ -    NotOnSameSide(Unit* source) : _onLiveSide(IN_LIVE_SIDE(source)) { } -    bool operator() (Unit const* target) +/* Wave data */ +typedef std::pair<uint32, uint8> GothikWaveEntry; // (npcEntry, npcCount) +typedef std::set<GothikWaveEntry> GothikWave; +typedef std::pair<GothikWave, uint8> GothikWaveInfo; // (wave, secondsToNext) +typedef std::vector<GothikWaveInfo> GothikWaveData; +const GothikWaveData waves10 = +{      { -        return (_onLiveSide != IN_LIVE_SIDE(target)); -    } +        {{NPC_LIVE_TRAINEE, 2}}, +    20}, +    { +        {{NPC_LIVE_TRAINEE, 2}}, +    20}, +    { +        {{NPC_LIVE_TRAINEE, 2}}, +    10}, +    { +        {{NPC_LIVE_KNIGHT, 1}}, +    10}, +    { +        {{NPC_LIVE_TRAINEE, 2}}, +    15}, +    { +        {{NPC_LIVE_KNIGHT, 1}}, +    5}, +    { +        {{NPC_LIVE_TRAINEE, 2}}, +    20}, +    { +        {{NPC_LIVE_TRAINEE, 2}, {NPC_LIVE_KNIGHT, 1}}, +    10}, +    { +        {{NPC_LIVE_RIDER, 1}}, +    10}, +    { +        {{NPC_LIVE_TRAINEE, 2}}, +    5}, +    { +        {{NPC_LIVE_KNIGHT, 1}}, +    15}, +    { +        {{NPC_LIVE_TRAINEE, 2}, {NPC_LIVE_RIDER, 1}}, +    10}, +    { +        {{NPC_LIVE_KNIGHT, 2}}, +    10}, +    { +        {{NPC_LIVE_TRAINEE, 2}}, +    10}, +    { +        {{NPC_LIVE_RIDER, 1}}, +    5}, +    { +        {{NPC_LIVE_KNIGHT, 1}}, +    5}, +    { +        {{NPC_LIVE_TRAINEE, 2}}, +    20}, +    { +        {{NPC_LIVE_RIDER, 1}, {NPC_LIVE_KNIGHT, 1}, {NPC_LIVE_TRAINEE, 2}}, +    15}, +    { +        {{NPC_LIVE_TRAINEE, 2}}, +    0} +}; -    private: -        bool _onLiveSide; +const GothikWaveData waves25 = +{ +    { +        {{NPC_LIVE_TRAINEE, 3}}, +    20}, +    { +        {{NPC_LIVE_TRAINEE, 3}}, +    20}, +    { +        {{NPC_LIVE_TRAINEE, 3}}, +    10}, +    { +        {{NPC_LIVE_KNIGHT, 2}}, +    10}, +    { +        {{NPC_LIVE_TRAINEE, 3}}, +    15}, +    { +        {{NPC_LIVE_KNIGHT, 2}}, +    5}, +    { +        {{NPC_LIVE_TRAINEE, 3}}, +    20}, +    { +        {{NPC_LIVE_TRAINEE, 3}, {NPC_LIVE_KNIGHT, 2}}, +    10}, +    { +        {{NPC_LIVE_TRAINEE, 3}}, +    10}, +    { +        {{NPC_LIVE_RIDER, 1}}, +    5}, +    { +        {{NPC_LIVE_TRAINEE, 3}}, +    15}, +    { +        {{NPC_LIVE_RIDER, 1}}, +    10}, +    { +        {{NPC_LIVE_KNIGHT, 2}}, +    10}, +    { +        {{NPC_LIVE_RIDER, 1}}, +    10}, +    { +        {{NPC_LIVE_RIDER, 1}, {NPC_LIVE_TRAINEE, 3}}, +    5}, +    { +        {{NPC_LIVE_KNIGHT, 1}, {NPC_LIVE_TRAINEE, 3}}, +    5}, +    { +        {{NPC_LIVE_RIDER, 1}, {NPC_LIVE_TRAINEE, 3}}, +    20}, +    { +        {{NPC_LIVE_RIDER, 1}, {NPC_LIVE_KNIGHT, 2}, {NPC_LIVE_TRAINEE, 3}}, +    0}  }; + +// GUID of first trigger NPC (used as offset for guid checks) +// 0-1 are living side soul triggers, 2-3 are spectral side soul triggers, 4 is living rider spawn trigger, 5-7 are living other spawn trigger, 8-12 are skull pile triggers +const uint32 CGUID_TRIGGER = 127618; +/* Creature AI */  class boss_gothik : public CreatureScript  {      public: @@ -165,29 +302,18 @@ class boss_gothik : public CreatureScript              boss_gothikAI(Creature* creature) : BossAI(creature, BOSS_GOTHIK)              {                  Initialize(); -                waveCount = 0;              }              void Initialize()              { -                mergedSides = false; -                phaseTwo = false; -                thirtyPercentReached = false; +                _waveCount = 0; +                _gateCanOpen = false; +                _gateIsOpen = true; +                _lastTeleportDead = false;              } -            uint32 waveCount; -            bool mergedSides; -            bool phaseTwo; -            bool thirtyPercentReached; - -            GuidVector LiveTriggerGUID; -            GuidVector DeadTriggerGUID; -              void Reset() override              { -                LiveTriggerGUID.clear(); -                DeadTriggerGUID.clear(); -                  me->SetReactState(REACT_PASSIVE);                  instance->SetData(DATA_GOTHIK_GATE, GO_STATE_ACTIVE);                  _Reset(); @@ -196,43 +322,29 @@ class boss_gothik : public CreatureScript              void EnterCombat(Unit* /*who*/) override              { -                for (uint32 i = 0; i < POS_LIVE; ++i) -                    if (Creature* trigger = DoSummon(WORLD_TRIGGER, PosSummonLive[i])) -                        LiveTriggerGUID.push_back(trigger->GetGUID()); -                for (uint32 i = 0; i < POS_DEAD; ++i) -                    if (Creature* trigger = DoSummon(WORLD_TRIGGER, PosSummonDead[i])) -                        DeadTriggerGUID.push_back(trigger->GetGUID()); - -                if (LiveTriggerGUID.size() < POS_LIVE || DeadTriggerGUID.size() < POS_DEAD) -                { -                    TC_LOG_ERROR("scripts", "Script Gothik: cannot summon triggers!"); -                    EnterEvadeMode(); -                    return; -                } -                  _EnterCombat(); -                waveCount = 0; -                events.ScheduleEvent(EVENT_SUMMON, 30000); -                DoTeleportTo(PosPlatform); -                Talk(SAY_SPEECH); +                events.SetPhase(PHASE_ONE); +                events.ScheduleEvent(EVENT_SUMMON, 25 * IN_MILLISECONDS, 0, PHASE_ONE); +                events.ScheduleEvent(EVENT_DOORS_UNLOCK, 205 * IN_MILLISECONDS, 0, PHASE_ONE); +                events.ScheduleEvent(EVENT_PHASE_TWO, 270 * IN_MILLISECONDS, 0, PHASE_ONE); +                Talk(SAY_INTRO_1); +                events.ScheduleEvent(EVENT_INTRO_2, 4 * IN_MILLISECONDS); +                events.ScheduleEvent(EVENT_INTRO_3, 9 * IN_MILLISECONDS); +                events.ScheduleEvent(EVENT_INTRO_4, 14 * IN_MILLISECONDS);                  instance->SetData(DATA_GOTHIK_GATE, GO_STATE_READY); +                _gateIsOpen = false;              }              void JustSummoned(Creature* summon) override              { -                if (summon->GetEntry() == WORLD_TRIGGER) -                    summon->setActive(true); -                else if (!mergedSides) +                summons.Summon(summon); +                if (me->IsInCombat())                  { -                    summon->AI()->DoAction(me->HasReactState(REACT_PASSIVE) ? 1 : 0); -                    summon->AI()->EnterEvadeMode(); +                    summon->AI()->DoAction(_gateIsOpen ? ACTION_GATE_OPENED : ACTION_ACQUIRE_TARGET); +                    summon->SetCombatPulseDelay(5);                  }                  else -                { -                    summon->AI()->DoAction(0); -                    summon->AI()->DoZoneInCombat(); -                } -                summons.Summon(summon); +                    summon->DespawnOrUnsummon();              }              void SummonedCreatureDespawn(Creature* summon) override @@ -240,256 +352,202 @@ class boss_gothik : public CreatureScript                  summons.Despawn(summon);              } -            void KilledUnit(Unit* /*victim*/) override +            void KilledUnit(Unit* victim) override              { -                if (!(rand32() % 5)) +                if (victim && victim->GetTypeId() == TYPEID_PLAYER)                      Talk(SAY_KILL);              }              void JustDied(Unit* /*killer*/) override              { -                LiveTriggerGUID.clear(); -                DeadTriggerGUID.clear();                  _JustDied();                  Talk(SAY_DEATH);                  instance->SetData(DATA_GOTHIK_GATE, GO_STATE_ACTIVE); +                _gateIsOpen = false;              } -            void DoGothikSummon(uint32 entry) +            void OpenGate()              { -                if (GetDifficulty() == RAID_DIFFICULTY_25MAN_NORMAL) -                { -                    switch (entry) -                    { -                        case NPC_LIVE_TRAINEE: -                        { -                            if (Creature* liveTrigger = ObjectAccessor::GetCreature(*me, LiveTriggerGUID[0])) -                                DoSummon(NPC_LIVE_TRAINEE, liveTrigger, 1); -                            if (Creature* liveTrigger1 = ObjectAccessor::GetCreature(*me, LiveTriggerGUID[1])) -                                DoSummon(NPC_LIVE_TRAINEE, liveTrigger1, 1); -                            if (Creature* liveTrigger2 = ObjectAccessor::GetCreature(*me, LiveTriggerGUID[2])) -                                DoSummon(NPC_LIVE_TRAINEE, liveTrigger2, 1); -                            break; -                        } -                        case NPC_LIVE_KNIGHT: -                        { -                            if (Creature* liveTrigger3 = ObjectAccessor::GetCreature(*me, LiveTriggerGUID[3])) -                                DoSummon(NPC_LIVE_KNIGHT, liveTrigger3, 1); -                            if (Creature* liveTrigger5 = ObjectAccessor::GetCreature(*me, LiveTriggerGUID[5])) -                                DoSummon(NPC_LIVE_KNIGHT, liveTrigger5, 1); -                            break; -                        } -                        case NPC_LIVE_RIDER: -                        { -                            if (Creature* liveTrigger4 = ObjectAccessor::GetCreature(*me, LiveTriggerGUID[4])) -                                DoSummon(NPC_LIVE_RIDER, liveTrigger4, 1); -                            break; -                        } -                    } -                } -                else -                { -                    switch (entry) -                    { -                        case NPC_LIVE_TRAINEE: -                        { -                            if (Creature* liveTrigger = ObjectAccessor::GetCreature(*me, LiveTriggerGUID[4])) -                                DoSummon(NPC_LIVE_TRAINEE, liveTrigger, 1); -                            if (Creature* liveTrigger2 = ObjectAccessor::GetCreature(*me, LiveTriggerGUID[4])) -                                DoSummon(NPC_LIVE_TRAINEE, liveTrigger2, 1); -                            break; -                        } -                        case NPC_LIVE_KNIGHT: -                        { -                            if (Creature* liveTrigger5 = ObjectAccessor::GetCreature(*me, LiveTriggerGUID[4])) -                                DoSummon(NPC_LIVE_KNIGHT, liveTrigger5, 1); -                            break; -                        } -                        case NPC_LIVE_RIDER: -                        { -                            if (Creature* liveTrigger4 = ObjectAccessor::GetCreature(*me, LiveTriggerGUID[4])) -                                DoSummon(NPC_LIVE_RIDER, liveTrigger4, 1); -                            break; -                        } -                    } -                } -            } - -            bool CheckGroupSplitted() -            { -                bool checklife = false; -                bool checkdead = false; -                Map::PlayerList const &PlayerList = me->GetMap()->GetPlayers(); -                for (Map::PlayerList::const_iterator i = PlayerList.begin(); i != PlayerList.end(); ++i) -                { -                    if (i->GetSource() && i->GetSource()->IsAlive() && -                        i->GetSource()->GetPositionX() <= POS_X_NORTH && -                        i->GetSource()->GetPositionX() >= POS_X_SOUTH && -                        i->GetSource()->GetPositionY() <= POS_Y_GATE && -                        i->GetSource()->GetPositionY() >= POS_Y_EAST) -                    { -                        checklife = true; -                    } -                    else if (i->GetSource() && i->GetSource()->IsAlive() && -                        i->GetSource()->GetPositionX() <= POS_X_NORTH && -                        i->GetSource()->GetPositionX() >= POS_X_SOUTH && -                        i->GetSource()->GetPositionY() >= POS_Y_GATE && -                        i->GetSource()->GetPositionY() <= POS_Y_WEST) -                    { -                        checkdead = true; -                    } - -                    if (checklife && checkdead) -                        return true; -                } - -                return false; -            } - -            void SpellHit(Unit* /*caster*/, SpellInfo const* spell) override -            { -                uint32 spellId = 0; -                switch (spell->Id) -                { -                    case SPELL_INFORM_LIVE_TRAINEE: spellId = SPELL_INFORM_DEAD_TRAINEE;    break; -                    case SPELL_INFORM_LIVE_KNIGHT:  spellId = SPELL_INFORM_DEAD_KNIGHT;     break; -                    case SPELL_INFORM_LIVE_RIDER:   spellId = SPELL_INFORM_DEAD_RIDER;      break; -                } -                if (spellId && me->IsInCombat()) -                { -                    me->HandleEmoteCommand(EMOTE_ONESHOT_SPELL_CAST); -                    if (Creature* pRandomDeadTrigger = ObjectAccessor::GetCreature(*me, DeadTriggerGUID[rand32() % POS_DEAD])) -                        me->CastSpell(pRandomDeadTrigger, spellId, true); -                } +                if (_gateIsOpen) +                    return; +                instance->SetData(DATA_GOTHIK_GATE, GO_STATE_ACTIVE); +                Talk(EMOTE_GATE_OPENED); +                _gateIsOpen = true; +                 +                for (ObjectGuid summonGuid : summons) +                    if (Creature* summon = ObjectAccessor::GetCreature(*me, summonGuid)) +                        summon->AI()->DoAction(ACTION_GATE_OPENED);              }              void DamageTaken(Unit* /*who*/, uint32& damage) override              { -                if (!phaseTwo) +                if (!events.IsInPhase(PHASE_TWO))                      damage = 0;              } -            void SpellHitTarget(Unit* target, SpellInfo const* spell) override +            void DoAction(int32 action) override              { -                if (!me->IsInCombat()) -                    return; - -                switch (spell->Id) +                switch (action)                  { -                    case SPELL_INFORM_DEAD_TRAINEE: -                        DoSummon(NPC_DEAD_TRAINEE, target, 0); -                        break; -                    case SPELL_INFORM_DEAD_KNIGHT: -                        DoSummon(NPC_DEAD_KNIGHT, target, 0); -                        break; -                    case SPELL_INFORM_DEAD_RIDER: -                        DoSummon(NPC_DEAD_RIDER, target, 1.0f); -                        DoSummon(NPC_DEAD_HORSE, target, 1.0f); +                    case ACTION_MINION_EVADE: +                        if (_gateIsOpen || me->getThreatManager().isThreatListEmpty()) +                            return EnterEvadeMode(EVADE_REASON_NO_HOSTILES); +                        if (_gateCanOpen) +                            OpenGate();                          break;                  }              } +            void EnterEvadeMode(EvadeReason why) override +            { +                BossAI::EnterEvadeMode(why); +                Position const& home = me->GetHomePosition(); +                me->NearTeleportTo(home.GetPositionX(), home.GetPositionY(), home.GetPositionZ(), home.GetOrientation()); +            } +              void UpdateAI(uint32 diff) override              {                  if (!UpdateVictim())                      return; -                events.Update(diff); - -                if (!thirtyPercentReached && HealthBelowPct(30) && phaseTwo) +                if (me->HasReactState(REACT_AGGRESSIVE) && !_gateIsOpen && !IsOnSameSide(me, me->GetVictim()))                  { -                    thirtyPercentReached = true; -                    instance->SetData(DATA_GOTHIK_GATE, GO_STATE_ACTIVE); +                    // NBD: this should only happen in practice if there is nobody left alive on our side (we should open gate) +                    // thus we only do a cursory check to make sure (edge cases?) +                    if (Player* newTarget = FindEligibleTarget(me, _gateIsOpen)) +                    { +                        me->getThreatManager().resetAllAggro(); +                        me->AddThreat(newTarget, 1.0f); +                        AttackStart(newTarget); +                    } +                    else +                        OpenGate();                  } -                if (me->HasUnitState(UNIT_STATE_CASTING)) -                    return; +                events.Update(diff); + +                if (!_gateIsOpen && HealthBelowPct(30) && events.IsInPhase(PHASE_TWO)) +                    OpenGate();                  while (uint32 eventId = events.ExecuteEvent())                  {                      switch (eventId)                      {                          case EVENT_SUMMON: -                            if (waves[waveCount].entry) +                        { +                            if (RAID_MODE(waves10,waves25).size() <= _waveCount) // bounds check                              { -                                if ((waves[waveCount].mode == 2) && (GetDifficulty() == RAID_DIFFICULTY_25MAN_NORMAL)) -                                   DoGothikSummon(waves[waveCount].entry); -                                else if ((waves[waveCount].mode == 0) && (GetDifficulty() == RAID_DIFFICULTY_10MAN_NORMAL)) -                                    DoGothikSummon(waves[waveCount].entry); -                                else if (waves[waveCount].mode == 1) -                                    DoGothikSummon(waves[waveCount].entry); - -                                // if group is not splitted, open gate and merge both sides at ~ 2 minutes (wave 11) -                                if (waveCount == 11) +                                TC_LOG_INFO("scripts", "GothikAI: Wave count %d is out of range for difficulty %d.", _waveCount, GetDifficulty()); +                                break; +                            } +                             +                            std::list<Creature*> triggers; +                            me->GetCreatureListWithEntryInGrid(triggers, NPC_TRIGGER, 150.0f); +                            for (GothikWaveEntry entry : RAID_MODE(waves10, waves25)[_waveCount].first) +                                for (uint8 i = 0; i < entry.second; ++i)                                  { -                                    if (!CheckGroupSplitted()) +                                    // GUID layout is as follows: +                                    // CGUID+4: center (back of platform) - primary rider spawn +                                    // CGUID+5: north (back of platform) - primary knight spawn +                                    // CGUID+6: center (front of platform) - second spawn +                                    // CGUID+7: south (front of platform) - primary trainee spawn +                                    uint32 targetDBGuid; +                                    switch (entry.first)                                      { -                                        instance->SetData(DATA_GOTHIK_GATE, GO_STATE_ACTIVE); -                                        DummyEntryCheckPredicate pred; -                                        summons.DoAction(0, pred);  //! Magic numbers fail -                                        summons.DoZoneInCombat(); -                                        mergedSides = true; +                                        case NPC_LIVE_RIDER: // only spawns from center (back) > north +                                            targetDBGuid = (CGUID_TRIGGER + 4) + (i % 2); +                                            break; +                                        case NPC_LIVE_KNIGHT: // spawns north > center (front) > south +                                            targetDBGuid = (CGUID_TRIGGER + 5) + (i % 3); +                                            break; +                                        case NPC_LIVE_TRAINEE: // spawns south > center (front) > north +                                            targetDBGuid = (CGUID_TRIGGER + 7) - (i % 3); +                                            break;                                      } +                                 +                                    for (Creature* trigger : triggers) +                                        if (trigger && trigger->GetSpawnId() == targetDBGuid) +                                        { +                                            DoSummon(entry.first, trigger, 1.0f, 15 * IN_MILLISECONDS, TEMPSUMMON_CORPSE_TIMED_DESPAWN); +                                            break; +                                        }                                  } -                                if (waves[waveCount].mode == 1) -                                    events.ScheduleEvent(EVENT_SUMMON, waves[waveCount].time); -                                else if ((waves[waveCount].mode == 2) && (GetDifficulty() == RAID_DIFFICULTY_25MAN_NORMAL)) -                                    events.ScheduleEvent(EVENT_SUMMON, waves[waveCount].time); -                                else if ((waves[waveCount].mode == 0) && (GetDifficulty() == RAID_DIFFICULTY_10MAN_NORMAL)) -                                    events.ScheduleEvent(EVENT_SUMMON, waves[waveCount].time); -                                else -                                    events.ScheduleEvent(EVENT_SUMMON, 0); +                            if (uint8 timeToNext = RAID_MODE(waves10, waves25)[_waveCount].second) +                                events.ScheduleEvent(EVENT_SUMMON, timeToNext * IN_MILLISECONDS, 0, PHASE_ONE); -                                ++waveCount; -                            } -                            else -                            { -                                phaseTwo = true; -                                Talk(SAY_TELEPORT); -                                DoTeleportTo(PosGroundLiveSide); -                                me->SetReactState(REACT_AGGRESSIVE); -                                DummyEntryCheckPredicate pred; -                                summons.DoAction(0, pred);  //! Magic numbers fail -                                summons.DoZoneInCombat(); -                                events.ScheduleEvent(EVENT_BOLT, 1000); -                                events.ScheduleEvent(EVENT_HARVEST, urand(3000, 15000)); -                                events.ScheduleEvent(EVENT_TELEPORT, 20000); -                            } +                            ++_waveCount;                              break; -                        case EVENT_BOLT: -                            DoCastVictim(SPELL_SHADOW_BOLT); -                            events.ScheduleEvent(EVENT_BOLT, 1000); +                        } +                        case EVENT_DOORS_UNLOCK: +                            _gateCanOpen = true; +                            for (ObjectGuid summonGuid : summons) +                                if (Creature* summon = ObjectAccessor::GetCreature(*me, summonGuid)) +                                    if (summon->IsAlive() && (!summon->IsInCombat() || summon->IsInEvadeMode())) +                                    { +                                        OpenGate(); +                                        break; +                                    }                              break; -                        case EVENT_HARVEST: -                            DoCastVictim(SPELL_HARVEST_SOUL, true); -                            events.ScheduleEvent(EVENT_HARVEST, urand(20000, 25000)); +                        case EVENT_PHASE_TWO: +                            events.SetPhase(PHASE_TWO); +                            events.ScheduleEvent(EVENT_TELEPORT, 20 * IN_MILLISECONDS, 0, PHASE_TWO); +                            events.ScheduleEvent(EVENT_HARVEST, 15 * IN_MILLISECONDS, 0, PHASE_TWO); +                            events.ScheduleEvent(EVENT_RESUME_ATTACK, 2 * IN_MILLISECONDS, 0, PHASE_TWO); +                            Talk(SAY_PHASE_TWO); +                            Talk(EMOTE_PHASE_TWO); +                            me->SetReactState(REACT_PASSIVE); +                            me->getThreatManager().resetAllAggro(); +                            DoCastAOE(SPELL_TELEPORT_LIVE);                              break;                          case EVENT_TELEPORT: -                            if (!thirtyPercentReached) +                            if (!HealthBelowPct(30))                              { +                                me->CastStop();                                  me->AttackStop(); -                                if (IN_LIVE_SIDE(me)) -                                    DoTeleportTo(PosGroundDeadSide); -                                else -                                    DoTeleportTo(PosGroundLiveSide); - -                                me->getThreatManager().resetAggro(NotOnSameSide(me)); -                                if (Unit* target = SelectTarget(SELECT_TARGET_NEAREST, 0)) -                                { -                                    me->getThreatManager().addThreat(target, 100.0f); -                                    AttackStart(target); -                                } - -                                events.ScheduleEvent(EVENT_TELEPORT, 20000); +                                me->StopMoving(); +                                me->SetReactState(REACT_PASSIVE); +                                me->getThreatManager().resetAllAggro(); +                                DoCastAOE(_lastTeleportDead ? SPELL_TELEPORT_LIVE : SPELL_TELEPORT_DEAD); +                                _lastTeleportDead = !_lastTeleportDead; + +                                events.CancelEvent(EVENT_BOLT); +                                events.ScheduleEvent(EVENT_TELEPORT, 20 * IN_MILLISECONDS, 0, PHASE_TWO); +                                events.ScheduleEvent(EVENT_RESUME_ATTACK, 2 * IN_MILLISECONDS, 0, PHASE_TWO);                              }                              break; + +                        case EVENT_HARVEST: +                            DoCastAOE(SPELL_HARVEST_SOUL, true); // triggered allows this to go "through" shadow bolt +                            events.ScheduleEvent(EVENT_HARVEST, 15 * IN_MILLISECONDS, 0, PHASE_TWO); +                            break; +                        case EVENT_RESUME_ATTACK: +                            me->SetReactState(REACT_AGGRESSIVE); +                            events.ScheduleEvent(EVENT_BOLT, 0, 0, PHASE_TWO); +                            // return to the start of this method so victim side etc is re-evaluated +                            return UpdateAI(0u); // tail recursion for efficiency +                        case EVENT_BOLT: +                            DoCastVictim(SPELL_SHADOW_BOLT); +                            events.ScheduleEvent(EVENT_BOLT, 1 * IN_MILLISECONDS, 0, PHASE_TWO); +                            break; +                        case EVENT_INTRO_2: +                            Talk(SAY_INTRO_2); +                            break; +                        case EVENT_INTRO_3: +                            Talk(SAY_INTRO_3); +                            break; +                        case EVENT_INTRO_4: +                            Talk(SAY_INTRO_4); +                            break;                      }                  } - -                if (!phaseTwo) -                    DoMeleeAttackIfReady();              } +             +            private: +                uint32 _waveCount; +                bool _gateCanOpen; +                bool _gateIsOpen; +                bool _lastTeleportDead;          };          CreatureAI* GetAI(Creature* creature) const override @@ -498,86 +556,407 @@ class boss_gothik : public CreatureScript          }  }; -class npc_gothik_minion : public CreatureScript +struct npc_gothik_minion_baseAI : public ScriptedAI  {      public: -        npc_gothik_minion() : CreatureScript("npc_gothik_minion") { } +        npc_gothik_minion_baseAI(Creature* creature, uint32 deathNotify=0) : ScriptedAI(creature), _deathNotify(deathNotify), _gateIsOpen(false) { } + +        void JustDied(Unit* /*killer*/) override +        { +            if (_deathNotify) +                DoCastAOE(_deathNotify, true); +        } + +        inline bool isOnSameSide(Unit const* who) const +        { +            return IsOnSameSide(me, who); +        } -        struct npc_gothik_minionAI : public CombatAI +        void DamageTaken(Unit* attacker, uint32 &damage) override +        { // do not allow minions to take damage before the gate is opened +            if (!_gateIsOpen && !isOnSameSide(attacker)) +                damage = 0; +        } + +        void DoAction(int32 action) override          { -            npc_gothik_minionAI(Creature* creature) : CombatAI(creature) +            switch (action)              { -                liveSide = IN_LIVE_SIDE(me); -                gateClose = false; +                case ACTION_GATE_OPENED: +                    _gateIsOpen = true; +                    // intentional missing break +                case ACTION_ACQUIRE_TARGET: +                    if (Player* target = FindEligibleTarget(me, _gateIsOpen)) +                    { +                        me->AddThreat(target, 1.0f); +                        AttackStart(target); +                    } +                    else +                        EnterEvadeMode(EVADE_REASON_NO_HOSTILES); +                    break; +            } +        } + +        void EnterEvadeMode(EvadeReason why) override +        { +            ScriptedAI::EnterEvadeMode(why); + +            if (InstanceScript* instance = me->GetInstanceScript()) +                if (Creature* gothik = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_GOTHIK))) +                    gothik->AI()->DoAction(ACTION_MINION_EVADE); +        } + +        void UpdateAI(uint32 diff) override +        { +            if (!UpdateVictim()) +                return; + +            if (!_gateIsOpen && !isOnSameSide(me->GetVictim())) +            { // reset threat, then try to find someone on same side as us to attack +                if (Player* newTarget = FindEligibleTarget(me, _gateIsOpen)) +                { +                    me->RemoveAurasByType(SPELL_AURA_MOD_TAUNT); +                    me->getThreatManager().resetAllAggro(); +                    me->AddThreat(newTarget, 1.0f); +                    AttackStart(newTarget); +                } +                else +                    EnterEvadeMode(EVADE_REASON_NO_HOSTILES);              } -            bool liveSide; -            bool gateClose; +            _UpdateAI(diff); +        } + +        virtual void _UpdateAI(uint32 diff) { ScriptedAI::UpdateAI(diff); }; -            bool isOnSameSide(Unit const* who) const +    private: +        uint32 _deathNotify; +        bool _gateIsOpen; +}; + +class npc_gothik_minion_livingtrainee : public CreatureScript +{ +    public: +        npc_gothik_minion_livingtrainee() : CreatureScript("npc_gothik_minion_livingtrainee") { } + +        struct npc_gothik_minion_livingtraineeAI : public npc_gothik_minion_baseAI +        { +            npc_gothik_minion_livingtraineeAI(Creature* creature) : npc_gothik_minion_baseAI(creature, SPELL_ANCHOR_1_TRAINEE), _deathPlagueTimer(urandms(5,20)) { } + +            void _UpdateAI(uint32 diff)              { -                return (liveSide == IN_LIVE_SIDE(who)); +                if (diff < _deathPlagueTimer) +                    _deathPlagueTimer -= diff; +                else +                { +                    DoCastAOE(SPELL_DEATH_PLAGUE); +                    _deathPlagueTimer = urandms(5, 20); +                } +                DoMeleeAttackIfReady();              } +            uint32 _deathPlagueTimer; +        }; + +        CreatureAI* GetAI(Creature* creature) const override +        { +            return GetInstanceAI<npc_gothik_minion_livingtraineeAI>(creature); +        } +}; + +class npc_gothik_minion_livingknight : public CreatureScript +{ +    public: +        npc_gothik_minion_livingknight() : CreatureScript("npc_gothik_minion_livingknight") { } -            void DoAction(int32 param) override +        struct npc_gothik_minion_livingknightAI : public npc_gothik_minion_baseAI +        { +            npc_gothik_minion_livingknightAI(Creature* creature) : npc_gothik_minion_baseAI(creature, SPELL_ANCHOR_1_DK), _whirlwindTimer(urandms(5,10)) { } + +            void _UpdateAI(uint32 diff)              { -                gateClose = param != 0; +                if (diff < _whirlwindTimer) +                    _whirlwindTimer -= diff; +                else +                { +                    DoCastAOE(SPELL_SHADOW_MARK); +                    _whirlwindTimer = urandms(15, 20); +                } +                DoMeleeAttackIfReady();              } +            uint32 _whirlwindTimer; +        }; -            void DamageTaken(Unit* attacker, uint32 &damage) override +        CreatureAI* GetAI(Creature* creature) const override +        { +            return GetInstanceAI<npc_gothik_minion_livingknightAI>(creature); +        } +}; + +class npc_gothik_minion_livingrider : public CreatureScript +{ +    public: +        npc_gothik_minion_livingrider() : CreatureScript("npc_gothik_minion_livingrider") { } + +        struct npc_gothik_minion_livingriderAI : public npc_gothik_minion_baseAI +        { +            npc_gothik_minion_livingriderAI(Creature* creature) : npc_gothik_minion_baseAI(creature, SPELL_ANCHOR_1_RIDER), _boltVolleyTimer(urandms(5,10)) { } + +            void _UpdateAI(uint32 diff)              { -                if (gateClose && !isOnSameSide(attacker)) -                    damage = 0; +                if (diff < _boltVolleyTimer) +                    _boltVolleyTimer -= diff; +                else +                { +                    DoCastAOE(SPELL_SHADOW_BOLT_VOLLEY); +                    _boltVolleyTimer = urandms(10, 15); +                } +                if (!me->HasUnitState(UNIT_STATE_CASTING)) +                    DoMeleeAttackIfReady();              } +            uint32 _boltVolleyTimer; +        }; -            void JustDied(Unit* /*killer*/) override +        CreatureAI* GetAI(Creature* creature) const override +        { +            return GetInstanceAI<npc_gothik_minion_livingriderAI>(creature); +        } +}; + +class npc_gothik_minion_spectraltrainee : public CreatureScript +{ +    public: +    npc_gothik_minion_spectraltrainee() : CreatureScript("npc_gothik_minion_spectraltrainee") { } + +    struct npc_gothik_minion_spectraltraineeAI : public npc_gothik_minion_baseAI +    { +        npc_gothik_minion_spectraltraineeAI(Creature* creature) : npc_gothik_minion_baseAI(creature), _explosionTimer(2 * IN_MILLISECONDS) { } + +        void _UpdateAI(uint32 diff) +        { +            if (diff < _explosionTimer) +                _explosionTimer -= diff; +            else              { -                if (me->IsSummon()) -                    if (Unit* owner = me->ToTempSummon()->GetSummoner()) -                        CombatAI::JustDied(owner); +                DoCastAOE(SPELL_ARCANE_EXPLOSION); +                _explosionTimer = 2 * IN_MILLISECONDS;              } +            DoMeleeAttackIfReady(); +        } +        uint32 _explosionTimer; +    }; -            void EnterEvadeMode(EvadeReason why) override +    CreatureAI* GetAI(Creature* creature) const override +    { +        return GetInstanceAI<npc_gothik_minion_spectraltraineeAI>(creature); +    } +}; + +class npc_gothik_minion_spectralknight : public CreatureScript +{ +    public: +    npc_gothik_minion_spectralknight() : CreatureScript("npc_gothik_minion_spectralknight") { } + +    struct npc_gothik_minion_spectralknightAI : public npc_gothik_minion_baseAI +    { +        npc_gothik_minion_spectralknightAI(Creature* creature) : npc_gothik_minion_baseAI(creature), _whirlwindTimer(urandms(15,25)) { } + +        void _UpdateAI(uint32 diff) +        { +            if (diff < _whirlwindTimer) +                _whirlwindTimer -= diff; +            else              { -                if (!gateClose) -                { -                    CombatAI::EnterEvadeMode(why); -                    return; -                } +                DoCastAOE(SPELL_WHIRLWIND); +                _whirlwindTimer = urandms(20, 25); +            } +            DoMeleeAttackIfReady(); +        } +        uint32 _whirlwindTimer; +    }; -                if (!_EnterEvadeMode()) -                    return; +    CreatureAI* GetAI(Creature* creature) const override +    { +        return GetInstanceAI<npc_gothik_minion_spectralknightAI>(creature); +    } +}; + +class npc_gothik_minion_spectralrider : public CreatureScript +{ +    public: +    npc_gothik_minion_spectralrider() : CreatureScript("npc_gothik_minion_spectralrider") { } + +    struct npc_gothik_minion_spectralriderAI : public npc_gothik_minion_baseAI +    { +        npc_gothik_minion_spectralriderAI(Creature* creature) : npc_gothik_minion_baseAI(creature), _frenzyTimer(urandms(2,5)), _drainTimer(urandms(8,12)) { } -                Map::PlayerList const &PlayerList = me->GetMap()->GetPlayers(); -                for (Map::PlayerList::const_iterator i = PlayerList.begin(); i != PlayerList.end(); ++i) +        void _UpdateAI(uint32 diff) +        { +            if (diff < _frenzyTimer) +                _frenzyTimer -= diff; +            else if (me->HasUnitState(UNIT_STATE_CASTING)) +                _frenzyTimer = 0; +            else +            { // target priority: knight > other rider > horse > gothik +                std::list<Creature*> potentialTargets = DoFindFriendlyMissingBuff(30.0, SPELLHELPER_UNHOLY_FRENZY); +                Creature *knightTarget = nullptr, *riderTarget = nullptr, *horseTarget = nullptr, *gothikTarget = nullptr; +                for (Creature* pTarget : potentialTargets)                  { -                    if (i->GetSource() && i->GetSource()->IsAlive() && isOnSameSide(i->GetSource())) +                    switch (pTarget->GetEntry())                      { -                        AttackStart(i->GetSource()); -                        return; +                        case NPC_DEAD_KNIGHT: +                            knightTarget = pTarget; +                            break; +                        case NPC_DEAD_RIDER: +                            riderTarget = pTarget; +                            break; +                        case NPC_DEAD_HORSE: +                            horseTarget = pTarget; +                            break; +                        case NPC_GOTHIK: +                            gothikTarget = pTarget; +                            break;                      } +                    if (knightTarget) +                        break;                  } +                Creature* target = knightTarget ? knightTarget : riderTarget ? riderTarget : horseTarget ? horseTarget : gothikTarget ? gothikTarget : nullptr; +                if (target) +                    DoCast(target, SPELL_UNHOLY_FRENZY); +                _frenzyTimer = 20 * IN_MILLISECONDS; +            } -                me->GetMotionMaster()->MoveIdle(); -                Reset(); +            if (diff < _drainTimer) +                _drainTimer -= diff; +            else +            { +                DoCastVictim(SPELL_DRAIN_LIFE); +                _drainTimer = urandms(10,15);              } -            void UpdateAI(uint32 diff) override +            if (!me->HasUnitState(UNIT_STATE_CASTING)) +                DoMeleeAttackIfReady(); +        } +        uint32 _frenzyTimer, _drainTimer; +    }; + +    CreatureAI* GetAI(Creature* creature) const override +    { +        return GetInstanceAI<npc_gothik_minion_spectralriderAI>(creature); +    } +}; + +class npc_gothik_minion_spectralhorse : public CreatureScript +{ +    public: +    npc_gothik_minion_spectralhorse() : CreatureScript("npc_gothik_minion_spectralhorse") { } + +    struct npc_gothik_minion_spectralhorseAI : public npc_gothik_minion_baseAI +    { +        npc_gothik_minion_spectralhorseAI(Creature* creature) : npc_gothik_minion_baseAI(creature), _stompTimer(urandms(10,15)) { } + +        void _UpdateAI(uint32 diff) +        { +            if (diff < _stompTimer) +                _stompTimer -= diff; +            else              { -                if (gateClose && (!isOnSameSide(me) || (me->GetVictim() && !isOnSameSide(me->GetVictim())))) -                { -                    EnterEvadeMode(EVADE_REASON_OTHER); -                    return; -                } +                DoCastAOE(SPELL_STOMP); +                _stompTimer = urandms(14, 18); +            } +            DoMeleeAttackIfReady(); +        } +        uint32 _stompTimer; +    }; -                CombatAI::UpdateAI(diff); +    CreatureAI* GetAI(Creature* creature) const override +    { +        return GetInstanceAI<npc_gothik_minion_spectralhorseAI>(creature); +    } +}; + +class npc_gothik_trigger : public CreatureScript +{ +public: +    npc_gothik_trigger() : CreatureScript("npc_gothik_trigger") { } + +    CreatureAI* GetAI(Creature* creature) const override +    { +        return GetInstanceAI<npc_gothik_triggerAI>(creature); +    } + +    struct npc_gothik_triggerAI : public ScriptedAI +    { +        npc_gothik_triggerAI(Creature* creature) : ScriptedAI(creature) { creature->SetDisableGravity(true); } + +        void EnterEvadeMode(EvadeReason /*why*/) override { } +        void UpdateAI(uint32 /*diff*/) override { } +        void EnterCombat(Unit* /*who*/) override { } +        void DamageTaken(Unit* /*who*/, uint32& damage) override { damage = 0;  } + +        Creature* SelectRandomSkullPile() +        { +            std::list<Creature*> triggers; +            me->GetCreatureListWithEntryInGrid(triggers, NPC_TRIGGER, 150.0f); +            uint32 targetDBGuid = CGUID_TRIGGER + urand(8, 12); // CGUID+8 to CGUID+12 are the triggers for the skull piles on dead side +            for (Creature* trigger : triggers) +                if (trigger && trigger->GetSpawnId() == targetDBGuid) +                    return trigger; + +            return nullptr; +        } +        void SpellHit(Unit* /*caster*/, SpellInfo const* spell) override +        { +            if (!spell) +                return; + +            switch (spell->Id) +            { +                case SPELL_ANCHOR_1_TRAINEE: +                    DoCastAOE(SPELL_ANCHOR_2_TRAINEE, true); +                    break; +                case SPELL_ANCHOR_1_DK: +                    DoCastAOE(SPELL_ANCHOR_2_DK, true); +                    break; +                case SPELL_ANCHOR_1_RIDER: +                    DoCastAOE(SPELL_ANCHOR_2_RIDER, true); +                    break; +                case SPELL_ANCHOR_2_TRAINEE: +                    if (Creature* target = SelectRandomSkullPile()) +                        DoCast(target, SPELL_SKULLS_TRAINEE, true); +                    break; +                case SPELL_ANCHOR_2_DK: +                    if (Creature* target = SelectRandomSkullPile()) +                        DoCast(target, SPELL_SKULLS_DK, true); +                    break; +                case SPELL_ANCHOR_2_RIDER: +                    if (Creature* target = SelectRandomSkullPile()) +                        DoCast(target, SPELL_SKULLS_RIDER, true); +                    break; +                case SPELL_SKULLS_TRAINEE: +                    DoSummon(NPC_DEAD_TRAINEE, me, 0.0f, 15 * IN_MILLISECONDS, TEMPSUMMON_CORPSE_TIMED_DESPAWN); +                    break; +                case SPELL_SKULLS_DK: +                    DoSummon(NPC_DEAD_KNIGHT, me, 0.0f, 15 * IN_MILLISECONDS, TEMPSUMMON_CORPSE_TIMED_DESPAWN); +                    break; +                case SPELL_SKULLS_RIDER: +                    DoSummon(NPC_DEAD_RIDER, me, 0.0f, 15 * IN_MILLISECONDS, TEMPSUMMON_CORPSE_TIMED_DESPAWN); +                    DoSummon(NPC_DEAD_HORSE, me, 0.0f, 15 * IN_MILLISECONDS, TEMPSUMMON_CORPSE_TIMED_DESPAWN); +                    break;              } -        }; +        } -        CreatureAI* GetAI(Creature* creature) const override +        // dead side summons are "owned" by gothik +        void JustSummoned(Creature* summon) override +        { +            if (Creature* gothik = ObjectAccessor::GetCreature(*me, me->GetInstanceScript()->GetGuidData(DATA_GOTHIK))) +                gothik->AI()->JustSummoned(summon); +        } +        void SummonedCreatureDespawn(Creature* summon) override          { -            return new npc_gothik_minionAI(creature); +            if (Creature* gothik = ObjectAccessor::GetCreature(*me, me->GetInstanceScript()->GetGuidData(DATA_GOTHIK))) +                gothik->AI()->SummonedCreatureDespawn(summon);          } +    };  };  class spell_gothik_shadow_bolt_volley : public SpellScriptLoader @@ -609,6 +988,13 @@ class spell_gothik_shadow_bolt_volley : public SpellScriptLoader  void AddSC_boss_gothik()  {      new boss_gothik(); -    new npc_gothik_minion(); +    new npc_gothik_minion_livingtrainee(); +    new npc_gothik_minion_livingknight(); +    new npc_gothik_minion_livingrider(); +    new npc_gothik_minion_spectraltrainee(); +    new npc_gothik_minion_spectralknight(); +    new npc_gothik_minion_spectralrider(); +    new npc_gothik_minion_spectralhorse(); +    new npc_gothik_trigger();      new spell_gothik_shadow_bolt_volley();  } diff --git a/src/server/scripts/Northrend/Naxxramas/instance_naxxramas.cpp b/src/server/scripts/Northrend/Naxxramas/instance_naxxramas.cpp index 35557b2301b..c8a4eb7fbc8 100644 --- a/src/server/scripts/Northrend/Naxxramas/instance_naxxramas.cpp +++ b/src/server/scripts/Northrend/Naxxramas/instance_naxxramas.cpp @@ -167,6 +167,9 @@ class instance_naxxramas : public InstanceMapScript                      case NPC_RAZUVIOUS:                          RazuviousGUID = creature->GetGUID();                          break; +                    case NPC_GOTHIK: +                        GothikGUID = creature->GetGUID(); +                        break;                      case NPC_THANE:                          ThaneGUID = creature->GetGUID();                          break; @@ -378,6 +381,8 @@ class instance_naxxramas : public InstanceMapScript                          return FaerlinaGUID;                      case DATA_RAZUVIOUS:                          return RazuviousGUID; +                    case DATA_GOTHIK: +                        return GothikGUID;                      case DATA_THANE:                          return ThaneGUID;                      case DATA_LADY: @@ -655,6 +660,7 @@ class instance_naxxramas : public InstanceMapScript              // Instructor Razuvious              ObjectGuid RazuviousGUID;              // Gothik the Harvester +            ObjectGuid GothikGUID;              ObjectGuid GothikGateGUID;              // The Four Horsemen              ObjectGuid ThaneGUID; diff --git a/src/server/scripts/Northrend/Naxxramas/naxxramas.h b/src/server/scripts/Northrend/Naxxramas/naxxramas.h index aef8e8cd52b..c0caa86e93f 100644 --- a/src/server/scripts/Northrend/Naxxramas/naxxramas.h +++ b/src/server/scripts/Northrend/Naxxramas/naxxramas.h @@ -64,6 +64,7 @@ enum Data64      DATA_ANUBREKHAN,      DATA_FAERLINA,      DATA_RAZUVIOUS, +    DATA_GOTHIK,      DATA_THANE,      DATA_LADY,      DATA_BARON, @@ -86,6 +87,7 @@ enum CreaturesIds      NPC_ANUBREKHAN              = 15956,      NPC_FAERLINA                = 15953,      NPC_RAZUVIOUS               = 16061, +    NPC_GOTHIK                  = 16060,      NPC_THANE                   = 16064,      NPC_LADY                    = 16065,      NPC_BARON                   = 30549, diff --git a/src/server/scripts/Outland/zone_zangarmarsh.cpp b/src/server/scripts/Outland/zone_zangarmarsh.cpp index 48bbb7bad4a..6f38cce0e5b 100644 --- a/src/server/scripts/Outland/zone_zangarmarsh.cpp +++ b/src/server/scripts/Outland/zone_zangarmarsh.cpp @@ -48,8 +48,6 @@ EndContentData */  enum AshyenAndKeleth  { -    GOSSIP_REWARD_BLESS         = 0, -      NPC_ASHYEN                  = 17900,      NPC_KELETH                  = 17901, @@ -117,7 +115,6 @@ public:                  if (spell)                  {                      creature->CastSpell(player, spell, true); -                    creature->AI()->Talk(GOSSIP_REWARD_BLESS);                  }              } @@ -145,7 +142,6 @@ public:                  if (spell)                  {                      creature->CastSpell(player, spell, true); -                    creature->AI()->Talk(GOSSIP_REWARD_BLESS);                  }              }              player->CLOSE_GOSSIP_MENU(); diff --git a/src/server/scripts/Spells/spell_paladin.cpp b/src/server/scripts/Spells/spell_paladin.cpp index 9f6b4947328..13e7697910b 100644 --- a/src/server/scripts/Spells/spell_paladin.cpp +++ b/src/server/scripts/Spells/spell_paladin.cpp @@ -43,6 +43,12 @@ enum PaladinSpells      SPELL_PALADIN_BLESSING_OF_LOWER_CITY_PRIEST  = 37880,      SPELL_PALADIN_BLESSING_OF_LOWER_CITY_SHAMAN  = 37881, +    SPELL_PALADIN_BEACON_OF_LIGHT                = 53563, +    SPELL_PALADIN_BEACON_OF_LIGHT_HEAL_1         = 53652, +    SPELL_PALADIN_BEACON_OF_LIGHT_HEAL_2         = 53653, +    SPELL_PALADIN_BEACON_OF_LIGHT_HEAL_3         = 53654, +    SPELL_PALADIN_HOLY_LIGHT                     = 635, +      SPELL_PALADIN_DIVINE_STORM                   = 53385,      SPELL_PALADIN_DIVINE_STORM_DUMMY             = 54171,      SPELL_PALADIN_DIVINE_STORM_HEAL              = 54172, @@ -1153,6 +1159,68 @@ class spell_pal_lay_on_hands : public SpellScriptLoader          }  }; +// 53651 - Light's Beacon - Beacon of Light +class spell_pal_light_s_beacon : public SpellScriptLoader +{ +    public: +        spell_pal_light_s_beacon() : SpellScriptLoader("spell_pal_light_s_beacon") { } + +        class spell_pal_light_s_beacon_AuraScript : public AuraScript +        { +            PrepareAuraScript(spell_pal_light_s_beacon_AuraScript); + +            bool Validate(SpellInfo const* /*spellInfo*/) override +            { +                if (!sSpellMgr->GetSpellInfo(SPELL_PALADIN_BEACON_OF_LIGHT) +                    || !sSpellMgr->GetSpellInfo(SPELL_PALADIN_BEACON_OF_LIGHT_HEAL_1) +                    || !sSpellMgr->GetSpellInfo(SPELL_PALADIN_BEACON_OF_LIGHT_HEAL_2) +                    || !sSpellMgr->GetSpellInfo(SPELL_PALADIN_BEACON_OF_LIGHT_HEAL_3) +                    || !sSpellMgr->GetSpellInfo(SPELL_PALADIN_HOLY_LIGHT)) +                    return false; +                return true; +            } + +            bool CheckProc(ProcEventInfo& eventInfo) +            { +                if (GetTarget()->HasAura(SPELL_PALADIN_BEACON_OF_LIGHT, eventInfo.GetActor()->GetGUID())) +                    return false; +                return true; +            } + +            void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) +            { +                PreventDefaultAction(); + +                SpellInfo const* procSpell = eventInfo.GetSpellInfo(); +                if (!procSpell) +                    return; + +                uint32 healSpellId = procSpell->IsRankOf(sSpellMgr->GetSpellInfo(SPELL_PALADIN_HOLY_LIGHT)) ? SPELL_PALADIN_BEACON_OF_LIGHT_HEAL_1 : SPELL_PALADIN_BEACON_OF_LIGHT_HEAL_3; +                uint32 heal = CalculatePct(eventInfo.GetHealInfo()->GetHeal(), aurEff->GetAmount()); + +                Unit* beaconTarget = GetCaster(); +                if (!beaconTarget || !beaconTarget->HasAura(SPELL_PALADIN_BEACON_OF_LIGHT, eventInfo.GetActor()->GetGUID())) +                    return; + +                /// @todo: caster must be the healed unit to perform distance checks correctly +                ///        but that will break animation on clientside +                ///        caster in spell packets must be the healing unit +                eventInfo.GetActor()->CastCustomSpell(healSpellId, SPELLVALUE_BASE_POINT0, heal, beaconTarget, true); +            } + +            void Register() override +            { +                DoCheckProc += AuraCheckProcFn(spell_pal_light_s_beacon_AuraScript::CheckProc); +                OnEffectProc += AuraEffectProcFn(spell_pal_light_s_beacon_AuraScript::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); +            } +        }; + +        AuraScript* GetAuraScript() const override +        { +            return new spell_pal_light_s_beacon_AuraScript(); +        } +}; +  // 31789 - Righteous Defense  class spell_pal_righteous_defense : public SpellScriptLoader  { @@ -1338,6 +1406,7 @@ void AddSC_paladin_spell_scripts()      new spell_pal_judgement("spell_pal_judgement_of_wisdom", SPELL_PALADIN_JUDGEMENT_OF_WISDOM);      new spell_pal_judgement_of_command();      new spell_pal_lay_on_hands(); +    new spell_pal_light_s_beacon();      new spell_pal_righteous_defense();      new spell_pal_sacred_shield();      new spell_pal_seal_of_righteousness();  | 
