/* * 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 "Cell.h" #include "CellImpl.h" #include "CreatureScript.h" #include "GridNotifiers.h" #include "PassiveAI.h" #include "ScriptedCreature.h" #include "SpellScript.h" #include "SpellScriptLoader.h" #include "sunwell_plateau.h" enum Yells { YELL_BIRTH = 0, // Glory to Kil'jaeden! Death to all who oppose! YELL_KILL = 1, // I kill for the master! OR The end has come! YELL_BREATH = 2, // Choke on your final breath YELL_TAKEOFF = 3, // I am stronger than ever before! YELL_BERSERK = 4, // No more hesitation! Your fates are written! YELL_DEATH = 5, // Kil'jaeden will... prevail! AND Kalecgos line EMOTE_BREATH = 6 // Felmyst takes a deep breath... }; enum Spells { //Aura SPELL_NOXIOUS_FUMES = 47002, //Land phase SPELL_BERSERK = 45078, SPELL_CLEAVE = 19983, SPELL_CORROSION = 45866, SPELL_GAS_NOVA = 45855, SPELL_ENCAPSULATE_CHANNEL = 45661, //Flight phase SPELL_TRIGGER_TOP_STRAFE = 45586, SPELL_TRIGGER_MIDDLE_STRAFE = 45622, SPELL_TRIGGER_BOTTOM_STRAFE = 45623, SPELL_STRAFE_TOP = 45585, SPELL_STRAFE_MIDDLE = 45633, SPELL_STRAFE_BOTTOM = 45635, SPELL_SUMMON_DEMONIC_VAPOR = 45391, SPELL_DEMONIC_VAPOR_SPAWN_TRIGGER = 45388, // Triggers visual beam SPELL_DEMONIC_VAPOR_PERIODIC = 45411, // Spawns cloud and deals damage SPELL_DEMONIC_VAPOR_TRAIL_PERIODIC = 45399, // periodic of cloud SPELL_DEMONIC_VAPOR = 45402, // cloud dot SPELL_SUMMON_BLAZING_DEAD = 45400, // spawns skeletons SPELL_FELMYST_SPEED_BURST = 45495, // speed burst and breath animation SPELL_FOG_OF_CORRUPTION = 45582, // trigger cast SPELL_FOG_OF_CORRUPTION_CHARM = 45717, // charm 1 SPELL_FOG_OF_CORRUPTION_CHARM2 = 45726, // charm 2 }; enum Misc { // Misc ACTION_START_EVENT = 1, POINT_GROUND = 1, POINT_TAKEOFF = 2, POINT_AIR = 3, POINT_AIR_UP = 4, POINT_LANE = 5, POINT_AIR_BREATH_START1 = 6, POINT_AIR_BREATH_END = 7, POINT_AIR_BREATH_START2 = 8, POINT_MISC = 9, POINT_KALECGOS = 1, GROUP_START_INTRO = 0, GROUP_BREATH = 1, GROUP_TAKEOFF = 2, NPC_FOG_TRIGGER = 23472, NPC_KALECGOS_FELMYST = 24844, // Same as Magister's Terrace NPC_WORLD_TRIGGER_RIGHT = 25358 }; const Position LeftSideLanes[3] = { { 1494.745f, 704.0001f, 50.084652f, 4.7472f }, // top { 1469.923f, 703.23914f, 50.08592f, 4.7472f }, // middle { 1446.5154f, 701.5184f, 50.085438f, 4.7472f } // bottom }; const Position RightSideLanes[3] = { { 1492.82f, 515.668f, 50.0833f, 1.4486f }, // top { 1466.7322f, 515.5953f, 50.571518f, 1.4486f }, // middle { 1441.64f, 520.52f, 50.0833f, 1.4486f } // bottom }; const Position RightSide = { 1458.5555f, 502.1995f, 59.899513f, 1.605702f }; const Position LeftSide = { 1469.0642f, 729.5854f, 59.823853f, 4.6774f }; const Position LandingLeftPos = { 1476.77f, 665.094f, 20.6423f }; const Position LandingRightPos = { 1469.93f, 557.009f, 22.631699f }; class CorruptTriggers : public BasicEvent { public: CorruptTriggers(Unit* caster, uint8 currentLane) : _caster(caster), _currentLane(currentLane) { } bool Execute(uint64 /*execTime*/, uint32 /*diff*/) override { switch (_currentLane) { case 0: // top _caster->CastSpell(_caster, SPELL_STRAFE_TOP, true); break; case 1: // middle _caster->CastSpell(_caster, SPELL_STRAFE_MIDDLE, true); break; case 2: // bottom _caster->CastSpell(_caster, SPELL_STRAFE_BOTTOM, true); break; } return true; } private: Unit* _caster; uint8 _currentLane; }; struct boss_felmyst : public BossAI { boss_felmyst(Creature* creature) : BossAI(creature, DATA_FELMYST), _currentLane(0), _strafeCount(0) { } void InitializeAI() override { me->SetReactState(REACT_PASSIVE); if (instance->GetBossState(DATA_FELMYST) == TO_BE_DECIDED) { me->SetStandState(UNIT_STAND_STATE_SLEEP); me->SetImmuneToPC(true); StartIntro(); } else { me->SetCanFly(true); me->SetDisableGravity(true); me->SendMovementFlagUpdate(); me->GetMotionMaster()->MoveWaypoint(me->GetEntry() * 10, true); } } void Reset() override { BossAI::Reset(); instance->DoRemoveAurasDueToSpellOnPlayers(SPELL_FOG_OF_CORRUPTION_CHARM); _currentLane = 0; _strafeCount = 0; me->SetCombatMovement(false); } void JustEngagedWith(Unit* who) override { BossAI::JustEngagedWith(who); ScheduleEnrageTimer(SPELL_BERSERK, 10min, YELL_BERSERK); me->GetMotionMaster()->Clear(); Position landPos = who->GetPosition(); me->m_Events.AddEventAtOffset([&, landPos] { me->GetMotionMaster()->MoveLand(POINT_GROUND, landPos); }, 1s); } void KilledUnit(Unit* victim) override { if (victim->IsPlayer() && roll_chance_i(50)) Talk(YELL_KILL); } void SpellHitTarget(Unit* target, const SpellInfo* spell) override { if (spell->Id == SPELL_STRAFE_TOP || spell->Id == SPELL_STRAFE_MIDDLE || spell->Id == SPELL_STRAFE_BOTTOM) target->CastSpell(target, SPELL_FOG_OF_CORRUPTION, true); } void JustDied(Unit* killer) override { BossAI::JustDied(killer); me->m_Events.KillAllEvents(false); Talk(YELL_DEATH); instance->DoRemoveAurasDueToSpellOnPlayers(SPELL_FOG_OF_CORRUPTION_CHARM); // Summon Kalecgos (human form of kalecgos fight) if (Creature* kalec = me->SummonCreature(NPC_KALECGOS_FELMYST, 1573.1461f, 755.20245f, 99.524956f, 3.595378f)) kalec->GetMotionMaster()->MovePoint(POINT_KALECGOS, 1474.2347f, 624.0703f, 29.32589f, FORCED_MOVEMENT_NONE, 0.f, 0.f, false, true); } void ScheduleGroundAbilities() { ScheduleTimedEvent(7500ms, [&] { DoCastVictim(SPELL_CLEAVE); }, 7500ms); ScheduleTimedEvent(13s, 30s, [&] { if (scheduler.GetNextGroupOccurrence(GROUP_TAKEOFF) > 2s) { Talk(YELL_BREATH); DoCastVictim(SPELL_CORROSION); } }, 30s, 39s); ScheduleTimedEvent(18s, 43s, [&] { if (scheduler.GetNextGroupOccurrence(GROUP_TAKEOFF) > 2s) DoCastSelf(SPELL_GAS_NOVA); }, 18s, 43s); ScheduleTimedEvent(26s, 53s, [&] { if (scheduler.GetNextGroupOccurrence(GROUP_TAKEOFF) > 9s) DoCastRandomTarget(SPELL_ENCAPSULATE_CHANNEL, 0, 50.0f); }, 26s, 53s); scheduler.Schedule(1min, GROUP_TAKEOFF, [&](TaskContext) { Talk(YELL_TAKEOFF); scheduler.CancelAll(); me->SetReactState(REACT_PASSIVE); me->SetTarget(); me->AttackStop(); me->SetCombatMovement(false); me->GetMotionMaster()->Clear(); me->GetMotionMaster()->MoveIdle(); me->SetCanFly(true); me->SetDisableGravity(true); me->SendMovementFlagUpdate(); SetInvincibility(true); me->HandleEmoteCommand(EMOTE_ONESHOT_LIFTOFF); me->GetMotionMaster()->MovePoint(POINT_TAKEOFF, me->GetPositionX(), me->GetPositionY(), me->GetPositionZ() + 20.0f); }); } void MovementInform(uint32 type, uint32 point) override { if (type != EFFECT_MOTION_TYPE && type != POINT_MOTION_TYPE) return; switch (point) { case POINT_GROUND: if (!me->HasAura(SPELL_NOXIOUS_FUMES)) DoCastSelf(SPELL_NOXIOUS_FUMES, true); me->GetMotionMaster()->MoveIdle(); me->HandleEmoteCommand(EMOTE_ONESHOT_LAND); me->SetCanFly(false); me->SetDisableGravity(false); me->SendMovementFlagUpdate(); SetInvincibility(false); me->m_Events.AddEventAtOffset([&] { me->SetReactState(REACT_AGGRESSIVE); if (me->GetVictim()) me->SetTarget(me->GetVictim()->GetGUID()); me->ResumeChasingVictim(); me->SetCombatMovement(true); }, 5s); ScheduleGroundAbilities(); break; case POINT_TAKEOFF: me->m_Events.AddEventAtOffset([&] { me->CastCustomSpell(SPELL_SUMMON_DEMONIC_VAPOR, SPELLVALUE_MAX_TARGETS, 1, me, true); }, 5s); me->m_Events.AddEventAtOffset([&] { me->CastCustomSpell(SPELL_SUMMON_DEMONIC_VAPOR, SPELLVALUE_MAX_TARGETS, 1, me, true); }, 17s); scheduler.Schedule(27s, GROUP_BREATH, [this](TaskContext) { if (me->GetDistance(LeftSide) < me->GetDistance(RightSide)) me->GetMotionMaster()->MovePoint(POINT_AIR_UP, LeftSide); else me->GetMotionMaster()->MovePoint(POINT_AIR_UP, RightSide); }); break; case POINT_AIR_UP: me->m_Events.AddEventAtOffset([&] { bool isRightSide = me->FindNearestCreature(NPC_WORLD_TRIGGER_RIGHT, 30.0f); if (_strafeCount >= 3) { _strafeCount = 0; me->GetMotionMaster()->MoveLand(POINT_GROUND, isRightSide ? LandingRightPos : LandingLeftPos); return; } ++_strafeCount; _currentLane = urand(0, 2); if (isRightSide) me->GetMotionMaster()->MovePoint(POINT_LANE, RightSideLanes[_currentLane], FORCED_MOVEMENT_NONE, 0.f, false); else me->GetMotionMaster()->MovePoint(POINT_LANE, LeftSideLanes[_currentLane], FORCED_MOVEMENT_NONE, 0.f, false); }, 5s); break; case POINT_LANE: Talk(EMOTE_BREATH); me->m_Events.AddEventAtOffset([this]() { for (uint8 i = 0; i < 16; ++i) me->m_Events.AddEventAtOffset(new CorruptTriggers(me, _currentLane), Milliseconds(i * 250)); }, 5s); me->m_Events.AddEventAtOffset([&] { DoCastSelf(SPELL_FELMYST_SPEED_BURST, true); if (me->FindNearestCreature(NPC_WORLD_TRIGGER_RIGHT, 30.0f)) me->GetMotionMaster()->MovePoint(POINT_AIR_BREATH_END, LeftSideLanes[_currentLane], FORCED_MOVEMENT_NONE, 0.f, false); else me->GetMotionMaster()->MovePoint(POINT_AIR_BREATH_END, RightSideLanes[_currentLane], FORCED_MOVEMENT_NONE, 0.f, false); }, 5s); break; case POINT_AIR_BREATH_END: me->RemoveAurasDueToSpell(SPELL_FELMYST_SPEED_BURST); me->m_Events.AddEventAtOffset([&] { if (me->FindNearestCreature(NPC_WORLD_TRIGGER_RIGHT, 30.0f)) me->GetMotionMaster()->MovePoint(POINT_AIR_UP, RightSide, FORCED_MOVEMENT_NONE, 0.f, false); else me->GetMotionMaster()->MovePoint(POINT_AIR_UP, LeftSide, FORCED_MOVEMENT_NONE, 0.f, false); }, 2s); break; } } void StartIntro() { scheduler.Schedule(3s, GROUP_START_INTRO, [this](TaskContext /*context*/) { me->SetStandState(UNIT_STAND_STATE_STAND); me->m_Events.AddEventAtOffset([&] { Talk(YELL_BIRTH); me->SetCanFly(true); me->SetDisableGravity(true); me->SendMovementFlagUpdate(); me->HandleEmoteCommand(EMOTE_ONESHOT_LIFTOFF); }, 7s); me->m_Events.AddEventAtOffset([&] { me->SetImmuneToPC(false); me->GetMotionMaster()->MoveWaypoint(me->GetEntry() * 10, true); }, 8500ms); }); } void UpdateAI(uint32 diff) override { scheduler.Update(diff); if (!UpdateVictim()) return; if (!me->HasUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY)) DoMeleeAttackIfReady(); } private: uint8 _currentLane = 0; uint8 _strafeCount = 0; }; struct npc_demonic_vapor : public NullCreatureAI { npc_demonic_vapor(Creature* creature) : NullCreatureAI(creature), _timer{1} { } void Reset() override { me->CastSpell(me, SPELL_DEMONIC_VAPOR_SPAWN_TRIGGER, true); } void IsSummonedBy(WorldObject* summoner) override { if (!summoner || !summoner->ToUnit()) return; me->m_Events.AddEventAtOffset([this, summoner] { me->GetMotionMaster()->MoveFollow(summoner->ToUnit(), 0.0f, 0.0f, MOTION_SLOT_CONTROLLED); }, 2s); } void UpdateAI(uint32 diff) override { if (_timer) { _timer += diff; if (_timer >= 2000) { me->CastSpell(me, SPELL_DEMONIC_VAPOR_PERIODIC, true); _timer = 0; } } } private: uint32 _timer; }; struct npc_demonic_vapor_trail : public NullCreatureAI { npc_demonic_vapor_trail(Creature* creature) : NullCreatureAI(creature), _timer{1} { } void Reset() override { me->CastSpell(me, SPELL_DEMONIC_VAPOR_TRAIL_PERIODIC, true); me->DespawnOrUnsummon(20s); } void SpellHitTarget(Unit* /*unit*/, SpellInfo const* spellInfo) override { if (spellInfo->Id == SPELL_DEMONIC_VAPOR && !_timer) _timer = 1; } void UpdateAI(uint32 diff) override { if (_timer) { _timer += diff; if (_timer >= 5000) { _timer = 0; me->CastSpell(me, SPELL_SUMMON_BLAZING_DEAD, true); } } } void JustSummoned(Creature* summon) override { summon->SetInCombatWithZone(); summon->AI()->AttackStart(summon->AI()->SelectTarget(SelectTargetMethod::Random, 0, 100.0f)); } private: uint32 _timer; }; class spell_felmyst_fog_of_corruption : public SpellScript { PrepareSpellScript(spell_felmyst_fog_of_corruption); bool Validate(SpellInfo const* /*spellInfo*/) override { return ValidateSpellInfo({ SPELL_FOG_OF_CORRUPTION_CHARM }); } void HandleScriptEffect(SpellEffIndex effIndex) { PreventHitDefaultEffect(effIndex); if (Unit* target = GetHitUnit()) target->CastSpell(GetCaster(), SPELL_FOG_OF_CORRUPTION_CHARM, true); } void Register() override { OnEffectHitTarget += SpellEffectFn(spell_felmyst_fog_of_corruption::HandleScriptEffect, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT); } }; class spell_felmyst_fog_of_corruption_charm_aura : public AuraScript { PrepareAuraScript(spell_felmyst_fog_of_corruption_charm_aura); bool Validate(SpellInfo const* /*spellInfo*/) override { return ValidateSpellInfo({ SPELL_FOG_OF_CORRUPTION_CHARM2, SPELL_FOG_OF_CORRUPTION_CHARM }); } void HandleApply(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/) { GetTarget()->CastSpell(GetTarget(), SPELL_FOG_OF_CORRUPTION_CHARM2, true); } void HandleRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/) { GetTarget()->RemoveAurasDueToSpell(SPELL_FOG_OF_CORRUPTION_CHARM); GetTarget()->RemoveAurasDueToSpell(SPELL_FOG_OF_CORRUPTION_CHARM2); Unit::Kill(GetCaster(), GetTarget(), false); } void Register() override { OnEffectApply += AuraEffectApplyFn(spell_felmyst_fog_of_corruption_charm_aura::HandleApply, EFFECT_0, SPELL_AURA_AOE_CHARM, AURA_EFFECT_HANDLE_REAL); OnEffectRemove += AuraEffectRemoveFn(spell_felmyst_fog_of_corruption_charm_aura::HandleRemove, EFFECT_0, SPELL_AURA_AOE_CHARM, AURA_EFFECT_HANDLE_REAL); } }; class DoorsGuidCheck { public: bool operator()(WorldObject* object) const { if (!object->IsCreature()) return true; Creature* cr = object->ToCreature(); return cr->GetSpawnId() != 54780 && cr->GetSpawnId() != 54787 && cr->GetSpawnId() != 54801; } }; class spell_felmyst_open_brutallus_back_doors : public SpellScript { PrepareSpellScript(spell_felmyst_open_brutallus_back_doors); bool Load() override { return GetCaster()->GetInstanceScript(); } void FilterTargets(std::list& unitList) { unitList.remove_if(DoorsGuidCheck()); } void HandleAfterCast() { GetCaster()->GetInstanceScript()->SetBossState(DATA_FELMYST_DOORS, NOT_STARTED); GetCaster()->GetInstanceScript()->SetBossState(DATA_FELMYST_DOORS, DONE); } void Register() override { OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_felmyst_open_brutallus_back_doors::FilterTargets, EFFECT_0, TARGET_UNIT_SRC_AREA_ENTRY); AfterCast += SpellCastFn(spell_felmyst_open_brutallus_back_doors::HandleAfterCast); } }; void AddSC_boss_felmyst() { RegisterSunwellPlateauCreatureAI(boss_felmyst); RegisterSunwellPlateauCreatureAI(npc_demonic_vapor); RegisterSunwellPlateauCreatureAI(npc_demonic_vapor_trail); RegisterSpellScript(spell_felmyst_fog_of_corruption); RegisterSpellScript(spell_felmyst_fog_of_corruption_charm_aura); RegisterSpellScript(spell_felmyst_open_brutallus_back_doors); }