/*
REWRITTEN FROM SCRATCH BY PUSSYWIZARD, IT OWNS NOW!
*/

#include "ObjectMgr.h"
#include "ScriptMgr.h"
#include "ScriptedCreature.h"
#include "SpellScript.h"
#include "SpellAuraEffects.h"
#include "Spell.h"
#include "Vehicle.h"
#include "Unit.h"
#include "Cell.h"
#include "CellImpl.h"
#include "GridNotifiers.h"
#include "GridNotifiersImpl.h"
#include "CreatureTextMgr.h"
#include "icecrown_citadel.h"

enum Texts
{
    // The Lich King
    SAY_LK_INTRO_1                  = 0,
    SAY_LK_INTRO_2                  = 1,
    SAY_LK_INTRO_3                  = 2,
    SAY_LK_REMORSELESS_WINTER       = 4,
    SAY_LK_QUAKE                    = 5,
    SAY_LK_SUMMON_VALKYR            = 6,
    SAY_LK_HARVEST_SOUL             = 7,
    SAY_LK_FROSTMOURNE_ESCAPE       = 8,    // not said on heroic
    SAY_LK_FROSTMOURNE_KILL         = 9,    // not said on heroic
    SAY_LK_KILL                     = 10,
    SAY_LK_BERSERK                  = 11,
    EMOTE_DEFILE_WARNING            = 12,
    EMOTE_NECROTIC_PLAGUE_WARNING   = 13,
    SAY_LK_OUTRO_1                  = 14,
    SAY_LK_OUTRO_2                  = 15,
    SAY_LK_OUTRO_3                  = 16,
    SAY_LK_OUTRO_4                  = 17,
    SAY_LK_OUTRO_5                  = 18,
    SAY_LK_OUTRO_6                  = 19,
    SAY_LK_OUTRO_7                  = 20,
    SAY_LK_OUTRO_8                  = 21,

    // Highlord Tirion Fordring
    SAY_TIRION_INTRO_1              = 0,
    SAY_TIRION_INTRO_2              = 1,
    SAY_TIRION_OUTRO_1              = 2,
    SAY_TIRION_OUTRO_2              = 3,

    // Terenas Menethil (outro)
    SAY_TERENAS_OUTRO_1             = 0,
    SAY_TERENAS_OUTRO_2             = 1,

    // Terenas Menethil (Frostmourne)
    SAY_TERENAS_INTRO_1             = 0,
    SAY_TERENAS_INTRO_2             = 1,
    SAY_TERENAS_INTRO_3             = 2,
};

enum Spells
{
    // Basic
    SPELL_PLAGUE_AVOIDANCE              = 72846,
    SPELL_EMOTE_SIT_NO_SHEATH           = 73220,
    SPELL_BOSS_HITTIN_YA                = 73878,
    SPELL_BOSS_HITTIN_YA_AURA           = 73879,
    SPELL_EMOTE_SHOUT_NO_SHEATH         = 73213,
    SPELL_ICE_LOCK                      = 71614,

    // Outro
    SPELL_FURY_OF_FROSTMOURNE           = 72350,
    SPELL_FURY_OF_FROSTMOURNE_NO_REZ    = 72351,
    SPELL_EMOTE_QUESTION_NO_SHEATH      = 73330,
    SPELL_RAISE_DEAD                    = 71769,
    SPELL_LIGHTS_BLESSING               = 71797,
    SPELL_JUMP                          = 71809,
    SPELL_JUMP_TRIGGERED                = 71811,
    SPELL_JUMP_2                        = 72431,
    SPELL_SUMMON_BROKEN_FROSTMOURNE     = 74081,
    SPELL_SUMMON_BROKEN_FROSTMOURNE_2   = 72406,
    SPELL_SUMMON_BROKEN_FROSTMOURNE_3   = 73017,
    SPELL_BROKEN_FROSTMOURNE            = 72398,
    SPELL_BROKEN_FROSTMOURNE_KNOCK      = 72405,
    SPELL_SOUL_BARRAGE                  = 72305,
    SPELL_SUMMON_TERENAS                = 72420,
    SPELL_MASS_RESURRECTION             = 72429,
    SPELL_MASS_RESURRECTION_REAL        = 72423,
    SPELL_PLAY_MOVIE                    = 73159,

    // Phase Transition
    SPELL_REMORSELESS_WINTER_1          = 68981,
    SPELL_REMORSELESS_WINTER_2          = 72259,
    SPELL_QUAKE                         = 72262,
    SPELL_PAIN_AND_SUFFERING            = 72133,
    SPELL_SUMMON_ICE_SPHERE             = 69104,
    SPELL_ICE_SPHERE                    = 69090,
    SPELL_ICE_BURST_TARGET_SEARCH       = 69109,
    SPELL_ICE_PULSE                     = 69091,
    SPELL_ICE_BURST                     = 69108,
    SPELL_RAGING_SPIRIT                 = 69200,
    SPELL_RAGING_SPIRIT_VISUAL          = 69197,
    SPELL_RAGING_SPIRIT_VISUAL_CLONE    = 69198,
    SPELL_SOUL_SHRIEK                   = 69242,

    // Phase 1
    SPELL_RISEN_WITCH_DOCTOR_SPAWN      = 69639,
    SPELL_SUMMON_SHAMBLING_HORROR       = 70372,
    SPELL_SUMMON_DRUDGE_GHOULS          = 70358,
    SPELL_INFEST                        = 70541,
    SPELL_NECROTIC_PLAGUE               = 70337,
    SPELL_NECROTIC_PLAGUE_JUMP          = 70338,
    SPELL_PLAGUE_SIPHON                 = 74074,
    SPELL_SHADOW_TRAP                   = 73539,
    SPELL_SHADOW_TRAP_AURA              = 73525,
    SPELL_SHADOW_TRAP_KNOCKBACK         = 73529,

    // Phase 2
    SPELL_DEFILE                        = 72762,
    SPELL_DEFILE_AURA                   = 72743,
    SPELL_DEFILE_GROW                   = 72756,
    SPELL_SOUL_REAPER                   = 69409,
    SPELL_SOUL_REAPER_BUFF              = 69410,
    SPELL_SUMMON_VALKYR                 = 69037,
    SPELL_SUMMON_VALKYR_PERIODIC        = 74361,
    SPELL_WINGS_OF_THE_DAMNED           = 74352,
    SPELL_VALKYR_TARGET_SEARCH          = 69030,
    SPELL_HARVEST_SOUL_VALKYR           = 68985, // vehicle aura used by Val'kyr Shadowguard and Strangulate Vehicle
    SPELL_CHARGE                        = 74399,
    SPELL_VALKYR_CARRY                  = 74445,
    SPELL_EJECT_ALL_PASSENGERS          = 68576,
    SPELL_LIFE_SIPHON                   = 73488,
    SPELL_LIFE_SIPHON_HEAL              = 73489,

    // Phase 3
    SPELL_VILE_SPIRITS                  = 70498,
    SPELL_VILE_SPIRIT_MOVE_SEARCH       = 70501,
    SPELL_VILE_SPIRIT_DAMAGE_SEARCH     = 70502,
    SPELL_SPIRIT_BURST                  = 70503,
    SPELL_HARVEST_SOUL                  = 68980,
    SPELL_HARVEST_SOUL_VEHICLE          = 68984,
    SPELL_HARVEST_SOUL_VISUAL           = 71372,
    SPELL_HARVEST_SOUL_TELEPORT         = 72546,
    SPELL_HARVEST_SOUL_TELEPORT_BACK    = 72597,
    SPELL_KILL_FROSTMOURNE_PLAYERS      = 75127,
    SPELL_HARVESTED_SOUL_LK_BUFF        = 72679,
    SPELL_HARVEST_SOULS                 = 73654,
    SPELL_HARVEST_SOULS_TELEPORT        = 73655,
    //SPELL_IN_FROSTMOURNE_ROOM           = 74276,

    // Frostmourne
    SPELL_LIGHTS_FAVOR                  = 69382,
    SPELL_RESTORE_SOUL                  = 72595,
    SPELL_RESTORE_SOULS                 = 73650,
    SPELL_TERENAS_LOSES_INSIDE          = 72572,
    SPELL_DESTROY_SOUL                  = 74086,
    SPELL_DARK_HUNGER                   = 69383,    // Passive proc healing
    SPELL_DARK_HUNGER_HEAL              = 69384,
    SPELL_SOUL_RIP                      = 69397,    // Deals increasing damage
    SPELL_SOUL_RIP_DAMAGE               = 69398,
    SPELL_SUMMON_SPIRIT_BOMB_1          = 73581,
    SPELL_SUMMON_SPIRIT_BOMB_2          = 74299,
    SPELL_TRIGGER_VILE_SPIRIT_HEROIC    = 73582,
    SPELL_EXPLOSION                     = 73576,

    // Shambling Horror
    SPELL_SHOCKWAVE                     = 72149,
    SPELL_ENRAGE                        = 72143,
    SPELL_FRENZY                        = 28747,
};

#define NECROTIC_PLAGUE_LK   RAID_MODE<uint32>(70337, 73912, 73913, 73914)
#define NECROTIC_PLAGUE_PLR  RAID_MODE<uint32>(70338, 73785, 73786, 73787)
#define REMORSELESS_WINTER_1 RAID_MODE<uint32>(68981, 74270, 74271, 74272)
#define REMORSELESS_WINTER_2 RAID_MODE<uint32>(72259, 74273, 74274, 74275)
#define SUMMON_VALKYR        RAID_MODE<uint32>(69037, 74361, 69037, 74361)
//#define HARVEST_SOUL         RAID_MODE<uint32>(68980, 74325, 74296, 74297)
#define HARVESTED_SOUL_BUFF  RAID_MODE<uint32>(72679, 74318, 74319, 74320)

enum Events
{
    EVENT_NONE,

    // Intro
    EVENT_INTRO_LK_MOVE,
    EVENT_INTRO_LK_TALK_1,
    EVENT_INTRO_LK_EMOTE_CAST_SHOUT,
    EVENT_INTRO_LK_EMOTE_1,
    EVENT_INTRO_LK_CAST_FREEZE,
    EVENT_INTRO_FORDRING_TALK_1,
    EVENT_INTRO_FORDRING_TALK_2,
    EVENT_INTRO_FORDRING_EMOTE_1,
    EVENT_INTRO_FORDRING_CHARGE,
    EVENT_INTRO_FINISH,

    // Outro
    EVENT_OUTRO_LK_TALK_1,
    EVENT_OUTRO_LK_TALK_2,
    EVENT_OUTRO_LK_EMOTE_TALK,
    EVENT_OUTRO_LK_TALK_3,
    EVENT_OUTRO_LK_EMOTE_CAST_SHOUT,
    EVENT_OUTRO_LK_MOVE_CENTER,
    EVENT_OUTRO_LK_TALK_4,
    EVENT_OUTRO_LK_RAISE_DEAD,
    EVENT_OUTRO_LK_TALK_5,
    EVENT_OUTRO_LK_TALK_6,
    EVENT_OUTRO_LK_TALK_7,
    EVENT_OUTRO_LK_TALK_8,
    EVENT_OUTRO_FORDRING_TALK_1,
    EVENT_OUTRO_FORDRING_BLESS,
    EVENT_OUTRO_FORDRING_REMOVE_ICE,
    EVENT_OUTRO_FORDRING_MOVE_1,
    EVENT_OUTRO_FORDRING_JUMP,
    EVENT_OUTRO_AFTER_SUMMON_BROKEN_FROSTMOURNE,
    EVENT_OUTRO_KNOCK_BACK,
    EVENT_OUTRO_SOUL_BARRAGE,
    EVENT_OUTRO_AFTER_SOUL_BARRAGE,
    EVENT_OUTRO_SUMMON_TERENAS,
    EVENT_OUTRO_TERENAS_TALK_1,
    EVENT_OUTRO_TERENAS_TALK_2,

    // General
    EVENT_BERSERK,
    EVENT_START_ATTACK,
    EVENT_QUAKE,
    EVENT_QUAKE_2,

    // Combat
    EVENT_SUMMON_SHAMBLING_HORROR,
    EVENT_SUMMON_DRUDGE_GHOUL,
    EVENT_INFEST,
    EVENT_NECROTIC_PLAGUE,
    EVENT_SHADOW_TRAP,
    EVENT_PAIN_AND_SUFFERING,
    EVENT_SUMMON_ICE_SPHERE,
    EVENT_SUMMON_RAGING_SPIRIT,
    EVENT_DEFILE,
    EVENT_SOUL_REAPER,
    EVENT_SUMMON_VALKYR,
    EVENT_VILE_SPIRITS,
    EVENT_HARVEST_SOUL,
    EVENT_HARVEST_SOULS,
    EVENT_FROSTMOURNE_HEROIC,

    // Shambling Horror
    EVENT_SHOCKWAVE,
    EVENT_ENRAGE,

    // Raging Spirit
    EVENT_SOUL_SHRIEK,
    EVENT_RAGING_SPIRIT_UNROOT,

    // Val'kyr Shadowguard
    EVENT_GRAB_PLAYER,
    EVENT_MOVE_TO_DROP_POS,
    EVENT_MOVE_TO_SIPHON_POS,
    EVENT_LIFE_SIPHON,

    // Strangulate Vehicle (Harvest Soul)
    EVENT_TELEPORT,
    EVENT_MOVE_TO_LICH_KING,
    EVENT_DESPAWN_SELF,

    // Inside Frostmourne
    EVENT_FROSTMOURNE_TALK_1,
    EVENT_FROSTMOURNE_TALK_2,
    EVENT_FROSTMOURNE_TALK_3,
    EVENT_DESTROY_SOUL,
    EVENT_TELEPORT_BACK,
    EVENT_SOUL_RIP,

};

enum EventGroups
{
    EVENT_GROUP_NONE,
    EVENT_GROUP_ABILITIES,
    EVENT_GROUP_BERSERK,
    EVENT_GROUP_VILE_SPIRITS,
};

enum Phases
{
    PHASE_NONE                  = 0,
    PHASE_INTRO                 = 1,
    PHASE_ONE                   = 2,
    PHASE_TWO                   = 3,
    PHASE_THREE                 = 4,
    PHASE_TRANSITION            = 5,
    PHASE_FROSTMOURNE           = 6, // only set on heroic mode when all players are sent into frostmourne
    PHASE_OUTRO                 = 7,

    PHASE_MASK_NO_CAST_CHECK    = (1 << PHASE_TRANSITION) | (1 << PHASE_FROSTMOURNE) | (1 << PHASE_OUTRO),
    PHASE_MASK_NO_VICTIM        = (1 << PHASE_INTRO) | (1 << PHASE_OUTRO) | (1 << PHASE_FROSTMOURNE),
};

Position const CenterPosition     = {503.6282f, -2124.655f, 840.8569f, 0.0f};
Position const TirionIntro        = {488.2970f, -2124.840f, 840.8569f, 0.0f};
Position const TirionCharge       = {472.8500f, -2124.350f, 840.8570f, 0.0f};
Position const LichKingIntro[3]   = { {432.0851f, -2123.673f, 864.6582f, 0.0f}, {457.8351f, -2123.423f, 841.1582f, 0.0f}, {465.0730f, -2123.470f, 840.8569f, 0.0f} };
Position const OutroPosition1     = {488.6100f, -2124.620f, 840.8569f, 0.0f};
Position const OutroFlying        = {509.6897f, -2124.561f, 845.3565f, 0.0f};
Position const TerenasSpawn       = {495.5542f, -2517.012f, 1050.000f, 4.6993f};
Position const TerenasSpawnHeroic = {495.7080f, -2523.760f, 1050.000f, 0.0f};
Position const SpiritWardenSpawn  = {495.3406f, -2529.983f, 1050.000f, 1.5592f};

enum MovePoints
{
    POINT_NONE,
    POINT_CENTER_1,
    POINT_CENTER_2,
    POINT_TIRION_INTRO,
    POINT_TIRION_OUTRO,
    POINT_DROP_PLAYER,
    POINT_START_SIPHON,
    POINT_GROUND,
};

enum EncounterActions
{
    ACTION_NONE,
    ACTION_START_ATTACK,
    ACTION_OUTRO,
    ACTION_BREAK_FROSTMOURNE,
    ACTION_TELEPORT_BACK,
};

enum MiscData
{
    LIGHT_SNOWSTORM             = 2490,
    LIGHT_SOULSTORM             = 2508,
    MUSIC_FROZEN_THRONE         = 17457,
    MUSIC_SPECIAL               = 17458, // Summon Shambling Horror, Remorseless Winter, Quake, Summon Val'kyr Periodic, Harvest Soul, Vile Spirits
    MUSIC_FURY_OF_FROSTMOURNE   = 17459,
    MUSIC_FINAL                 = 17460, // Raise Dead, Light's Blessing
    SOUND_PAIN                  = 17360, // separate sound, not attached to any text
    EQUIP_ASHBRINGER_GLOWING    = 50442,
    EQUIP_BROKEN_FROSTMOURNE    = 50840,
    MOVIE_FALL_OF_THE_LICH_KING = 16,
};

#define DATA_PLAGUE_STACK 70337
#define DATA_VILE 45814622

bool IsValidPlatformTarget(Unit const* target)
{
    return target->GetExactDist2dSq(&CenterPosition) < 90.0f*90.0f && target->GetPositionZ() > 840.0f && target->GetPositionZ() < 875.0f;
}

void SendPacketToPlayers(WorldPacket const* data, Unit* source)
{
    // Send packet to all players in The Frozen Throne
    Map::PlayerList const& players = source->GetMap()->GetPlayers();
    if (!players.isEmpty())
        for (Map::PlayerList::const_iterator itr = players.begin(); itr != players.end(); ++itr)
            if (Player* player = itr->GetSource())
                if (player->GetAreaId() == AREA_THE_FROZEN_THRONE)
                    player->GetSession()->SendPacket(data);
}


struct NonTankLKTargetSelector : public std::unary_function<Unit*, bool>
{
public:
    NonTankLKTargetSelector(Creature* source, bool playerOnly = true, bool reqLOS = false, float maxDist = 0.0f, uint32 exclude1 = 0, uint32 exclude2 = 0) : _source(source), _playerOnly(playerOnly), _reqLOS(reqLOS), _maxDist(maxDist), _exclude1(exclude1), _exclude2(exclude2) { }
    bool operator()(Unit const* target) const
    {
        if (!target)
            return false;
        if (!target->IsAlive())
            return false;
        if (_playerOnly && target->GetTypeId() != TYPEID_PLAYER)
            return false;
        if (target == _source->GetVictim())
            return false;
        if (target->HasAura(SPELL_BOSS_HITTIN_YA_AURA))
            return false;
        if (_maxDist && _source->GetExactDist(target) > _maxDist)
            return false;
        if (_exclude1 && target->HasAura(_exclude1) || _exclude2 && target->HasAura(_exclude2))
            return false;
        if (_reqLOS && !_source->IsWithinLOSInMap(target))
            return false;
        return true;
    }

private:
    Creature const* _source;
    bool _playerOnly;
    bool _reqLOS;
    float _maxDist;
    uint32 _exclude1;
    uint32 _exclude2;
};


struct DefileTargetSelector : public std::unary_function<Unit*, bool>
{
public:
    DefileTargetSelector(Creature* source) : _source(source) { }
    bool operator()(Unit const* target) const
    {
        if (!target)
            return false;
        if (!target->IsAlive())
            return false;
        if (target->GetTypeId() != TYPEID_PLAYER)
            return false;
        if (_source->GetExactDist(target) > 100.0f)
            return false;
        if (target->HasAura(SPELL_HARVEST_SOUL_VALKYR) || target->HasAura(SPELL_VALKYR_TARGET_SEARCH))
            return false;
        if (!_source->IsWithinLOSInMap(target))
            return false;
        return true;
    }

private:
    Creature const* _source;
};

class FrozenThroneResetWorker
{
    public:
        FrozenThroneResetWorker() { }

        bool operator()(GameObject* go)
        {
            switch (go->GetEntry())
            {
                case GO_ARTHAS_PLATFORM:
                    go->SetDestructibleState(GO_DESTRUCTIBLE_INTACT, NULL, true);
                    break;
                case GO_DOODAD_ICECROWN_THRONEFROSTYWIND01:
                    go->SetGoState(GO_STATE_ACTIVE);
                    break;
                case GO_DOODAD_ICECROWN_THRONEFROSTYEDGE01:
                    go->SetGoState(GO_STATE_READY);
                    break;
                case GO_DOODAD_ICECROWN_SNOWEDGEWARNING01:
                    go->SetGoState(GO_STATE_READY);
                    break;
                case GO_DOODAD_ICESHARD_STANDING02:
                case GO_DOODAD_ICESHARD_STANDING01:
                case GO_DOODAD_ICESHARD_STANDING03:
                case GO_DOODAD_ICESHARD_STANDING04:
                    go->ResetDoorOrButton();
                    break;
                default:
                    break;
            }

            return false;
        }
};

class StartMovementEvent : public BasicEvent
{
    public:
        StartMovementEvent(Creature* summoner, Creature* owner) : _summoner(summoner), _owner(owner) {}

        bool Execute(uint64 /*time*/, uint32 /*diff*/)
        {
            _owner->SetReactState(REACT_AGGRESSIVE);
            if (!_owner->getThreatManager().isThreatListEmpty())
                if (Unit* target = _owner->SelectVictim())
                    _owner->AI()->AttackStart(target);
            if (!_owner->GetVictim())
                if (Unit* target = _summoner->AI()->SelectTarget(SELECT_TARGET_RANDOM, 0, NonTankLKTargetSelector(_summoner)))
                    _owner->AI()->AttackStart(target);
            _owner->AI()->DoZoneInCombat();
            return true;
        }

    private:
        Creature* _summoner;
        Creature* _owner;
};

class VileSpiritActivateEvent : public BasicEvent
{
    public:
        explicit VileSpiritActivateEvent(Creature* owner) : _owner(owner) {}

        bool Execute(uint64 /*time*/, uint32 /*diff*/)
        {
            _owner->SetReactState(REACT_AGGRESSIVE);
            _owner->CastSpell(_owner, SPELL_VILE_SPIRIT_MOVE_SEARCH, true);
            _owner->CastSpell((Unit*)NULL, SPELL_VILE_SPIRIT_DAMAGE_SEARCH, true);
            return true;
        }

    private:
        Creature* _owner;
};

class TriggerWickedSpirit : public BasicEvent
{
    public:
        explicit TriggerWickedSpirit(Creature* owner)
            : _owner(owner), _counter(13)
        {
        }

        bool Execute(uint64 /*time*/, uint32 /*diff*/)
        {
            _owner->CastCustomSpell(SPELL_TRIGGER_VILE_SPIRIT_HEROIC, SPELLVALUE_MAX_TARGETS, 1, NULL, true);

            if (--_counter)
            {
                _owner->m_Events.AddEvent(this, _owner->m_Events.CalculateTime(3000));
                return false;
            }

            return true;
        }

    private:
        Creature* _owner;
        uint32 _counter;
};

class LichKingDeathEvent : public BasicEvent
{
public:
    LichKingDeathEvent(Creature& owner) : _owner(owner) { }

    bool Execute(uint64 /*eventTime*/, uint32 /*updateTime*/)
    {
        _owner.RemoveAllAuras();
        Unit::Kill(&_owner, &_owner);
        return true;
    }

private:
    Creature& _owner;
};

class LichKingMovieEvent : public BasicEvent
{
public:
    LichKingMovieEvent(Creature& owner) : _owner(owner) { }

    bool Execute(uint64 /*eventTime*/, uint32 /*updateTime*/)
    {
        _owner.CastSpell((Unit*)NULL, SPELL_PLAY_MOVIE, false);
        return true;
    }

private:
    Creature& _owner;
};

class NecroticPlagueTargetCheck : public std::unary_function<Unit*, bool>
{
    public:
        NecroticPlagueTargetCheck(Unit const* obj, uint32 notAura1, uint32 notAura2) : _sourceObj(obj), _notAura1(notAura1), _notAura2(notAura2) {}

        bool operator()(Unit* unit) const
        {
            if (!unit || unit->GetTypeId() != TYPEID_PLAYER || unit == _sourceObj || _sourceObj->GetVictim() == unit || !unit->isTargetableForAttack())
                return false;
            if (unit->HasAura(SPELL_PLAGUE_AVOIDANCE) || unit->HasAura(SPELL_BOSS_HITTIN_YA_AURA) || unit->HasAura(_notAura1) || unit->HasAura(_notAura2))
                return false;
            if (!_sourceObj->IsWithinLOSInMap(unit))
                return false;
            return true;
        }

    private:
        Unit const* _sourceObj;
        uint32 _notAura1;
        uint32 _notAura2;
};

class HeightDifferenceCheck
{
    public:
        HeightDifferenceCheck(GameObject* go, float diff, bool reverse) : _baseObject(go), _difference(diff), _reverse(reverse) {}
        bool operator()(WorldObject* unit) const { return (unit->GetPositionZ() - _baseObject->GetPositionZ() > _difference) != _reverse; }
    private:
        GameObject* _baseObject;
        float _difference;
        bool _reverse;
};


class boss_the_lich_king : public CreatureScript
{
    public:
        boss_the_lich_king() : CreatureScript("boss_the_lich_king") { }

        struct boss_the_lich_kingAI : public BossAI
        {
            boss_the_lich_kingAI(Creature* creature) : BossAI(creature, DATA_THE_LICH_KING)
            {
                me->AddAura(SPELL_EMOTE_SIT_NO_SHEATH, me);
                me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC);
                me->SetReactState(REACT_PASSIVE);
            }

            uint8 _phase;
            uint32 _necroticPlagueStack;
            uint32 _vileSpiritExplosions;
            uint16 _positionCheckTimer;
            uint32 _lastTalkTimeKill;
            uint32 _lastTalkTimeBuff;
            bool _bFrostmournePhase;
            bool _bFordringMustFallYell;

            void Reset()
            {
                _phase = PHASE_NONE;
                _necroticPlagueStack = 0;
                _vileSpiritExplosions = 0;
                _positionCheckTimer = 5000;
                _lastTalkTimeKill = 0;
                _lastTalkTimeBuff = 0;
                _bFrostmournePhase = false;
                _bFordringMustFallYell = false;

                _Reset();
                DoAction(ACTION_RESTORE_LIGHT);
                SetEquipmentSlots(true);
                if (me->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC))
                    me->SetStandState(UNIT_STAND_STATE_SIT);
            }

            void JustDied(Unit* /*killer*/)
            {
                _JustDied();
                DoAction(ACTION_RESTORE_LIGHT);
                me->PlayDirectSound(17374);
            }

            void EnterCombat(Unit* target)
            {
                if (!instance->CheckRequiredBosses(DATA_THE_LICH_KING, target->ToPlayer()) || !me->IsVisible())
                {
                    EnterEvadeMode();
                    instance->DoCastSpellOnPlayers(LIGHT_S_HAMMER_TELEPORT);
                    return;
                }

                _phase = PHASE_ONE;
                instance->SetBossState(DATA_THE_LICH_KING, IN_PROGRESS);
                me->setActive(true);
                me->SetInCombatWithZone();
                me->RemoveAurasDueToSpell(SPELL_EMOTE_SIT_NO_SHEATH); // just to be sure

                events.ScheduleEvent(EVENT_BERSERK, 900000, EVENT_GROUP_BERSERK);
                events.ScheduleEvent(EVENT_SUMMON_SHAMBLING_HORROR, 15000, EVENT_GROUP_ABILITIES);
                events.ScheduleEvent(EVENT_SUMMON_DRUDGE_GHOUL, 10000, EVENT_GROUP_ABILITIES);
                events.ScheduleEvent(EVENT_INFEST, 5000, EVENT_GROUP_ABILITIES);
                events.ScheduleEvent(EVENT_NECROTIC_PLAGUE, urand(30000, 31000), EVENT_GROUP_ABILITIES);
                if (IsHeroic())
                    events.ScheduleEvent(EVENT_SHADOW_TRAP, 15500, EVENT_GROUP_ABILITIES);
            }

            void JustReachedHome()
            {
                _JustReachedHome();
                DoAction(ACTION_RESTORE_LIGHT);

                // Reset The Frozen Throne gameobjects
                FrozenThroneResetWorker reset;
                Trinity::GameObjectWorker<FrozenThroneResetWorker> worker(me, reset);
                me->VisitNearbyGridObject(333.0f, worker);

                me->AddAura(SPELL_EMOTE_SIT_NO_SHEATH, me);
                me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC);
                me->SetReactState(REACT_PASSIVE);
                me->SetStandState(UNIT_STAND_STATE_SIT);

            }

            bool CanAIAttack(Unit const* target) const
            {
                return me->IsVisible() && IsValidPlatformTarget(target) && !target->GetVehicle();
            }

            /*bool CanBeSeen(Player const* p)
            {
                return me->GetExactDistSq(p) < 200.0f*200.0f;
            }*/

            void KilledUnit(Unit* victim)
            {
                if (victim->GetTypeId() == TYPEID_PLAYER && !me->IsInEvadeMode() && _phase != PHASE_OUTRO && _lastTalkTimeKill+5 < time(NULL))
                {
                    _lastTalkTimeKill = time(NULL);
                    Talk(SAY_LK_KILL);
                }
            }

            void DoAction(int32 action)
            {
                switch (action)
                {
                    case ACTION_RESTORE_LIGHT:
                        me->GetMap()->SetZoneOverrideLight(AREA_THE_FROZEN_THRONE, 0, 5000);
                        me->GetMap()->SetZoneWeather(AREA_THE_FROZEN_THRONE, WEATHER_STATE_FINE, 0.5f);
                        break;
                    case ACTION_START_ATTACK:
                        events.ScheduleEvent(EVENT_START_ATTACK, 5250);
                        break;
                    case ACTION_BREAK_FROSTMOURNE:
                        me->CastSpell((Unit*)NULL, SPELL_SUMMON_BROKEN_FROSTMOURNE, true);
                        me->CastSpell((Unit*)NULL, SPELL_SUMMON_BROKEN_FROSTMOURNE_2, false);
                        SetEquipmentSlots(false, EQUIP_BROKEN_FROSTMOURNE);
                        if (Creature* tirion = ObjectAccessor::GetCreature(*me, instance->GetData64(DATA_HIGHLORD_TIRION_FORDRING)))
                            tirion->AI()->DoAction(ACTION_BREAK_FROSTMOURNE);
                        break;
                    case ACTION_TELEPORT_BACK:
                        {
                            if (_phase == PHASE_FROSTMOURNE)
                                events.RescheduleEvent(EVENT_START_ATTACK, 1000);
                            EntryCheckPredicate pred(NPC_STRANGULATE_VEHICLE);
                            summons.DoAction(ACTION_TELEPORT_BACK, pred);
                            if (!IsHeroic() && _phase != PHASE_OUTRO && me->IsInCombat() && _lastTalkTimeBuff+5 <= time(NULL))
                                Talk(SAY_LK_FROSTMOURNE_ESCAPE);
                        }
                        break;
                    default:
                        break;
                }
            }

            void DamageTaken(Unit* attacker, uint32& damage, DamageEffectType, SpellSchoolMask)
            {
                if (!attacker || (_bFrostmournePhase && attacker->GetExactDistSq(495.708f, -2523.76f, 1049.95f) > 40.0f*40.0f)) // frostmourne room, prevent exploiting (tele hack to get back and damage him)
                {
                    damage = 0;
                    return;
                }

                if (_phase == PHASE_ONE && !HealthAbovePct(70) && !me->HasUnitState(UNIT_STATE_CASTING))
                {
                    _phase = PHASE_TRANSITION;
                    me->SetReactState(REACT_PASSIVE);
                    me->AttackStop();
                    events.CancelEventGroup(EVENT_GROUP_ABILITIES);
                    me->InterruptNonMeleeSpells(false);
                    me->GetMotionMaster()->MovePoint(POINT_CENTER_1, CenterPosition);
                    return;
                }

                if (_phase == PHASE_TWO && !HealthAbovePct(40) && !me->HasUnitState(UNIT_STATE_CASTING))
                {
                    _phase = PHASE_TRANSITION;
                    me->SetReactState(REACT_PASSIVE);
                    me->AttackStop();
                    events.CancelEventGroup(EVENT_GROUP_ABILITIES);
                    me->InterruptNonMeleeSpells(false);
                    me->GetMotionMaster()->MovePoint(POINT_CENTER_2, CenterPosition);
                    return;
                }

                if (_phase == PHASE_THREE && HealthBelowPct(10) && !me->HasUnitState(UNIT_STATE_CASTING))
                {
                    _phase = PHASE_OUTRO;
                    EntryCheckPredicate pred(NPC_STRANGULATE_VEHICLE);
                    summons.DoAction(ACTION_TELEPORT_BACK, pred);
                    events.Reset();
                    summons.DespawnAll();
                    me->SetReactState(REACT_PASSIVE);
                    me->AttackStop();
                    me->GetMap()->SetZoneMusic(AREA_THE_FROZEN_THRONE, MUSIC_FURY_OF_FROSTMOURNE);
                    me->InterruptNonMeleeSpells(true);
                    me->CastSpell((Unit*)NULL, SPELL_FURY_OF_FROSTMOURNE, false);
                    me->SetWalk(true);

                    if (Creature* tirion = ObjectAccessor::GetCreature(*me, instance->GetData64(DATA_HIGHLORD_TIRION_FORDRING)))
                        tirion->AI()->DoAction(ACTION_OUTRO);
                    return;
                }

                if (_phase == PHASE_OUTRO)
                {
                    if (!me->HasByteFlag(UNIT_FIELD_BYTES_1, 3, UNIT_BYTE1_FLAG_HOVER))
                        damage = me->GetHealth() > 1 ? 1 : 0;
                    else if (damage >= me->GetHealth()) // dying...
                    {
                        damage = me->GetHealth()-1;
                        me->SetDisableGravity(false);
                        me->RemoveByteFlag(UNIT_FIELD_BYTES_1, 3, UNIT_BYTE1_FLAG_ALWAYS_STAND | UNIT_BYTE1_FLAG_HOVER);
                        me->SendMonsterMove(me->GetPositionX()+0.25f, me->GetPositionY(), 840.86f, 300, SPLINEFLAG_FALLING);
                        me->m_positionZ = 840.86f;
                        me->SetOrientation(0.0f);
                        if (Creature* frostmourne = me->FindNearestCreature(NPC_FROSTMOURNE_TRIGGER, 50.0f))
                            frostmourne->DespawnOrUnsummon(1);
                        if (Creature* terenas = me->FindNearestCreature(NPC_TERENAS_MENETHIL_OUTRO, 50.0f))
                            terenas->DespawnOrUnsummon(1);

                        me->m_Events.AddEvent(new LichKingDeathEvent(*me), me->m_Events.CalculateTime(2500)); // die after spinning anim is over, so death anim is visible
                        me->m_Events.AddEvent(new LichKingMovieEvent(*me), me->m_Events.CalculateTime(11500));
                    }

                    if (!_bFordringMustFallYell && me->GetHealth() < 500000)
                    {
                        _bFordringMustFallYell = true;
                        if (Creature* tirion = ObjectAccessor::GetCreature(*me, instance->GetData64(DATA_HIGHLORD_TIRION_FORDRING)))
                        {
                            tirion->MonsterYell("The Lich King must fall!", LANG_UNIVERSAL, 0);
                            tirion->PlayDirectSound(17389);
                        }
                    }
                    
                }
                else if (damage >= me->GetHealth())
                    damage = me->GetHealth()-1;
            }

            void JustSummoned(Creature* summon)
            {
                switch (summon->GetEntry())
                {
                    case NPC_SHAMBLING_HORROR:
                    case NPC_DRUDGE_GHOUL:
                        summon->SetHomePosition(CenterPosition);
                        summon->CastSpell(summon, SPELL_RISEN_WITCH_DOCTOR_SPAWN, true);
                        summon->SetReactState(REACT_PASSIVE);
                        summon->HandleEmoteCommand(EMOTE_ONESHOT_EMERGE);
                        summon->m_Events.AddEvent(new StartMovementEvent(me, summon), summon->m_Events.CalculateTime(5000));
                        break;
                    case NPC_RAGING_SPIRIT:
                        summon->SetHomePosition(CenterPosition);
                        break;
                    case NPC_VILE_SPIRIT:
                    {
                        summon->SetReactState(REACT_PASSIVE);
                        summon->GetMotionMaster()->MoveRandom(10.0f);
                        if (_phase == PHASE_THREE)
                            summon->m_Events.AddEvent(new VileSpiritActivateEvent(summon), summon->m_Events.CalculateTime(15000));
                        break;
                    }
                    case NPC_STRANGULATE_VEHICLE:
                        summons.Summon(summon);
                        return;
                    case NPC_DEFILE:
                    case NPC_SHADOW_TRAP_TRIGGER:
                        summon->m_positionZ = 840.86f;
                        summon->UpdatePosition(summon->GetPositionX(), summon->GetPositionY(), summon->GetPositionZ(), summon->GetOrientation(), true);
                        summon->StopMovingOnCurrentPos();
                        break;
                    case NPC_VALKYR_SHADOWGUARD:
                        if (_phase == PHASE_THREE || events.GetNextEventTime(EVENT_QUAKE_2))
                            summon->DespawnOrUnsummon(1);
                        break;
                    default:
                        break;
                }

                BossAI::JustSummoned(summon);
            }

            void SummonedCreatureDies(Creature* summon, Unit*)
            {
                switch (summon->GetEntry())
                {
                    case NPC_SHAMBLING_HORROR:
                    case NPC_DRUDGE_GHOUL:
                    case NPC_ICE_SPHERE:
                    case NPC_VALKYR_SHADOWGUARD:
                    case NPC_RAGING_SPIRIT:
                    case NPC_VILE_SPIRIT:
                        summon->ToTempSummon()->SetTimer(5000);
                        summon->ToTempSummon()->SetTempSummonType(TEMPSUMMON_TIMED_DESPAWN);
                        break;
                    default:
                        break;
                }
            }

            void SpellHit(Unit* /*caster*/, SpellInfo const* spell)
            {
                if (spell->Id == HARVESTED_SOUL_BUFF && me->IsInCombat() && !IsHeroic() && _phase != PHASE_OUTRO && _lastTalkTimeBuff+5 <= time(NULL))
                {
                    _lastTalkTimeBuff = time(NULL);
                    Talk(SAY_LK_FROSTMOURNE_KILL);
                }
            }

            void SpellHitTarget(Unit* /*target*/, SpellInfo const* spell)
            {
                if (spell->Id == REMORSELESS_WINTER_1 || spell->Id == REMORSELESS_WINTER_2)
                {
                    me->GetMap()->SetZoneOverrideLight(AREA_THE_FROZEN_THRONE, LIGHT_SNOWSTORM, 5000);
                    me->GetMap()->SetZoneWeather(AREA_THE_FROZEN_THRONE, WEATHER_STATE_LIGHT_SNOW, 0.5f);
                    summons.DespawnEntry(NPC_SHADOW_TRAP_TRIGGER);
                }
            }

            void MovementInform(uint32 type, uint32 pointId)
            {
                if (type != POINT_MOTION_TYPE)
                    return;

                switch (pointId)
                {
                    case POINT_CENTER_1:
                        me->SetFacingTo(0.0f);
                        Talk(SAY_LK_REMORSELESS_WINTER);
                        me->GetMap()->SetZoneMusic(AREA_THE_FROZEN_THRONE, MUSIC_SPECIAL);
                        me->CastSpell(me, SPELL_REMORSELESS_WINTER_1, false);
                        //events.DelayEvents(62500, EVENT_GROUP_BERSERK); // delay berserk timer, its not ticking during phase transitions, bullshit, 15mins on movies
                        events.ScheduleEvent(EVENT_QUAKE, 62500);
                        events.ScheduleEvent(EVENT_PAIN_AND_SUFFERING, 3500, EVENT_GROUP_ABILITIES);
                        events.ScheduleEvent(EVENT_SUMMON_ICE_SPHERE, 8000, EVENT_GROUP_ABILITIES);
                        events.ScheduleEvent(EVENT_SUMMON_RAGING_SPIRIT, 4000, EVENT_GROUP_ABILITIES);
                        break;
                    case POINT_CENTER_2:
                        me->SetFacingTo(0.0f);
                        Talk(SAY_LK_REMORSELESS_WINTER);
                        me->GetMap()->SetZoneMusic(AREA_THE_FROZEN_THRONE, MUSIC_SPECIAL);
                        me->CastSpell(me, SPELL_REMORSELESS_WINTER_2, false);
                        summons.DespawnEntry(NPC_VALKYR_SHADOWGUARD);
                        //events.DelayEvents(62500, EVENT_GROUP_BERSERK); // delay berserk timer, its not ticking during phase transitions, bullshit, 15 mins on movies
                        events.ScheduleEvent(EVENT_QUAKE_2, 62500);
                        events.ScheduleEvent(EVENT_PAIN_AND_SUFFERING, 3500, EVENT_GROUP_ABILITIES);
                        events.ScheduleEvent(EVENT_SUMMON_ICE_SPHERE, 8000, EVENT_GROUP_ABILITIES);
                        events.ScheduleEvent(EVENT_SUMMON_RAGING_SPIRIT, 4000, EVENT_GROUP_ABILITIES);
                        break;
                    default:
                        break;
                }
            }

            void UpdateAI(uint32 diff)
            {
                if (_phase == PHASE_NONE)
                    return;

                // check phase first to prevent updating victim and entering evade mode when not wanted
                if (!((1 << _phase) & PHASE_MASK_NO_VICTIM))
                    if (!UpdateVictim())
                        return;

                // handle falling players so they don't fall infinitely
                if (_positionCheckTimer <= diff)
                {
                    _positionCheckTimer = 5000;
                    Map::PlayerList const& players = me->GetMap()->GetPlayers();
                    if (!players.isEmpty())
                        for (Map::PlayerList::const_iterator itr = players.begin(); itr != players.end(); ++itr)
                            if (Player* player = itr->GetSource())
                                if (player->GetPositionZ() < 700.0f)
                                    Unit::Kill(me, player);
                }
                else
                    _positionCheckTimer -= diff;

                events.Update(diff);

                // during Remorseless Winter phases The Lich King is channeling a spell, but we must continue casting other spells
                if (me->HasUnitState(UNIT_STATE_CASTING) && !((1 << _phase) & PHASE_MASK_NO_CAST_CHECK))
                    return;

                switch (uint32 eventId = events.ExecuteEvent())
                {
                    case EVENT_BERSERK:
                        Talk(SAY_LK_BERSERK);
                        me->CastSpell(me, SPELL_BERSERK2, true);
                        break;
                    case EVENT_START_ATTACK:
                        me->SetReactState(REACT_AGGRESSIVE);
                        if (_phase == PHASE_FROSTMOURNE)
                        {
                            _bFrostmournePhase = false;
                            _phase = PHASE_THREE;
                            events.RescheduleEvent(EVENT_DEFILE, 0, EVENT_GROUP_ABILITIES);
                            events.RescheduleEvent(EVENT_SOUL_REAPER, urand(7000, 12000), EVENT_GROUP_ABILITIES);

                            for (SummonList::iterator i = summons.begin(); i != summons.end(); ++i)
                                if (Creature* summon = ObjectAccessor::GetCreature(*me, *i))
                                    if (summon->GetEntry() == NPC_RAGING_SPIRIT)
                                        summon->SetReactState(REACT_AGGRESSIVE);
                        }
                        break;
                    case EVENT_QUAKE:
                        _phase = PHASE_TWO;
                        events.CancelEventGroup(EVENT_GROUP_ABILITIES);
                        events.ScheduleEvent(EVENT_INFEST, 8000, EVENT_GROUP_ABILITIES);
                        events.ScheduleEvent(EVENT_SUMMON_VALKYR, 15000, EVENT_GROUP_ABILITIES);
                        events.ScheduleEvent(EVENT_SOUL_REAPER, 22000, EVENT_GROUP_ABILITIES);
                        events.ScheduleEvent(EVENT_DEFILE, 32500, EVENT_GROUP_ABILITIES);

                        me->InterruptNonMeleeSpells(false);
                        me->ClearUnitState(UNIT_STATE_CASTING);
                        me->SetFacingTo(0.0f);
                        me->CastSpell((Unit*)NULL, SPELL_QUAKE, false);
                        me->GetMap()->SetZoneMusic(AREA_THE_FROZEN_THRONE, MUSIC_SPECIAL);
                        Talk(SAY_LK_QUAKE);
                        break;
                    case EVENT_QUAKE_2:
                        _phase = PHASE_THREE;
                        events.CancelEventGroup(EVENT_GROUP_ABILITIES);
                        events.ScheduleEvent(EVENT_SOUL_REAPER, 25000, EVENT_GROUP_ABILITIES);
                        events.ScheduleEvent(EVENT_DEFILE, 32500, EVENT_GROUP_ABILITIES);
                        events.ScheduleEvent(EVENT_VILE_SPIRITS, 18000, EVENT_GROUP_VILE_SPIRITS);
                        events.ScheduleEvent(IsHeroic() ? EVENT_HARVEST_SOULS : EVENT_HARVEST_SOUL, 11000, EVENT_GROUP_ABILITIES);

                        me->InterruptNonMeleeSpells(false);
                        me->ClearUnitState(UNIT_STATE_CASTING);
                        me->SetFacingTo(0.0f);
                        me->CastSpell((Unit*)NULL, SPELL_QUAKE, false);
                        me->GetMap()->SetZoneMusic(AREA_THE_FROZEN_THRONE, MUSIC_SPECIAL);
                        Talk(SAY_LK_QUAKE);
                        break;

                    // ABILITIES:
                    case EVENT_SUMMON_SHAMBLING_HORROR:
                        me->CastSpell(me, SPELL_SUMMON_SHAMBLING_HORROR, false);
                        me->GetMap()->SetZoneMusic(AREA_THE_FROZEN_THRONE, MUSIC_SPECIAL);
                        events.ScheduleEvent(EVENT_SUMMON_SHAMBLING_HORROR, 60000, EVENT_GROUP_ABILITIES);
                        break;
                    case EVENT_SUMMON_DRUDGE_GHOUL:
                        me->CastSpell(me, SPELL_SUMMON_DRUDGE_GHOULS, false);
                        events.ScheduleEvent(EVENT_SUMMON_DRUDGE_GHOUL, 30000, EVENT_GROUP_ABILITIES);
                        break;
                    case EVENT_INFEST:
                        me->CastSpell(me, SPELL_INFEST, false);
                        events.ScheduleEvent(EVENT_INFEST, urand(21000, 22000), EVENT_GROUP_ABILITIES);
                        break;
                    case EVENT_NECROTIC_PLAGUE:
                        if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, NecroticPlagueTargetCheck(me, NECROTIC_PLAGUE_LK, NECROTIC_PLAGUE_PLR)))
                        {
                            Talk(EMOTE_NECROTIC_PLAGUE_WARNING, target);
                            me->CastSpell(target, SPELL_NECROTIC_PLAGUE, false);
                            events.ScheduleEvent(EVENT_NECROTIC_PLAGUE, urand(30000, 31000), EVENT_GROUP_ABILITIES);
                        }
                        else
                            events.ScheduleEvent(EVENT_NECROTIC_PLAGUE, 5000, EVENT_GROUP_ABILITIES);
                        break;
                    case EVENT_SHADOW_TRAP:
                        if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, NonTankLKTargetSelector(me, true, true, 100.0f)))
                            me->CastSpell(target, SPELL_SHADOW_TRAP, false);
                        events.ScheduleEvent(EVENT_SHADOW_TRAP, 15500, EVENT_GROUP_ABILITIES);
                        break;
                    case EVENT_PAIN_AND_SUFFERING:
                        if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, 0.0f, true))
                        {
                            events.DelayEventsToMax(500, EVENT_GROUP_ABILITIES);
                            me->SetOrientation(me->GetAngle(target));
                            me->CastSpell(target, SPELL_PAIN_AND_SUFFERING, false);
                        }
                        events.ScheduleEvent(EVENT_PAIN_AND_SUFFERING, (IsHeroic() ? urand(1250, 1750) : urand(1750, 2250)), EVENT_GROUP_ABILITIES);
                        break;
                    case EVENT_SUMMON_ICE_SPHERE:
                        me->CastSpell((Unit*)NULL, SPELL_SUMMON_ICE_SPHERE, false);
                        events.ScheduleEvent(EVENT_SUMMON_ICE_SPHERE, 7500, EVENT_GROUP_ABILITIES);
                        break;
                    case EVENT_SUMMON_RAGING_SPIRIT:
                        if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, 100.0f, true))
                            me->CastSpell(target, SPELL_RAGING_SPIRIT, false);
                        events.ScheduleEvent(EVENT_SUMMON_RAGING_SPIRIT, (!HealthAbovePct(40) ? 15000 : 20000), EVENT_GROUP_ABILITIES);
                        break;
                    case EVENT_DEFILE:
                        {
                            uint32 evTime = events.GetNextEventTime(EVENT_SUMMON_VALKYR);
                            if (evTime && (events.GetTimer() > evTime || evTime - events.GetTimer() < 5000)) // defile cast 2sec -> valkyr in less than 3 secs after defile appears
                            {
                                if (events.GetTimer() > evTime || evTime - events.GetTimer() < 3500) // valkyr is less than 1.5 secs after defile - reschedule defile
                                {
                                    uint32 t = events.GetTimer() > evTime ? 0 : evTime - events.GetTimer();
                                    events.ScheduleEvent(EVENT_DEFILE, t+(Is25ManRaid() ? 5000 : 4000), EVENT_GROUP_ABILITIES);
                                    break;
                                }
                                // delay valkyr just a bit
                                events.RescheduleEvent(EVENT_SUMMON_VALKYR, 5000, EVENT_GROUP_ABILITIES);
                            }
                            if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, DefileTargetSelector(me)))
                            {
                                Talk(EMOTE_DEFILE_WARNING);
                                me->CastSpell(target, SPELL_DEFILE, false);
                                events.ScheduleEvent(EVENT_DEFILE, urand(31000, 34000), EVENT_GROUP_ABILITIES);
                            }
                            else
                                events.ScheduleEvent(EVENT_DEFILE, 2000, EVENT_GROUP_ABILITIES);
                        }
                        break;
                    case EVENT_SOUL_REAPER:
                        if (me->IsWithinMeleeRange(me->GetVictim()))
                        {
                            me->CastSpell(me->GetVictim(), SPELL_SOUL_REAPER, false);
                            events.DelayEventsToMax(12000, EVENT_GROUP_ABILITIES);
                            events.ScheduleEvent(EVENT_SOUL_REAPER, 30000, EVENT_GROUP_ABILITIES);
                        }
                        else
                            events.ScheduleEvent(EVENT_SOUL_REAPER, 1000, EVENT_GROUP_ABILITIES);
                        break;
                    case EVENT_SUMMON_VALKYR:
                        {
                            me->GetMap()->SetZoneMusic(AREA_THE_FROZEN_THRONE, MUSIC_SPECIAL);
                            Talk(SAY_LK_SUMMON_VALKYR);
                            me->CastSpell((Unit*)NULL, SUMMON_VALKYR, false);
                            events.ScheduleEvent(EVENT_SUMMON_VALKYR, urand(45000, 48000), EVENT_GROUP_ABILITIES);

                            uint32 minTime = (Is25ManRaid() ? 5000 : 4000);
                            if (uint32 evTime = events.GetNextEventTime(EVENT_DEFILE))
                                if (events.GetTimer() > evTime || evTime - events.GetTimer() < minTime)
                                    events.RescheduleEvent(EVENT_DEFILE, minTime, EVENT_GROUP_ABILITIES);
                        }
                        break;
                    case EVENT_VILE_SPIRITS:
                        me->GetMap()->SetZoneMusic(AREA_THE_FROZEN_THRONE, MUSIC_SPECIAL);
                        me->CastSpell((Unit*)NULL, SPELL_VILE_SPIRITS, false);
                        events.ScheduleEvent(EVENT_VILE_SPIRITS, 30000, EVENT_GROUP_VILE_SPIRITS);
                        break;
                    case EVENT_HARVEST_SOUL:
                        if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, NonTankLKTargetSelector(me, true, true, 55.0f)))
                        {
                            Talk(SAY_LK_HARVEST_SOUL);
                            me->CastSpell(target, SPELL_HARVEST_SOUL, false);
                            events.ScheduleEvent(EVENT_HARVEST_SOUL, 70000, EVENT_GROUP_ABILITIES);
                        }
                        else
                            events.ScheduleEvent(EVENT_HARVEST_SOUL, 10000, EVENT_GROUP_ABILITIES);
                        break;
                    case EVENT_HARVEST_SOULS:
                        Talk(SAY_LK_HARVEST_SOUL);
                        me->CastSpell((Unit*)NULL, SPELL_HARVEST_SOULS, false);
                        _phase = PHASE_FROSTMOURNE;
                        me->SetReactState(REACT_PASSIVE);
                        me->AttackStop();
                        events.ScheduleEvent(EVENT_START_ATTACK, 55000);
                        events.DelayEvents(52500, EVENT_GROUP_VILE_SPIRITS);
                        events.CancelEvent(EVENT_DEFILE);
                        events.CancelEvent(EVENT_SOUL_REAPER);
                        events.ScheduleEvent(EVENT_FROSTMOURNE_HEROIC, 6000, EVENT_GROUP_ABILITIES);
                        events.ScheduleEvent(EVENT_HARVEST_SOULS, urand(100000, 110000), EVENT_GROUP_ABILITIES);

                        for (SummonList::iterator i = summons.begin(); i != summons.end(); ++i)
                            if (Creature* summon = ObjectAccessor::GetCreature(*me, *i))
                            {
                                if (summon->GetEntry() == NPC_VILE_SPIRIT)
                                {
                                    summon->m_Events.KillAllEvents(true);
                                    summon->m_Events.AddEvent(new VileSpiritActivateEvent(summon), summon->m_Events.CalculateTime(55000));
                                    summon->GetMotionMaster()->Clear(true);
                                    summon->StopMoving();
                                    summon->SetReactState(REACT_PASSIVE);
                                    summon->AttackStop();
                                }
                                else if (summon->GetEntry() == NPC_RAGING_SPIRIT)
                                {
                                    summon->GetMotionMaster()->Clear(true);
                                    summon->StopMoving();
                                    summon->SetReactState(REACT_PASSIVE);
                                    summon->AttackStop();
                                }
                            }
                        break;
                    case EVENT_FROSTMOURNE_HEROIC:
                        _bFrostmournePhase = true;
                        if (TempSummon* terenas = me->GetMap()->SummonCreature(NPC_TERENAS_MENETHIL_FROSTMOURNE_H, TerenasSpawnHeroic, NULL, 55000))
                        {
                            terenas->AI()->DoAction(ACTION_FROSTMOURNE_INTRO);
                            if (Creature* spawner = terenas->FindNearestCreature(NPC_WORLD_TRIGGER_INFINITE_AOI, 100.0f, true))
                            {
                                spawner->CastSpell(spawner, SPELL_SUMMON_SPIRIT_BOMB_1, true);  // summons bombs randomly
                                spawner->CastSpell(spawner, SPELL_SUMMON_SPIRIT_BOMB_2, true);  // summons bombs on players
                                spawner->m_Events.AddEvent(new TriggerWickedSpirit(spawner), spawner->m_Events.CalculateTime(3000));
                                terenas->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC | UNIT_FLAG_IMMUNE_TO_NPC); // to avoid being healed by player trinket procs. terenas' health doesn't matter on heroic
                            }
                        }
                        break;

                    default:
                        break;
                }

                if (!me->HasReactState(REACT_PASSIVE) && me->GetVictim() && me->IsWithinMeleeRange(me->GetVictim()) && me->GetMotionMaster()->GetCurrentMovementGeneratorType() != POINT_MOTION_TYPE && !me->IsWithinLOSInMap(me->GetVictim()))
                    me->GetMotionMaster()->MoveCharge(me->GetVictim()->GetPositionX(), me->GetVictim()->GetPositionY(), me->GetVictim()->GetPositionZ(), me->GetSpeed(MOVE_RUN), POINT_NONE);

                DoMeleeAttackIfReady();
            }

            uint32 GetData(uint32 type) const
            {
                switch (type)
                {
                    case DATA_PLAGUE_STACK:
                        return _necroticPlagueStack;
                    case DATA_VILE:
                        return _vileSpiritExplosions;
                    default:
                        break;
                }

                return 0;
            }

            void SetData(uint32 type, uint32 value)
            {
                switch (type)
                {
                    case DATA_PLAGUE_STACK:
                        _necroticPlagueStack = std::max(value, _necroticPlagueStack);
                        break;
                    case DATA_VILE:
                        _vileSpiritExplosions += value;
                        break;
                    default:
                        break;
                }
            }

            void EnterEvadeMode()
            {
                EntryCheckPredicate pred(NPC_STRANGULATE_VEHICLE);
                summons.DoAction(ACTION_TELEPORT_BACK, pred);
                instance->SetBossState(DATA_THE_LICH_KING, FAIL);
                me->CastSpell((Unit*)NULL, SPELL_KILL_FROSTMOURNE_PLAYERS, true);
                BossAI::EnterEvadeMode();
                me->SetReactState(REACT_AGGRESSIVE);

                if (Creature* tirion = ObjectAccessor::GetCreature(*me, instance->GetData64(DATA_HIGHLORD_TIRION_FORDRING)))
                    tirion->AI()->EnterEvadeMode();
            }
        };

        CreatureAI* GetAI(Creature* creature) const
        {
            return GetIcecrownCitadelAI<boss_the_lich_kingAI>(creature);
        }
};

class npc_tirion_fordring_tft : public CreatureScript
{
    public:
        npc_tirion_fordring_tft() : CreatureScript("npc_tirion_fordring_tft") { }

        struct npc_tirion_fordringAI : public ScriptedAI
        {
            npc_tirion_fordringAI(Creature* creature) : ScriptedAI(creature), _instance(creature->GetInstanceScript()) {}

            void Reset()
            {
                _events.Reset();
                if (_instance->GetBossState(DATA_THE_LICH_KING) == DONE || me->GetMap()->IsHeroic() && !_instance->GetData(DATA_LK_HC_AVAILABLE))
                    me->RemoveFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_GOSSIP);
                me->SetReactState(REACT_PASSIVE);
            }

            void MovementInform(uint32 type, uint32 id)
            {
                if (type != POINT_MOTION_TYPE)
                    return;

                switch (id)
                {
                    case POINT_TIRION_INTRO:
                        if (Creature* theLichKing = ObjectAccessor::GetCreature(*me, _instance->GetData64(DATA_THE_LICH_KING)))
                        {
                            if (!theLichKing->IsAlive() || !theLichKing->IsVisible())
                                break;
                            me->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_STATE_READY2H);
                            theLichKing->SetStandState(UNIT_STAND_STATE_STAND);
                            theLichKing->SetSheath(SHEATH_STATE_MELEE);
                            theLichKing->RemoveAurasDueToSpell(SPELL_EMOTE_SIT_NO_SHEATH);
                            theLichKing->AI()->Talk(SAY_LK_INTRO_1);
                            me->GetMap()->SetZoneMusic(AREA_THE_FROZEN_THRONE, MUSIC_FROZEN_THRONE);
                            _events.ScheduleEvent(EVENT_INTRO_LK_MOVE, 3000);
                        }
                        break;
                    case POINT_TIRION_OUTRO:
                        _events.ScheduleEvent(EVENT_OUTRO_FORDRING_JUMP, 1);
                        break;
                }
            }

            void DoAction(int32 action)
            {
                switch (action)
                {
                    case ACTION_OUTRO:
                        _events.ScheduleEvent(EVENT_OUTRO_LK_TALK_1, 2600);
                        _events.ScheduleEvent(EVENT_OUTRO_LK_EMOTE_TALK, 6600);
                        _events.ScheduleEvent(EVENT_OUTRO_LK_EMOTE_TALK, 17600);
                        _events.ScheduleEvent(EVENT_OUTRO_LK_TALK_2, 30000);
                        _events.ScheduleEvent(EVENT_OUTRO_LK_TALK_3, 39000);
                        _events.ScheduleEvent(EVENT_OUTRO_LK_EMOTE_CAST_SHOUT, 50000);
                        _events.ScheduleEvent(EVENT_OUTRO_LK_EMOTE_TALK, 54000);
                        _events.ScheduleEvent(EVENT_OUTRO_LK_MOVE_CENTER, 65000);
                        break;
                    case ACTION_BREAK_FROSTMOURNE:
                        _events.ScheduleEvent(EVENT_OUTRO_LK_TALK_6, 2500);
                        _events.ScheduleEvent(EVENT_OUTRO_SOUL_BARRAGE, 6500);
                        break;
                }
            }

            void SpellHit(Unit* /*caster*/, SpellInfo const* spell)
            {
                if (spell->Id == SPELL_ICE_LOCK)
                {
                    me->StopMoving();
                    me->GetMotionMaster()->Clear(true);
                    me->SetFacingTo(3.085098f);
                }
                else if (spell->Id == SPELL_BROKEN_FROSTMOURNE_KNOCK)
                {
                    // remove glow on ashbringer and tirion
                    me->RemoveAllAuras();
                    SetEquipmentSlots(true);
                }
            }

            void JustReachedHome()
            {
                ScriptedAI::JustReachedHome();
                if (!(_instance->GetBossState(DATA_THE_LICH_KING) == DONE || me->GetMap()->IsHeroic() && !_instance->GetData(DATA_LK_HC_AVAILABLE)))
                    me->SetFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_GOSSIP);
                me->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_ONESHOT_NONE);
            }

            void sGossipSelect(Player* /*player*/, uint32 sender, uint32 action)
            {
                Creature* theLichKing = ObjectAccessor::GetCreature(*me, _instance->GetData64(DATA_THE_LICH_KING));
                if (me->GetCreatureTemplate()->GossipMenuId == sender && !action && me->HasFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_GOSSIP) && theLichKing && !theLichKing->IsInEvadeMode())
                {
                    if (me->GetMap()->IsHeroic() && !_instance->GetData(DATA_LK_HC_AVAILABLE))
                        return;
                    me->RemoveFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_GOSSIP);
                    me->SetWalk(true);
                    me->GetMotionMaster()->MovePoint(POINT_TIRION_INTRO, TirionIntro);
                }
            }

            /*bool CanBeSeen(Player const* p)
            {
                return me->GetExactDistSq(p) < 200.0f*200.0f;
            }*/

            void UpdateAI(uint32 diff)
            {
                UpdateVictim();

                _events.Update(diff);

                if (me->HasUnitState(UNIT_STATE_CASTING))
                    return;

                switch (uint32 eventId = _events.ExecuteEvent())
                {
                    case EVENT_INTRO_LK_MOVE:
                        if (Creature* theLichKing = ObjectAccessor::GetCreature(*me, _instance->GetData64(DATA_THE_LICH_KING)))
                        {
                            Movement::PointsArray path;
                            path.push_back(G3D::Vector3(theLichKing->GetPositionX(), theLichKing->GetPositionY(), theLichKing->GetPositionZ()));
                            for (uint8 i=0; i<3; ++i)
                                path.push_back(G3D::Vector3(LichKingIntro[i].GetPositionX(), LichKingIntro[i].GetPositionY(), LichKingIntro[i].GetPositionZ()));
                            theLichKing->SetWalk(true);
                            theLichKing->GetMotionMaster()->MoveSplinePath(&path);
                            _events.ScheduleEvent(EVENT_INTRO_FORDRING_TALK_1, 11000);
                        }
                        break;
                    case EVENT_INTRO_LK_TALK_1:
                        if (Creature* theLichKing = ObjectAccessor::GetCreature(*me, _instance->GetData64(DATA_THE_LICH_KING)))
                        {
                            theLichKing->AI()->Talk(SAY_LK_INTRO_2);
                            theLichKing->HandleEmoteCommand(EMOTE_ONESHOT_TALK_NO_SHEATHE);
                            _events.ScheduleEvent(EVENT_INTRO_LK_EMOTE_CAST_SHOUT, 7000);
                            _events.ScheduleEvent(EVENT_INTRO_LK_EMOTE_1, 13000);
                            _events.ScheduleEvent(EVENT_INTRO_LK_EMOTE_CAST_SHOUT, 18000);
                            _events.ScheduleEvent(EVENT_INTRO_LK_CAST_FREEZE, 31000);
                        }
                        break;
                    case EVENT_INTRO_LK_EMOTE_CAST_SHOUT:
                        if (Creature* theLichKing = ObjectAccessor::GetCreature(*me, _instance->GetData64(DATA_THE_LICH_KING)))
                            theLichKing->CastSpell(theLichKing, SPELL_EMOTE_SHOUT_NO_SHEATH, false);
                        break;
                    case EVENT_INTRO_LK_EMOTE_1:
                        if (Creature* theLichKing = ObjectAccessor::GetCreature(*me, _instance->GetData64(DATA_THE_LICH_KING)))
                            theLichKing->HandleEmoteCommand(EMOTE_ONESHOT_POINT_NO_SHEATHE);
                        break;
                    case EVENT_INTRO_LK_CAST_FREEZE:
                        if (Creature* theLichKing = ObjectAccessor::GetCreature(*me, _instance->GetData64(DATA_THE_LICH_KING)))
                        {
                            theLichKing->AI()->Talk(SAY_LK_INTRO_3);
                            theLichKing->CastSpell((Unit*)NULL, SPELL_ICE_LOCK, false);
                            _events.ScheduleEvent(EVENT_INTRO_FINISH, 1000);
                        }
                        break;
                    case EVENT_INTRO_FORDRING_TALK_1:
                        {
                            Talk(SAY_TIRION_INTRO_1);
                            _events.ScheduleEvent(EVENT_INTRO_LK_TALK_1, 9000);
                            _events.ScheduleEvent(EVENT_INTRO_FORDRING_TALK_2, 34000);
                        }
                        break;
                    case EVENT_INTRO_FORDRING_TALK_2:
                        {
                            Talk(SAY_TIRION_INTRO_2);
                            _events.ScheduleEvent(EVENT_INTRO_FORDRING_EMOTE_1, 2000);
                            _events.ScheduleEvent(EVENT_INTRO_FORDRING_CHARGE, 5000);
                        }
                        break;
                    case EVENT_INTRO_FORDRING_EMOTE_1:
                        me->HandleEmoteCommand(EMOTE_ONESHOT_POINT_NO_SHEATHE);
                        break;
                    case EVENT_INTRO_FORDRING_CHARGE:
                        me->SetWalk(false);
                        me->GetMotionMaster()->MovePoint(0, TirionCharge);
                        break;
                    case EVENT_INTRO_FINISH:
                        if (Creature* theLichKing = ObjectAccessor::GetCreature(*me, _instance->GetData64(DATA_THE_LICH_KING)))
                        {
                            theLichKing->SetWalk(false);
                            theLichKing->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC);
                            theLichKing->SetReactState(REACT_AGGRESSIVE);
                            theLichKing->SetInCombatWithZone();
                            if (!theLichKing->IsInCombat())
                                theLichKing->AI()->EnterEvadeMode();
                        }
                        break;


                    case EVENT_OUTRO_LK_TALK_1:
                        if (Creature* theLichKing = ObjectAccessor::GetCreature(*me, _instance->GetData64(DATA_THE_LICH_KING)))
                        {
                            theLichKing->AI()->Talk(SAY_LK_OUTRO_1);
                            theLichKing->CastSpell((Unit*)NULL, SPELL_FURY_OF_FROSTMOURNE_NO_REZ, true);
                            Map::PlayerList const& pl = me->GetMap()->GetPlayers();
                            for (Map::PlayerList::const_iterator itr = pl.begin(); itr != pl.end(); ++itr)
                                if (Player* p = itr->GetSource())
                                    if (p->IsAlive())
                                        Unit::Kill(me, p);
                        }
                        break;
                    case EVENT_OUTRO_LK_TALK_2:
                        if (Creature* theLichKing = ObjectAccessor::GetCreature(*me, _instance->GetData64(DATA_THE_LICH_KING)))
                        {
                            theLichKing->AI()->Talk(SAY_LK_OUTRO_2);
                            theLichKing->CastSpell((Unit*)NULL, SPELL_EMOTE_QUESTION_NO_SHEATH, false);
                        }
                        break;
                    case EVENT_OUTRO_LK_EMOTE_TALK:
                        if (Creature* theLichKing = ObjectAccessor::GetCreature(*me, _instance->GetData64(DATA_THE_LICH_KING)))
                            theLichKing->HandleEmoteCommand(EMOTE_ONESHOT_TALK_NO_SHEATHE);
                        break;
                    case EVENT_OUTRO_LK_TALK_3:
                        if (Creature* theLichKing = ObjectAccessor::GetCreature(*me, _instance->GetData64(DATA_THE_LICH_KING)))
                        {
                            theLichKing->SetFacingToObject(me);
                            theLichKing->AI()->Talk(SAY_LK_OUTRO_3);
                        }
                        break;
                    case EVENT_OUTRO_LK_MOVE_CENTER:
                        if (Creature* theLichKing = ObjectAccessor::GetCreature(*me, _instance->GetData64(DATA_THE_LICH_KING)))
                        {
                            theLichKing->GetMotionMaster()->MovePoint(0, CenterPosition);
                            uint32 travelTime = 1000*theLichKing->GetExactDist(&CenterPosition)/theLichKing->GetSpeed(MOVE_WALK) + 1000;
                            
                            _events.ScheduleEvent(EVENT_OUTRO_LK_TALK_4, 1+travelTime);
                            _events.ScheduleEvent(EVENT_OUTRO_LK_RAISE_DEAD, 1000+travelTime);
                            _events.ScheduleEvent(EVENT_OUTRO_LK_TALK_5, 29000+travelTime);
                        }
                        break;
                    case EVENT_OUTRO_LK_TALK_4:
                        if (Creature* theLichKing = ObjectAccessor::GetCreature(*me, _instance->GetData64(DATA_THE_LICH_KING)))
                        {
                            theLichKing->SetFacingTo(0.01745329f);
                            theLichKing->AI()->Talk(SAY_LK_OUTRO_4);
                        }
                        break;
                    case EVENT_OUTRO_LK_RAISE_DEAD:
                        if (Creature* theLichKing = ObjectAccessor::GetCreature(*me, _instance->GetData64(DATA_THE_LICH_KING)))
                        {
                            theLichKing->CastSpell((Unit*)NULL, SPELL_RAISE_DEAD, false);
                            theLichKing->ClearUnitState(UNIT_STATE_CASTING);
                            me->GetMap()->SetZoneMusic(AREA_THE_FROZEN_THRONE, MUSIC_FINAL);
                        }
                        break;
                    case EVENT_OUTRO_LK_TALK_5:
                        if (Creature* theLichKing = ObjectAccessor::GetCreature(*me, _instance->GetData64(DATA_THE_LICH_KING)))
                        {
                            theLichKing->AI()->Talk(SAY_LK_OUTRO_5);
                            _events.ScheduleEvent(EVENT_OUTRO_FORDRING_TALK_1, 7000);
                            _events.ScheduleEvent(EVENT_OUTRO_FORDRING_BLESS, 18000);
                            _events.ScheduleEvent(EVENT_OUTRO_FORDRING_REMOVE_ICE, 23000);
                            _events.ScheduleEvent(EVENT_OUTRO_FORDRING_MOVE_1, 25000);
                        }
                        break;
                    case EVENT_OUTRO_LK_TALK_6:
                        if (Creature* theLichKing = ObjectAccessor::GetCreature(*me, _instance->GetData64(DATA_THE_LICH_KING)))
                        {
                            theLichKing->AI()->Talk(SAY_LK_OUTRO_6);
                            me->SetFacingToObject(theLichKing);
                            theLichKing->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID, EQUIP_UNEQUIP);
                            theLichKing->CastSpell((Unit*)NULL, SPELL_SUMMON_BROKEN_FROSTMOURNE_3, true);
                            me->GetMap()->SetZoneOverrideLight(AREA_THE_FROZEN_THRONE, LIGHT_SOULSTORM, 10000);
                            me->GetMap()->SetZoneWeather(AREA_THE_FROZEN_THRONE, WEATHER_STATE_BLACKSNOW, 0.5f);

                            _events.ScheduleEvent(EVENT_OUTRO_AFTER_SUMMON_BROKEN_FROSTMOURNE, 1000);
                            _events.ScheduleEvent(EVENT_OUTRO_KNOCK_BACK, 3000);
                            break;
                        }
                        break;
                    case EVENT_OUTRO_AFTER_SUMMON_BROKEN_FROSTMOURNE:
                        if (Creature* frostmourne = me->FindNearestCreature(NPC_FROSTMOURNE_TRIGGER, 50.0f))
                            frostmourne->CastSpell((Unit*)NULL, SPELL_BROKEN_FROSTMOURNE, true);
                        break;
                    case EVENT_OUTRO_KNOCK_BACK:
                        if (Creature* frostmourne = me->FindNearestCreature(NPC_FROSTMOURNE_TRIGGER, 50.0f))
                            frostmourne->CastSpell((Unit*)NULL, SPELL_BROKEN_FROSTMOURNE_KNOCK, false);
                        break;
                    case EVENT_OUTRO_SOUL_BARRAGE:
                        if (Creature* theLichKing = ObjectAccessor::GetCreature(*me, _instance->GetData64(DATA_THE_LICH_KING)))
                        {
                            theLichKing->CastSpell((Unit*)NULL, SPELL_SOUL_BARRAGE, TRIGGERED_IGNORE_CAST_IN_PROGRESS);
                            sCreatureTextMgr->SendSound(theLichKing, SOUND_PAIN, CHAT_MSG_MONSTER_YELL, 0, TEXT_RANGE_NORMAL, TEAM_NEUTRAL, false);
                            theLichKing->SetDisableGravity(true);
                            theLichKing->SetByteFlag(UNIT_FIELD_BYTES_1, 3, UNIT_BYTE1_FLAG_ALWAYS_STAND | UNIT_BYTE1_FLAG_HOVER);
                            theLichKing->GetMotionMaster()->MovePoint(0, OutroFlying);
                            
                            _events.ScheduleEvent(EVENT_OUTRO_AFTER_SOUL_BARRAGE, 3000);
                        }
                        break;
                    case EVENT_OUTRO_AFTER_SOUL_BARRAGE:
                        Talk(SAY_TIRION_OUTRO_2);
                        _events.ScheduleEvent(EVENT_OUTRO_SUMMON_TERENAS, 6000);
                        break;
                    case EVENT_OUTRO_SUMMON_TERENAS:
                        if (Creature* frostmourne = me->FindNearestCreature(NPC_FROSTMOURNE_TRIGGER, 50.0f))
                        {
                            frostmourne->CastSpell((Unit*)NULL, SPELL_SUMMON_TERENAS, false);
                            if (Creature* terenas = me->FindNearestCreature(NPC_TERENAS_MENETHIL_OUTRO, 50.0f))
                                terenas->SetFacingToObject(frostmourne);
                        }
                        _events.ScheduleEvent(EVENT_OUTRO_TERENAS_TALK_1, 2000);
                        _events.ScheduleEvent(EVENT_OUTRO_TERENAS_TALK_2, 14000);
                        break;
                    case EVENT_OUTRO_TERENAS_TALK_1:
                        if (Creature* terenas = me->FindNearestCreature(NPC_TERENAS_MENETHIL_OUTRO, 50.0f))
                            terenas->AI()->Talk(SAY_TERENAS_OUTRO_1);
                        break;
                    case EVENT_OUTRO_TERENAS_TALK_2:
                        if (Creature* terenas = me->FindNearestCreature(NPC_TERENAS_MENETHIL_OUTRO, 50.0f))
                        {
                            terenas->AI()->Talk(SAY_TERENAS_OUTRO_2);
                            terenas->CastSpell((Unit*)NULL, SPELL_MASS_RESURRECTION, false);
                            if (Creature* lichKing = ObjectAccessor::GetCreature(*me, _instance->GetData64(DATA_THE_LICH_KING)))
                            {
                                lichKing->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_NPC);
                                me->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_ONESHOT_NONE);
                                me->RemoveAllAuras();
                                SetEquipmentSlots(true);
                                me->Attack(lichKing, true);
                                me->GetMotionMaster()->MovePoint(0, 512.16f, -2120.25f, 840.86f);
                            }
                            _events.ScheduleEvent(EVENT_OUTRO_LK_TALK_7, 7000);
                            _events.ScheduleEvent(EVENT_OUTRO_LK_TALK_8, 17000);
                        }
                        break;
                    case EVENT_OUTRO_LK_TALK_7:
                        if (Creature* theLichKing = ObjectAccessor::GetCreature(*me, _instance->GetData64(DATA_THE_LICH_KING)))
                            theLichKing->AI()->Talk(SAY_LK_OUTRO_7);
                        break;
                    case EVENT_OUTRO_LK_TALK_8:
                        if (Creature* theLichKing = ObjectAccessor::GetCreature(*me, _instance->GetData64(DATA_THE_LICH_KING)))
                            theLichKing->AI()->Talk(SAY_LK_OUTRO_8);
                        break;
                    case EVENT_OUTRO_FORDRING_TALK_1:
                        Talk(SAY_TIRION_OUTRO_1);
                        break;
                    case EVENT_OUTRO_FORDRING_BLESS:
                        me->CastSpell(me, SPELL_LIGHTS_BLESSING, false);
                        break;
                    case EVENT_OUTRO_FORDRING_REMOVE_ICE:
                        me->RemoveAurasDueToSpell(SPELL_ICE_LOCK);
                        SetEquipmentSlots(false, EQUIP_ASHBRINGER_GLOWING);
                        if (Creature* lichKing = ObjectAccessor::GetCreature(*me, _instance->GetData64(DATA_THE_LICH_KING)))
                        {
                            me->SetFacingToObject(lichKing);
                            me->GetMap()->SetZoneMusic(AREA_THE_FROZEN_THRONE, MUSIC_FINAL);
                        }
                        break;
                    case EVENT_OUTRO_FORDRING_MOVE_1:
                        me->GetMotionMaster()->MovePoint(POINT_TIRION_OUTRO, OutroPosition1);
                        break;
                    case EVENT_OUTRO_FORDRING_JUMP:
                        me->CastSpell((Unit*)NULL, SPELL_JUMP, false);
                        break;


                    default:
                        break;
                }

                DoMeleeAttackIfReady();
            }

            void EnterEvadeMode()
            {
                if (!me->IsAlive())
                    return;

                if (Creature* theLichKing = ObjectAccessor::GetCreature(*me, _instance->GetData64(DATA_THE_LICH_KING)))
                    if (theLichKing->IsInEvadeMode())
                    {
                        ScriptedAI::EnterEvadeMode();
                        return;
                    }

                me->DeleteThreatList();
                me->CombatStop(false);
            }

            bool CanAIAttack(Unit const* target) const
            {
                return target->GetEntry() == NPC_THE_LICH_KING;
            }

        private:
            EventMap _events;
            InstanceScript* _instance;
        };

        CreatureAI* GetAI(Creature* creature) const
        {
            return GetIcecrownCitadelAI<npc_tirion_fordringAI>(creature);
        }
};

class spell_the_lich_king_quake : public SpellScriptLoader
{
    public:
        spell_the_lich_king_quake() : SpellScriptLoader("spell_the_lich_king_quake") { }

        class spell_the_lich_king_quake_SpellScript : public SpellScript
        {
            PrepareSpellScript(spell_the_lich_king_quake_SpellScript);

            bool Load()
            {
                return GetCaster()->GetInstanceScript() != NULL;
            }

            void FilterTargets(std::list<WorldObject*>& targets)
            {
                if (GameObject* platform = ObjectAccessor::GetGameObject(*GetCaster(), GetCaster()->GetInstanceScript()->GetData64(DATA_ARTHAS_PLATFORM)))
                    targets.remove_if(HeightDifferenceCheck(platform, 5.0f, false));
            }

            void HandleSendEvent(SpellEffIndex /*effIndex*/)
            {
                if (GetCaster()->IsAIEnabled)
                    GetCaster()->GetAI()->DoAction(ACTION_START_ATTACK);
            }

            void Register()
            {
                OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_the_lich_king_quake_SpellScript::FilterTargets, EFFECT_0, TARGET_UNIT_SRC_AREA_ENTRY);
                OnEffectHit += SpellEffectFn(spell_the_lich_king_quake_SpellScript::HandleSendEvent, EFFECT_1, SPELL_EFFECT_SEND_EVENT);
            }
        };

        SpellScript* GetSpellScript() const
        {
            return new spell_the_lich_king_quake_SpellScript();
        }
};

class spell_the_lich_king_jump : public SpellScriptLoader
{
    public:
        spell_the_lich_king_jump() : SpellScriptLoader("spell_the_lich_king_jump") { }

        class spell_the_lich_king_jump_SpellScript : public SpellScript
        {
            PrepareSpellScript(spell_the_lich_king_jump_SpellScript);

            void HandleScript(SpellEffIndex effIndex)
            {
                PreventHitDefaultEffect(effIndex);
                GetHitUnit()->RemoveAurasDueToSpell(SPELL_RAISE_DEAD);
                GetHitUnit()->InterruptNonMeleeSpells(true);
                GetHitUnit()->CastSpell((Unit*)NULL, SPELL_JUMP_2, true);
                if (Creature* creature = GetHitCreature())
                    creature->AI()->DoAction(ACTION_BREAK_FROSTMOURNE);
            }

            void Register()
            {
                OnEffectHitTarget += SpellEffectFn(spell_the_lich_king_jump_SpellScript::HandleScript, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT);
            }
        };

        SpellScript* GetSpellScript() const
        {
            return new spell_the_lich_king_jump_SpellScript();
        }
};

class spell_the_lich_king_jump_remove_aura : public SpellScriptLoader
{
    public:
        spell_the_lich_king_jump_remove_aura() : SpellScriptLoader("spell_the_lich_king_jump_remove_aura") { }

        class spell_the_lich_king_jump_SpellScript : public SpellScript
        {
            PrepareSpellScript(spell_the_lich_king_jump_SpellScript);

            void HandleScript(SpellEffIndex effIndex)
            {
                PreventHitDefaultEffect(effIndex);
                GetHitUnit()->RemoveAurasDueToSpell(uint32(GetEffectValue()));
            }

            void Register()
            {
                OnEffectHitTarget += SpellEffectFn(spell_the_lich_king_jump_SpellScript::HandleScript, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT);
            }
        };

        SpellScript* GetSpellScript() const
        {
            return new spell_the_lich_king_jump_SpellScript();
        }
};

class spell_the_lich_king_play_movie : public SpellScriptLoader
{
    public:
        spell_the_lich_king_play_movie() : SpellScriptLoader("spell_the_lich_king_play_movie") { }

        class spell_the_lich_king_play_movie_SpellScript : public SpellScript
        {
            PrepareSpellScript(spell_the_lich_king_play_movie_SpellScript);

            bool Validate(SpellInfo const* /*spell*/)
            {
                if (!sMovieStore.LookupEntry(MOVIE_FALL_OF_THE_LICH_KING))
                    return false;
                return true;
            }

            void HandleScript(SpellEffIndex effIndex)
            {
                PreventHitDefaultEffect(effIndex);
                if (Player* player = GetHitPlayer())
                    player->SendMovieStart(MOVIE_FALL_OF_THE_LICH_KING);
            }

            void Register()
            {
                OnEffectHitTarget += SpellEffectFn(spell_the_lich_king_play_movie_SpellScript::HandleScript, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT);
            }
        };

        SpellScript* GetSpellScript() const
        {
            return new spell_the_lich_king_play_movie_SpellScript();
        }
};

/*** FIGHT STUFF BELOW ***/

class npc_shambling_horror_icc : public CreatureScript
{
    public:
        npc_shambling_horror_icc() :  CreatureScript("npc_shambling_horror_icc") { }

        struct npc_shambling_horror_iccAI : public ScriptedAI
        {
            npc_shambling_horror_iccAI(Creature* creature) : ScriptedAI(creature)
            {
                _frenzied = false;
            }

            EventMap _events;
            bool _frenzied;

            void Reset()
            {
                _events.Reset();
                _events.ScheduleEvent(EVENT_SHOCKWAVE, urand(20000, 25000));
                _events.ScheduleEvent(EVENT_ENRAGE, urand(11000, 14000));
            }

            void DamageTaken(Unit*, uint32& damage, DamageEffectType, SpellSchoolMask)
            {
                if (!_frenzied && IsHeroic() && me->HealthBelowPctDamaged(20, damage))
                {
                    _frenzied = true;
                    me->CastSpell(me, SPELL_FRENZY, true);
                }
            }

            void UpdateAI(uint32 diff)
            {
                if (!UpdateVictim())
                    return;

                _events.Update(diff);

                if (me->HasUnitState(UNIT_STATE_CASTING))
                    return;

                switch (uint32 eventId = _events.ExecuteEvent())
                {
                    case EVENT_SHOCKWAVE:
                        me->CastSpell(me->GetVictim(), SPELL_SHOCKWAVE, false);
                        _events.ScheduleEvent(EVENT_SHOCKWAVE, urand(20000, 25000));
                        break;
                    case EVENT_ENRAGE:
                        me->CastSpell(me, SPELL_ENRAGE, false);
                        _events.ScheduleEvent(EVENT_ENRAGE, urand(20000, 25000));
                        break;
                    default:
                        break;
                }

                DoMeleeAttackIfReady();
            }

            bool CanAIAttack(Unit const* target) const
            {
                return IsValidPlatformTarget(target) && !target->GetVehicle();
            }
        };

        CreatureAI* GetAI(Creature* creature) const
        {
            return GetIcecrownCitadelAI<npc_shambling_horror_iccAI>(creature);
        }
};

class spell_the_lich_king_infest : public SpellScriptLoader
{
    public:
        spell_the_lich_king_infest() :  SpellScriptLoader("spell_the_lich_king_infest") { }

        class spell_the_lich_king_infest_AuraScript : public AuraScript
        {
            PrepareAuraScript(spell_the_lich_king_infest_AuraScript);

            void OnPeriodic(AuraEffect const* /*aurEff*/)
            {
                if (GetUnitOwner()->HealthAbovePct(90))
                {
                    PreventDefaultAction();
                    Remove(AURA_REMOVE_BY_ENEMY_SPELL);
                }
            }

            void OnUpdate(AuraEffect* aurEff)
            {
                // multiply, starting from 2nd tick
                if (aurEff->GetTickNumber() == 1)
                    return;

                aurEff->SetAmount(int32(aurEff->GetAmount() * 1.15f));
            }

            void Register()
            {
                OnEffectPeriodic += AuraEffectPeriodicFn(spell_the_lich_king_infest_AuraScript::OnPeriodic, EFFECT_0, SPELL_AURA_PERIODIC_DAMAGE);
                OnEffectUpdatePeriodic += AuraEffectUpdatePeriodicFn(spell_the_lich_king_infest_AuraScript::OnUpdate, EFFECT_0, SPELL_AURA_PERIODIC_DAMAGE);
            }
        };

        AuraScript* GetAuraScript() const
        {
            return new spell_the_lich_king_infest_AuraScript();
        }
};

class spell_the_lich_king_necrotic_plague : public SpellScriptLoader
{
    public:
        spell_the_lich_king_necrotic_plague() :  SpellScriptLoader("spell_the_lich_king_necrotic_plague") { }

        class spell_the_lich_king_necrotic_plague_AuraScript : public AuraScript
        {
            PrepareAuraScript(spell_the_lich_king_necrotic_plague_AuraScript);

            bool Validate(SpellInfo const* /*spell*/)
            {
                if (!sSpellMgr->GetSpellInfo(SPELL_NECROTIC_PLAGUE_JUMP))
                    return false;
                return true;
            }

            void OnRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
            {
                bool dispel = false;
                switch (GetTargetApplication()->GetRemoveMode())
                {
                    case AURA_REMOVE_BY_ENEMY_SPELL:
                        dispel = true;
                    case AURA_REMOVE_BY_EXPIRE:
                    case AURA_REMOVE_BY_DEATH:
                        break;
                    default:
                        return;
                }

                CustomSpellValues values;
                if (dispel)
                    values.AddSpellMod(SPELLVALUE_BASE_POINT1, AURA_REMOVE_BY_ENEMY_SPELL); // add as marker (spell has no effect 1)
                GetTarget()->CastCustomSpell(SPELL_NECROTIC_PLAGUE_JUMP, values, NULL, TRIGGERED_FULL_MASK, NULL, NULL, GetCasterGUID());

                if (Unit* caster = GetCaster())
                    caster->CastSpell(caster, SPELL_PLAGUE_SIPHON, true);
            }

            void Register()
            {
                AfterEffectRemove += AuraEffectRemoveFn(spell_the_lich_king_necrotic_plague_AuraScript::OnRemove, EFFECT_0, SPELL_AURA_PERIODIC_DAMAGE, AURA_EFFECT_HANDLE_REAL);
            }
        };

        AuraScript* GetAuraScript() const
        {
            return new spell_the_lich_king_necrotic_plague_AuraScript();
        }
};

class spell_the_lich_king_necrotic_plague_jump : public SpellScriptLoader
{
    public:
        spell_the_lich_king_necrotic_plague_jump() :  SpellScriptLoader("spell_the_lich_king_necrotic_plague_jump") { }

        class spell_the_lich_king_necrotic_plague_SpellScript : public SpellScript
        {
            PrepareSpellScript(spell_the_lich_king_necrotic_plague_SpellScript);

            bool Load()
            {
                _hadJumpingAura = false;
                _hadInitialAura = false;
                return true;
            }

            void FilterTargets(std::list<WorldObject*>& targets)
            {
                targets.sort(Trinity::ObjectDistanceOrderPred(GetCaster()));
                if (targets.size() <= 1)
                    return;

                targets.resize(1);
            }

            void CheckAura()
            {
                if (GetHitUnit()->HasAura(GetSpellInfo()->Id))
                    _hadJumpingAura = true;
                else if (uint32 spellId = sSpellMgr->GetSpellIdForDifficulty(SPELL_NECROTIC_PLAGUE, GetHitUnit()))
                    if (GetHitUnit()->HasAura(spellId))
                        _hadInitialAura = true;
            }

            void AddMissingStack()
            {
                if (GetHitAura() && !_hadJumpingAura)
                {
                    uint32 spellId = sSpellMgr->GetSpellIdForDifficulty(SPELL_NECROTIC_PLAGUE, GetHitUnit());
                    if (GetSpellValue()->EffectBasePoints[EFFECT_1] != AURA_REMOVE_BY_ENEMY_SPELL || _hadInitialAura)
                        GetHitAura()->ModStackAmount(1);
                    if (_hadInitialAura)
                        if (Aura* a = GetHitUnit()->GetAura(spellId))
                            a->Remove(AURA_REMOVE_BY_DEFAULT);
                }
            }

            void Register()
            {
                OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_the_lich_king_necrotic_plague_SpellScript::FilterTargets, EFFECT_0, TARGET_UNIT_SRC_AREA_ENTRY);
                BeforeHit += SpellHitFn(spell_the_lich_king_necrotic_plague_SpellScript::CheckAura);
                OnHit += SpellHitFn(spell_the_lich_king_necrotic_plague_SpellScript::AddMissingStack);
            }

            bool _hadJumpingAura;
            bool _hadInitialAura;
        };

        class spell_the_lich_king_necrotic_plague_AuraScript : public AuraScript
        {
            PrepareAuraScript(spell_the_lich_king_necrotic_plague_AuraScript);

            bool Load()
            {
                _lastAmount = 0;
                return true;
            }

            void OnApply(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
            {
                if (Unit* caster = GetCaster())
                    if (caster->GetAI())
                        caster->GetAI()->SetData(DATA_PLAGUE_STACK, GetStackAmount());
            }

            void OnRemove(AuraEffect const* aurEff, AuraEffectHandleModes /*mode*/)
            {
                _lastAmount = aurEff->GetAmount();
                switch (GetTargetApplication()->GetRemoveMode())
                {
                    case AURA_REMOVE_BY_EXPIRE:
                    case AURA_REMOVE_BY_DEATH:
                        break;
                    default:
                        return;
                }

                CustomSpellValues values;
                values.AddSpellMod(SPELLVALUE_AURA_STACK, GetStackAmount());
                GetTarget()->CastCustomSpell(SPELL_NECROTIC_PLAGUE_JUMP, values, NULL, TRIGGERED_FULL_MASK, NULL, NULL, GetCasterGUID());
                if (Unit* caster = GetCaster())
                    caster->CastSpell(caster, SPELL_PLAGUE_SIPHON, true);
            }

            void OnDispel(AuraEffect const* aurEff, AuraEffectHandleModes /*mode*/)
            {
                _lastAmount = aurEff->GetAmount();
            }

            void AfterDispel(AuraEffect const* aurEff, AuraEffectHandleModes /*mode*/)
            {
                // this means the stack increased so don't process as if dispelled
                if (aurEff->GetAmount() > _lastAmount)
                    return;

                CustomSpellValues values;
                values.AddSpellMod(SPELLVALUE_AURA_STACK, GetStackAmount());
                values.AddSpellMod(SPELLVALUE_BASE_POINT1, AURA_REMOVE_BY_ENEMY_SPELL); // add as marker (spell has no effect 1)
                GetTarget()->CastCustomSpell(SPELL_NECROTIC_PLAGUE_JUMP, values, NULL, TRIGGERED_FULL_MASK, NULL, NULL, GetCasterGUID());
                if (Unit* caster = GetCaster())
                    caster->CastSpell(caster, SPELL_PLAGUE_SIPHON, true);

                Remove(AURA_REMOVE_BY_ENEMY_SPELL);
            }

            void Register()
            {
                OnEffectApply += AuraEffectApplyFn(spell_the_lich_king_necrotic_plague_AuraScript::OnApply, EFFECT_0, SPELL_AURA_PERIODIC_DAMAGE, AURA_EFFECT_HANDLE_REAL_OR_REAPPLY_MASK);
                AfterEffectRemove += AuraEffectRemoveFn(spell_the_lich_king_necrotic_plague_AuraScript::OnRemove, EFFECT_0, SPELL_AURA_PERIODIC_DAMAGE, AURA_EFFECT_HANDLE_REAL);
                AfterEffectRemove += AuraEffectRemoveFn(spell_the_lich_king_necrotic_plague_AuraScript::OnDispel, EFFECT_0, SPELL_AURA_PERIODIC_DAMAGE, AURA_EFFECT_HANDLE_REAPPLY);
                AfterEffectApply += AuraEffectApplyFn(spell_the_lich_king_necrotic_plague_AuraScript::AfterDispel, EFFECT_0, SPELL_AURA_PERIODIC_DAMAGE, AURA_EFFECT_HANDLE_REAPPLY);
            }

            int32 _lastAmount;
        };

        SpellScript* GetSpellScript() const
        {
            return new spell_the_lich_king_necrotic_plague_SpellScript();
        }

        AuraScript* GetAuraScript() const
        {
            return new spell_the_lich_king_necrotic_plague_AuraScript();
        }
};

class spell_the_lich_king_shadow_trap_visual : public SpellScriptLoader
{
    public:
        spell_the_lich_king_shadow_trap_visual() : SpellScriptLoader("spell_the_lich_king_shadow_trap_visual") { }

        class spell_the_lich_king_shadow_trap_visual_AuraScript : public AuraScript
        {
            PrepareAuraScript(spell_the_lich_king_shadow_trap_visual_AuraScript);

            void OnRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
            {
                if (GetTargetApplication()->GetRemoveMode() == AURA_REMOVE_BY_EXPIRE)
                    GetTarget()->CastSpell(GetTarget(), SPELL_SHADOW_TRAP_AURA, TRIGGERED_NONE);
            }

            void Register()
            {
                AfterEffectRemove += AuraEffectRemoveFn(spell_the_lich_king_shadow_trap_visual_AuraScript::OnRemove, EFFECT_0, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL);
            }
        };

        AuraScript* GetAuraScript() const
        {
            return new spell_the_lich_king_shadow_trap_visual_AuraScript();
        }
};

class spell_the_lich_king_shadow_trap_periodic : public SpellScriptLoader
{
    public:
        spell_the_lich_king_shadow_trap_periodic() : SpellScriptLoader("spell_the_lich_king_shadow_trap_periodic") { }

        class spell_the_lich_king_shadow_trap_periodic_SpellScript : public SpellScript
        {
            PrepareSpellScript(spell_the_lich_king_shadow_trap_periodic_SpellScript);

            void CheckTargetCount(std::list<WorldObject*>& targets)
            {
                if (targets.empty())
                    return;

                GetCaster()->CastSpell((Unit*)NULL, SPELL_SHADOW_TRAP_KNOCKBACK, true);
                if (Aura* a = GetCaster()->GetAura(SPELL_SHADOW_TRAP_AURA))
                    a->SetDuration(0);
                if (GetCaster()->GetTypeId() == TYPEID_UNIT)
                    GetCaster()->ToCreature()->DespawnOrUnsummon(3000);

            }

            void Register()
            {
                OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_the_lich_king_shadow_trap_periodic_SpellScript::CheckTargetCount, EFFECT_0, TARGET_UNIT_SRC_AREA_ENEMY);
            }
        };

        SpellScript* GetSpellScript() const
        {
            return new spell_the_lich_king_shadow_trap_periodic_SpellScript();
        }
};

class spell_the_lich_king_ice_burst_target_search : public SpellScriptLoader
{
    public:
        spell_the_lich_king_ice_burst_target_search() : SpellScriptLoader("spell_the_lich_king_ice_burst_target_search") { }

        class spell_the_lich_king_ice_burst_target_search_SpellScript : public SpellScript
        {
            PrepareSpellScript(spell_the_lich_king_ice_burst_target_search_SpellScript);

            bool Validate(SpellInfo const* /*spell*/)
            {
                if (!sSpellMgr->GetSpellInfo(SPELL_ICE_BURST))
                    return false;
                return true;
            }

            void CheckTargetCount(std::list<WorldObject*>& unitList)
            {
                if (unitList.empty())
                    return;

                if (GetCaster()->GetTypeId() == TYPEID_UNIT)
                    GetCaster()->ToCreature()->AI()->DoAction(-1);
            }

            void Register()
            {
                OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_the_lich_king_ice_burst_target_search_SpellScript::CheckTargetCount, EFFECT_0, TARGET_UNIT_SRC_AREA_ENEMY);
            }
        };

        SpellScript* GetSpellScript() const
        {
            return new spell_the_lich_king_ice_burst_target_search_SpellScript();
        }
};

class npc_icc_ice_sphere : public CreatureScript
{
    public:
        npc_icc_ice_sphere() : CreatureScript("npc_icc_ice_sphere") { }

        struct npc_icc_ice_sphereAI : public ScriptedAI
        {
            npc_icc_ice_sphereAI(Creature* creature) : ScriptedAI(creature)
            {
                targetGUID = 0;
                timer = 250;
                me->SetReactState(REACT_PASSIVE);
            }

            uint64 targetGUID;
            uint16 timer;

            void DoAction(int32 a)
            {
                if (a == -1)
                {
                    me->RemoveAllAuras();
                    me->CastSpell(me, SPELL_ICE_BURST, true);
                    me->DespawnOrUnsummon(1000);
                    targetGUID = 0;
                    timer = 9999;
                    me->InterruptNonMeleeSpells(true);
                    me->AttackStop();
                    me->GetMotionMaster()->Clear();
                    me->StopMoving();
                }
            }

            void SelectNewTarget()
            {
                if (!me->HasAura(SPELL_ICE_SPHERE))
                    me->CastSpell(me, SPELL_ICE_SPHERE, true);
                targetGUID = 0;
                me->InterruptNonMeleeSpells(true);
                me->AttackStop();
                me->GetMotionMaster()->Clear();
                me->StopMoving();
                if (Player* p = ScriptedAI::SelectTargetFromPlayerList(120.0f, SPELL_ICE_PULSE, false))
                {
                    targetGUID = p->GetGUID();
                    me->CastSpell(p, SPELL_ICE_PULSE, false);
                    me->ClearUnitState(UNIT_STATE_CASTING);
                    me->Attack(p, true);
                    me->GetMotionMaster()->MoveFollow(p, 0.01f, 0.0f);
                }
            }

            void UpdateAI(uint32 diff)
            {
                if (timer > diff)
                {
                    timer -= diff;
                    return;
                }
                timer = 1000;

                if (!targetGUID || !me->GetVictim() || !me->IsNonMeleeSpellCast(false, false, true, false, true))
                    SelectNewTarget();
                else
                {
                    Unit* target = ObjectAccessor::GetUnit(*me, targetGUID);
                    if (me->GetVictim()->GetGUID() != targetGUID || !target || !me->IsValidAttackTarget(target) || target->HasFlag(UNIT_FIELD_FLAGS_2, UNIT_FLAG2_FEIGN_DEATH) || target->GetExactDist2dSq(&CenterPosition) > 75.0f*75.0f || target->GetPositionZ() < 830.0f || target->GetPositionZ() > 855.0f)
                        SelectNewTarget();
                }
            }
        };

        CreatureAI* GetAI(Creature* creature) const
        {
            return GetIcecrownCitadelAI<npc_icc_ice_sphereAI>(creature);
        }
};

class spell_the_lich_king_raging_spirit : public SpellScriptLoader
{
    public:
        spell_the_lich_king_raging_spirit() : SpellScriptLoader("spell_the_lich_king_raging_spirit") { }

        class spell_the_lich_king_raging_spirit_SpellScript : public SpellScript
        {
            PrepareSpellScript(spell_the_lich_king_raging_spirit_SpellScript);

            void HandleScript(SpellEffIndex effIndex)
            {
                PreventHitDefaultEffect(effIndex);
                if (Unit* target = GetHitUnit())
                    target->CastSpell(target, uint32(GetEffectValue()), true, 0, 0, target->GetGUID());
            }

            void Register()
            {
                OnEffectHitTarget += SpellEffectFn(spell_the_lich_king_raging_spirit_SpellScript::HandleScript, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT);
            }
        };

        SpellScript* GetSpellScript() const
        {
            return new spell_the_lich_king_raging_spirit_SpellScript();
        }
};

class npc_raging_spirit : public CreatureScript
{
    public:
        npc_raging_spirit() : CreatureScript("npc_raging_spirit") { }

        struct npc_raging_spiritAI : public ScriptedAI
        {
            npc_raging_spiritAI(Creature* creature) : ScriptedAI(creature), _instance(creature->GetInstanceScript())
            {
                me->SetControlled(true, UNIT_STATE_ROOT);
            }

            void Reset()
            {
                _events.Reset();
                _events.ScheduleEvent(EVENT_RAGING_SPIRIT_UNROOT, 3000);
                _events.ScheduleEvent(EVENT_SOUL_SHRIEK, urand(12000, 15000));

                bool valid = false;
                me->CastSpell(me, SPELL_RAGING_SPIRIT_VISUAL, true);
                if (TempSummon* summon = me->ToTempSummon())
                    if (Unit* summoner = summon->GetSummoner())
                        if (summoner->GetTypeId() == TYPEID_PLAYER && summoner->IsAlive() && !summoner->ToPlayer()->IsBeingTeleported() && summoner->FindMap() == me->GetMap())
                        {
                            valid = true;
                            summoner->CastSpell(me, SPELL_RAGING_SPIRIT_VISUAL_CLONE, true);
                        }
                if (!valid)
                {
                    if (Player* plr = ScriptedAI::SelectTargetFromPlayerList(100.0f, 0, true))
                        plr->CastSpell(me, SPELL_RAGING_SPIRIT_VISUAL_CLONE, true);
                    else
                        me->DespawnOrUnsummon(1);
                }
            }

            void IsSummonedBy(Unit* /*summoner*/)
            {
                // player is the spellcaster so register summon manually
                if (Creature* lichKing = ObjectAccessor::GetCreature(*me, _instance->GetData64(DATA_THE_LICH_KING)))
                    lichKing->AI()->JustSummoned(me);
            }

            void JustDied(Unit* /*killer*/)
            {
                if (Creature* lichKing = ObjectAccessor::GetCreature(*me, _instance->GetData64(DATA_THE_LICH_KING)))
                    lichKing->AI()->SummonedCreatureDespawn(me);
                if (TempSummon* summon = me->ToTempSummon())
                {
                    summon->SetTimer(5000);
                    summon->SetTempSummonType(TEMPSUMMON_TIMED_DESPAWN);
                }
            }

            void AttackStart(Unit* who)
            {
                if (!me->HasUnitState(UNIT_STATE_ROOT))
                    ScriptedAI::AttackStart(who);
            }

            void UpdateAI(uint32 diff)
            {
                UpdateVictim();

                _events.Update(diff);

                if (me->HasUnitState(UNIT_STATE_CASTING))
                    return;

                switch (uint32 eventId = _events.ExecuteEvent())
                {
                    case EVENT_RAGING_SPIRIT_UNROOT:
                        {
                            me->SetControlled(false, UNIT_STATE_ROOT);

                            if (!me->getThreatManager().isThreatListEmpty())
                                if (Unit* target = me->SelectVictim())
                                    AttackStart(target);
                            if (!me->GetVictim())
                            {
                                bool valid = false;
                                if (TempSummon* summon = me->ToTempSummon())
                                    if (Unit* summoner = summon->GetSummoner())
                                        if (summoner->GetTypeId() == TYPEID_PLAYER && summoner->IsAlive() && !summoner->ToPlayer()->IsBeingTeleported() && summoner->FindMap() == me->GetMap())
                                        {
                                            valid = true;
                                            AttackStart(summoner);
                                        }
                                if (!valid)
                                    if (Player* plr = ScriptedAI::SelectTargetFromPlayerList(100.0f, 0, true))
                                        AttackStart(plr);
                            }
                            DoZoneInCombat(NULL, 150.0f);
                        }
                        break;
                    case EVENT_SOUL_SHRIEK:
                        if (!me->HasReactState(REACT_PASSIVE))
                            me->CastSpell(me->GetVictim(), SPELL_SOUL_SHRIEK, false);
                        _events.ScheduleEvent(EVENT_SOUL_SHRIEK, urand(12000, 15000));
                        break;
                    default:
                        break;
                }

                if (!me->HasUnitState(UNIT_STATE_ROOT))
                    DoMeleeAttackIfReady();
            }

            bool CanAIAttack(Unit const* target) const
            {
                return IsValidPlatformTarget(target) && !target->GetVehicle();
            }

        private:
            EventMap _events;
            InstanceScript* _instance;
        };

        CreatureAI* GetAI(Creature* creature) const
        {
            return GetIcecrownCitadelAI<npc_raging_spiritAI>(creature);
        }
};

class VehicleCheck
{
public:
    bool operator()(WorldObject* unit)
    {
        return (unit->GetTypeId() != TYPEID_UNIT && unit->GetTypeId() != TYPEID_PLAYER) || unit->ToUnit()->GetVehicle();
    }
};

class spell_the_lich_king_defile : public SpellScriptLoader
{
    public:
        spell_the_lich_king_defile() : SpellScriptLoader("spell_the_lich_king_defile") { }

        class spell_the_lich_king_defile_SpellScript : public SpellScript
        {
            PrepareSpellScript(spell_the_lich_king_defile_SpellScript);

            void CorrectRange(std::list<WorldObject*>& targets)
            {
                targets.remove_if(VehicleCheck());
                targets.remove_if(Trinity::AllWorldObjectsInExactRange(GetCaster(), 10.0f * GetCaster()->GetFloatValue(OBJECT_FIELD_SCALE_X), true));
                uint32 strangulatedAura[4] = {68980, 74325, 74296, 74297};
                targets.remove_if(Trinity::UnitAuraCheck(true, strangulatedAura[GetCaster()->GetMap()->GetDifficulty()]));
            }

            void ChangeDamageAndGrow()
            {
                SetHitDamage(int32(GetHitDamage() * GetCaster()->GetFloatValue(OBJECT_FIELD_SCALE_X)));
                // HACK: target player should cast this spell on defile
                // however with current aura handling auras cast by different units
                // cannot stack on the same aura object increasing the stack count
                GetCaster()->CastSpell(GetCaster(), SPELL_DEFILE_GROW, true);
            }

            void Register()
            {
                OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_the_lich_king_defile_SpellScript::CorrectRange, EFFECT_0, TARGET_UNIT_SRC_AREA_ENEMY);
                OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_the_lich_king_defile_SpellScript::CorrectRange, EFFECT_1, TARGET_UNIT_SRC_AREA_ENEMY);
                OnHit += SpellHitFn(spell_the_lich_king_defile_SpellScript::ChangeDamageAndGrow);
            }
        };

        SpellScript* GetSpellScript() const
        {
            return new spell_the_lich_king_defile_SpellScript();
        }
};

class spell_the_lich_king_soul_reaper : public SpellScriptLoader
{
    public:
        spell_the_lich_king_soul_reaper() :  SpellScriptLoader("spell_the_lich_king_soul_reaper") { }

        class spell_the_lich_king_soul_reaper_AuraScript : public AuraScript
        {
            PrepareAuraScript(spell_the_lich_king_soul_reaper_AuraScript);

            bool Validate(SpellInfo const* /*spell*/)
            {
                if (!sSpellMgr->GetSpellInfo(SPELL_SOUL_REAPER_BUFF))
                    return false;
                return true;
            }

            void OnPeriodic(AuraEffect const* /*aurEff*/)
            {
                if (Unit* caster = GetCaster())
                    GetTarget()->CastSpell(caster, SPELL_SOUL_REAPER_BUFF, true);
            }

            void Register()
            {
                OnEffectPeriodic += AuraEffectPeriodicFn(spell_the_lich_king_soul_reaper_AuraScript::OnPeriodic, EFFECT_1, SPELL_AURA_PERIODIC_DAMAGE);
            }
        };

        AuraScript* GetAuraScript() const
        {
            return new spell_the_lich_king_soul_reaper_AuraScript();
        }
};

class npc_valkyr_shadowguard : public CreatureScript
{
    public:
        npc_valkyr_shadowguard() : CreatureScript("npc_valkyr_shadowguard") { }

        struct npc_valkyr_shadowguardAI : public NullCreatureAI
        {
            npc_valkyr_shadowguardAI(Creature* creature) : NullCreatureAI(creature), _grabbedPlayer(0), didbelow50pct(false), dropped(false), _instance(creature->GetInstanceScript())
            {
                me->SetReactState(REACT_PASSIVE);
                me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_TAUNT, true);
                me->ApplySpellImmune(0, IMMUNITY_EFFECT, SPELL_EFFECT_ATTACK_ME, true);
                _events.Reset();
                _events.ScheduleEvent(EVENT_GRAB_PLAYER, 2500);
                me->SetWalk(false);
            }

            EventMap _events;
            InstanceScript* _instance;
            Position _destPoint;
            uint64 _grabbedPlayer;
            bool didbelow50pct;
            bool dropped;

            bool IsHeroic() { return me->GetMap()->IsHeroic(); }

            void GoSiphon()
            {
                didbelow50pct = true;
                me->CastSpell((Unit*)NULL, SPELL_EJECT_ALL_PASSENGERS, false);
                float dist = rand_norm()*10.0f + 5.0f;
                float angle = CenterPosition.GetAngle(me);
                _destPoint.Relocate(CenterPosition.GetPositionX()+dist*cos(angle), CenterPosition.GetPositionY()+dist*sin(angle), 855.0f+frand(0.0f, 4.0f), 0.0f);
                me->SetHomePosition(_destPoint);
                _events.Reset();
                _events.ScheduleEvent(EVENT_MOVE_TO_SIPHON_POS, 0);
            }

            void OnCharmed(bool apply) {}

            void PassengerBoarded(Unit* pass, int8 seat, bool apply)
            {
                if (apply)
                {
                    //pass->ClearUnitState(UNIT_STATE_ONVEHICLE);
                    return;
                }
                pass->RemoveAurasDueToSpell(VEHICLE_SPELL_PARACHUTE);
                if (didbelow50pct || dropped)
                    return;
                if (IsHeroic())
                    GoSiphon();
                else
                    me->DespawnOrUnsummon(1000);
            }

            void DamageTaken(Unit*, uint32& damage, DamageEffectType, SpellSchoolMask)
            {
                if (IsHeroic() && !didbelow50pct && !dropped && me->HealthBelowPctDamaged(50, damage))
                    GoSiphon();
            }

            void MovementInform(uint32 type, uint32 id)
            {
                if (type != POINT_MOTION_TYPE)
                    return;

                switch (id)
                {
                    case EVENT_CHARGE:
                        {
                        bool valid = false;
                        if (Player* target = ObjectAccessor::GetPlayer(*me, _grabbedPlayer))
                            if (target->FindMap() == me->GetMap() && target->GetExactDist(me) < 15.0f && !target->GetVehicle())
                                if (GameObject* platform = ObjectAccessor::GetGameObject(*me, _instance->GetData64(DATA_ARTHAS_PLATFORM)))
                                {
                                    std::list<Creature*> triggers;
                                    GetCreatureListWithEntryInGrid(triggers, me, NPC_WORLD_TRIGGER, 150.0f);
                                    triggers.remove_if(HeightDifferenceCheck(platform, 5.0f, true));
                                    if (!triggers.empty())
                                    {
                                        valid = true;
                                        triggers.sort(Trinity::ObjectDistanceOrderPred(me));

                                        target->GetMotionMaster()->Clear();
                                        target->UpdatePosition(*me, true);
                                        target->StopMovingOnCurrentPos();

                                        me->CastSpell(target, SPELL_VALKYR_CARRY, false);
                                        _destPoint.Relocate(triggers.front());
                                        _events.Reset();
                                        _events.ScheduleEvent(EVENT_MOVE_TO_DROP_POS, 1000);
                                    }
                                }
                        if (!valid)
                        {
                            _events.Reset();
                            _events.ScheduleEvent(EVENT_GRAB_PLAYER, 500);
                            _grabbedPlayer = 0;
                        }
                        }
                        break;
                    case POINT_DROP_PLAYER:
                        {
                            if (didbelow50pct || dropped)
                                break;
                            if (me->GetExactDist(&_destPoint) > 1.5f) // movement was interrupted (probably by a stun, start again)
                            {
                                _events.Reset();
                                _events.ScheduleEvent(EVENT_MOVE_TO_DROP_POS, 0);
                                break;
                            }
                            dropped = true;
                            _events.Reset();
                            Player* p = NULL;
                            if (Vehicle* v = me->GetVehicleKit())
                                if (Unit* passenger = v->GetPassenger(0))
                                    p = passenger->ToPlayer();
                            me->CastSpell((Unit*)NULL, SPELL_EJECT_ALL_PASSENGERS, false);

                            if (IsHeroic())
                                GoSiphon();
                            else
                                me->DespawnOrUnsummon(1000);
                        }
                        break;
                    case POINT_START_SIPHON:
                        if (me->GetExactDist(&_destPoint) > 1.5f) // movement was interrupted (probably by a stun, start again)
                        {
                            _events.Reset();
                            _events.ScheduleEvent(EVENT_MOVE_TO_SIPHON_POS, 0);
                            break;
                        }
                        me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_TAUNT, false);
                        me->ApplySpellImmune(0, IMMUNITY_EFFECT, SPELL_EFFECT_ATTACK_ME, false);
                        _events.ScheduleEvent(EVENT_LIFE_SIPHON, 2000);
                        break;
                }
            }

            void SetGUID(uint64 guid, int32 /* = 0*/)
            {
                _grabbedPlayer = guid;
            }

            void UpdateAI(uint32 diff)
            {
                _events.Update(diff);

                if (me->HasUnitState(UNIT_STATE_CASTING | UNIT_STATE_STUNNED))
                    return;

                switch (uint32 eventId = _events.ExecuteEvent())
                {
                    case EVENT_GRAB_PLAYER:
                        if (!_grabbedPlayer)
                        {
                            me->CastSpell((Unit*)NULL, SPELL_VALKYR_TARGET_SEARCH, false);
                            _events.ScheduleEvent(EVENT_GRAB_PLAYER, 2000);
                        }
                        break;
                    case EVENT_MOVE_TO_DROP_POS:
                        me->AddUnitState(UNIT_STATE_NO_ENVIRONMENT_UPD);
                        me->SetDisableGravity(false);
                        me->SetHover(false);
                        me->SetCanFly(false);
                        me->GetMotionMaster()->MovePoint(POINT_DROP_PLAYER, _destPoint, false);
                        me->SetDisableGravity(true, true);
                        me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE);
                        break;
                    case EVENT_MOVE_TO_SIPHON_POS:
                        me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE); // just in case if passenger disappears so quickly that EVENT_MOVE_TO_DROP_POS is never executed
                        { int32 bp0 = 80; me->CastCustomSpell(me, 1557, &bp0, NULL, NULL, true); }
                        me->SetDisableGravity(true);
                        me->SetHover(true);
                        me->SetCanFly(true);
                        me->SendMovementFlagUpdate();
                        me->GetMotionMaster()->MovePoint(POINT_START_SIPHON, _destPoint);
                        break;
                    case EVENT_LIFE_SIPHON:
                        {
                            Unit* target = NULL;
                            Unit::AuraEffectList const& tauntAuras = me->GetAuraEffectsByType(SPELL_AURA_MOD_TAUNT);
                            if (!tauntAuras.empty())
                                for (Unit::AuraEffectList::const_reverse_iterator itr = tauntAuras.rbegin(); itr != tauntAuras.rend(); ++itr)
                                    if (Unit* caster = (*itr)->GetCaster())
                                        if (me->IsValidAttackTarget(caster))
                                        {
                                            target = caster;
                                            break;
                                        }
                            if (!target)
                                if (Creature* lichKing = ObjectAccessor::GetCreature(*me, _instance->GetData64(DATA_THE_LICH_KING)))
                                    target = lichKing->AI()->SelectTarget(SELECT_TARGET_RANDOM, 0, NonTankLKTargetSelector(lichKing, true, false, 100.0f));
                            if (target)
                                me->CastSpell(target, SPELL_LIFE_SIPHON, false);
                            _events.ScheduleEvent(EVENT_LIFE_SIPHON, 2500);
                        }
                        break;
                    default:
                        break;
                }
            }
        };

        CreatureAI* GetAI(Creature* creature) const
        {
            return GetIcecrownCitadelAI<npc_valkyr_shadowguardAI>(creature);
        }
};

class spell_the_lich_king_summon_into_air : public SpellScriptLoader
{
    public:
        spell_the_lich_king_summon_into_air() : SpellScriptLoader("spell_the_lich_king_summon_into_air") { }

        class spell_the_lich_king_summon_into_air_SpellScript : public SpellScript
        {
            PrepareSpellScript(spell_the_lich_king_summon_into_air_SpellScript);

            void ModDestHeight(SpellEffIndex effIndex)
            {
                float addZ;
                switch (GetSpellInfo()->Effects[effIndex].MiscValue)
                {
                    case NPC_SPIRIT_BOMB: addZ = 30.0f; break;
                    case NPC_VILE_SPIRIT: addZ = 13.0f; break;
                    default: addZ = 15.0f; break;
                }
                Position const offset = {0.0f, 0.0f, addZ, 0.0f};
                WorldLocation* dest = const_cast<WorldLocation*>(GetExplTargetDest());
                dest->RelocateOffset(offset);
                GetHitDest()->RelocateOffset(offset);
            }

            void Register()
            {
                OnEffectHit += SpellEffectFn(spell_the_lich_king_summon_into_air_SpellScript::ModDestHeight, EFFECT_0, SPELL_EFFECT_SUMMON);
            }
        };

        SpellScript* GetSpellScript() const
        {
            return new spell_the_lich_king_summon_into_air_SpellScript();
        }
};

class spell_the_lich_king_teleport_to_frostmourne_hc : public SpellScriptLoader
{
    public:
        spell_the_lich_king_teleport_to_frostmourne_hc() : SpellScriptLoader("spell_the_lich_king_teleport_to_frostmourne_hc") { }

        class spell_the_lich_king_teleport_to_frostmourne_hc_SpellScript : public SpellScript
        {
            PrepareSpellScript(spell_the_lich_king_teleport_to_frostmourne_hc_SpellScript);

            void ModDest(SpellEffIndex effIndex)
            {
                float dist = 2.0f + rand_norm()*18.0f;
                float angle = rand_norm()*2*M_PI;
                Position const offset = {dist*cos(angle), dist*sin(angle), 0.0f, 0.0f};
                WorldLocation* dest = const_cast<WorldLocation*>(GetExplTargetDest());
                dest->RelocateOffset(offset);
                GetHitDest()->RelocateOffset(offset);
            }

            void Register()
            {
                OnEffectHit += SpellEffectFn(spell_the_lich_king_teleport_to_frostmourne_hc_SpellScript::ModDest, EFFECT_1, SPELL_EFFECT_TELEPORT_UNITS);
            }
        };

        SpellScript* GetSpellScript() const
        {
            return new spell_the_lich_king_teleport_to_frostmourne_hc_SpellScript();
        }
};

class spell_the_lich_king_valkyr_target_search : public SpellScriptLoader
{
    public:
        spell_the_lich_king_valkyr_target_search() : SpellScriptLoader("spell_the_lich_king_valkyr_target_search") { }

        class spell_the_lich_king_valkyr_target_search_SpellScript : public SpellScript
        {
            PrepareSpellScript(spell_the_lich_king_valkyr_target_search_SpellScript);

            WorldObject* _target;

            bool Load()
            {
                _target = NULL;
                return true;
            }

            void SelectTarget(std::list<WorldObject*>& targets)
            {
                if (targets.empty())
                    return;
                Creature* caster = GetCaster()->ToCreature();
                if (!caster)
                {
                    targets.clear();
                    return;
                }
                targets.remove_if(Trinity::UnitAuraCheck(true, GetSpellInfo()->Id));
                targets.remove_if(Trinity::UnitAuraCheck(true, SPELL_BOSS_HITTIN_YA_AURA)); // done in dbc, but just to be sure xd
                targets.remove_if(Trinity::UnitAuraCheck(true, SPELL_HARVEST_SOUL_VALKYR));
                if (InstanceScript* _instance = caster->GetInstanceScript())
                    if (Creature* lichKing = ObjectAccessor::GetCreature(*caster, _instance->GetData64(DATA_THE_LICH_KING)))
                        if (Spell* s = lichKing->GetCurrentSpell(CURRENT_GENERIC_SPELL))
                            if (s->GetSpellInfo()->Id == SPELL_DEFILE && s->m_targets.GetUnitTarget())
                                targets.remove(s->m_targets.GetUnitTarget());

                if (targets.empty())
                    return;

                _target = Trinity::Containers::SelectRandomContainerElement(targets);
                targets.clear();
                targets.push_back(_target);
                if (Creature* caster = GetCaster()->ToCreature())
                    caster->AI()->SetGUID(_target->GetGUID());
            }

            void ReplaceTarget(std::list<WorldObject*>& targets)
            {
                targets.clear();
                if (_target)
                    targets.push_back(_target);
            }

            void HandleScript(SpellEffIndex effIndex)
            {
                PreventHitDefaultEffect(effIndex);
                if (Unit* target = GetHitUnit())
                {
                    GetCaster()->GetMotionMaster()->MoveCharge(target->GetPositionX(), target->GetPositionY(), target->GetPositionZ()+4.0f, 42.0f, EVENT_CHARGE);
                    GetCaster()->SetDisableGravity(true, true);
                }
            }

            void Register()
            {
                OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_the_lich_king_valkyr_target_search_SpellScript::SelectTarget, EFFECT_0, TARGET_UNIT_SRC_AREA_ENEMY);
                OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_the_lich_king_valkyr_target_search_SpellScript::ReplaceTarget, EFFECT_1, TARGET_UNIT_SRC_AREA_ENEMY);
                OnEffectHitTarget += SpellEffectFn(spell_the_lich_king_valkyr_target_search_SpellScript::HandleScript, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT);
            }
        };

        SpellScript* GetSpellScript() const
        {
            return new spell_the_lich_king_valkyr_target_search_SpellScript();
        }
};

class spell_the_lich_king_cast_back_to_caster : public SpellScriptLoader
{
    public:
        spell_the_lich_king_cast_back_to_caster() :  SpellScriptLoader("spell_the_lich_king_cast_back_to_caster") { }

        class spell_the_lich_king_cast_back_to_caster_SpellScript : public SpellScript
        {
            PrepareSpellScript(spell_the_lich_king_cast_back_to_caster_SpellScript);

            void HandleScript(SpellEffIndex /*effIndex*/)
            {
                GetHitUnit()->CastSpell(GetCaster(), uint32(GetEffectValue()), true);
            }

            void Register()
            {
                OnEffectHitTarget += SpellEffectFn(spell_the_lich_king_cast_back_to_caster_SpellScript::HandleScript, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT);
            }
        };

        SpellScript* GetSpellScript() const
        {
            return new spell_the_lich_king_cast_back_to_caster_SpellScript();
        }
};

class spell_the_lich_king_life_siphon : public SpellScriptLoader
{
    public:
        spell_the_lich_king_life_siphon() : SpellScriptLoader("spell_the_lich_king_life_siphon") { }

        class spell_the_lich_king_life_siphon_SpellScript : public SpellScript
        {
            PrepareSpellScript(spell_the_lich_king_life_siphon_SpellScript);

            bool Validate(SpellInfo const* /*spell*/)
            {
                if (!sSpellMgr->GetSpellInfo(SPELL_LIFE_SIPHON_HEAL))
                    return false;
                return true;
            }

            void TriggerHeal()
            {
                GetHitUnit()->CastCustomSpell(SPELL_LIFE_SIPHON_HEAL, SPELLVALUE_BASE_POINT0, GetHitDamage() * 10, GetCaster(), true);
            }

            void Register()
            {
                AfterHit += SpellHitFn(spell_the_lich_king_life_siphon_SpellScript::TriggerHeal);
            }
        };

        SpellScript* GetSpellScript() const
        {
            return new spell_the_lich_king_life_siphon_SpellScript();
        }
};

class spell_the_lich_king_vile_spirits : public SpellScriptLoader
{
    public:
        spell_the_lich_king_vile_spirits() : SpellScriptLoader("spell_the_lich_king_vile_spirits") { }

        class spell_the_lich_king_vile_spirits_AuraScript : public AuraScript
        {
            PrepareAuraScript(spell_the_lich_king_vile_spirits_AuraScript);

            bool Load()
            {
                _is25Man = GetUnitOwner()->GetMap()->Is25ManRaid();
                return true;
            }

            void OnPeriodic(AuraEffect const* aurEff)
            {
                if (_is25Man || ((aurEff->GetTickNumber() - 1) % 5))
                    GetTarget()->CastSpell((Unit*)NULL, GetSpellInfo()->Effects[aurEff->GetEffIndex()].TriggerSpell, true, NULL, aurEff, GetCasterGUID());
            }

            void Register()
            {
                OnEffectPeriodic += AuraEffectPeriodicFn(spell_the_lich_king_vile_spirits_AuraScript::OnPeriodic, EFFECT_0, SPELL_AURA_PERIODIC_DUMMY);
            }

            bool _is25Man;
        };

        AuraScript* GetAuraScript() const
        {
            return new spell_the_lich_king_vile_spirits_AuraScript();
        }
};

class spell_the_lich_king_vile_spirits_visual : public SpellScriptLoader
{
    public:
        spell_the_lich_king_vile_spirits_visual() : SpellScriptLoader("spell_the_lich_king_vile_spirits_visual") { }

        class spell_the_lich_king_vile_spirits_visual_SpellScript : public SpellScript
        {
            PrepareSpellScript(spell_the_lich_king_vile_spirits_visual_SpellScript);

            void ModDestHeight(SpellEffIndex /*effIndex*/)
            {
                Position offset = {0.0f, 0.0f, 15.0f, 0.0f};
                const_cast<WorldLocation*>(GetExplTargetDest())->RelocateOffset(offset);
            }

            void Register()
            {
                OnEffectLaunch += SpellEffectFn(spell_the_lich_king_vile_spirits_visual_SpellScript::ModDestHeight, EFFECT_0, SPELL_EFFECT_DUMMY);
            }
        };

        SpellScript* GetSpellScript() const
        {
            return new spell_the_lich_king_vile_spirits_visual_SpellScript();
        }
};

class spell_the_lich_king_vile_spirit_move_target_search : public SpellScriptLoader
{
    public:
        spell_the_lich_king_vile_spirit_move_target_search() : SpellScriptLoader("spell_the_lich_king_vile_spirit_move_target_search") { }

        class spell_the_lich_king_vile_spirit_move_target_search_SpellScript : public SpellScript
        {
            PrepareSpellScript(spell_the_lich_king_vile_spirit_move_target_search_SpellScript);

            bool Load()
            {
                _target = NULL;
                return GetCaster()->GetTypeId() == TYPEID_UNIT;
            }

            void SelectTarget(std::list<WorldObject*>& targets)
            {
                if (targets.empty())
                    return;

                _target = Trinity::Containers::SelectRandomContainerElement(targets);
            }

            void HandleScript(SpellEffIndex effIndex)
            {
                PreventHitDefaultEffect(effIndex);
                if (GetHitUnit() != _target)
                    return;

                GetCaster()->ToCreature()->SetInCombatWithZone();
                GetCaster()->ToCreature()->AI()->AttackStart(GetHitUnit());
                GetCaster()->AddThreat(GetHitUnit(), GetCaster()->GetMaxHealth()*0.2f);
            }

            void Register()
            {
                OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_the_lich_king_vile_spirit_move_target_search_SpellScript::SelectTarget, EFFECT_0, TARGET_UNIT_SRC_AREA_ENEMY);
                OnEffectHitTarget += SpellEffectFn(spell_the_lich_king_vile_spirit_move_target_search_SpellScript::HandleScript, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT);
            }

            WorldObject* _target;
        };

        SpellScript* GetSpellScript() const
        {
            return new spell_the_lich_king_vile_spirit_move_target_search_SpellScript();
        }
};

class spell_the_lich_king_vile_spirit_damage_target_search : public SpellScriptLoader
{
    public:
        spell_the_lich_king_vile_spirit_damage_target_search() : SpellScriptLoader("spell_the_lich_king_vile_spirit_damage_target_search") { }

        class spell_the_lich_king_vile_spirit_damage_target_search_SpellScript : public SpellScript
        {
            PrepareSpellScript(spell_the_lich_king_vile_spirit_damage_target_search_SpellScript);

            void CheckTargetCount(std::list<WorldObject*>& targets)
            {
                if (targets.empty())
                    return;

                if (TempSummon* summon = GetCaster()->ToTempSummon())
                    if (Unit* summoner = summon->GetSummoner())
                        summoner->GetAI()->SetData(DATA_VILE, 1);

                if (Creature* c = GetCaster()->ToCreature())
                {
                    c->RemoveAurasDueToSpell(SPELL_VILE_SPIRIT_DAMAGE_SEARCH);
                    c->GetMotionMaster()->Clear(true);
                    c->StopMoving();
                    c->CastSpell((Unit*)NULL, SPELL_SPIRIT_BURST, true);
                    c->DespawnOrUnsummon(3000);
                    c->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE);
                }
            }

            void Register()
            {
                OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_the_lich_king_vile_spirit_damage_target_search_SpellScript::CheckTargetCount, EFFECT_0, TARGET_UNIT_SRC_AREA_ENEMY);
            }
        };

        SpellScript* GetSpellScript() const
        {
            return new spell_the_lich_king_vile_spirit_damage_target_search_SpellScript();
        }
};

class spell_the_lich_king_harvest_soul : public SpellScriptLoader
{
    public:
        spell_the_lich_king_harvest_soul() : SpellScriptLoader("spell_the_lich_king_harvest_soul") { }

        class spell_the_lich_king_harvest_soul_AuraScript : public AuraScript
        {
            PrepareAuraScript(spell_the_lich_king_harvest_soul_AuraScript);

            bool Load()
            {
                return GetOwner()->GetInstanceScript() != NULL;
            }

            void OnRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
            {
                // m_originalCaster to allow stacking from different casters, meh
                if (GetTargetApplication()->GetRemoveMode() == AURA_REMOVE_BY_DEATH)
                    GetTarget()->CastSpell((Unit*)NULL, SPELL_HARVESTED_SOUL_LK_BUFF, true, NULL, NULL, GetTarget()->GetInstanceScript()->GetData64(DATA_THE_LICH_KING));
            }

            void Register()
            {
                AfterEffectRemove += AuraEffectRemoveFn(spell_the_lich_king_harvest_soul_AuraScript::OnRemove, EFFECT_0, SPELL_AURA_PERIODIC_DAMAGE, AURA_EFFECT_HANDLE_REAL);
            }
        };

        AuraScript* GetAuraScript() const
        {
            return new spell_the_lich_king_harvest_soul_AuraScript();
        }
};

class npc_strangulate_vehicle : public CreatureScript
{
    public:
        npc_strangulate_vehicle() : CreatureScript("npc_strangulate_vehicle") { }

        struct npc_strangulate_vehicleAI : public NullCreatureAI
        {
            npc_strangulate_vehicleAI(Creature* creature) : NullCreatureAI(creature), _instance(creature->GetInstanceScript()) {}

            EventMap _events;
            InstanceScript* _instance;

            void IsSummonedBy(Unit* summoner)
            {
                if (!summoner)
                    return;

                if (Creature* lichKing = ObjectAccessor::GetCreature(*me, _instance->GetData64(DATA_THE_LICH_KING)))
                {
                    me->SetWalk(false);
                    Movement::PointsArray path;
                    path.push_back(G3D::Vector3(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ()));
                    path.push_back(G3D::Vector3(me->GetPositionX(), me->GetPositionY(), 843.0f));
                    me->GetMotionMaster()->MoveSplinePath(&path);

                    uint64 petGUID = summoner->GetPetGUID();
                    summoner->SetPetGUID(0);
                    summoner->StopMoving();
                    me->CastSpell(summoner, SPELL_HARVEST_SOUL_VEHICLE, true);
                    //summoner->ClearUnitState(UNIT_STATE_ONVEHICLE);
                    summoner->SendMovementFlagUpdate(true);
                    summoner->SetPetGUID(petGUID);
                    _events.Reset();
                    _events.ScheduleEvent(EVENT_MOVE_TO_LICH_KING, 1000);
                    _events.ScheduleEvent(EVENT_TELEPORT, 6250);
                    _events.ScheduleEvent(EVENT_DESPAWN_SELF, 6000+70000);

                    // this will let us easily access all creatures of this entry on heroic mode when its time to teleport back
                    lichKing->AI()->JustSummoned(me);
                }
            }

            bool IsHeroic() { return me->GetMap()->IsHeroic(); }
            void OnCharmed(bool apply) {}
            void PassengerBoarded(Unit* pass, int8 seat, bool apply)
            {
                if (!apply)
                    pass->RemoveAurasDueToSpell(VEHICLE_SPELL_PARACHUTE);
            }

            void DoAction(int32 action)
            {
                if (action != ACTION_TELEPORT_BACK)
                    return;

                if (TempSummon* summ = me->ToTempSummon())
                {
                    if (Unit* summoner = summ->GetSummoner())
                    {
                        bool buff = _instance->GetBossState(DATA_THE_LICH_KING) == IN_PROGRESS && summoner->GetTypeId() == TYPEID_PLAYER && (!summoner->IsAlive() || summoner->ToPlayer()->IsBeingTeleported() || summoner->FindMap() != me->GetMap());
                        if (summoner->GetTypeId() == TYPEID_PLAYER && !summoner->ToPlayer()->IsBeingTeleported() && summoner->FindMap() == me->GetMap())
                        {
                            if (buff)
                                summoner->CastSpell((Unit*)NULL, SPELL_HARVESTED_SOUL_LK_BUFF, true, NULL, NULL, _instance->GetData64(DATA_THE_LICH_KING));

                            me->CastSpell(summoner, SPELL_HARVEST_SOUL_TELEPORT_BACK, false);
                        }
                        else if (buff)
                            me->CastSpell((Unit*)NULL, SPELL_HARVESTED_SOUL_LK_BUFF, true, NULL, NULL, _instance->GetData64(DATA_THE_LICH_KING));

                        summoner->RemoveAurasDueToSpell(IsHeroic() ? SPELL_HARVEST_SOULS_TELEPORT : SPELL_HARVEST_SOUL_TELEPORT);
                    }
                    else
                        me->CastSpell((Unit*)NULL, SPELL_HARVESTED_SOUL_LK_BUFF, true, NULL, NULL, _instance->GetData64(DATA_THE_LICH_KING));
                }

                _events.Reset();
                me->RemoveAllAuras();
                me->DespawnOrUnsummon(500);

                if (Creature* lichKing = ObjectAccessor::GetCreature(*me, _instance->GetData64(DATA_THE_LICH_KING)))
                    lichKing->AI()->SummonedCreatureDespawn(me);
            }

            void UpdateAI(uint32 diff)
            {
                _events.Update(diff);

                switch (uint32 eventId = _events.ExecuteEvent())
                {
                    case EVENT_TELEPORT:
                        me->GetMotionMaster()->Clear(false);
                        me->GetMotionMaster()->MoveIdle();
                        if (TempSummon* summ = me->ToTempSummon())
                            if (Unit* summoner = summ->GetSummoner())
                            {
                                if (summoner->IsAlive() && summoner->GetTypeId() == TYPEID_PLAYER)
                                {
                                    summoner->CastSpell((Unit*)NULL, SPELL_HARVEST_SOUL_VISUAL, true);
                                    summoner->ExitVehicle(summoner);
                                    me->UpdatePosition(me->GetPositionX(), me->GetPositionY(), 840.87f, me->GetOrientation(), true);
                                    me->StopMovingOnCurrentPos();
                                    if (!IsHeroic())
                                        summoner->CastSpell(summoner, SPELL_HARVEST_SOUL_TELEPORT, true);
                                    else
                                        summoner->CastSpell(summoner, SPELL_HARVEST_SOULS_TELEPORT, true);
                                }
                                else
                                {
                                    summoner->ExitVehicle(summoner);
                                    _events.RescheduleEvent(EVENT_DESPAWN_SELF, 0);
                                }
                            }
                        break;
                    case EVENT_MOVE_TO_LICH_KING:
                        if (Creature* lichKing = ObjectAccessor::GetCreature(*me, _instance->GetData64(DATA_THE_LICH_KING)))
                            if (me->GetExactDist(lichKing) > 8.0f)
                            {
                                float dist = float(rand_norm()) * 5.0f + 8.0f;
                                float angle = lichKing->GetAngle(me);
                                Movement::PointsArray path;
                                path.push_back(G3D::Vector3(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ()));
                                path.push_back(G3D::Vector3(lichKing->GetPositionX()+dist*cos(angle), lichKing->GetPositionY()+dist*sin(angle), 843.0f));
                                me->GetMotionMaster()->MoveSplinePath(&path);
                            }
                        break;
                    case EVENT_DESPAWN_SELF:
                        if (Creature* lichKing = ObjectAccessor::GetCreature(*me, _instance->GetData64(DATA_THE_LICH_KING)))
                            lichKing->AI()->SummonedCreatureDespawn(me);
                        me->DespawnOrUnsummon(1);
                        break;
                    default:
                        break;
                }
            }
        };

        CreatureAI* GetAI(Creature* creature) const
        {
            return GetIcecrownCitadelAI<npc_strangulate_vehicleAI>(creature);
        }
};

class npc_terenas_menethil : public CreatureScript
{
    public:
        npc_terenas_menethil() : CreatureScript("npc_terenas_menethil") { }

        struct npc_terenas_menethilAI : public ScriptedAI
        {
            npc_terenas_menethilAI(Creature* creature) : ScriptedAI(creature), _instance(creature->GetInstanceScript()) {}

            EventMap _events;
            InstanceScript* _instance;

            void DoAction(int32 action)
            {
                switch (action)
                {
                    case ACTION_FROSTMOURNE_INTRO:
                        me->SetControlled(true, UNIT_STATE_ROOT);
                        me->setActive(true);
                        _events.Reset();
                        _events.ScheduleEvent(EVENT_FROSTMOURNE_TALK_1, 2000);
                        _events.ScheduleEvent(EVENT_FROSTMOURNE_TALK_2, 11000);
                        if (!IsHeroic())
                        {
                            me->SetHealth(me->GetMaxHealth() / 2);
                            _events.ScheduleEvent(EVENT_DESTROY_SOUL, 60000);
                            _events.ScheduleEvent(EVENT_FROSTMOURNE_TALK_3, 25000);
                        }
                        break;
                    case ACTION_TELEPORT_BACK:
                        if (!me->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE))
                        {
                            _events.Reset();
                            me->CastSpell((Unit*)NULL, SPELL_RESTORE_SOUL, false);
                            me->DespawnOrUnsummon(3000);
                        }
                        break;
                }
            }

            void DamageTaken(Unit*, uint32& damage, DamageEffectType, SpellSchoolMask)
            {
                if (damage >= me->GetHealth())
                {
                    damage = me->GetHealth() - 1;
                    if (IsHeroic())
                        return;
                    if (!me->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE))
                    {
                        _events.Reset();
                        _events.ScheduleEvent(EVENT_TELEPORT_BACK, 1000);
                        if (Creature* warden = me->FindNearestCreature(NPC_SPIRIT_WARDEN, 20.0f))
                        {
                            warden->CastSpell((Unit*)NULL, SPELL_DESTROY_SOUL, false);
                            warden->DespawnOrUnsummon(2000);
                        }
                        me->CastSpell(me, SPELL_TERENAS_LOSES_INSIDE, false);
                        me->SetDisplayId(16946);
                        me->SetReactState(REACT_PASSIVE);
                        me->AttackStop();
                        me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE);
                        me->DespawnOrUnsummon(2000);
                    }
                }
            }

            void UpdateAI(uint32 diff)
            {
                UpdateVictim();

                _events.Update(diff);

                switch (uint32 eventId = _events.ExecuteEvent())
                {
                    case EVENT_FROSTMOURNE_TALK_1:
                        me->SetControlled(false, UNIT_STATE_ROOT);
                        Talk(SAY_TERENAS_INTRO_1);
                        if (IsHeroic())
                            me->CastSpell((Unit*)NULL, SPELL_RESTORE_SOULS, false);
                        break;
                    case EVENT_FROSTMOURNE_TALK_2:
                        Talk(SAY_TERENAS_INTRO_2);
                        break;
                    case EVENT_FROSTMOURNE_TALK_3:
                        Talk(SAY_TERENAS_INTRO_3);
                        break;
                    case EVENT_DESTROY_SOUL:
                        if (Creature* warden = me->FindNearestCreature(NPC_SPIRIT_WARDEN, 20.0f))
                            warden->CastSpell((Unit*)NULL, SPELL_DESTROY_SOUL, false);
                        me->CastSpell(me, SPELL_TERENAS_LOSES_INSIDE, false);
                        me->SetDisplayId(16946);
                        me->SetReactState(REACT_PASSIVE);
                        me->AttackStop();
                        me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE);
                        _events.Reset();
                        _events.ScheduleEvent(EVENT_TELEPORT_BACK, 1000);
                        break;
                    case EVENT_TELEPORT_BACK:
                        if (Creature* lichKing = ObjectAccessor::GetCreature(*me, _instance->GetData64(DATA_THE_LICH_KING)))
                            lichKing->AI()->DoAction(ACTION_TELEPORT_BACK);
                        break;
                    default:
                        break;
                }

                DoMeleeAttackIfReady();
            }

            bool CanAIAttack(Unit const* target) const
            {
                return target->GetEntry() != NPC_THE_LICH_KING;
            }

            void EnterEvadeMode()
            {
                if (!me->IsAlive())
                    return;

                me->DeleteThreatList();
                me->CombatStop(false);
            }
        };

        CreatureAI* GetAI(Creature* creature) const
        {
            return GetIcecrownCitadelAI<npc_terenas_menethilAI>(creature);
        }
};

class spell_the_lich_king_lights_favor : public SpellScriptLoader
{
    public:
        spell_the_lich_king_lights_favor() : SpellScriptLoader("spell_the_lich_king_lights_favor") { }

        class spell_the_lich_king_lights_favor_AuraScript : public AuraScript
        {
            PrepareAuraScript(spell_the_lich_king_lights_favor_AuraScript);

            void OnPeriodic(AuraEffect const* /*aurEff*/)
            {
                if (Unit* caster = GetCaster())
                    if (AuraEffect* effect = GetAura()->GetEffect(EFFECT_1))
                        effect->RecalculateAmount(caster);
            }

            void CalculateBonus(AuraEffect const* /*aurEff*/, int32& amount, bool& canBeRecalculated)
            {
                canBeRecalculated = true;
                amount = 0;
                if (Unit* caster = GetCaster())
                    amount = int32(caster->GetHealthPct());
            }

            void Register()
            {
                OnEffectPeriodic += AuraEffectPeriodicFn(spell_the_lich_king_lights_favor_AuraScript::OnPeriodic, EFFECT_0, SPELL_AURA_PERIODIC_HEAL);
                DoEffectCalcAmount += AuraEffectCalcAmountFn(spell_the_lich_king_lights_favor_AuraScript::CalculateBonus, EFFECT_1, SPELL_AURA_MOD_DAMAGE_PERCENT_DONE);
            }
        };

        AuraScript* GetAuraScript() const
        {
            return new spell_the_lich_king_lights_favor_AuraScript();
        }
};

class spell_the_lich_king_restore_soul : public SpellScriptLoader
{
    public:
        spell_the_lich_king_restore_soul() : SpellScriptLoader("spell_the_lich_king_restore_soul") { }

        class spell_the_lich_king_restore_soul_SpellScript : public SpellScript
        {
            PrepareSpellScript(spell_the_lich_king_restore_soul_SpellScript);

            bool Load()
            {
                _instance = GetCaster()->GetInstanceScript();
                return _instance != NULL;
            }

            void FilterTargets(std::list<WorldObject*>& unitList)
            {
                for (std::list<WorldObject*>::const_iterator itr = unitList.begin(); itr != unitList.end(); ++itr)
                    if (Unit* target = (*itr)->ToUnit())
                        target->RemoveAurasDueToSpell(target->GetMap()->IsHeroic() ? SPELL_HARVEST_SOULS_TELEPORT : SPELL_HARVEST_SOUL_TELEPORT);
                if (Creature* lichKing = ObjectAccessor::GetCreature(*GetCaster(), _instance->GetData64(DATA_THE_LICH_KING)))
                    lichKing->AI()->DoAction(ACTION_TELEPORT_BACK);
                if (Creature* spawner = GetCaster()->FindNearestCreature(NPC_WORLD_TRIGGER_INFINITE_AOI, 50.0f, true))
                {
                    spawner->RemoveAllAuras();
                    spawner->m_Events.KillAllEvents(true);
                }

                std::list<Creature*> spirits;
                GetCaster()->GetCreatureListWithEntryInGrid(spirits, NPC_WICKED_SPIRIT, 200.0f);
                for (std::list<Creature*>::iterator itr = spirits.begin(); itr != spirits.end(); ++itr)
                {
                    (*itr)->m_Events.KillAllEvents(true);
                    (*itr)->RemoveAllAuras();
                    (*itr)->AI()->EnterEvadeMode();
                    (*itr)->SetReactState(REACT_PASSIVE);
                }
            }

            void Register()
            {
                OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_the_lich_king_restore_soul_SpellScript::FilterTargets, EFFECT_0, TARGET_UNIT_SRC_AREA_ENTRY);
            }

            InstanceScript* _instance;
        };

        SpellScript* GetSpellScript() const
        {
            return new spell_the_lich_king_restore_soul_SpellScript();
        }
};

class npc_spirit_warden : public CreatureScript
{
    public:
        npc_spirit_warden() : CreatureScript("npc_spirit_warden") { }

        struct npc_spirit_wardenAI : public ScriptedAI
        {
            npc_spirit_wardenAI(Creature* creature) : ScriptedAI(creature), _instance(creature->GetInstanceScript()) {}

            EventMap _events;
            InstanceScript* _instance;

            void Reset()
            {
                _events.Reset();
                _events.ScheduleEvent(EVENT_SOUL_RIP, urand(12000, 15000));
            }

            void JustDied(Unit* /*killer*/)
            {
                if (Creature* terenas = ObjectAccessor::GetCreature(*me, _instance->GetData64(DATA_TERENAS_MENETHIL)))
                    terenas->AI()->DoAction(ACTION_TELEPORT_BACK);
            }

            void UpdateAI(uint32 diff)
            {
                if (!UpdateVictim())
                    return;

                _events.Update(diff);

                if (_events.ExecuteEvent() == EVENT_SOUL_RIP)
                {
                    me->CastSpell(me->GetVictim(), SPELL_SOUL_RIP, false);
                    _events.ScheduleEvent(EVENT_SOUL_RIP, urand(23000, 27000));
                }

                DoMeleeAttackIfReady();
            }
        };

        CreatureAI* GetAI(Creature* creature) const
        {
            return GetIcecrownCitadelAI<npc_spirit_wardenAI>(creature);
        }
};

class spell_the_lich_king_dark_hunger : public SpellScriptLoader
{
    public:
        spell_the_lich_king_dark_hunger() : SpellScriptLoader("spell_the_lich_king_dark_hunger") { }

        class spell_the_lich_king_dark_hunger_AuraScript : public AuraScript
        {
            PrepareAuraScript(spell_the_lich_king_dark_hunger_AuraScript);

            bool Validate(SpellInfo const* /*spellInfo*/)
            {
                if (!sSpellMgr->GetSpellInfo(SPELL_DARK_HUNGER_HEAL))
                    return false;
                return true;
            }

            void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo)
            {
                PreventDefaultAction();
                int32 heal = int32(eventInfo.GetDamageInfo()->GetDamage() / 2);
                GetTarget()->CastCustomSpell(SPELL_DARK_HUNGER_HEAL, SPELLVALUE_BASE_POINT0, heal, GetTarget(), true, NULL, aurEff);
            }

            void Register()
            {
                OnEffectProc += AuraEffectProcFn(spell_the_lich_king_dark_hunger_AuraScript::HandleProc, EFFECT_0, SPELL_AURA_DUMMY);
            }
        };

        AuraScript* GetAuraScript() const
        {
            return new spell_the_lich_king_dark_hunger_AuraScript();
        }
};

class spell_the_lich_king_soul_rip : public SpellScriptLoader
{
    public:
        spell_the_lich_king_soul_rip() : SpellScriptLoader("spell_the_lich_king_soul_rip") { }

        class spell_the_lich_king_soul_rip_AuraScript : public AuraScript
        {
            PrepareAuraScript(spell_the_lich_king_soul_rip_AuraScript);

            void OnPeriodic(AuraEffect const* aurEff)
            {
                PreventDefaultAction();
                // shouldn't be needed, this is channeled
                if (Unit* caster = GetCaster())
                    caster->CastCustomSpell(SPELL_SOUL_RIP_DAMAGE, SPELLVALUE_BASE_POINT0, 5000 * aurEff->GetTickNumber(), GetTarget(), true, NULL, aurEff, GetCasterGUID());
            }

            void Register()
            {
                OnEffectPeriodic += AuraEffectPeriodicFn(spell_the_lich_king_soul_rip_AuraScript::OnPeriodic, EFFECT_0, SPELL_AURA_PERIODIC_DUMMY);
            }
        };

        AuraScript* GetAuraScript() const
        {
            return new spell_the_lich_king_soul_rip_AuraScript();
        }
};

class npc_icc_lk_checktarget : public CreatureScript
{
    public:
        npc_icc_lk_checktarget() : CreatureScript("npc_icc_lk_checktarget") { }

        struct npc_icc_lk_checktargetAI : public ScriptedAI
        {
            npc_icc_lk_checktargetAI(Creature* creature) : ScriptedAI(creature) {}

            bool CanAIAttack(Unit const* target) const
            {
                return IsValidPlatformTarget(target) && !target->GetVehicle();
            }
        };

        CreatureAI* GetAI(Creature* creature) const
        {
            return GetIcecrownCitadelAI<npc_icc_lk_checktargetAI>(creature);
        }
};

class spell_the_lich_king_summon_spirit_bomb : public SpellScriptLoader
{
    public:
        spell_the_lich_king_summon_spirit_bomb() : SpellScriptLoader("spell_the_lich_king_summon_spirit_bomb") { }

        class spell_the_lich_king_summon_spirit_bomb_SpellScript : public SpellScript
        {
            PrepareSpellScript(spell_the_lich_king_summon_spirit_bomb_SpellScript);

            void HandleScript(SpellEffIndex effIndex)
            {
                PreventHitDefaultEffect(effIndex);
                GetHitUnit()->CastSpell((Unit*)NULL, uint32(GetEffectValue()), true);
            }

            void Register()
            {
                OnEffectHitTarget += SpellEffectFn(spell_the_lich_king_summon_spirit_bomb_SpellScript::HandleScript, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT);
            }
        };

        SpellScript* GetSpellScript() const
        {
            return new spell_the_lich_king_summon_spirit_bomb_SpellScript();
        }
};

class npc_lk_spirit_bomb : public CreatureScript
{
    public:
        npc_lk_spirit_bomb() : CreatureScript("npc_lk_spirit_bomb") { }

        struct npc_lk_spirit_bombAI : public NullCreatureAI
        {
            npc_lk_spirit_bombAI(Creature* creature) : NullCreatureAI(creature)
            {
                me->SetReactState(REACT_PASSIVE);
                me->DespawnOrUnsummon(45000); // for safety
                timer = 0;
            }

            uint16 timer;

            void IsSummonedBy(Unit* /*summoner*/)
            {
                float destX, destY, destZ;
                me->GetPosition(destX, destY);
                destZ = 1055.0f;    // approximation, gets more precise later
                me->UpdateGroundPositionZ(destX, destY, destZ);
                me->SetWalk(false);
                me->GetMotionMaster()->MovePoint(POINT_GROUND, destX, destY, destZ);
            }

            void MovementInform(uint32 type, uint32 point)
            {
                if (type != POINT_MOTION_TYPE || point != POINT_GROUND)
                    return;

                timer = 1000;
            }

            void UpdateAI(uint32 diff)
            {
                if (timer)
                {
                    if (timer <= diff)
                    {
                        timer = 0;
                        me->RemoveAllAuras();
                        me->CastSpell((Unit*)NULL, SPELL_EXPLOSION, false);
                        me->DespawnOrUnsummon(1000);
                    }
                    else
                        timer -= diff;
                }
            }
        };

        CreatureAI* GetAI(Creature* creature) const
        {
            return GetIcecrownCitadelAI<npc_lk_spirit_bombAI>(creature);
        }
};

class spell_the_lich_king_trigger_vile_spirit : public SpellScriptLoader
{
    public:
        spell_the_lich_king_trigger_vile_spirit() : SpellScriptLoader("spell_the_lich_king_trigger_vile_spirit") { }

        class spell_the_lich_king_trigger_vile_spirit_SpellScript : public SpellScript
        {
            PrepareSpellScript(spell_the_lich_king_trigger_vile_spirit_SpellScript);

            void ActivateSpirit()
            {
                Creature* target = GetHitCreature();
                if (!target)
                    return;

                target->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE | UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NOT_ATTACKABLE_1 | UNIT_FLAG_IMMUNE_TO_PC | UNIT_FLAG_IMMUNE_TO_NPC);
                target->ForceValuesUpdateAtIndex(UNIT_FIELD_FLAGS);
                VileSpiritActivateEvent(target).Execute(0, 0);
            }

            void Register()
            {
                OnHit += SpellHitFn(spell_the_lich_king_trigger_vile_spirit_SpellScript::ActivateSpirit);
            }
        };

        SpellScript* GetSpellScript() const
        {
            return new spell_the_lich_king_trigger_vile_spirit_SpellScript();
        }
};

class npc_lk_wicked_spirit : public CreatureScript
{
    public:
        npc_lk_wicked_spirit() : CreatureScript("npc_lk_wicked_spirit") { }

        struct npc_lk_wicked_spiritAI : public ScriptedAI
        {
            npc_lk_wicked_spiritAI(Creature* creature) : ScriptedAI(creature) { }

            void Reset()
            {
                me->SetCorpseDelay(0);
                me->SetReactState(REACT_PASSIVE);
            }

            void JustDied(Unit* /*killer*/)
            {
                me->SetReactState(REACT_PASSIVE);
            }

            void JustRespawned()
            {
                me->SetReactState(REACT_PASSIVE);
            }

            bool CanAIAttack(Unit const* target) const
            {
                return me->GetReactState() == REACT_AGGRESSIVE && target->GetTypeId() == TYPEID_PLAYER && target->GetExactDistSq(495.708f, -2523.76f, 1049.95f) < 40.0f*40.0f;
            }
        };

        CreatureAI* GetAI(Creature* creature) const
        {
            return GetIcecrownCitadelAI<npc_lk_wicked_spiritAI>(creature);
        }
};

class achievement_been_waiting_long_time : public AchievementCriteriaScript
{
    public:
        achievement_been_waiting_long_time() : AchievementCriteriaScript("achievement_been_waiting_long_time") { }

        bool OnCheck(Player* /*source*/, Unit* target)
        {
            if (!target)
                return false;

            return target->GetAI()->GetData(DATA_PLAGUE_STACK) >= 30;
        }
};

class achievement_neck_deep_in_vile : public AchievementCriteriaScript
{
    public:
        achievement_neck_deep_in_vile() : AchievementCriteriaScript("achievement_neck_deep_in_vile") { }

        bool OnCheck(Player* /*source*/, Unit* target)
        {
            if (!target)
                return false;

            return !target->GetAI()->GetData(DATA_VILE);
        }
};

void AddSC_boss_the_lich_king()
{
    new boss_the_lich_king();
    new npc_tirion_fordring_tft();
    new spell_the_lich_king_quake();
    new spell_the_lich_king_jump();
    new spell_the_lich_king_jump_remove_aura();
    new spell_trigger_spell_from_caster("spell_the_lich_king_mass_resurrection", SPELL_MASS_RESURRECTION_REAL);
    new spell_the_lich_king_play_movie();

    // fight stuff below
    new npc_shambling_horror_icc();
    new spell_the_lich_king_infest();
    new spell_the_lich_king_necrotic_plague();
    new spell_the_lich_king_necrotic_plague_jump();
    new spell_the_lich_king_shadow_trap_visual();
    new spell_the_lich_king_shadow_trap_periodic();
    new spell_the_lich_king_ice_burst_target_search();
    new npc_icc_ice_sphere();
    new spell_the_lich_king_raging_spirit();
    new npc_raging_spirit();
    new spell_the_lich_king_defile();
    new spell_the_lich_king_soul_reaper();
    new npc_valkyr_shadowguard();
    new spell_the_lich_king_summon_into_air();
    new spell_the_lich_king_teleport_to_frostmourne_hc();
    new spell_the_lich_king_valkyr_target_search();
    new spell_the_lich_king_cast_back_to_caster();
    new spell_the_lich_king_life_siphon();
    new spell_the_lich_king_vile_spirits();
    new spell_the_lich_king_vile_spirits_visual();
    new spell_the_lich_king_vile_spirit_move_target_search();
    new spell_the_lich_king_vile_spirit_damage_target_search();
    new spell_the_lich_king_harvest_soul();
    new npc_strangulate_vehicle();
    new npc_terenas_menethil();
    new spell_the_lich_king_lights_favor();
    new spell_the_lich_king_restore_soul();
    new npc_spirit_warden();
    new spell_the_lich_king_dark_hunger();
    new spell_the_lich_king_soul_rip();
    new npc_icc_lk_checktarget();
    new spell_the_lich_king_summon_spirit_bomb();
    new npc_lk_spirit_bomb();
    new spell_the_lich_king_trigger_vile_spirit();
    new npc_lk_wicked_spirit();
    new achievement_been_waiting_long_time();
    new achievement_neck_deep_in_vile();
}