/* * 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 General Public License as published by * the Free Software Foundation; either version 2 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 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 . */ #include "CreatureScript.h" #include "Player.h" #include "ScriptedCreature.h" #include "SpellAuraEffects.h" #include "SpellScript.h" #include "SpellScriptLoader.h" #include "WorldSession.h" #include "sunwell_plateau.h" enum Yells { SAY_SATH_AGGRO = 0, SAY_SATH_SLAY = 1, SAY_SATH_DEATH = 2, SAY_SATH_SPELL1 = 3, SAY_SATH_SPELL2 = 4, SAY_EVIL_AGGRO = 0, SAY_EVIL_SLAY = 1, SAY_GOOD_PLRWIN = 2, SAY_EVIL_ENRAGE = 3, SAY_SATH_ENRAGE_ME = 4, SAY_KALEC_ENRAGE_SATH = 5, SAY_GOOD_AGGRO = 0, SAY_GOOD_NEAR_DEATH = 1, SAY_GOOD_NEAR_DEATH2 = 2, SAY_GOOD_MADRIGOSA = 3 // Madrigosa deserved a far better fate. You did what had to be done, but this battle is far from over! }; enum Spells { SPELL_SPECTRAL_EXHAUSTION = 44867, SPELL_SPECTRAL_BLAST = 44869, SPELL_SPECTRAL_BLAST_PORTAL = 44866, SPELL_SPECTRAL_BLAST_AA = 46648, SPELL_TELEPORT_SPECTRAL = 46019, SPELL_TELEPORT_NORMAL_REALM = 46020, SPELL_SPECTRAL_REALM = 46021, SPELL_SPECTRAL_INVISIBILITY = 44801, SPELL_DEMONIC_VISUAL = 44800, SPELL_ARCANE_BUFFET = 45018, SPELL_FROST_BREATH = 44799, SPELL_TAIL_LASH = 45122, SPELL_BANISH = 44836, SPELL_TRANSFORM_KALEC = 44670, SPELL_CRAZED_RAGE = 44807, SPELL_CORRUPTION_STRIKE = 45029, SPELL_CURSE_OF_BOUNDLESS_AGONY = 45032, SPELL_CURSE_OF_BOUNDLESS_AGONY_PLR = 45034, SPELL_CURSE_OF_BOUNDLESS_AGONY_REMOVE = 45050, SPELL_CURSE_OF_BOUNDLESS_AGONY_DUMMY_1 = 45083, SPELL_CURSE_OF_BOUNDLESS_AGONY_DUMMY_2 = 45085, SPELL_CURSE_OF_BOUNDLESS_AGONY_DUMMY_3 = 45084, SPELL_SHADOW_BOLT = 45031, SPELL_HEROIC_STRIKE = 45026, SPELL_REVITALIZE = 45027 }; enum SWPActions { ACTION_ENRAGE = 1, ACTION_BANISH = 2, ACTION_SATH_BANISH = 3, ACTION_KALEC_DIED = 4, ACTION_ENRAGE_OTHER = 5, }; #define DRAGON_REALM_Z 53.079f struct boss_kalecgos : public BossAI { boss_kalecgos(Creature* creature) : BossAI(creature, DATA_KALECGOS) { SetInvincibility(true); } bool CanAIAttack(Unit const* target) const override { return target->GetPositionZ() > 50.0f; } bool CheckInRoom() override { if (me->GetDistance(me->GetHomePosition()) > 50.0f) return false; return true; } void Reset() override { BossAI::Reset(); me->SetFullHealth(); me->SetStandState(UNIT_STAND_STATE_SLEEP); me->SetDisableGravity(false); me->SetReactState(REACT_AGGRESSIVE); me->RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NOT_SELECTABLE); _sathBanished = false; ClearPlayerAuras(); ScheduleHealthCheckEvent(10, [&] { if (Creature* Sath = instance->GetCreature(DATA_SATHROVARR)) Sath->AI()->DoAction(ACTION_ENRAGE_OTHER); DoAction(ACTION_ENRAGE); }); ScheduleHealthCheckEvent(1, [&] { DoAction(ACTION_BANISH); }); } void ClearPlayerAuras() const { instance->DoRemoveAurasDueToSpellOnPlayers(SPELL_CURSE_OF_BOUNDLESS_AGONY); instance->DoRemoveAurasDueToSpellOnPlayers(SPELL_CURSE_OF_BOUNDLESS_AGONY_PLR); instance->DoRemoveAurasDueToSpellOnPlayers(SPELL_SPECTRAL_REALM); } void DoAction(int32 param) override { if (param == ACTION_ENRAGE || param == ACTION_ENRAGE_OTHER) { Talk(param == ACTION_ENRAGE ? SAY_KALEC_ENRAGE_SATH : SAY_SATH_ENRAGE_ME); DoCastSelf(SPELL_CRAZED_RAGE, true); return; } else if (param == ACTION_BANISH) { DoCastSelf(SPELL_BANISH, true); scheduler.CancelAll(); } else if (param == ACTION_SATH_BANISH) _sathBanished = true; else if (param == ACTION_KALEC_DIED) { scheduler.CancelAll(); me->m_Events.AddEventAtOffset([&] { me->SetReactState(REACT_PASSIVE); me->CombatStop(); me->RemoveAllAuras(); me->SetUnitFlag(UNIT_FLAG_NON_ATTACKABLE); Talk(SAY_EVIL_ENRAGE); }, 1s); me->m_Events.AddEventAtOffset([&] { me->SetDisableGravity(true); me->HandleEmoteCommand(EMOTE_ONESHOT_LIFTOFF); }, 4s); me->m_Events.AddEventAtOffset([&] { EnterEvadeMode(); }, 9s); ClearPlayerAuras(); return; } if (me->HasAura(SPELL_BANISH) && _sathBanished) { scheduler.CancelAll(); me->m_Events.AddEventAtOffset([&] { me->SetRegeneratingHealth(false); me->RemoveAllAuras(); me->SetReactState(REACT_PASSIVE); me->CombatStop(); me->SetUnitFlag(UNIT_FLAG_NON_ATTACKABLE); me->SetFaction(FACTION_FRIENDLY); }, 1s); me->m_Events.AddEventAtOffset([&] { if (Creature* Sath = instance->GetCreature(DATA_SATHROVARR)) { summons.Despawn(Sath); Unit::Kill(me, Sath); } }, 2s); Talk(SAY_GOOD_PLRWIN, 10s); me->m_Events.AddEventAtOffset([&] { me->SetDisableGravity(true); me->HandleEmoteCommand(EMOTE_ONESHOT_LIFTOFF); }, 15s); me->m_Events.AddEventAtOffset([&] { me->SetVisible(false); me->KillSelf(); }, 20s); ClearPlayerAuras(); if (Creature* Sath = instance->GetCreature(DATA_SATHROVARR)) { Sath->RemoveAllAuras(); Sath->GetMotionMaster()->MovementExpired(); Sath->SetReactState(REACT_PASSIVE); Sath->NearTeleportTo(1696.20f, 915.0f, DRAGON_REALM_Z, Sath->GetOrientation()); } } } void JustEngagedWith(Unit* who) override { BossAI::JustEngagedWith(who); ScheduleTimedEvent(8s, [&] { DoCastAOE(SPELL_ARCANE_BUFFET); }, 8s); ScheduleTimedEvent(15s, [&] { DoCastVictim(SPELL_FROST_BREATH); }, 15s); ScheduleTimedEvent(6s, [&] { me->CastCustomSpell(RAND(44978, 45001, 45002, 45004, 45006, 45010), SPELLVALUE_MAX_TARGETS, 1, me, false); }, 6s, 7s); ScheduleTimedEvent(25s, [&] { DoCastVictim(SPELL_TAIL_LASH); }, 15s); ScheduleTimedEvent(13s, [&] { DoCastAOE(SPELL_SPECTRAL_BLAST); }, 20s, 30s); scheduler.Schedule(9s, [this](TaskContext) { if (Creature* kalec = me->SummonCreature(NPC_KALEC, 1702.21f, 931.7f, -74.56f, 5.07f, TEMPSUMMON_MANUAL_DESPAWN)) kalec->CastSpell(kalec, SPELL_SPECTRAL_INVISIBILITY, true); me->SummonCreature(NPC_SATHROVARR, 1704.62f, 927.78f, -73.9f, 2.0f, TEMPSUMMON_MANUAL_DESPAWN); }); me->SetStandState(UNIT_STAND_STATE_STAND); Talk(SAY_EVIL_AGGRO); } void KilledUnit(Unit* victim) override { if (victim->IsPlayer() && roll_chance_i(50)) Talk(SAY_EVIL_SLAY); } private: bool _sathBanished; }; struct boss_kalec : public ScriptedAI { boss_kalec(Creature* creature) : ScriptedAI(creature) { } void JustEngagedWith(Unit*) override { ScheduleTimedEvent(5s, [&] { DoCastSelf(SPELL_REVITALIZE); }, 10s); ScheduleTimedEvent(3s, [&] { DoCastVictim(SPELL_HEROIC_STRIKE); }, 5s); scheduler.Schedule(1s, [this](TaskContext context) { if (me->HealthBelowPct(50)) Talk(SAY_GOOD_NEAR_DEATH); else context.Repeat(); }); scheduler.Schedule(1s, [this](TaskContext context) { if (me->HealthBelowPct(10)) Talk(SAY_GOOD_NEAR_DEATH2); else context.Repeat(); }); Talk(SAY_GOOD_AGGRO); } void JustDied(Unit*) override { if (InstanceScript* instance = me->GetInstanceScript()) if (Creature* kalecgos = instance->GetCreature(DATA_KALECGOS)) kalecgos->AI()->DoAction(ACTION_KALEC_DIED); } void UpdateAI(uint32 diff) override { if (!UpdateVictim()) return; scheduler.Update(diff, std::bind(&BossAI::DoMeleeAttackIfReady, this)); } }; struct boss_sathrovarr : public ScriptedAI { boss_sathrovarr(Creature* creature) : ScriptedAI(creature) { _instance = creature->GetInstanceScript(); SetInvincibility(true); } bool CanAIAttack(Unit const* target) const override { return target->GetPositionZ() < 50.0f; } void Reset() override { DoCastSelf(SPELL_DEMONIC_VISUAL, true); DoCastSelf(SPELL_SPECTRAL_INVISIBILITY, true); } void JustEngagedWith(Unit* /*who*/) override { Talk(SAY_SATH_AGGRO); ScheduleTimedEvent(7s, [&] { if (roll_chance_i(20)) Talk(SAY_SATH_SPELL1); DoCastVictim(SPELL_SHADOW_BOLT); }, 9s); ScheduleTimedEvent(40s, [&] { me->CastCustomSpell(SPELL_CURSE_OF_BOUNDLESS_AGONY, SPELLVALUE_MAX_TARGETS, 1, me, false); }, 40s); ScheduleTimedEvent(20s, [&] { if (roll_chance_i(20)) Talk(SAY_SATH_SPELL2); DoCastVictim(SPELL_CORRUPTION_STRIKE); }, 9s); scheduler.Schedule(1s, [this](TaskContext context) { if (me->HealthBelowPct(10)) { if (Creature* kalecgos = _instance->GetCreature(DATA_KALECGOS)) kalecgos->AI()->DoAction(ACTION_ENRAGE_OTHER); DoAction(ACTION_ENRAGE); } else context.Repeat(); }); scheduler.Schedule(1s, [this](TaskContext context) { if (me->HealthBelowPct(1)) { if (Creature* kalecgos = _instance->GetCreature(DATA_KALECGOS)) kalecgos->AI()->DoAction(ACTION_SATH_BANISH); DoAction(ACTION_BANISH); } else context.Repeat(); }); } void KilledUnit(Unit* target) override { if (target->IsPlayer()) Talk(SAY_SATH_SLAY); } void JustDied(Unit* /*killer*/) override { DoCastSelf(SPELL_CURSE_OF_BOUNDLESS_AGONY_REMOVE, true); Talk(SAY_SATH_DEATH); } void DoAction(int32 param) override { if (param == ACTION_ENRAGE || param == ACTION_ENRAGE_OTHER) DoCastSelf(SPELL_CRAZED_RAGE, true); else if (param == ACTION_BANISH) DoCastSelf(SPELL_BANISH, true); } void UpdateAI(uint32 diff) override { if (!UpdateVictim()) return; scheduler.Update(diff, std::bind(&BossAI::DoMeleeAttackIfReady, this)); } private: InstanceScript* _instance; }; class SpectralBlastCheck { public: SpectralBlastCheck(Unit* victim) : _victim(victim) { } bool operator()(WorldObject* unit) { return unit->GetPositionZ() < 50.0f || unit->ToUnit()->HasAura(SPELL_SPECTRAL_EXHAUSTION) || unit->GetGUID() == _victim->GetGUID(); } private: Unit* _victim; }; class spell_kalecgos_spectral_blast_dummy : public SpellScript { PrepareSpellScript(spell_kalecgos_spectral_blast_dummy); bool Validate(SpellInfo const* /*spellInfo*/) override { return ValidateSpellInfo({ SPELL_SPECTRAL_BLAST_PORTAL, SPELL_SPECTRAL_BLAST_AA, SPELL_TELEPORT_SPECTRAL }); } void FilterTargets(std::list& targets) { targets.remove_if(SpectralBlastCheck(GetCaster()->GetVictim())); Acore::Containers::RandomResize(targets, 1); } void HandleDummy(SpellEffIndex effIndex) { PreventHitDefaultEffect(effIndex); if (Unit* target = GetHitUnit()) { target->CastSpell(target, SPELL_SPECTRAL_BLAST_PORTAL, true); target->CastSpell(target, SPELL_SPECTRAL_BLAST_AA, true); target->CastSpell(target, SPELL_TELEPORT_SPECTRAL, true); } } void Register() override { OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_kalecgos_spectral_blast_dummy::FilterTargets, EFFECT_0, TARGET_UNIT_SRC_AREA_ENEMY); OnEffectHitTarget += SpellEffectFn(spell_kalecgos_spectral_blast_dummy::HandleDummy, EFFECT_0, SPELL_EFFECT_DUMMY); } }; class spell_kalecgos_curse_of_boundless_agony_aura : public AuraScript { PrepareAuraScript(spell_kalecgos_curse_of_boundless_agony_aura); bool Validate(SpellInfo const* /*spellInfo*/) override { return ValidateSpellInfo({ SPELL_CURSE_OF_BOUNDLESS_AGONY_PLR, SPELL_CURSE_OF_BOUNDLESS_AGONY_DUMMY_1, SPELL_CURSE_OF_BOUNDLESS_AGONY_DUMMY_2, SPELL_CURSE_OF_BOUNDLESS_AGONY_DUMMY_3 }); } void OnRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/) { if (InstanceScript* instance = GetUnitOwner()->GetInstanceScript()) if (instance->IsEncounterInProgress()) GetUnitOwner()->CastCustomSpell(SPELL_CURSE_OF_BOUNDLESS_AGONY_PLR, SPELLVALUE_MAX_TARGETS, 1, GetUnitOwner(), true); } void OnPeriodic(AuraEffect const* aurEff) { uint32 tickNumber = aurEff->GetTickNumber(); if (tickNumber > 1 && tickNumber % 5 == 1) GetAura()->GetEffect(aurEff->GetEffIndex())->SetAmount(aurEff->GetAmount() * 2); uint32 spellId = 0; if (tickNumber <= 10) spellId = SPELL_CURSE_OF_BOUNDLESS_AGONY_DUMMY_1; else if (tickNumber <= 20) spellId = SPELL_CURSE_OF_BOUNDLESS_AGONY_DUMMY_2; else spellId = SPELL_CURSE_OF_BOUNDLESS_AGONY_DUMMY_3; GetTarget()->CastSpell(GetTarget(), spellId, true); } void Register() override { AfterEffectRemove += AuraEffectRemoveFn(spell_kalecgos_curse_of_boundless_agony_aura::OnRemove, EFFECT_0, SPELL_AURA_PERIODIC_DAMAGE, AURA_EFFECT_HANDLE_REAL); OnEffectPeriodic += AuraEffectPeriodicFn(spell_kalecgos_curse_of_boundless_agony_aura::OnPeriodic, EFFECT_0, SPELL_AURA_PERIODIC_DAMAGE); } }; class spell_kalecgos_spectral_realm_dummy : public SpellScript { PrepareSpellScript(spell_kalecgos_spectral_realm_dummy); bool Validate(SpellInfo const* /*spellInfo*/) override { return ValidateSpellInfo({ SPELL_SPECTRAL_EXHAUSTION, SPELL_TELEPORT_SPECTRAL }); } SpellCastResult CheckCast() { if (GetCaster()->HasAura(SPELL_SPECTRAL_EXHAUSTION)) return SPELL_FAILED_CASTER_AURASTATE; return SPELL_CAST_OK; } void HandleScriptEffect(SpellEffIndex effIndex) { PreventHitDefaultEffect(effIndex); GetCaster()->CastSpell(GetCaster(), SPELL_TELEPORT_SPECTRAL, true); } void Register() override { OnCheckCast += SpellCheckCastFn(spell_kalecgos_spectral_realm_dummy::CheckCast); OnEffectHitTarget += SpellEffectFn(spell_kalecgos_spectral_realm_dummy::HandleScriptEffect, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT); } }; class spell_kalecgos_spectral_realm_aura : public AuraScript { PrepareAuraScript(spell_kalecgos_spectral_realm_aura); bool Validate(SpellInfo const* /*spellInfo*/) override { return ValidateSpellInfo({ SPELL_SPECTRAL_EXHAUSTION, SPELL_TELEPORT_NORMAL_REALM }); } void OnRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/) { GetUnitOwner()->CastSpell(GetUnitOwner(), SPELL_SPECTRAL_EXHAUSTION, true); GetUnitOwner()->CastSpell(GetUnitOwner(), SPELL_TELEPORT_NORMAL_REALM, true); } void Register() override { OnEffectRemove += AuraEffectRemoveFn(spell_kalecgos_spectral_realm_aura::OnRemove, EFFECT_1, SPELL_AURA_MOD_INVISIBILITY, AURA_EFFECT_HANDLE_REAL); } }; void AddSC_boss_kalecgos() { RegisterSunwellPlateauCreatureAI(boss_kalecgos); RegisterSunwellPlateauCreatureAI(boss_sathrovarr); RegisterSunwellPlateauCreatureAI(boss_kalec); RegisterSpellScript(spell_kalecgos_spectral_blast_dummy); RegisterSpellScript(spell_kalecgos_curse_of_boundless_agony_aura); RegisterSpellScript(spell_kalecgos_spectral_realm_dummy); RegisterSpellScript(spell_kalecgos_spectral_realm_aura); }