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

#include "ScriptMgr.h"
#include "ScriptedCreature.h"
#include "ScriptedGossip.h"
#include "SpellScript.h"
#include "ulduar.h"
#include "SpellAuras.h"
#include "PassiveAI.h"
#include "Player.h"
#include "WaypointManager.h"
#include "MoveSplineInit.h"

#define SPELL_FLAMEBUFFET_10                64016
#define SPELL_FLAMEBUFFET_25                64023
#define S_FLAMEBUFFET                       RAID_MODE(SPELL_FLAMEBUFFET_10, SPELL_FLAMEBUFFET_25)
#define SPELL_FIREBALL                      63815
#define SPELL_WINGBUFFET                    62666
#define SPELL_FLAMEBREATH_10                63317
#define SPELL_FLAMEBREATH_25                64021
#define S_FLAMEBREATH                       RAID_MODE(SPELL_FLAMEBREATH_10, SPELL_FLAMEBREATH_25)
#define SPELL_FUSEARMOR                     64771
#define SPELL_DEVOURINGFLAME                63236
#define SPELL_BERSERK                       47008

#define SPELL_CHAIN_1                       49679
#define SPELL_CHAIN_2                       49682
#define SPELL_CHAIN_3                       49683
#define SPELL_CHAIN_4                       49684
#define SPELL_LAUNCH_CHAIN                  62505
#define SPELL_HARPOON_SHOT_BUFF             62509
#define SPELL_HARPOON_FIRE_STATE            62696
#define REQ_CHAIN_COUNT                     RAID_MODE(2, 4)

#define SPELL_DEVOURINGFLAME_SUMMON         63308
#define SPELL_DEVOURINGFLAME_GROUNDAURA_10  64709
#define SPELL_DEVOURINGFLAME_GROUNDAURA_25  64734
#define S_DEVOURINGFLAME_GROUNDAURA         RAID_MODE(SPELL_DEVOURINGFLAME_GROUNDAURA_10, SPELL_DEVOURINGFLAME_GROUNDAURA_25)
#define NPC_DEVOURINGFLAME                  34188
#define SPELL_STORMSTRIKE                   51876
#define SPELL_WHIRLWIND                     63808
#define SPELL_LIGHTINGBOLT                  63809
#define SPELL_CHAINLIGHTNING                64758

#define NPC_DARK_RUNE_SENTINEL              33846
#define NPC_DARK_RUNE_GUARDIAN              33388
#define NPC_DARK_RUNE_WATCHER               33453
#define NPC_EXPEDITION_ENGINEER             33287
#define NPC_EXPEDITION_COMMANDER            33210
#define NPC_EXPEDITION_DEFENDER             33816
#define NPC_EXPEDITION_TRAPPER              33259
#define NPC_RAZORSCALE                      33186
#define NPC_HARPOON_FIRE_STATE              33282

#define GO_DRILL                            195305
#define GO_HARPOON_GUN_1                    194519
#define GO_HARPOON_GUN_2                    194541
#define GO_HARPOON_GUN_3                    194542
#define GO_HARPOON_GUN_4                    194543
#define GO_BROKEN_HARPOON                   194565

#define TEXT_GOSSIP_ACTION                  "We are ready to help!"
#define TEXT_EE_AGGRO                       "Give us a moment to prepare to build the turrets."
#define TEXT_EE_MOVE_OUT                    "Ready to move out, keep those dwarves off of our backs!"
#define TEXT_EE_FIRES_OUT                   "Fires out! Let's rebuild those turrets!"

#define TEXT_TURRET_READY                   "Harpoon Turret is ready for use!"
#define TEXT_DEEP_BREATH                    "Razorscale takes a deep breath..."
#define TEXT_GROUNDED_PERMANENTLY           "Razorscale grounded permanently!"


#define CORDS_GROUND                        588.0f, -166.0f, 391.1f
#define CORDS_AIR                           588.0f, -178.0f, 490.0f
#define REPAIR_POINTS                       25

enum eSay
{
    SAY_COMMANDER_INTRO             = 0,
    SAY_COMMANDER_GROUND            = 1,
    SAY_COMMANDER_AGGRO             = 2
};

enum eEvents
{
    EVENT_NONE = 0,
    EVENT_COMMANDER_SAY_AGGRO,
    EVENT_EE_SAY_MOVE_OUT,
    EVENT_ENRAGE,
    EVENT_SPELL_FIREBALL,
    EVENT_SPELL_DEVOURING_FLAME,
    EVENT_SUMMON_MOLE_MACHINES,
    EVENT_SUMMON_ADDS,
    EVENT_WARN_DEEP_BREATH,
    EVENT_PHASE2_FLAME_BREATH,
    EVENT_FLY_UP,
    EVENT_RESUME_FIXING,
    EVENT_SPELL_FLAME_BREATH,
    EVENT_SPELL_DEVOURING_FLAME_GROUND,
    EVENT_SPELL_FUSE_ARMOR,
    EVENT_SPELL_FLAME_BUFFET,
};

enum eMisc
{
    POINT_RAZORSCALE_INIT           = 1
};

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

    CreatureAI* GetAI(Creature* pCreature) const
    {
        return new boss_razorscaleAI (pCreature);
    }

    struct boss_razorscaleAI : public ScriptedAI
    {
        boss_razorscaleAI(Creature *pCreature) : ScriptedAI(pCreature), summons(me)
        {
            pInstance = me->GetInstanceScript();
            startPath = true;
        }

        InstanceScript* pInstance;
        EventMap events;
        SummonList summons;
        uint64 ExpeditionEngineerGUIDs[3];
        uint64 CommanderGUID;
        float cords[4][2];
        bool bGroundPhase;
        bool startPath;
        uint8 flyTimes;

        void Reset()
        {
            events.Reset();
            summons.DespawnAll();
            memset(&ExpeditionEngineerGUIDs, 0, sizeof(ExpeditionEngineerGUIDs));
            CommanderGUID = 0;
            bGroundPhase = false;
            flyTimes = 0;

            me->SetCanFly(true);
            me->SetDisableGravity(true);
            me->SetHover(true);
            me->SendMovementFlagUpdate();
            me->setActive(true);

            if( pInstance )
                pInstance->SetData(TYPE_RAZORSCALE, NOT_STARTED);
        }

        void AttackStart(Unit* who)
        {
            if (who && me->Attack(who, true) && bGroundPhase)
                me->GetMotionMaster()->MoveChase(who);
        }

        void EnterCombat(Unit* who)
        {
            me->SetInCombatWithZone();
            events.Reset();
            events.ScheduleEvent(EVENT_COMMANDER_SAY_AGGRO, 5000);
            events.ScheduleEvent(EVENT_EE_SAY_MOVE_OUT, 10000);
            events.ScheduleEvent(EVENT_ENRAGE, 600000);
            events.ScheduleEvent(EVENT_SPELL_FIREBALL, 6000);
            events.ScheduleEvent(EVENT_SPELL_DEVOURING_FLAME, 13000);
            events.ScheduleEvent(EVENT_SUMMON_MOLE_MACHINES, 11000);

            std::list<Creature*> eeList;
            me->GetCreaturesWithEntryInRange(eeList, 300.0f, NPC_EXPEDITION_ENGINEER);
            uint8 i = 0;
            for( std::list<Creature*>::iterator itr = eeList.begin(); itr != eeList.end(); ++itr )
            {
                if( i > 2 )
                    break;
                ExpeditionEngineerGUIDs[i] = (*itr)->GetGUID();
                if (!i)
                    (*itr)->MonsterYell(TEXT_EE_AGGRO, LANG_UNIVERSAL, 0);
                ++i;
            }
            if (Creature* c = me->FindNearestCreature(NPC_EXPEDITION_COMMANDER, 300.0f, true))
                CommanderGUID = c->GetGUID();

            if( pInstance )
                pInstance->SetData(TYPE_RAZORSCALE, IN_PROGRESS);
        }

        void JustDied(Unit* Killer)
        {
            summons.DespawnAll();

            if( pInstance )
                pInstance->SetData(TYPE_RAZORSCALE, DONE);
        }

        void SpellHit(Unit* caster, const SpellInfo* spell)
        {
            if (!caster || !pInstance)
                return;

            switch (spell->Id)
            {
                case SPELL_LAUNCH_CHAIN:
                    {
                        uint32 spell = 0;
                        if( caster->GetGUID() == pInstance->GetData64(DATA_HARPOON_FIRE_STATE_1) )
                            spell = SPELL_CHAIN_1;
                        else if( caster->GetGUID() == pInstance->GetData64(DATA_HARPOON_FIRE_STATE_2) )
                            spell = SPELL_CHAIN_2;
                        else if( caster->GetGUID() == pInstance->GetData64(DATA_HARPOON_FIRE_STATE_3) )
                            spell = SPELL_CHAIN_3;
                        else
                            spell = SPELL_CHAIN_4;
                        caster->CastSpell(me, spell, true);
                    }
                    break;
                case SPELL_CHAIN_1:
                case SPELL_CHAIN_2:
                case SPELL_CHAIN_3:
                case SPELL_CHAIN_4:
                    {
                        uint8 count = 0;
                        if( me->HasAura(SPELL_CHAIN_1) )
                            count++;
                        if( me->HasAura(SPELL_CHAIN_3) )
                            count++;
                        if (RAID_MODE(0,1))
                        {
                            if( me->HasAura(SPELL_CHAIN_2) )
                                count++;
                            if( me->HasAura(SPELL_CHAIN_4) )
                                count++;
                        }
                        if( count >= REQ_CHAIN_COUNT )
                        {
                            if (Creature* commander = ObjectAccessor::GetCreature(*me, CommanderGUID))
                                commander->AI()->Talk(SAY_COMMANDER_GROUND);

                            me->InterruptNonMeleeSpells(true);
                            events.CancelEvent(EVENT_SPELL_FIREBALL);
                            events.CancelEvent(EVENT_SPELL_DEVOURING_FLAME);
                            events.CancelEvent(EVENT_SUMMON_MOLE_MACHINES);
                            me->SetTarget(0);
                            me->SendMeleeAttackStop(me->GetVictim());
                            me->GetMotionMaster()->MoveLand(0, CORDS_GROUND, 25.0f);
                        }
                    }
                    break;
            }
        }

        void DamageTaken(Unit*, uint32& damage, DamageEffectType /*damagetype*/, SpellSchoolMask /*damageSchoolMask*/)
        {
            if (me->GetPositionZ() > 440.0f) // protection, razorscale is attackable (so harpoons can hit him, etc.), but should not receive dmg while in air
                damage = 0;
            else if (!bGroundPhase && ((me->GetHealth()*100)/me->GetMaxHealth() < 50) && me->HasAura(62794)) // already below 50%, but still in chains and stunned
                events.RescheduleEvent(EVENT_WARN_DEEP_BREATH, 0);
        }

        void MovementInform(uint32 type, uint32 id)
        {
            if (type == POINT_MOTION_TYPE && id == POINT_RAZORSCALE_INIT)
            {
                me->SetFacingTo(1.6f);
                return;
            }
            else if (type == ESCORT_MOTION_TYPE && me->movespline->Finalized() && !me->IsInCombat())
            {
                startPath = true;
                return;
            }

            if (type != EFFECT_MOTION_TYPE)
                return;
            if (id == 0) // landed
            {
                me->SetControlled(true, UNIT_STATE_ROOT);
                me->DisableRotate(true);
                me->SetOrientation((float)(M_PI+0.01)/2);
                me->SetFacingTo(M_PI/2);
                me->SetCanFly(false);
                me->SetDisableGravity(false);
                me->SetHover(false);
                me->CastSpell(me, 62794, true);
                events.ScheduleEvent(EVENT_WARN_DEEP_BREATH, 30000);
            }
            else if (id == 1) // flied up
            {
                events.ScheduleEvent(EVENT_SPELL_FIREBALL, 2000);
                events.ScheduleEvent(EVENT_SPELL_DEVOURING_FLAME, 4000);
                events.ScheduleEvent(EVENT_SUMMON_MOLE_MACHINES, 5000);
            }
        }

        void UpdateAI(uint32 diff)
        {
            if (startPath)
            {
                me->StopMoving();
                startPath = false;
                if (WaypointPath const* i_path = sWaypointMgr->GetPath(me->GetWaypointPath()))
                {
                    Movement::PointsArray pathPoints;
                    pathPoints.push_back(G3D::Vector3(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ()));
                    for (uint8 i = 0; i < i_path->size(); ++i)
                    {
                        WaypointData const* node = i_path->at(i);
                        pathPoints.push_back(G3D::Vector3(node->x, node->y, node->z));
                    }
                    me->GetMotionMaster()->MoveSplinePath(&pathPoints);
                }
            }

            if (!UpdateVictim())
                return;

            events.Update(diff);

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

            switch (events.GetEvent())
            {
                case 0:
                    break;
                case EVENT_ENRAGE:
                    me->CastSpell(me, SPELL_BERSERK, true);
                    events.RepeatEvent(600000);
                    break;
                case EVENT_COMMANDER_SAY_AGGRO:
                    if (Creature* commander = ObjectAccessor::GetCreature(*me, CommanderGUID))
                        commander->AI()->Talk(SAY_COMMANDER_AGGRO);
                    events.PopEvent();
                    break;
                case EVENT_EE_SAY_MOVE_OUT:
                    for (uint8 i=0; i<3; ++i)
                        if (Creature* c = ObjectAccessor::GetCreature(*me, ExpeditionEngineerGUIDs[i]))
                        {
                            if (!i)
                                c->MonsterYell(TEXT_EE_MOVE_OUT, LANG_UNIVERSAL, 0);
                            c->AI()->SetData(1, 0); // start repairing
                        }
                    events.PopEvent();
                    break;
                case EVENT_SPELL_FIREBALL:
                    if( Unit *pTarget = SelectTarget(SELECT_TARGET_RANDOM, 0, 200.0f, true) )
                        me->CastSpell(pTarget, SPELL_FIREBALL, false);
                    events.RepeatEvent(4000);
                    break;
                case EVENT_SPELL_DEVOURING_FLAME:
                    if( Unit *pTarget = SelectTarget(SELECT_TARGET_RANDOM, 0, 200.0f, true) )
                        me->CastSpell(pTarget, SPELL_DEVOURINGFLAME, false);
                    events.RepeatEvent(13000);
                    break;
                case EVENT_SUMMON_MOLE_MACHINES:
                    {
                        memset(cords, '\0', sizeof(cords));
                        uint8 num = RAID_MODE( urand(2,3), urand(2,4) );
                        for( int i=0; i<num; ++i )
                        {
                            // X: (550, 625) Y: (-185, -230)
                            cords[i][0] = urand(550, 625);
                            cords[i][1] = -230 + rand()%45;
                            if( GameObject* drill = me->SummonGameObject(GO_DRILL, cords[i][0], cords[i][1], 391.1f, M_PI/4, 0.0f, 0.0f, 0.0f, 0.0f, 8) )
                            {
                                //drill->SetGoAnimProgress(0);
                                //drill->SetLootState(GO_READY);
                                //drill->UseDoorOrButton(8);
                                //drill->SetGoState(GO_STATE_READY);
                                drill->SetGoState(GO_STATE_ACTIVE);
                                drill->SetGoAnimProgress(0);
                            }
                        }
                        events.RepeatEvent(45000);
                        events.RescheduleEvent(EVENT_SUMMON_ADDS, 4000);
                    }
                    break;
                case EVENT_SUMMON_ADDS:
                    for( int i=0; i<4; ++i )
                    {
                        if( !cords[i][0] )
                            break;

                        uint8 opt;
                        uint8 r = urand(1,100);
                        if( r <= 30 ) opt = 1;
                        else if( r <= 65 ) opt = 2;
                        else opt = 3;

                        for( int j=0; j<4; ++j )
                        {
                            float x = cords[i][0] + 4.0f*cos(j*M_PI/2);
                            float y = cords[i][1] + 4.0f*sin(j*M_PI/2);

                            uint32 npc_entry = 0;
                            switch( opt )
                            {
                                case 1: if( j == 1 ) npc_entry = NPC_DARK_RUNE_SENTINEL; break;
                                case 2:
                                    switch( j )
                                    {
                                        case 1: npc_entry = NPC_DARK_RUNE_WATCHER; break;
                                        case 2: npc_entry = NPC_DARK_RUNE_GUARDIAN; break;
                                    }
                                    break;
                                default: // case 3:
                                    switch( j )
                                    {
                                        case 1: npc_entry = NPC_DARK_RUNE_WATCHER; break;
                                        case 2: npc_entry = NPC_DARK_RUNE_GUARDIAN; break;
                                        case 3: npc_entry = NPC_DARK_RUNE_GUARDIAN; break;
                                    }
                                    break;
                            }

                            if( npc_entry )
                                if (Creature* c = me->SummonCreature(npc_entry, x, y, 391.1f, j*M_PI/2, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 5000))
                                    DoZoneInCombat(c);

                        }
                    }
                    events.PopEvent();
                    break;
                case EVENT_WARN_DEEP_BREATH:
                    me->MonsterTextEmote(TEXT_DEEP_BREATH, 0, true);
                    me->RemoveAura(62794);
                    events.PopEvent();
                    events.ScheduleEvent(EVENT_PHASE2_FLAME_BREATH, 2500);
                    break;
                case EVENT_PHASE2_FLAME_BREATH:
                    me->CastSpell(me, S_FLAMEBREATH, true);
                    events.PopEvent();
                    events.ScheduleEvent(EVENT_FLY_UP, 2000);
                    break;
                case EVENT_FLY_UP:
                    me->SetInCombatWithZone(); // just in case
                    if (pInstance)
                        for( int i=0; i<4; ++i )
                            if( uint64 guid = pInstance->GetData64(DATA_HARPOON_FIRE_STATE_1 + i) )
                                if( Creature* hfs = ObjectAccessor::GetCreature(*me, guid) )
                                {
                                    me->SummonCreature(34188, hfs->GetPositionX(), hfs->GetPositionY(), hfs->GetPositionZ(), 0, TEMPSUMMON_TIMED_DESPAWN, 22000);
                                    hfs->AI()->SetData(1, 0);
                                }

                    me->RemoveAura(SPELL_LAUNCH_CHAIN);
                    me->RemoveAura(SPELL_CHAIN_1);
                    me->RemoveAura(SPELL_CHAIN_3);
                    if (RAID_MODE(0,1))
                    {
                        me->RemoveAura(SPELL_CHAIN_2);
                        me->RemoveAura(SPELL_CHAIN_4);
                    }
                    me->CastSpell(me, SPELL_WINGBUFFET, true);
                    
                    if( (me->GetHealth()*100) / me->GetMaxHealth() < 50 ) // start phase 3
                    {
                        me->SetControlled(false, UNIT_STATE_ROOT);
                        me->DisableRotate(false);
                        DoResetThreat();
                        Unit* target = SelectTarget(SELECT_TARGET_NEAREST, 0, 0.0, true);
                        if (!target)
                            target = me->SelectNearestPlayer(200.0f);
                        if (target)
                        {
                            AttackStart(target);
                            me->GetMotionMaster()->MoveChase(target);
                        }
                        bGroundPhase = true;
                        events.PopEvent();
                        events.CancelEvent(EVENT_SPELL_FIREBALL);
                        events.CancelEvent(EVENT_SPELL_DEVOURING_FLAME);
                        events.CancelEvent(EVENT_SUMMON_MOLE_MACHINES);

                        events.ScheduleEvent(EVENT_SPELL_FLAME_BREATH, 20000);
                        events.ScheduleEvent(EVENT_SPELL_DEVOURING_FLAME_GROUND, 5000);
                        events.ScheduleEvent(EVENT_SPELL_FUSE_ARMOR, 10000);
                        events.ScheduleEvent(EVENT_SPELL_FLAME_BUFFET, 3000);

                        break;
                    }
                    else
                    {
                        ++flyTimes;
                        me->SetControlled(false, UNIT_STATE_ROOT);
                        me->DisableRotate(false);
                        me->SendMeleeAttackStop(me->GetVictim());
                        me->GetMotionMaster()->MoveIdle();
                        me->StopMoving();
                        me->SetCanFly(true);
                        me->SetDisableGravity(true);
                        me->SetHover(true);
                        me->SendMovementFlagUpdate();
                        me->GetMotionMaster()->MoveTakeoff(1, CORDS_AIR, 25.0f);
                        events.ScheduleEvent(EVENT_RESUME_FIXING, 22000);
                    }

                    events.PopEvent();
                    break;
                case EVENT_RESUME_FIXING:
                    for (uint8 i=0; i<3; ++i)
                        if (Creature* c = ObjectAccessor::GetCreature(*me, ExpeditionEngineerGUIDs[i]))
                        {
                            if (!i)
                                c->MonsterYell(TEXT_EE_FIRES_OUT, LANG_UNIVERSAL, 0);
                            c->AI()->SetData(1, 0); // start repairing
                        }
                    events.PopEvent();
                    break;
                case EVENT_SPELL_FLAME_BREATH:
                    me->CastSpell(me->GetVictim(), S_FLAMEBREATH, false);
                    events.RepeatEvent(20000);
                    break;
                case EVENT_SPELL_DEVOURING_FLAME_GROUND:
                    me->CastSpell(me->GetVictim(), SPELL_DEVOURINGFLAME, false);
                    events.RepeatEvent(13000);
                    break;
                case EVENT_SPELL_FUSE_ARMOR:
                    if (Unit* victim = me->GetVictim())
                        if (me->IsWithinMeleeRange(victim))
                        {
                            me->CastSpell(victim, SPELL_FUSEARMOR, false);
                            if (Aura* aur = victim->GetAura(SPELL_FUSEARMOR))
                                if (aur->GetStackAmount() == 5)
                                    victim->CastSpell(victim, 64774, true);
                            events.RepeatEvent(10000);
                            break;
                        }
                    events.RepeatEvent(2000);
                    break;
                case EVENT_SPELL_FLAME_BUFFET:
                    me->CastSpell(me->GetVictim(), S_FLAMEBUFFET, false);
                    events.RepeatEvent(7000);
                    break;
            }

            if (bGroundPhase)
                DoMeleeAttackIfReady();
        }

        void MoveInLineOfSight(Unit* /*who*/) {}

        void JustReachedHome()
        {
            startPath = true;
        }

        void JustSummoned(Creature* s)
        {
            summons.Summon(s);
        }

        uint32 GetData(uint32 id) const
        {
            if (id == 1)
                return (flyTimes <= 1 ? 1 : 0);
            return 0;
        }

        void KilledUnit(Unit* victim)
        {
            if (victim && victim->GetEntry() == NPC_DARK_RUNE_GUARDIAN)
                if (pInstance)
                    pInstance->DoUpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_KILL_CREATURE, NPC_DARK_RUNE_GUARDIAN, 1, me);
        }

        void EnterEvadeMode()
        {
            me->SetControlled(false, UNIT_STATE_ROOT);
            me->DisableRotate(false);
            ScriptedAI::EnterEvadeMode();
        }
    };
};

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

        bool OnGossipHello(Player* player, Creature* creature)
        {
            if (!player || !creature)
                return true;

            InstanceScript* instance = creature->GetInstanceScript();
            if (!instance)
                return true;

            if (instance->GetData(TYPE_RAZORSCALE) == DONE)
                return true;

            Creature* razorscale = ObjectAccessor::GetCreature(*creature, instance->GetData64(TYPE_RAZORSCALE));
            if (!razorscale || razorscale->IsInCombat())
                return true;

            player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, TEXT_GOSSIP_ACTION, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF+1);
            player->PlayerTalkClass->SendGossipMenu(40100, creature->GetGUID());
            return true;
        }

        bool OnGossipSelect(Player* player, Creature* creature, uint32 uiSender, uint32 uiAction)
        {
            if (!player || !creature)
                return true;

            if (uiAction == GOSSIP_ACTION_INFO_DEF+1)
            {
                InstanceScript* instance = creature->GetInstanceScript();
                if (!instance || instance->GetData(TYPE_RAZORSCALE) == DONE)
                    return true;

                Creature* razorscale = ObjectAccessor::GetCreature(*creature, instance->GetData64(TYPE_RAZORSCALE));
                if (razorscale && !razorscale->IsInCombat())
                {
                    // reset npcs NPC_HARPOON_FIRE_STATE
                    for (uint8 i = 0; i < 4; ++i)
                        if (Creature* hfs = ObjectAccessor::GetCreature(*creature, instance->GetData64(DATA_HARPOON_FIRE_STATE_1 + i)))
                            hfs->AI()->SetData(1, 0);

                    if (razorscale->AI())
                    {
                        razorscale->AI()->AttackStart(player);
                        razorscale->GetMotionMaster()->MoveIdle();
                        razorscale->GetMotionMaster()->MovePoint(POINT_RAZORSCALE_INIT, 588.0f, -178.0f, 490.0f, false, false);
                    }
                }
            }

            player->PlayerTalkClass->SendCloseGossip();
            return true;
        }

        CreatureAI* GetAI(Creature* creature) const
        {
            return GetInstanceAI<npc_ulduar_expedition_commanderAI>(creature);
        }

        struct npc_ulduar_expedition_commanderAI : public NullCreatureAI
        {
            npc_ulduar_expedition_commanderAI(Creature* creature) : NullCreatureAI(creature)
            {
                _instance = creature->GetInstanceScript();
                _introSpoken = _instance->GetData(TYPE_RAZORSCALE) == DONE;
                me->SetReactState(REACT_AGGRESSIVE);
            }

            void MoveInLineOfSight(Unit* who)
            {
                if (_introSpoken)
                    return;

                if (who->GetTypeId() != TYPEID_PLAYER || me->GetExactDist2d(who) > 15.0f)
                    return;

                _introSpoken = true;
                Talk(SAY_COMMANDER_INTRO);
            }

        private:
            InstanceScript* _instance;
            bool _introSpoken;
        };
};

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

    CreatureAI* GetAI(Creature* pCreature) const
    {
        return new npc_ulduar_harpoonfirestateAI (pCreature);
    }

    struct npc_ulduar_harpoonfirestateAI : public NullCreatureAI
    {
        npc_ulduar_harpoonfirestateAI(Creature *pCreature) : NullCreatureAI(pCreature)
        {
            pInstance = me->GetInstanceScript();
        }

        InstanceScript* pInstance;
        uint8 repairPoints;
        
        void Reset()
        {
            repairPoints = 0;
        }

        uint32 GetHarpoonGunIdForThisHFS()
        {
            if (pInstance)
            {
                if( me->GetGUID() == pInstance->GetData64(DATA_HARPOON_FIRE_STATE_1) )
                    return GO_HARPOON_GUN_1;
                else if( me->GetGUID() == pInstance->GetData64(DATA_HARPOON_FIRE_STATE_2) )
                    return GO_HARPOON_GUN_2;
                else if( me->GetGUID() == pInstance->GetData64(DATA_HARPOON_FIRE_STATE_3) )
                    return GO_HARPOON_GUN_3;
                else
                    return GO_HARPOON_GUN_4;
            }
            return 0;
        }

        void SetData(uint32 id, uint32 value)
        {
            switch (id)
            {
                case 1: // cleanup at the start of the fight
                    if (pInstance)
                    {
                        uint32 h_entry = GetHarpoonGunIdForThisHFS();
                        if( GameObject* wh = me->FindNearestGameObject(h_entry, 5.0f) )
                        {
                            wh->SetRespawnTime(0);
                            wh->Delete();
                        }
                        if( GameObject* bh = me->FindNearestGameObject(GO_BROKEN_HARPOON, 5.0f) )
                            if (bh->GetPhaseMask() != 1)
                                bh->SetPhaseMask(1, true);
                    }
                    Reset();
                    break;
                case 2: // repairing
                    if (repairPoints < REPAIR_POINTS)
                    {
                        if (++repairPoints >= REPAIR_POINTS)
                        {
                            if( GameObject* bh = me->FindNearestGameObject(GO_BROKEN_HARPOON, 4.0f) )
                                bh->SetPhaseMask(2, true);
                            if( GameObject* wh = me->SummonGameObject(GetHarpoonGunIdForThisHFS(), me->GetPositionX(), me->GetPositionY(), me->GetPositionZ(), 3*M_PI/2, 0.0f, 0.0f, 0.0f, 0.0f, 0) )
                            {
                                me->RemoveGameObject(wh, false);
                                me->MonsterTextEmote(TEXT_TURRET_READY, 0, true);
                            }
                        }
                    }
                    break;
                case 3: // shoot
                    if (pInstance)
                    {
                        Creature* razorscale = ObjectAccessor::GetCreature(*me, pInstance->GetData64(TYPE_RAZORSCALE));
                        if (!razorscale)
                            return;
                        if (!razorscale->HasAura(value))
                            me->CastSpell(razorscale, SPELL_LAUNCH_CHAIN, true);
                    }
                    break;
            }
        }

        uint32 GetData(uint32 id) const
        {
            switch (id)
            {
                case 2:
                    return (repairPoints >= REPAIR_POINTS ? 1 : 0);
            }
            return 0;
        }
    };
};

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

    CreatureAI* GetAI(Creature* pCreature) const
    {
        return new npc_ulduar_expedition_engineerAI (pCreature);
    }

    struct npc_ulduar_expedition_engineerAI : public NullCreatureAI
    {
        npc_ulduar_expedition_engineerAI(Creature *pCreature) : NullCreatureAI(pCreature)
        {
            pInstance = me->GetInstanceScript();
        }

        InstanceScript* pInstance;
        bool working;
        uint16 timer;
        uint64 fixingGUID;

        void Reset()
        {
            working = false;
            timer = 0;
            fixingGUID = 0;
        }

        void SetData(uint32 id, uint32 value)
        {
            switch (id)
            {
                case 1: // start/resume repairing
                    working = true;
                    timer = 0;
                    fixingGUID = 0;
                    break;
                case 2: // stop repairing
                    Reset();
                    me->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_STATE_STAND);
                    me->GetMotionMaster()->MoveTargetedHome();
                    break;
            }
        }

        void UpdateAI(uint32 diff)
        {
            if (working)
            {
                if (timer <= diff)
                {
                    timer = 3000;

                    if (fixingGUID)
                    {
                        if (Creature* c = ObjectAccessor::GetCreature(*me, fixingGUID))
                            if (me->GetExactDist2dSq(c) <= 25.0f)
                            {
                                if( me->GetUInt32Value(UNIT_NPC_EMOTESTATE) != EMOTE_STATE_WORK )
                                    me->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_STATE_WORK);

                                if (fabs(me->GetOrientation()-me->GetAngle(c)) > M_PI/4)
                                    me->SetFacingToObject(c);

                                c->AI()->SetData(2, 0);
                                if (c->AI()->GetData(2))
                                    fixingGUID = 0;
                            }
                    }

                    if (!fixingGUID)
                    {
                        Creature* razorscale = NULL;
                        if( uint64 rsGUID = pInstance->GetData64(TYPE_RAZORSCALE) )
                            razorscale = ObjectAccessor::GetCreature(*me, rsGUID);

                        if( !razorscale || !razorscale->IsInCombat() )
                        {
                            Reset();
                            me->GetMotionMaster()->MoveTargetedHome();
                            return;
                        }

                        for( int i=0; i<4; ++i )
                            if( uint64 fs_GUID = pInstance->GetData64(DATA_HARPOON_FIRE_STATE_1 + i) )
                                if( Creature* fs = ObjectAccessor::GetCreature(*me, fs_GUID) )
                                    if (!fs->AI()->GetData(2))
                                        {
                                            float a = rand_norm()*M_PI;
                                            me->GetMotionMaster()->MovePoint(0, fs->GetPositionX()+3.0f*cos(a), fs->GetPositionY()+3.0f*sin(a), fs->GetPositionZ());
                                            fixingGUID = fs->GetGUID();
                                            return;
                                        }

                        Reset(); // all harpoons repaired
                        me->GetMotionMaster()->MoveTargetedHome();
                    }
                }
                else
                    timer -= diff;
            }
            else if (me->GetUInt32Value(UNIT_NPC_EMOTESTATE) == EMOTE_STATE_WORK)
                me->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_STATE_STAND);
        }
    };
};

class go_ulduar_working_harpoon : public GameObjectScript
{ 
public: 
    go_ulduar_working_harpoon() : GameObjectScript("go_ulduar_working_harpoon") { } 

    bool OnGossipHello(Player* user, GameObject* go)
    {
        if( !user || !go )
            return true;

        InstanceScript* pInstance = go->GetInstanceScript();
        if( !pInstance )
            return true;

        Creature* rs = NULL;
        if( uint64 rsGUID = pInstance->GetData64(TYPE_RAZORSCALE) )
            rs = ObjectAccessor::GetCreature(*go, rsGUID);

        if( !rs || !rs->IsInCombat() )
        {
            go->SetRespawnTime(0);
            go->Delete();
            return true;
        }

        uint32 npc = 0;
        uint32 spell = 0;
        switch( go->GetEntry() )
        {
            case GO_HARPOON_GUN_1:
                npc = DATA_HARPOON_FIRE_STATE_1;
                spell = SPELL_CHAIN_1;
                break;
            case GO_HARPOON_GUN_2:
                npc = DATA_HARPOON_FIRE_STATE_2;
                spell = SPELL_CHAIN_2;
                break;
            case GO_HARPOON_GUN_3:
                npc = DATA_HARPOON_FIRE_STATE_3;
                spell = SPELL_CHAIN_3;
                break;
            case GO_HARPOON_GUN_4:
                npc = DATA_HARPOON_FIRE_STATE_4;
                spell = SPELL_CHAIN_4;
                break;
        }

        if( uint64 g = pInstance->GetData64(npc) )
            if( Creature* hfs = ObjectAccessor::GetCreature(*go, g) )
                hfs->AI()->SetData(3, spell);
        
        go->SetLootState(GO_JUST_DEACTIVATED);
        return true;
    }
};

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

    CreatureAI* GetAI(Creature* pCreature) const
    {
        return new npc_ulduar_dark_rune_guardianAI (pCreature);
    }

    struct npc_ulduar_dark_rune_guardianAI : public ScriptedAI
    {
        npc_ulduar_dark_rune_guardianAI(Creature *pCreature) : ScriptedAI(pCreature) { }

        uint32 timer2;

        void Reset()
        {
            timer2 = 6000;
        }

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

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

            if (timer2 <= diff) timer2 = 0;
            else timer2 -= diff;
            if (timer2 == 0 && me->GetVictim() && me->IsWithinMeleeRange(me->GetVictim()))
            {
                me->CastSpell(me->GetVictim(), 65971, true);
                me->CastSpell(me->GetVictim(), 65971, true); // me->CastSpell(me->GetVictim(), 65972, true); // cast the same twice cus second one requires setting offhand damage
                me->CastSpell(me->GetVictim(), 64757, true);
                timer2 = urand(8000, 10000);
                return;
            }

            DoMeleeAttackIfReady();
        }
    };
};

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

    CreatureAI* GetAI(Creature* pCreature) const
    {
        return new npc_ulduar_dark_rune_watcherAI (pCreature);
    }

    struct npc_ulduar_dark_rune_watcherAI : public ScriptedAI
    {
        npc_ulduar_dark_rune_watcherAI(Creature *pCreature) : ScriptedAI(pCreature) { }

        uint32 timer1;
        uint32 timer2;

        void Reset()
        {
            timer1 = 6000;
            timer2 = 2000;
        }

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

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

            if( timer1 <= diff )
            {
                me->CastSpell(me->GetVictim(), RAID_MODE(64758, 64759), false);
                timer1 = urand(10000, 12000);
                return;
            }
            else
                timer1 -= diff;

            if (timer2 <= diff)
            {
                me->CastSpell(me->GetVictim(), RAID_MODE(63809, 64696), false);
                timer2 = 4000;
                return;
            }
            else
                timer2 -= diff;

            DoMeleeAttackIfReady();
        }
    };
};

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

    CreatureAI* GetAI(Creature* pCreature) const
    {
        return new npc_ulduar_dark_rune_sentinelAI (pCreature);
    }

    struct npc_ulduar_dark_rune_sentinelAI : public ScriptedAI
    {
        npc_ulduar_dark_rune_sentinelAI(Creature *pCreature) : ScriptedAI(pCreature) { }

        uint32 timer1;
        uint32 timer2;

        void Reset()
        {
            timer1 = urand(1000,2000);
            timer2 = 6000;
        }

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

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

            if( timer1 <= diff )
            {
                me->CastSpell(me, RAID_MODE(46763, 64062), false);
                timer1 = urand(15000, 20000);
            }
            else
                timer1 -= diff;

            if (timer2 <= diff) timer2 = 0;
            else timer2 -= diff;
            if (timer2 == 0 && me->GetVictim() && me->IsWithinMeleeRange(me->GetVictim()))
            {
                me->CastSpell(me, 63808, false);
                timer2 = urand(10000, 12000);
            }

            DoMeleeAttackIfReady();
        }
    };
};

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

        bool OnCheck(Player* player, Unit* target)
        {
            return target && target->GetTypeId() == TYPEID_UNIT && target->GetEntry() == NPC_RAZORSCALE && target->ToCreature()->AI()->GetData(1);
        }
};

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

        bool OnCheck(Player* player, Unit* target)
        {
            return target && target->GetEntry() == NPC_RAZORSCALE;
        }
};

void AddSC_boss_razorscale()
{
    new boss_razorscale();
    new npc_ulduar_expedition_commander();
    new npc_ulduar_harpoonfirestate();
    new npc_ulduar_expedition_engineer();
    new go_ulduar_working_harpoon();
    new npc_ulduar_dark_rune_guardian();
    new npc_ulduar_dark_rune_watcher();
    new npc_ulduar_dark_rune_sentinel();
    new achievement_quick_shave();
    new achievement_iron_dwarf_medium_rare();
}
