/*
 * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Affero General Public License as published by the
 * Free Software Foundation; either version 3 of the License, or (at your
 * option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program. If not, see <http://www.gnu.org/licenses/>.
 */

/*
 * Scripts for spells with SPELLFAMILY_HUNTER, SPELLFAMILY_PET and SPELLFAMILY_GENERIC spells used by hunter players.
 * Ordered alphabetically using scriptname.
 * Scriptnames of files in this file should be prefixed with "spell_hun_".
 */

#include "Cell.h"
#include "CellImpl.h"
#include "GridNotifiers.h"
#include "Pet.h"
#include "ScriptMgr.h"
#include "SpellAuras.h"
#include "SpellAuraEffects.h"
#include "SpellScript.h"

// TODO: this import is not necessary for compilation and marked as unused by the IDE
//  however, for some reasons removing it would cause a damn linking issue
//  there is probably some underlying problem with imports which should properly addressed
#include "GridNotifiersImpl.h"

enum HunterSpells
{
    // Ours
    SPELL_HUNTER_WYVERN_STING_DOT                   = 24131,

    // Theirs
    SPELL_HUNTER_ASPECT_OF_THE_BEAST                = 13161,
    SPELL_HUNTER_ASPECT_OF_THE_BEAST_PET            = 61669,
    SPELL_HUNTER_ASPECT_OF_THE_VIPER                = 34074,
    SPELL_HUNTER_ASPECT_OF_THE_VIPER_ENERGIZE       = 34075,
    SPELL_HUNTER_BESTIAL_WRATH                      = 19574,
    SPELL_HUNTER_CHIMERA_SHOT_SERPENT               = 53353,
    SPELL_HUNTER_CHIMERA_SHOT_VIPER                 = 53358,
    SPELL_HUNTER_CHIMERA_SHOT_SCORPID               = 53359,
    SPELL_HUNTER_GLYPH_OF_ASPECT_OF_THE_VIPER       = 56851,
    SPELL_HUNTER_IMPROVED_MEND_PET                  = 24406,
    SPELL_HUNTER_INVIGORATION_TRIGGERED             = 53398,
    SPELL_HUNTER_MASTERS_CALL_TRIGGERED             = 62305,
    SPELL_HUNTER_MISDIRECTION_PROC                  = 35079,
    SPELL_HUNTER_PET_LAST_STAND_TRIGGERED           = 53479,
    SPELL_HUNTER_PET_HEART_OF_THE_PHOENIX           = 55709,
    SPELL_HUNTER_PET_HEART_OF_THE_PHOENIX_TRIGGERED = 54114,
    SPELL_HUNTER_PET_HEART_OF_THE_PHOENIX_DEBUFF    = 55711,
    SPELL_HUNTER_PET_CARRION_FEEDER_TRIGGERED       = 54045,
    SPELL_HUNTER_READINESS                          = 23989,
    SPELL_HUNTER_SNIPER_TRAINING_R1                 = 53302,
    SPELL_HUNTER_SNIPER_TRAINING_BUFF_R1            = 64418,
    SPELL_HUNTER_VICIOUS_VIPER                      = 61609,
    SPELL_HUNTER_VIPER_ATTACK_SPEED                 = 60144,
    SPELL_DRAENEI_GIFT_OF_THE_NAARU                 = 59543,
    SPELL_HUNTER_GLYPH_OF_ARCANE_SHOT               = 61389,
    SPELL_LOCK_AND_LOAD_TRIGGER                     = 56453,
    SPELL_LOCK_AND_LOAD_MARKER                      = 67544
};

// Ours
class spell_hun_check_pet_los : public SpellScriptLoader
{
public:
    spell_hun_check_pet_los() : SpellScriptLoader("spell_hun_check_pet_los") {}

    class spell_hun_check_pet_los_SpellScript : public SpellScript
    {
        PrepareSpellScript(spell_hun_check_pet_los_SpellScript);

        SpellCastResult CheckCast()
        {
            Unit* pet = GetCaster()->GetGuardianPet();
            if (!pet)
                pet = GetCaster()->GetCharm();

            if (!pet)
                return SPELL_FAILED_NO_PET;

            if (!pet->IsAlive())
            {
                SetCustomCastResultMessage(SPELL_CUSTOM_ERROR_PET_IS_DEAD);
                return SPELL_FAILED_CUSTOM_ERROR;
            }

            if (!GetCaster()->IsWithinLOSInMap(pet))
                return SPELL_FAILED_LINE_OF_SIGHT;

            return SPELL_CAST_OK;
        }

        void Register() override
        {
            OnCheckCast += SpellCheckCastFn(spell_hun_check_pet_los_SpellScript::CheckCast);
        }
    };

    SpellScript* GetSpellScript() const override
    {
        return new spell_hun_check_pet_los_SpellScript();
    }
};

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

    class spell_hun_cower_AuraScript : public AuraScript
    {
        PrepareAuraScript(spell_hun_cower_AuraScript);

        void CalculateAmount(AuraEffect const* /*aurEff*/, int32& amount, bool& /*canBeRecalculated*/)
        {
            if (AuraEffect* aurEff = GetUnitOwner()->GetAuraEffect(SPELL_AURA_DUMMY, SPELLFAMILY_PET, GetSpellInfo()->SpellIconID, EFFECT_0))
                AddPct(amount, aurEff->GetAmount());
        }

        void Register() override
        {
            DoEffectCalcAmount += AuraEffectCalcAmountFn(spell_hun_cower_AuraScript::CalculateAmount, EFFECT_1, SPELL_AURA_MOD_DECREASE_SPEED);
        }
    };

    AuraScript* GetAuraScript() const override
    {
        return new spell_hun_cower_AuraScript();
    }
};

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

    class spell_hun_wyvern_sting_AuraScript : public AuraScript
    {
        PrepareAuraScript(spell_hun_wyvern_sting_AuraScript)

        void HandleEffectRemove(AuraEffect const*  /*aurEff*/, AuraEffectHandleModes /*mode*/)
        {
            if (Unit* caster = GetCaster())
                caster->CastSpell(GetTarget(), sSpellMgr->GetSpellWithRank(SPELL_HUNTER_WYVERN_STING_DOT, GetSpellInfo()->GetRank()), true);
        }

        void Register() override
        {
            AfterEffectRemove += AuraEffectRemoveFn(spell_hun_wyvern_sting_AuraScript::HandleEffectRemove, EFFECT_0, SPELL_AURA_MOD_STUN, AURA_EFFECT_HANDLE_REAL);
        }
    };

    AuraScript* GetAuraScript() const override
    {
        return new spell_hun_wyvern_sting_AuraScript();
    }
};

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

    class spell_hun_animal_handler_AuraScript : public AuraScript
    {
        PrepareAuraScript(spell_hun_animal_handler_AuraScript);

        void CalculateAmount(AuraEffect const*  /*aurEff*/, int32& amount, bool& /*canBeRecalculated*/)
        {
            amount = 0;
            if (Unit* owner = GetUnitOwner()->GetOwner())
                if (AuraEffect const* animalHandlerEff = owner->GetDummyAuraEffect(SPELLFAMILY_HUNTER, 2234, EFFECT_1))
                    amount = animalHandlerEff->GetAmount();
        }

        void Register() override
        {
            DoEffectCalcAmount += AuraEffectCalcAmountFn(spell_hun_animal_handler_AuraScript::CalculateAmount, EFFECT_0, SPELL_AURA_MOD_ATTACK_POWER_PCT);
        }
    };

    AuraScript* GetAuraScript() const override
    {
        return new spell_hun_animal_handler_AuraScript();
    }
};

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

    class spell_hun_generic_scaling_AuraScript : public AuraScript
    {
        PrepareAuraScript(spell_hun_generic_scaling_AuraScript);

        void CalculateResistanceAmount(AuraEffect const* aurEff, int32& amount, bool& /*canBeRecalculated*/)
        {
            // xinef: pet inherits 40% of resistance from owner and 35% of armor
            if (Unit* owner = GetUnitOwner()->GetOwner())
            {
                SpellSchoolMask schoolMask = SpellSchoolMask(aurEff->GetSpellInfo()->Effects[aurEff->GetEffIndex()].MiscValue);
                int32 modifier = schoolMask == SPELL_SCHOOL_MASK_NORMAL ? 35 : 40;
                amount = CalculatePct(std::max<int32>(0, owner->GetResistance(schoolMask)), modifier);
            }
        }

        void CalculateStatAmount(AuraEffect const* aurEff, int32& amount, bool& /*canBeRecalculated*/)
        {
            if (Unit* owner = GetUnitOwner()->GetOwner())
            {
                // xinef: by default pet inherits 45% of stamina
                int32 modifier = 45;

                // xinef: Wild Hunt bonus for stamina
                if  (AuraEffect* wildHuntEff = GetUnitOwner()->GetDummyAuraEffect(SPELLFAMILY_PET, 3748, EFFECT_0))
                    AddPct(modifier, wildHuntEff->GetAmount());

                amount = CalculatePct(std::max<int32>(0, owner->GetStat(Stats(aurEff->GetSpellInfo()->Effects[aurEff->GetEffIndex()].MiscValue))), modifier);
            }
        }

        void CalculateAPAmount(AuraEffect const*  /*aurEff*/, int32& amount, bool& /*canBeRecalculated*/)
        {
            if (Unit* owner = GetUnitOwner()->GetOwner())
            {
                // xinef: by default 22% of RAP
                int32 modifier = 22;

                // xinef: Wild Hunt bonus for AP
                if (AuraEffect* wildHuntEff = GetUnitOwner()->GetDummyAuraEffect(SPELLFAMILY_PET, 3748, EFFECT_1))
                    AddPct(modifier, wildHuntEff->GetAmount());

                float ownerAP = owner->GetTotalAttackPowerValue(RANGED_ATTACK);

                // Xinef: Hunter vs. Wild
                if (AuraEffect* HvWEff = owner->GetAuraEffect(SPELL_AURA_MOD_ATTACK_POWER_OF_STAT_PERCENT, SPELLFAMILY_HUNTER, 3647, EFFECT_0))
                    ownerAP += CalculatePct(owner->GetStat(STAT_STAMINA), HvWEff->GetAmount());

                amount = CalculatePct(std::max<int32>(0, ownerAP), modifier);
            }
        }

        void CalculateSPAmount(AuraEffect const*  /*aurEff*/, int32& amount, bool& /*canBeRecalculated*/)
        {
            if (Unit* owner = GetUnitOwner()->GetOwner())
            {
                // xinef: by default 12.87% of RAP
                float modifier = 12.87f;

                // xinef: Wild Hunt bonus for AP
                if (AuraEffect* wildHuntEff = GetUnitOwner()->GetDummyAuraEffect(SPELLFAMILY_PET, 3748, EFFECT_1))
                    AddPct(modifier, wildHuntEff->GetAmount());

                amount = CalculatePct(std::max<int32>(0, owner->GetTotalAttackPowerValue(RANGED_ATTACK)), modifier);

                // xinef: Update appropriate player field
                if (owner->GetTypeId() == TYPEID_PLAYER)
                    owner->SetUInt32Value(PLAYER_PET_SPELL_POWER, (uint32)amount);
            }
        }

        void CalcPeriodic(AuraEffect const* /*aurEff*/, bool& isPeriodic, int32& amplitude)
        {
            isPeriodic = true;
            amplitude = 2 * IN_MILLISECONDS;
        }

        void HandlePeriodic(AuraEffect const* aurEff)
        {
            PreventDefaultAction();
            if (aurEff->GetAuraType() == SPELL_AURA_MOD_STAT && (aurEff->GetMiscValue() == STAT_STAMINA || aurEff->GetMiscValue() == STAT_INTELLECT))
            {
                int32 currentAmount = aurEff->GetAmount();
                int32 newAmount = GetEffect(aurEff->GetEffIndex())->CalculateAmount(GetCaster());
                if (newAmount != currentAmount)
                {
                    if (aurEff->GetMiscValue() == STAT_STAMINA)
                    {
                        uint32 actStat = GetUnitOwner()->GetHealth();
                        GetEffect(aurEff->GetEffIndex())->ChangeAmount(newAmount, false);
                        GetUnitOwner()->SetHealth(std::min<uint32>(GetUnitOwner()->GetMaxHealth(), actStat));
                    }
                    else
                    {
                        uint32 actStat = GetUnitOwner()->GetPower(POWER_MANA);
                        GetEffect(aurEff->GetEffIndex())->ChangeAmount(newAmount, false);
                        GetUnitOwner()->SetPower(POWER_MANA, std::min<uint32>(GetUnitOwner()->GetMaxPower(POWER_MANA), actStat));
                    }
                }
            }
            else
                GetEffect(aurEff->GetEffIndex())->RecalculateAmount();
        }

        void Register() override
        {
            if (m_scriptSpellId != 34902)
                DoEffectCalcAmount += AuraEffectCalcAmountFn(spell_hun_generic_scaling_AuraScript::CalculateResistanceAmount, EFFECT_ALL, SPELL_AURA_MOD_RESISTANCE);
            else
            {
                DoEffectCalcAmount += AuraEffectCalcAmountFn(spell_hun_generic_scaling_AuraScript::CalculateStatAmount, EFFECT_ALL, SPELL_AURA_MOD_STAT);
                DoEffectCalcAmount += AuraEffectCalcAmountFn(spell_hun_generic_scaling_AuraScript::CalculateAPAmount, EFFECT_ALL, SPELL_AURA_MOD_ATTACK_POWER);
                DoEffectCalcAmount += AuraEffectCalcAmountFn(spell_hun_generic_scaling_AuraScript::CalculateSPAmount, EFFECT_ALL, SPELL_AURA_MOD_DAMAGE_DONE);
            }

            DoEffectCalcPeriodic += AuraEffectCalcPeriodicFn(spell_hun_generic_scaling_AuraScript::CalcPeriodic, EFFECT_ALL, SPELL_AURA_ANY);
            OnEffectPeriodic += AuraEffectPeriodicFn(spell_hun_generic_scaling_AuraScript::HandlePeriodic, EFFECT_ALL, SPELL_AURA_ANY);
        }
    };

    AuraScript* GetAuraScript() const override
    {
        return new spell_hun_generic_scaling_AuraScript();
    }
};

// Taming the Beast quests (despawn creature after dismiss)
class spell_hun_taming_the_beast : public SpellScriptLoader
{
public:
    spell_hun_taming_the_beast() : SpellScriptLoader("spell_hun_taming_the_beast") { }

    class spell_hun_taming_the_beast_AuraScript : public AuraScript
    {
        PrepareAuraScript(spell_hun_taming_the_beast_AuraScript);

        void HandleOnEffectRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
        {
            if (Unit* target = GetTarget())
                if (Creature* creature = target->ToCreature())
                    creature->DespawnOrUnsummon(1);
        }

        void Register() override
        {
            OnEffectRemove += AuraEffectRemoveFn(spell_hun_taming_the_beast_AuraScript::HandleOnEffectRemove, EFFECT_0, SPELL_AURA_MOD_CHARM, AURA_EFFECT_HANDLE_REAL);
        }
    };

    AuraScript* GetAuraScript() const override
    {
        return new spell_hun_taming_the_beast_AuraScript();
    }
};

// Theirs
// 13161 Aspect of the Beast
class spell_hun_aspect_of_the_beast : public SpellScriptLoader
{
public:
    spell_hun_aspect_of_the_beast() : SpellScriptLoader("spell_hun_aspect_of_the_beast") { }

    class spell_hun_aspect_of_the_beast_AuraScript : public AuraScript
    {
        PrepareAuraScript(spell_hun_aspect_of_the_beast_AuraScript);

        bool Load() override
        {
            return GetCaster() && GetCaster()->GetTypeId() == TYPEID_PLAYER;
        }

        bool Validate(SpellInfo const* /*spellInfo*/) override
        {
            return ValidateSpellInfo({ SPELL_HUNTER_ASPECT_OF_THE_BEAST_PET });
        }

        void OnRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
        {
            if (GetCaster())
                if (Player* caster = GetCaster()->ToPlayer())
                    if (Pet* pet = caster->GetPet())
                        pet->RemoveAurasDueToSpell(SPELL_HUNTER_ASPECT_OF_THE_BEAST_PET);
        }

        void OnApply(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
        {
            if (GetCaster())
                if (Player* caster = GetCaster()->ToPlayer())
                    if (caster->GetPet())
                        caster->CastSpell(caster, SPELL_HUNTER_ASPECT_OF_THE_BEAST_PET, true);
        }

        void OnPetApply(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
        {
            Unit* pet = GetUnitOwner();
            if (Unit* owner = pet->GetOwner())
                if (owner->HasAura(SPELL_HUNTER_ASPECT_OF_THE_BEAST))
                    return;

            SetDuration(0);
        }

        void Register() override
        {
            if (m_scriptSpellId == 13161)
            {
                AfterEffectApply += AuraEffectApplyFn(spell_hun_aspect_of_the_beast_AuraScript::OnApply, EFFECT_0, SPELL_AURA_UNTRACKABLE, AURA_EFFECT_HANDLE_REAL);
                AfterEffectRemove += AuraEffectRemoveFn(spell_hun_aspect_of_the_beast_AuraScript::OnRemove, EFFECT_0, SPELL_AURA_UNTRACKABLE, AURA_EFFECT_HANDLE_REAL);
            }
            else
                AfterEffectApply += AuraEffectApplyFn(spell_hun_aspect_of_the_beast_AuraScript::OnPetApply, EFFECT_0, SPELL_AURA_UNTRACKABLE, AURA_EFFECT_HANDLE_REAL);
        }
    };

    AuraScript* GetAuraScript() const override
    {
        return new spell_hun_aspect_of_the_beast_AuraScript();
    }
};

// Theirs
// 34074 - Aspect of the Viper
class spell_hun_ascpect_of_the_viper : public SpellScriptLoader
{
public:
    spell_hun_ascpect_of_the_viper() : SpellScriptLoader("spell_hun_ascpect_of_the_viper") { }

    class spell_hun_ascpect_of_the_viper_AuraScript : public AuraScript
    {
        PrepareAuraScript(spell_hun_ascpect_of_the_viper_AuraScript);

        bool Validate(SpellInfo const* /*spellInfo*/) override
        {
            return ValidateSpellInfo(
                {
                    SPELL_HUNTER_ASPECT_OF_THE_VIPER_ENERGIZE,
                    SPELL_HUNTER_GLYPH_OF_ASPECT_OF_THE_VIPER,
                    SPELL_HUNTER_VIPER_ATTACK_SPEED,
                    SPELL_HUNTER_VICIOUS_VIPER
                });
        }

        bool CheckProc(ProcEventInfo& procInfo)
        {
            SpellInfo const* spellInfo = procInfo.GetSpellInfo();
            // Xinef: cannot proc from volley damage
            if (spellInfo && (spellInfo->SpellFamilyFlags[0] & 0x2000) && spellInfo->Effects[EFFECT_0].Effect == SPELL_EFFECT_SCHOOL_DAMAGE)
                return false;
            return true;
        }

        void HandleProc(AuraEffect const* aurEff, ProcEventInfo&  /*eventInfo*/)
        {
            PreventDefaultAction();

            uint32 maxMana = GetTarget()->GetMaxPower(POWER_MANA);
            int32 mana = CalculatePct(maxMana, GetTarget()->GetAttackTime(RANGED_ATTACK) / 1000.0f);

            if (AuraEffect const* glyph = GetTarget()->GetAuraEffect(SPELL_HUNTER_GLYPH_OF_ASPECT_OF_THE_VIPER, EFFECT_0))
                AddPct(mana, glyph->GetAmount());

            GetTarget()->CastCustomSpell(SPELL_HUNTER_ASPECT_OF_THE_VIPER_ENERGIZE, SPELLVALUE_BASE_POINT0, mana, GetTarget(), true, nullptr, aurEff);
        }

        void OnApply(AuraEffect const* aurEff, AuraEffectHandleModes /*mode*/)
        {
            // Hunter T7 4P Bonus
            if (GetTarget()->HasAura(SPELL_HUNTER_VIPER_ATTACK_SPEED))
                GetTarget()->CastSpell(GetTarget(), SPELL_HUNTER_VICIOUS_VIPER, true, nullptr, aurEff);
        }

        void OnRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
        {
            // Hunter T7 4P Bonus
            if (GetTarget()->HasAura(SPELL_HUNTER_VIPER_ATTACK_SPEED))
                GetTarget()->RemoveAurasDueToSpell(SPELL_HUNTER_VICIOUS_VIPER);
        }

        void Register() override
        {
            DoCheckProc += AuraCheckProcFn(spell_hun_ascpect_of_the_viper_AuraScript::CheckProc);
            OnEffectProc += AuraEffectProcFn(spell_hun_ascpect_of_the_viper_AuraScript::HandleProc, EFFECT_0, SPELL_AURA_OBS_MOD_POWER);
            AfterEffectApply += AuraEffectApplyFn(spell_hun_ascpect_of_the_viper_AuraScript::OnApply, EFFECT_0, SPELL_AURA_OBS_MOD_POWER, AURA_EFFECT_HANDLE_REAL);
            AfterEffectRemove += AuraEffectRemoveFn(spell_hun_ascpect_of_the_viper_AuraScript::OnRemove, EFFECT_0, SPELL_AURA_OBS_MOD_POWER, AURA_EFFECT_HANDLE_REAL);
        }
    };

    AuraScript* GetAuraScript() const override
    {
        return new spell_hun_ascpect_of_the_viper_AuraScript();
    }
};

// 53209 Chimera Shot
class spell_hun_chimera_shot : public SpellScriptLoader
{
public:
    spell_hun_chimera_shot() : SpellScriptLoader("spell_hun_chimera_shot") { }

    class spell_hun_chimera_shot_SpellScript : public SpellScript
    {
        PrepareSpellScript(spell_hun_chimera_shot_SpellScript);

        bool Validate(SpellInfo const* /*spellInfo*/) override
        {
            return ValidateSpellInfo({ SPELL_HUNTER_CHIMERA_SHOT_SERPENT, SPELL_HUNTER_CHIMERA_SHOT_VIPER, SPELL_HUNTER_CHIMERA_SHOT_SCORPID });
        }

        void HandleScriptEffect(SpellEffIndex /*effIndex*/)
        {
            Unit* caster = GetCaster();
            if (Unit* unitTarget = GetHitUnit())
            {
                uint32 spellId = 0;
                int32 basePoint = 0;
                Unit::AuraApplicationMap& Auras = unitTarget->GetAppliedAuras();
                for (Unit::AuraApplicationMap::iterator i = Auras.begin(); i != Auras.end(); ++i)
                {
                    Aura* aura = i->second->GetBase();
                    if (aura->GetCasterGUID() != caster->GetGUID())
                        continue;

                    // Search only Serpent Sting, Viper Sting, Scorpid Sting auras
                    flag96 familyFlag = aura->GetSpellInfo()->SpellFamilyFlags;
                    if (!(familyFlag[1] & 0x00000080 || familyFlag[0] & 0x0000C000))
                        continue;
                    if (AuraEffect* aurEff = aura->GetEffect(0))
                    {
                        // Serpent Sting - Instantly deals 40% of the damage done by your Serpent Sting.
                        if (familyFlag[0] & 0x4000)
                        {
                            int32 TickCount = aurEff->GetTotalTicks();
                            spellId = SPELL_HUNTER_CHIMERA_SHOT_SERPENT;
                            basePoint = aurEff->GetAmount();
                            ApplyPct(basePoint, TickCount * 40);
                            basePoint = unitTarget->SpellDamageBonusTaken(caster, aura->GetSpellInfo(), basePoint, DOT, aura->GetStackAmount());
                        }
                        // Viper Sting - Instantly restores mana to you equal to 60% of the total amount drained by your Viper Sting.
                        else if (familyFlag[1] & 0x00000080)
                        {
                            int32 TickCount = aura->GetEffect(0)->GetTotalTicks();
                            spellId = SPELL_HUNTER_CHIMERA_SHOT_VIPER;

                            // Amount of one aura tick
                            basePoint = int32(CalculatePct(unitTarget->GetMaxPower(POWER_MANA), aurEff->GetAmount()));
                            int32 casterBasePoint = aurEff->GetAmount() * unitTarget->GetMaxPower(POWER_MANA) / 50; // TODO: WTF? caster uses unitTarget?
                            if (basePoint > casterBasePoint)
                                basePoint = casterBasePoint;
                            ApplyPct(basePoint, TickCount * 60);
                        }
                        // Scorpid Sting - Attempts to Disarm the target for 10 sec. This effect cannot occur more than once per 1 minute.
                        else if (familyFlag[0] & 0x00008000)
                        {
                            if (caster->ToPlayer()) // Scorpid Sting - Add 1 minute cooldown
                            {
                                if (caster->ToPlayer()->HasSpellCooldown(SPELL_HUNTER_CHIMERA_SHOT_SCORPID))
                                    break;

                                caster->ToPlayer()->AddSpellCooldown(SPELL_HUNTER_CHIMERA_SHOT_SCORPID, 0, 60000);
                            }

                            spellId = SPELL_HUNTER_CHIMERA_SHOT_SCORPID;
                        }

                        // Refresh aura duration
                        aura->RefreshDuration();
                        aurEff->ChangeAmount(aurEff->CalculateAmount(caster), false);
                    }
                    break;
                }

                if (spellId)
                    caster->CastCustomSpell(unitTarget, spellId, &basePoint, 0, 0, true);
            }
        }

        void Register() override
        {
            OnEffectHitTarget += SpellEffectFn(spell_hun_chimera_shot_SpellScript::HandleScriptEffect, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT);
        }
    };

    SpellScript* GetSpellScript() const override
    {
        return new spell_hun_chimera_shot_SpellScript();
    }
};

// -19572 - Improved Mend Pet
class spell_hun_improved_mend_pet : public SpellScriptLoader
{
public:
    spell_hun_improved_mend_pet() : SpellScriptLoader("spell_hun_improved_mend_pet") { }

    class spell_hun_improved_mend_pet_AuraScript : public AuraScript
    {
        PrepareAuraScript(spell_hun_improved_mend_pet_AuraScript);

        bool Validate(SpellInfo const* /*spellInfo*/) override
        {
            return ValidateSpellInfo({ SPELL_HUNTER_IMPROVED_MEND_PET });
        }

        bool CheckProc(ProcEventInfo& /*eventInfo*/)
        {
            return roll_chance_i(GetEffect(EFFECT_0)->GetAmount());
        }

        void HandleProc(AuraEffect const* aurEff, ProcEventInfo& /*eventInfo*/)
        {
            PreventDefaultAction();
            GetTarget()->CastSpell(GetTarget(), SPELL_HUNTER_IMPROVED_MEND_PET, true, nullptr, aurEff);
        }

        void Register() override
        {
            DoCheckProc += AuraCheckProcFn(spell_hun_improved_mend_pet_AuraScript::CheckProc);
            OnEffectProc += AuraEffectProcFn(spell_hun_improved_mend_pet_AuraScript::HandleProc, EFFECT_0, SPELL_AURA_DUMMY);
        }
    };

    AuraScript* GetAuraScript() const override
    {
        return new spell_hun_improved_mend_pet_AuraScript();
    }
};
// 53412 - Invigoration
class spell_hun_invigoration : public SpellScriptLoader
{
public:
    spell_hun_invigoration() : SpellScriptLoader("spell_hun_invigoration") { }

    class spell_hun_invigoration_SpellScript : public SpellScript
    {
        PrepareSpellScript(spell_hun_invigoration_SpellScript);

        bool Validate(SpellInfo const* /*spellInfo*/) override
        {
            return ValidateSpellInfo({ SPELL_HUNTER_INVIGORATION_TRIGGERED });
        }

        void HandleScriptEffect(SpellEffIndex /*effIndex*/)
        {
            if (Unit* unitTarget = GetHitUnit())
                if (AuraEffect* aurEff = unitTarget->GetDummyAuraEffect(SPELLFAMILY_HUNTER, 3487, 0))
                    if (roll_chance_i(aurEff->GetAmount()))
                        unitTarget->CastSpell(unitTarget, SPELL_HUNTER_INVIGORATION_TRIGGERED, true);
        }

        void Register() override
        {
            OnEffectHitTarget += SpellEffectFn(spell_hun_invigoration_SpellScript::HandleScriptEffect, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT);
        }
    };

    SpellScript* GetSpellScript() const override
    {
        return new spell_hun_invigoration_SpellScript();
    }
};

// 53478 - Last Stand Pet
class spell_hun_last_stand_pet : public SpellScriptLoader
{
public:
    spell_hun_last_stand_pet() : SpellScriptLoader("spell_hun_last_stand_pet") { }

    class spell_hun_last_stand_pet_SpellScript : public SpellScript
    {
        PrepareSpellScript(spell_hun_last_stand_pet_SpellScript);

        bool Validate(SpellInfo const* /*spellInfo*/) override
        {
            return ValidateSpellInfo({ SPELL_HUNTER_PET_LAST_STAND_TRIGGERED });
        }

        void HandleDummy(SpellEffIndex /*effIndex*/)
        {
            Unit* caster = GetCaster();
            int32 healthModSpellBasePoints0 = int32(caster->CountPctFromMaxHealth(30));
            caster->CastCustomSpell(caster, SPELL_HUNTER_PET_LAST_STAND_TRIGGERED, &healthModSpellBasePoints0, nullptr, nullptr, true, nullptr);
        }

        void Register() override
        {
            OnEffectHitTarget += SpellEffectFn(spell_hun_last_stand_pet_SpellScript::HandleDummy, EFFECT_0, SPELL_EFFECT_DUMMY);
        }
    };

    SpellScript* GetSpellScript() const override
    {
        return new spell_hun_last_stand_pet_SpellScript();
    }
};

// 53271 - Masters Call
class spell_hun_masters_call : public SpellScriptLoader
{
public:
    spell_hun_masters_call() : SpellScriptLoader("spell_hun_masters_call") { }

    class spell_hun_masters_call_SpellScript : public SpellScript
    {
        PrepareSpellScript(spell_hun_masters_call_SpellScript);

        bool Validate(SpellInfo const* /*spellInfo*/) override
        {
            return ValidateSpellInfo({ SPELL_HUNTER_MASTERS_CALL_TRIGGERED });
        }

        SpellCastResult DoCheckCast()
        {
            Pet* pet = GetCaster()->ToPlayer()->GetPet();
            if (!pet || !pet->IsAlive())
                return SPELL_FAILED_NO_PET;

            // Do a mini Spell::CheckCasterAuras on the pet, no other way of doing this
            SpellCastResult result = SPELL_CAST_OK;
            uint32 const unitflag = pet->GetUInt32Value(UNIT_FIELD_FLAGS);
            if (pet->GetCharmerGUID())
                result = SPELL_FAILED_CHARMED;
            else if (unitflag & UNIT_FLAG_STUNNED)
                result = SPELL_FAILED_STUNNED;
            else if (unitflag & UNIT_FLAG_FLEEING)
                result = SPELL_FAILED_FLEEING;
            else if (unitflag & UNIT_FLAG_CONFUSED)
                result = SPELL_FAILED_CONFUSED;

            if (result != SPELL_CAST_OK)
                return result;

            Unit* target = GetExplTargetUnit();
            if (!target)
                return SPELL_FAILED_BAD_TARGETS;

            if (!pet->IsWithinLOSInMap(target))
                return SPELL_FAILED_LINE_OF_SIGHT;

            return SPELL_CAST_OK;
        }

        void HandleDummy(SpellEffIndex /*effIndex*/)
        {
            GetCaster()->ToPlayer()->GetPet()->CastSpell(GetHitUnit(), GetEffectValue(), true);
        }

        void HandleScriptEffect(SpellEffIndex /*effIndex*/)
        {
            GetHitUnit()->CastSpell((Unit*)nullptr, SPELL_HUNTER_MASTERS_CALL_TRIGGERED, true);
        }

        void Register() override
        {
            OnCheckCast += SpellCheckCastFn(spell_hun_masters_call_SpellScript::DoCheckCast);

            OnEffectHitTarget += SpellEffectFn(spell_hun_masters_call_SpellScript::HandleDummy, EFFECT_0, SPELL_EFFECT_DUMMY);
            OnEffectHitTarget += SpellEffectFn(spell_hun_masters_call_SpellScript::HandleScriptEffect, EFFECT_1, SPELL_EFFECT_SCRIPT_EFFECT);
        }
    };

    SpellScript* GetSpellScript() const override
    {
        return new spell_hun_masters_call_SpellScript();
    }
};

// 23989 - Readiness
class spell_hun_readiness : public SpellScriptLoader
{
public:
    spell_hun_readiness() : SpellScriptLoader("spell_hun_readiness") { }

    class spell_hun_readiness_SpellScript : public SpellScript
    {
        PrepareSpellScript(spell_hun_readiness_SpellScript);

        bool Load() override
        {
            return GetCaster()->GetTypeId() == TYPEID_PLAYER;
        }

        void HandleDummy(SpellEffIndex /*effIndex*/)
        {
            Player* caster = GetCaster()->ToPlayer();
            // immediately finishes the cooldown on your other Hunter abilities except Bestial Wrath

            PlayerSpellMap const& spellMap = caster->GetSpellMap();
            for (PlayerSpellMap::const_iterator itr = spellMap.begin(); itr != spellMap.end(); ++itr)
            {
                SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(itr->first);
                if (spellInfo->SpellFamilyName == SPELLFAMILY_HUNTER &&
                        spellInfo->Id != SPELL_HUNTER_READINESS &&
                        spellInfo->Id != SPELL_HUNTER_BESTIAL_WRATH &&
                        spellInfo->Id != SPELL_DRAENEI_GIFT_OF_THE_NAARU &&
                        spellInfo->GetRecoveryTime() > 0)
                {
                    SpellCooldowns::iterator citr = caster->GetSpellCooldownMap().find(spellInfo->Id);
                    if (citr != caster->GetSpellCooldownMap().end() && citr->second.needSendToClient)
                        caster->RemoveSpellCooldown(spellInfo->Id, true);
                    else
                        caster->RemoveSpellCooldown(spellInfo->Id, false);
                }

                // force removal of the disarm cooldown
                caster->RemoveSpellCooldown(SPELL_HUNTER_CHIMERA_SHOT_SCORPID, false);
            }
        }

        void Register() override
        {
            OnEffectHitTarget += SpellEffectFn(spell_hun_readiness_SpellScript::HandleDummy, EFFECT_0, SPELL_EFFECT_DUMMY);
        }
    };

    SpellScript* GetSpellScript() const override
    {
        return new spell_hun_readiness_SpellScript();
    }
};

// 37506 - Scatter Shot
class spell_hun_scatter_shot : public SpellScriptLoader
{
public:
    spell_hun_scatter_shot() : SpellScriptLoader("spell_hun_scatter_shot") { }

    class spell_hun_scatter_shot_SpellScript : public SpellScript
    {
        PrepareSpellScript(spell_hun_scatter_shot_SpellScript);

        bool Load() override
        {
            return GetCaster()->GetTypeId() == TYPEID_PLAYER;
        }

        void HandleDummy(SpellEffIndex /*effIndex*/)
        {
            Player* caster = GetCaster()->ToPlayer();
            // break Auto Shot and autohit
            caster->InterruptSpell(CURRENT_AUTOREPEAT_SPELL);
            caster->AttackStop();
            caster->SendAttackSwingCancelAttack();
        }

        void Register() override
        {
            OnEffectHitTarget += SpellEffectFn(spell_hun_scatter_shot_SpellScript::HandleDummy, EFFECT_0, SPELL_EFFECT_DUMMY);
        }
    };

    SpellScript* GetSpellScript() const override
    {
        return new spell_hun_scatter_shot_SpellScript();
    }
};

// -53302 - Sniper Training
class spell_hun_sniper_training : public SpellScriptLoader
{
public:
    spell_hun_sniper_training() : SpellScriptLoader("spell_hun_sniper_training") { }

    class spell_hun_sniper_training_AuraScript : public AuraScript
    {
        PrepareAuraScript(spell_hun_sniper_training_AuraScript);

        bool Validate(SpellInfo const* /*spellInfo*/) override
        {
            return ValidateSpellInfo({ SPELL_HUNTER_SNIPER_TRAINING_R1, SPELL_HUNTER_SNIPER_TRAINING_BUFF_R1 });
        }

        void HandlePeriodic(AuraEffect const* aurEff)
        {
            PreventDefaultAction();
            if (aurEff->GetAmount() <= 0)
            {
                Unit* caster = GetCaster();
                uint32 spellId = SPELL_HUNTER_SNIPER_TRAINING_BUFF_R1 + GetId() - SPELL_HUNTER_SNIPER_TRAINING_R1;
                if (Unit* target = GetTarget())
                {
                    SpellInfo const* triggeredSpellInfo = sSpellMgr->GetSpellInfo(spellId);
                    Unit* triggerCaster = triggeredSpellInfo->NeedsToBeTriggeredByCaster(GetSpellInfo()) ? caster : target;
                    triggerCaster->CastSpell(target, triggeredSpellInfo, true, 0, aurEff);
                }
            }
        }

        void HandleUpdatePeriodic(AuraEffect* aurEff)
        {
            if (Player* playerTarget = GetUnitOwner()->ToPlayer())
            {
                int32 baseAmount = aurEff->GetBaseAmount();
                int32 amount = playerTarget->isMoving() || aurEff->GetAmount() <= 0 ?
                               playerTarget->CalculateSpellDamage(playerTarget, GetSpellInfo(), aurEff->GetEffIndex(), &baseAmount) :
                               aurEff->GetAmount() - 1;
                aurEff->SetAmount(amount);
            }
        }

        void Register() override
        {
            OnEffectPeriodic += AuraEffectPeriodicFn(spell_hun_sniper_training_AuraScript::HandlePeriodic, EFFECT_0, SPELL_AURA_PERIODIC_TRIGGER_SPELL);
            OnEffectUpdatePeriodic += AuraEffectUpdatePeriodicFn(spell_hun_sniper_training_AuraScript::HandleUpdatePeriodic, EFFECT_0, SPELL_AURA_PERIODIC_TRIGGER_SPELL);
        }
    };

    AuraScript* GetAuraScript() const override
    {
        return new spell_hun_sniper_training_AuraScript();
    }
};

// 55709 - Pet Heart of the Phoenix
class spell_hun_pet_heart_of_the_phoenix : public SpellScriptLoader
{
public:
    spell_hun_pet_heart_of_the_phoenix() : SpellScriptLoader("spell_hun_pet_heart_of_the_phoenix") { }

    class spell_hun_pet_heart_of_the_phoenix_SpellScript : public SpellScript
    {
        PrepareSpellScript(spell_hun_pet_heart_of_the_phoenix_SpellScript);

        bool Load() override
        {
            if (!GetCaster()->IsPet())
                return false;
            return true;
        }

        bool Validate(SpellInfo const* /*spellInfo*/) override
        {
            return ValidateSpellInfo({ SPELL_HUNTER_PET_HEART_OF_THE_PHOENIX_TRIGGERED, SPELL_HUNTER_PET_HEART_OF_THE_PHOENIX_DEBUFF });
        }

        SpellCastResult CheckCast()
        {
            Unit* caster = GetCaster();
            if (caster->IsAlive())
                return SPELL_FAILED_TARGET_NOT_DEAD;
            if (caster->HasAura(SPELL_HUNTER_PET_HEART_OF_THE_PHOENIX_DEBUFF))
                return SPELL_FAILED_CASTER_AURASTATE;
            return SPELL_CAST_OK;
        }

        void HandleScript(SpellEffIndex /*effIndex*/)
        {
            Unit* caster = GetCaster();
            if (Unit* owner = caster->GetOwner())
                if (!caster->HasAura(SPELL_HUNTER_PET_HEART_OF_THE_PHOENIX_DEBUFF))
                {
                    owner->CastCustomSpell(SPELL_HUNTER_PET_HEART_OF_THE_PHOENIX_TRIGGERED, SPELLVALUE_BASE_POINT0, 100, caster, true);
                    caster->CastSpell(caster, SPELL_HUNTER_PET_HEART_OF_THE_PHOENIX_DEBUFF, true);
                }
        }

        void Register() override
        {
            OnCheckCast += SpellCheckCastFn(spell_hun_pet_heart_of_the_phoenix_SpellScript::CheckCast);
            OnEffectHitTarget += SpellEffectFn(spell_hun_pet_heart_of_the_phoenix_SpellScript::HandleScript, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT);
        }
    };

    SpellScript* GetSpellScript() const override
    {
        return new spell_hun_pet_heart_of_the_phoenix_SpellScript();
    }
};

// 54044 - Pet Carrion Feeder
class spell_hun_pet_carrion_feeder : public SpellScriptLoader
{
public:
    spell_hun_pet_carrion_feeder() : SpellScriptLoader("spell_hun_pet_carrion_feeder") { }

    class spell_hun_pet_carrion_feeder_SpellScript : public SpellScript
    {
        PrepareSpellScript(spell_hun_pet_carrion_feeder_SpellScript);

        bool Load() override
        {
            if (!GetCaster()->IsPet())
                return false;
            return true;
        }

        bool Validate(SpellInfo const* /*spellInfo*/) override
        {
            return ValidateSpellInfo({ SPELL_HUNTER_PET_CARRION_FEEDER_TRIGGERED });
        }

        SpellCastResult CheckIfCorpseNear()
        {
            Unit* caster = GetCaster();
            float max_range = GetSpellInfo()->GetMaxRange(false);
            WorldObject* result = nullptr;
            // search for nearby enemy corpse in range
            Acore::AnyDeadUnitSpellTargetInRangeCheck check(caster, max_range, GetSpellInfo(), TARGET_CHECK_ENEMY);
            Acore::WorldObjectSearcher<Acore::AnyDeadUnitSpellTargetInRangeCheck> searcher(caster, result, check);
            Cell::VisitWorldObjects(caster, searcher, max_range);
            if (!result)
            {
                Cell::VisitGridObjects(caster, searcher, max_range);
            }
            if (!result)
            {
                return SPELL_FAILED_NO_EDIBLE_CORPSES;
            }
            return SPELL_CAST_OK;
        }

        void HandleDummy(SpellEffIndex /*effIndex*/)
        {
            Unit* caster = GetCaster();
            caster->CastSpell(caster, SPELL_HUNTER_PET_CARRION_FEEDER_TRIGGERED, false);
        }

        void Register() override
        {
            OnEffectHit += SpellEffectFn(spell_hun_pet_carrion_feeder_SpellScript::HandleDummy, EFFECT_0, SPELL_EFFECT_DUMMY);
            OnCheckCast += SpellCheckCastFn(spell_hun_pet_carrion_feeder_SpellScript::CheckIfCorpseNear);
        }
    };

    SpellScript* GetSpellScript() const override
    {
        return new spell_hun_pet_carrion_feeder_SpellScript();
    }
};

// 34477 - Misdirection
class spell_hun_misdirection : public SpellScriptLoader
{
public:
    spell_hun_misdirection() : SpellScriptLoader("spell_hun_misdirection") { }

    class spell_hun_misdirection_AuraScript : public AuraScript
    {
        PrepareAuraScript(spell_hun_misdirection_AuraScript);

        bool Validate(SpellInfo const* /*spellInfo*/) override
        {
            return ValidateSpellInfo({ SPELL_HUNTER_MISDIRECTION_PROC });
        }

        void OnRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
        {
            if (GetTargetApplication()->GetRemoveMode() != AURA_REMOVE_BY_DEFAULT || !GetTarget()->HasAura(SPELL_HUNTER_MISDIRECTION_PROC))
                GetTarget()->ResetRedirectThreat();
        }

        bool CheckProc(ProcEventInfo& /*eventInfo*/)
        {
            return GetTarget()->GetRedirectThreatTarget();
        }

        void HandleProc(AuraEffect const* aurEff, ProcEventInfo& /*eventInfo*/)
        {
            PreventDefaultAction();
            GetTarget()->CastSpell(GetTarget(), SPELL_HUNTER_MISDIRECTION_PROC, true, nullptr, aurEff);
        }

        void Register() override
        {
            AfterEffectRemove += AuraEffectRemoveFn(spell_hun_misdirection_AuraScript::OnRemove, EFFECT_1, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL);
            DoCheckProc += AuraCheckProcFn(spell_hun_misdirection_AuraScript::CheckProc);
            OnEffectProc += AuraEffectProcFn(spell_hun_misdirection_AuraScript::HandleProc, EFFECT_1, SPELL_AURA_DUMMY);
        }
    };

    AuraScript* GetAuraScript() const override
    {
        return new spell_hun_misdirection_AuraScript();
    }
};

// 35079 - Misdirection (Proc)
class spell_hun_misdirection_proc : public SpellScriptLoader
{
public:
    spell_hun_misdirection_proc() : SpellScriptLoader("spell_hun_misdirection_proc") { }

    class spell_hun_misdirection_proc_AuraScript : public AuraScript
    {
        PrepareAuraScript(spell_hun_misdirection_proc_AuraScript);

        void OnRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
        {
            GetTarget()->ResetRedirectThreat();
        }

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

    AuraScript* GetAuraScript() const override
    {
        return new spell_hun_misdirection_proc_AuraScript();
    }
};

// 781 - Disengage
class spell_hun_disengage : public SpellScriptLoader
{
public:
    spell_hun_disengage() : SpellScriptLoader("spell_hun_disengage") { }

    class spell_hun_disengage_SpellScript : public SpellScript
    {
        PrepareSpellScript(spell_hun_disengage_SpellScript);

        SpellCastResult CheckCast()
        {
            Unit* caster = GetCaster();
            if (caster->GetTypeId() == TYPEID_PLAYER && !caster->IsInCombat())
                return SPELL_FAILED_CANT_DO_THAT_RIGHT_NOW;

            return SPELL_CAST_OK;
        }

        void Register() override
        {
            OnCheckCast += SpellCheckCastFn(spell_hun_disengage_SpellScript::CheckCast);
        }
    };

    SpellScript* GetSpellScript() const override
    {
        return new spell_hun_disengage_SpellScript();
    }
};

// 1515 - Tame Beast
class spell_hun_tame_beast : public SpellScriptLoader
{
public:
    spell_hun_tame_beast() : SpellScriptLoader("spell_hun_tame_beast") { }

    class spell_hun_tame_beast_SpellScript : public SpellScript
    {
        PrepareSpellScript(spell_hun_tame_beast_SpellScript);

        SpellCastResult CheckCast()
        {
            Unit* caster = GetCaster();
            if (caster->GetTypeId() != TYPEID_PLAYER)
                return SPELL_FAILED_DONT_REPORT;

            Player* player = GetCaster()->ToPlayer();

            if (!GetExplTargetUnit())
            {
                player->SendTameFailure(PET_TAME_INVALID_CREATURE);
                return SPELL_FAILED_DONT_REPORT;
            }

            if (Creature* target = GetExplTargetUnit()->ToCreature())
            {
                if (target->getLevel() > player->getLevel())
                {
                    player->SendTameFailure(PET_TAME_TOO_HIGHLEVEL);
                    return SPELL_FAILED_DONT_REPORT;
                }

                if (target->GetCreatureTemplate()->IsExotic() && !player->CanTameExoticPets())
                {
                    player->SendTameFailure(PET_TAME_CANT_CONTROL_EXOTIC);
                    return SPELL_FAILED_DONT_REPORT;
                }

                if (!target->GetCreatureTemplate()->IsTameable(player->CanTameExoticPets()))
                {
                    player->SendTameFailure(PET_TAME_NOT_TAMEABLE);
                    return SPELL_FAILED_DONT_REPORT;
                }

                if (caster->GetPetGUID() || player->GetTemporaryUnsummonedPetNumber() || player->IsPetDismissed() || player->GetCharmGUID())
                {
                    player->SendTameFailure(PET_TAME_ANOTHER_SUMMON_ACTIVE);
                    return SPELL_FAILED_DONT_REPORT;
                }
            }
            else
            {
                player->SendTameFailure(PET_TAME_INVALID_CREATURE);
                return SPELL_FAILED_DONT_REPORT;
            }

            return SPELL_CAST_OK;
        }

        void Register() override
        {
            OnCheckCast += SpellCheckCastFn(spell_hun_tame_beast_SpellScript::CheckCast);
        }
    };

    SpellScript* GetSpellScript() const override
    {
        return new spell_hun_tame_beast_SpellScript();
    }
};

// 60144 - Viper Attack Speed
class spell_hun_viper_attack_speed : public SpellScriptLoader
{
public:
    spell_hun_viper_attack_speed() : SpellScriptLoader("spell_hun_viper_attack_speed") { }

    class spell_hun_viper_attack_speed_AuraScript : public AuraScript
    {
        PrepareAuraScript(spell_hun_viper_attack_speed_AuraScript);

        bool Validate(SpellInfo const* /*spellInfo*/) override
        {
            return ValidateSpellInfo({ SPELL_HUNTER_ASPECT_OF_THE_VIPER, SPELL_HUNTER_VICIOUS_VIPER });
        }

        void OnApply(AuraEffect const* aurEff, AuraEffectHandleModes /*mode*/)
        {
            if (GetTarget()->HasAura(SPELL_HUNTER_ASPECT_OF_THE_VIPER))
                GetTarget()->CastSpell(GetTarget(), SPELL_HUNTER_VICIOUS_VIPER, true, nullptr, aurEff);
        }

        void OnRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
        {
            // possible exploit
            GetTarget()->RemoveAurasDueToSpell(SPELL_HUNTER_VICIOUS_VIPER);
        }

        void Register() override
        {
            AfterEffectApply += AuraEffectApplyFn(spell_hun_viper_attack_speed_AuraScript::OnApply, EFFECT_0, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL);
            AfterEffectRemove += AuraEffectRemoveFn(spell_hun_viper_attack_speed_AuraScript::OnRemove, EFFECT_0, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL);
        }
    };

    AuraScript* GetAuraScript() const override
    {
        return new spell_hun_viper_attack_speed_AuraScript();
    }
};

// 56841 - Glyph of Arcane Shot
class spell_hun_glyph_of_arcane_shot : public SpellScriptLoader
{
public:
    spell_hun_glyph_of_arcane_shot() : SpellScriptLoader("spell_hun_glyph_of_arcane_shot") {}

    class spell_hun_glyph_of_arcane_shot_AuraScript : public AuraScript
    {
        PrepareAuraScript(spell_hun_glyph_of_arcane_shot_AuraScript);

        bool Validate(SpellInfo const* /*spellInfo*/) override
        {
            return ValidateSpellInfo({ SPELL_HUNTER_GLYPH_OF_ARCANE_SHOT });
        }

        bool CheckProc(ProcEventInfo& eventInfo)
        {
            if (Unit* procTarget = eventInfo.GetProcTarget())
            {
                // Find Serpent Sting, Viper Sting, Scorpid Sting, Wyvern Sting
                const auto found = std::find_if(std::begin(procTarget->GetAppliedAuras()), std::end(procTarget->GetAppliedAuras()),
                    [&](std::pair<uint32, AuraApplication*> pair)
                    {
                        Aura const* aura = pair.second->GetBase();
                        return ((aura->GetCasterGUID() == GetTarget()->GetGUID())
                            && aura->GetSpellInfo()->SpellFamilyName == SPELLFAMILY_HUNTER
                            && aura->GetSpellInfo()->SpellFamilyFlags.HasFlag(0xC000, 0x1080));
                    });

                if (found != std::end(procTarget->GetAppliedAuras()))
                {
                    return true;
                }
            }
            return false;
        }

        void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo)
        {
            PreventDefaultAction();
            SpellInfo const* procSpell = eventInfo.GetSpellInfo();
            if (!procSpell)
            {
                return;
            }

            int32 mana = procSpell->CalcPowerCost(GetTarget(), procSpell->GetSchoolMask());
            ApplyPct(mana, aurEff->GetAmount());

            GetTarget()->CastCustomSpell(SPELL_HUNTER_GLYPH_OF_ARCANE_SHOT, SPELLVALUE_BASE_POINT0, mana, GetTarget());
        }

        void Register() override
        {
            DoCheckProc += AuraCheckProcFn(spell_hun_glyph_of_arcane_shot_AuraScript::CheckProc);
            OnEffectProc += AuraEffectProcFn(spell_hun_glyph_of_arcane_shot_AuraScript::HandleProc, EFFECT_0, SPELL_AURA_DUMMY);
        }
    };

    AuraScript* GetAuraScript() const override
    {
        return new spell_hun_glyph_of_arcane_shot_AuraScript();
    }
};

// -42243 - Volley (Trigger one)
class spell_hun_volley_trigger : public SpellScriptLoader
{
public:
    spell_hun_volley_trigger() : SpellScriptLoader("spell_hun_volley_trigger") {}

    class spell_hun_volley_trigger_SpellScript : public SpellScript
    {
        PrepareSpellScript(spell_hun_volley_trigger_SpellScript);

        void SelectTarget(std::list<WorldObject*>& targets)
        {
            // It's here because Volley is an AOE spell so there is no specific target to be attacked
            // Let's select one of our targets
            if (!targets.empty())
            {
                _target = *(targets.begin());
            }
        }

        void HandleFinish()
        {
            if (!_target)
            {
                return;
            }

            Unit* caster = GetCaster();
            if (!caster || !caster->IsPlayer())
            {
                return;
            }

            for (Unit::ControlSet::iterator itr = caster->m_Controlled.begin(); itr != caster->m_Controlled.end(); ++itr)
            {
                if (Unit* pet = *itr)
                {
                    if (pet->IsAlive() && pet->GetTypeId() == TYPEID_UNIT)
                    {
                        pet->ToCreature()->AI()->OwnerAttacked(_target->ToUnit());
                    }
                }
            }
        }

        void Register() override
        {
            OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_hun_volley_trigger_SpellScript::SelectTarget, EFFECT_0, TARGET_UNIT_DEST_AREA_ENEMY);
            AfterCast += SpellCastFn(spell_hun_volley_trigger_SpellScript::HandleFinish);
        }

    private:
        WorldObject* _target = nullptr;
    };

    SpellScript* GetSpellScript() const override
    {
        return new spell_hun_volley_trigger_SpellScript();
    }
};

enum LocknLoadSpells
{
    SPELL_FROST_TRAP_SLOW = 67035
};

// -56342 - Lock and Load
class spell_hun_lock_and_load : public SpellScriptLoader
{
    public:
        spell_hun_lock_and_load() : SpellScriptLoader("spell_hun_lock_and_load") { }

        class spell_hun_lock_and_load_AuraScript : public AuraScript
        {
            PrepareAuraScript(spell_hun_lock_and_load_AuraScript);

            bool Validate(SpellInfo const* /*spellInfo*/) override
            {
                return ValidateSpellInfo({ SPELL_LOCK_AND_LOAD_TRIGGER, SPELL_LOCK_AND_LOAD_MARKER, SPELL_FROST_TRAP_SLOW });
            }

            bool CheckTrapProc(ProcEventInfo& eventInfo)
            {
                SpellInfo const* spellInfo = eventInfo.GetSpellInfo();
                if (!spellInfo)
                {
                    return false;
                }

                // Black Arrow and Fire traps may trigger on periodic tick only.
                if (((spellInfo->GetSchoolMask() & SPELL_SCHOOL_MASK_FIRE) || (spellInfo->GetSchoolMask() & SPELL_SCHOOL_MASK_SHADOW))
                    && (spellInfo->Effects[0].ApplyAuraName == SPELL_AURA_PERIODIC_DAMAGE || spellInfo->Effects[1].ApplyAuraName == SPELL_AURA_PERIODIC_DAMAGE))
                {
                    return true;
                }

                return IsTargetValid(spellInfo, eventInfo.GetProcTarget()) && !eventInfo.GetActor()->HasAura(SPELL_LOCK_AND_LOAD_MARKER);
            }

            bool IsTargetValid(SpellInfo const* spellInfo, Unit* target)
            {
                if (!spellInfo || !target)
                {
                    return false;
                }

                // Don't check it for fire traps and black arrow, they proc on periodic only and not spell hit.
                // So it's wrong to check for immunity, it was already checked when the spell was applied.
                if ((spellInfo->GetSchoolMask() & SPELL_SCHOOL_MASK_FIRE) || (spellInfo->GetSchoolMask() & SPELL_SCHOOL_MASK_SHADOW))
                {
                    return false;
                }

                // HitMask for Frost Trap can't be checked correctly as it is.
                // That's because the talent is triggered by the spell that fires the trap (63487)...
                // ...and not the actual spell that applies the slow effect (67035).
                // So the IMMUNE result is never sent by the spell that triggers this.
                if (spellInfo->GetSchoolMask() & SPELL_SCHOOL_MASK_NATURE)
                {
                    if (SpellInfo const* triggerSpell = sSpellMgr->GetSpellInfo(SPELL_FROST_TRAP_SLOW))
                    {
                        return !target->IsImmunedToSpell(triggerSpell);
                    }
                }

                return true;
            }

            template <uint32 mask>
            void HandleProcs(AuraEffect const* aurEff, ProcEventInfo& eventInfo)
            {
                PreventDefaultAction();

                SpellInfo const* spellInfo = eventInfo.GetSpellInfo();

                if (!(eventInfo.GetTypeMask() & mask) || !spellInfo)
                {
                    return;
                }

                // Also check if the proc from the fire traps and black arrow actually comes from the periodic ticks here.
                // Normally this wouldn't be required, but we are circumventing the current proc system limitations.
                if (((spellInfo->GetSchoolMask() & SPELL_SCHOOL_MASK_FIRE) || (spellInfo->GetSchoolMask() & SPELL_SCHOOL_MASK_SHADOW))
                    && (spellInfo->Effects[0].ApplyAuraName == SPELL_AURA_PERIODIC_DAMAGE || spellInfo->Effects[1].ApplyAuraName == SPELL_AURA_PERIODIC_DAMAGE)
                    && !(mask & PROC_FLAG_DONE_PERIODIC))
                {
                    return;
                }

                if (!roll_chance_i(aurEff->GetAmount()))
                {
                    return;
                }

                Unit* caster = eventInfo.GetActor();
                caster->CastSpell(caster, SPELL_LOCK_AND_LOAD_TRIGGER, true);
            }

            void ApplyMarker(ProcEventInfo& eventInfo)
            {
                if (IsTargetValid(eventInfo.GetSpellInfo(), eventInfo.GetProcTarget()))
                {
                    Unit* caster = eventInfo.GetActor();
                    caster->CastSpell(caster, SPELL_LOCK_AND_LOAD_MARKER, true);
                }
            }

            void Register() override
            {
                DoCheckProc += AuraCheckProcFn(spell_hun_lock_and_load_AuraScript::CheckTrapProc);

                OnEffectProc += AuraEffectProcFn(spell_hun_lock_and_load_AuraScript::HandleProcs<PROC_FLAG_DONE_TRAP_ACTIVATION>, EFFECT_0, SPELL_AURA_PROC_TRIGGER_SPELL);
                OnEffectProc += AuraEffectProcFn(spell_hun_lock_and_load_AuraScript::HandleProcs<PROC_FLAG_DONE_PERIODIC>, EFFECT_1, SPELL_AURA_DUMMY);

                AfterProc += AuraProcFn(spell_hun_lock_and_load_AuraScript::ApplyMarker);
            }

        };

        AuraScript* GetAuraScript() const override
        {
            return new spell_hun_lock_and_load_AuraScript();
        }
};

void AddSC_hunter_spell_scripts()
{
    // Ours
    new spell_hun_check_pet_los();
    new spell_hun_cower();
    new spell_hun_wyvern_sting();
    new spell_hun_animal_handler();
    new spell_hun_generic_scaling();
    new spell_hun_taming_the_beast();
    new spell_hun_glyph_of_arcane_shot();

    // Theirs
    new spell_hun_aspect_of_the_beast();
    new spell_hun_ascpect_of_the_viper();
    new spell_hun_chimera_shot();
    new spell_hun_disengage();
    new spell_hun_improved_mend_pet();
    new spell_hun_invigoration();
    new spell_hun_last_stand_pet();
    new spell_hun_masters_call();
    new spell_hun_misdirection();
    new spell_hun_misdirection_proc();
    new spell_hun_pet_carrion_feeder();
    new spell_hun_pet_heart_of_the_phoenix();
    new spell_hun_readiness();
    new spell_hun_scatter_shot();
    new spell_hun_sniper_training();
    new spell_hun_tame_beast();
    new spell_hun_viper_attack_speed();
    new spell_hun_volley_trigger();
    new spell_hun_lock_and_load();
}
