/* * 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 "GameObjectAI.h" #include "MoveSplineInit.h" #include "ScriptedCreature.h" #include "SmartAI.h" #include "SpellScript.h" #include "SpellScriptLoader.h" #include "TaskScheduler.h" #include "zulgurub.h" enum Says { // Jeklik SAY_AGGRO = 0, SAY_CALL_RIDERS = 1, SAY_DEATH = 2, EMOTE_SUMMON_BATS = 3, EMOTE_GREAT_HEAL = 4, // Bat Rider EMOTE_BATRIDER_LOW_HEALTH = 0 }; enum Spells { // Intro SPELL_GREEN_CHANNELING = 13540, SPELL_BAT_FORM = 23966, // Phase one SPELL_PIERCE_ARMOR = 12097, SPELL_BLOOD_LEECH = 22644, SPELL_CHARGE = 22911, SPELL_SONIC_BURST = 23918, SPELL_SWOOP = 23919, // Phase two SPELL_CURSE_OF_BLOOD = 16098, SPELL_PSYCHIC_SCREAM = 22884, SPELL_SHADOW_WORD_PAIN = 23952, SPELL_MIND_FLAY = 23953, SPELL_GREATER_HEAL = 23954, // Bat Rider (Boss) SPELL_BATRIDER_THROW_LIQUID_FIRE = 23970, SPELL_BATRIDER_SUMMON_LIQUID_FIRE = 23971, // Bat Rider (Trash) SPELL_BATRIDER_DEMO_SHOUT = 23511, SPELL_BATRIDER_BATTLE_COMMAND = 5115, SPELL_BATRIDER_INFECTED_BITE = 16128, SPELL_BATRIDER_PASSIVE_THRASH = 8876, SPELL_BATRIDER_UNSTABLE_CONCOCTION = 24024 }; enum BatIds { NPC_BLOODSEEKER_BAT = 11368, NPC_BATRIDER = 14750 }; enum Phase { PHASE_ONE = 1, PHASE_TWO = 2 }; Position const SpawnBat[6] = { { -12291.6220f, -1380.2640f, 144.8304f, 5.483f }, { -12289.6220f, -1380.2640f, 144.8304f, 5.483f }, { -12293.6220f, -1380.2640f, 144.8304f, 5.483f }, { -12291.6220f, -1380.2640f, 144.8304f, 5.483f }, { -12289.6220f, -1380.2640f, 144.8304f, 5.483f }, { -12293.6220f, -1380.2640f, 144.8304f, 5.483f } }; Position const SpawnBatRider = { -12301.689, -1371.2921, 145.09244 }; Position const JeklikCaveHomePosition = { -12291.9f, -1380.08f, 144.902f, 2.28638f }; enum PathID { PATH_JEKLIK_INTRO = 145170, PATH_BATRIDER_LOOP = 147500 }; enum BatRiderMode { BATRIDER_MODE_TRASH = 1, BATRIDER_MODE_BOSS }; // High Priestess Jeklik (14517) struct boss_jeklik : public BossAI { // Bat Riders (14750) counter uint8 batRidersCount = 0; boss_jeklik(Creature* creature) : BossAI(creature, DATA_JEKLIK) { } void Reset() override { BossAI::Reset(); me->SetHomePosition(JeklikCaveHomePosition); me->SetDisableGravity(false); me->SetReactState(REACT_PASSIVE); BossAI::me->SetCombatMovement(false); batRidersCount = 0; DoCastSelf(SPELL_GREEN_CHANNELING, true); } void JustEngagedWith(Unit* who) override { BossAI::JustEngagedWith(who); Talk(SAY_AGGRO); DoZoneInCombat(); me->RemoveAurasDueToSpell(SPELL_GREEN_CHANNELING); me->SetDisableGravity(true); DoCastSelf(SPELL_BAT_FORM, true); me->GetMotionMaster()->MoveWaypoint(PATH_JEKLIK_INTRO, false); } void PathEndReached(uint32 pathId) override { BossAI::PathEndReached(pathId); me->SetDisableGravity(false); me->SetCombatMovement(true); me->SetReactState(REACT_AGGRESSIVE); // // Phase 1 // scheduler.Schedule(10s, 20s, PHASE_ONE, [this](TaskContext context) { if (Unit* target = SelectTarget(SelectTargetMethod::MinDistance, 0, -8.0f, false, false)) { DoCast(target, SPELL_CHARGE); AttackStart(target); } context.Repeat(15s, 30s); }).Schedule(5s, 15s, PHASE_ONE, [this](TaskContext context) { DoCastVictim(SPELL_PIERCE_ARMOR); context.Repeat(20s, 30s); }).Schedule(5s, 15s, PHASE_ONE, [this](TaskContext context) { DoCastVictim(SPELL_BLOOD_LEECH); context.Repeat(10s, 20s); }).Schedule(5s, 15s, PHASE_ONE, [this](TaskContext context) { DoCastVictim(SPELL_SONIC_BURST); context.Repeat(20s, 30s); }).Schedule(20s, PHASE_ONE, [this](TaskContext context) { DoCastVictim(SPELL_SWOOP); context.Repeat(20s, 30s); }).Schedule(30s, PHASE_ONE, [this](TaskContext context) { Talk(EMOTE_SUMMON_BATS); if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0)) { for (uint8 i = 0; i < 6; ++i) { if (Creature* bat = me->SummonCreature(NPC_BLOODSEEKER_BAT, SpawnBat[i], TEMPSUMMON_TIMED_DESPAWN_OUT_OF_COMBAT, 15000)) { bat->AI()->AttackStart(target); } } } context.Repeat(30s); }); // // Phase 2 (@ 50% health) // ScheduleHealthCheckEvent(50, [&] { me->RemoveAurasDueToSpell(SPELL_BAT_FORM); DoResetThreatList(); scheduler.CancelGroup(PHASE_ONE); scheduler.Schedule(5s, 15s, PHASE_TWO, [this](TaskContext context) { DoCastSelf(SPELL_CURSE_OF_BLOOD); context.Repeat(25s, 30s); }).Schedule(25s, 35s, PHASE_TWO, [this](TaskContext context) { DoCastVictim(SPELL_PSYCHIC_SCREAM); context.Repeat(35s, 45s); }).Schedule(10s, 15s, PHASE_TWO, [this](TaskContext context) { DoCastRandomTarget(SPELL_SHADOW_WORD_PAIN, 0, true); context.Repeat(12s, 18s); }).Schedule(10s, 30s, PHASE_TWO, [this](TaskContext context) { DoCastVictim(SPELL_MIND_FLAY); context.Repeat(20s, 40s); }).Schedule(25s, PHASE_TWO, [this](TaskContext context) { Talk(EMOTE_GREAT_HEAL); me->InterruptNonMeleeSpells(false); DoCastSelf(SPELL_GREATER_HEAL); context.Repeat(25s); }).Schedule(10s, PHASE_TWO, [this](TaskContext context) { if (me->GetThreatMgr().GetThreatListSize()) { // summon up to 2 bat riders if (batRidersCount < 2) { Talk(SAY_CALL_RIDERS); // only if the bat rider was successfully created if (me->SummonCreature(NPC_BATRIDER, SpawnBatRider, TEMPSUMMON_TIMED_DESPAWN_OUT_OF_COMBAT)) { batRidersCount++; } if (batRidersCount == 1) { context.Repeat(10s, 15s); } } } }); }); } void EnterEvadeMode(EvadeReason why) override { if (why != EvadeReason::EVADE_REASON_NO_PATH) { me->DespawnOnEvade(5s); } BossAI::EnterEvadeMode(why); } void JustDied(Unit* killer) override { BossAI::JustDied(killer); Talk(SAY_DEATH); } }; // Gurubashi Bat Rider (14750) - trash and boss summon are same creature ID struct npc_batrider : public CreatureAI { BatRiderMode _mode; // the version of this creature (trash or boss) TaskScheduler _scheduler; npc_batrider(Creature* creature) : CreatureAI(creature) { // if this is a summon of Jeklik, it is in boss mode if ( me->GetEntry() == NPC_BATRIDER && me->IsSummon() && me->ToTempSummon() && me->ToTempSummon()->GetSummoner() && me->ToTempSummon()->GetSummoner()->GetEntry() == NPC_PRIESTESS_JEKLIK ) { _mode = BATRIDER_MODE_BOSS; me->SetUnitFlag(UNIT_FLAG_NOT_SELECTABLE); me->SetUnitFlag(UNIT_FLAG_NON_ATTACKABLE); me->SetUnitFlag(UNIT_FLAG_IMMUNE_TO_NPC); me->SetReactState(REACT_PASSIVE); me->SetSpeed(MOVE_WALK, 5.0f, true); me->SetCanFly(true); me->GetMotionMaster()->MovePath(PATH_BATRIDER_LOOP); } else { _mode = BATRIDER_MODE_TRASH; me->SetReactState(REACT_DEFENSIVE); // don't interrupt casting _scheduler.SetValidator([this] { return !me->HasUnitState(UNIT_STATE_CASTING); }); } } void Reset() override { CreatureAI::Reset(); _scheduler.CancelAll(); if (_mode == BATRIDER_MODE_BOSS) { me->GetMotionMaster()->Clear(); } else if (_mode == BATRIDER_MODE_TRASH) { me->CastSpell(me, SPELL_BATRIDER_PASSIVE_THRASH); } } void JustEngagedWith(Unit* who) override { CreatureAI::JustEngagedWith(who); if (_mode == BATRIDER_MODE_BOSS) { _scheduler.Schedule(2s, [this](TaskContext context) { DoCastRandomTarget(SPELL_BATRIDER_THROW_LIQUID_FIRE); context.Repeat(8s); }); } else if (_mode == BATRIDER_MODE_TRASH) { _scheduler.Schedule(1s, [this](TaskContext /*context*/) { DoCastSelf(SPELL_BATRIDER_DEMO_SHOUT); }).Schedule(8s, [this](TaskContext context) { DoCastSelf(SPELL_BATRIDER_BATTLE_COMMAND); context.Repeat(25s); }).Schedule(6500ms, [this](TaskContext context) { DoCastVictim(SPELL_BATRIDER_INFECTED_BITE); context.Repeat(8s); }); } } void DamageTaken(Unit* /*attacker*/, uint32& damage, DamageEffectType, SpellSchoolMask) override { if (_mode == BATRIDER_MODE_TRASH) { if (me->HealthBelowPctDamaged(30, damage)) { _scheduler.CancelAll(); DoCastSelf(SPELL_BATRIDER_UNSTABLE_CONCOCTION); } } } void UpdateAI(uint32 /*diff*/) override { if (_mode == BATRIDER_MODE_BOSS) { if (!me->isMoving()) { me->SetCanFly(true); me->GetMotionMaster()->MovePath(PATH_BATRIDER_LOOP); } } else if (_mode == BATRIDER_MODE_TRASH) { if (!UpdateVictim()) { return; } DoMeleeAttackIfReady(); } _scheduler.Update(); } }; class spell_batrider_bomb : public SpellScript { PrepareSpellScript(spell_batrider_bomb); bool Validate(SpellInfo const* /*spellInfo*/) override { return ValidateSpellInfo({ SPELL_BATRIDER_SUMMON_LIQUID_FIRE }); } void HandleScriptEffect(SpellEffIndex effIndex) { PreventHitDefaultEffect(effIndex); if (Unit* target = GetHitUnit()) { target->CastSpell(target, SPELL_BATRIDER_SUMMON_LIQUID_FIRE, true); } } void Register() override { OnEffectHitTarget += SpellEffectFn(spell_batrider_bomb::HandleScriptEffect, EFFECT_1, SPELL_EFFECT_SCRIPT_EFFECT); } }; void AddSC_boss_jeklik() { RegisterCreatureAI(boss_jeklik); RegisterCreatureAI(npc_batrider); RegisterSpellScript(spell_batrider_bomb); }