/* * 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 "GameObjectScript.h" #include "PassiveAI.h" #include "Player.h" #include "ScriptedCreature.h" #include "SpellScript.h" #include "SpellScriptLoader.h" #include "TaskScheduler.h" #include "karazhan.h" enum Spells { // Ground Phase SPELL_CLEAVE = 30131, SPELL_TAIL_SWEEP = 25653, SPELL_SMOLDERING_BREATH = 30210, SPELL_CHARRED_EARTH = 30129, SPELL_BELLOWING_ROAR = 36922, // Air Phase SPELL_SMOKING_BLAST = 30128, SPELL_SMOKING_BLAST_T = 37057, SPELL_RAIN_OF_BONES = 37098, SPELL_SUMMON_SKELETON = 30170, SPELL_DISTRACTING_ASH = 30130, // Both Phases SPELL_FIREBALL_BARRAGE = 30282 }; enum Says { EMOTE_SUMMON = 0, YELL_AGGRO = 1, YELL_AIR_PHASE = 2, YELL_LAND_PHASE = 3, EMOTE_BREATH = 4 }; enum Actions { ACTION_START_INTRO = 0 }; enum Phases { PHASE_INTRO = 0, PHASE_GROUND = 1, PHASE_FLY = 2, PHASE_TRANSITION = 3, }; enum Groups { GROUP_GROUND = 0, GROUP_AIR = 1, GROUP_LAND = 2 }; enum Points { POINT_INTRO_TAKE_OFF = 11, POINT_INTRO_PRE_LAND = 8, POINT_INTRO_LAND = 12, POINT_PRE_FLY_EAST = 21, POINT_PRE_FLY_SOUTH = 22, POINT_PRE_FLY_WEST = 23, POINT_PRE_FLY = 24, POINT_FLY = 31, POINT_LANDING_PRE = 41, POINT_LANDING_WEST = 42, POINT_PRE_LAND = 5, POINT_LAND = 51, }; float IntroWay[8][3] = { {-11053.37f, -1794.48f, 149.00f}, {-11141.07f, -1841.40f, 125.00f}, {-11187.28f, -1890.23f, 125.00f}, {-11189.20f, -1931.25f, 125.00f}, {-11153.76f, -1948.93f, 125.00f}, {-11128.73f, -1929.75f, 125.00f}, {-11140.00f, -1915.00f, 122.00f}, {-11163.00f, -1903.00f, 91.473f} }; //TODO: move to table Position const homePos = {-11003.7f, -1760.19f, 140.253f}; Position const introLandPos = {-11142.712f, -1891.193f, 92.25038f}; Position const preFlySouthPos = {-11193.77f, -1921.983f, 107.9845f}; Position const preFlyEastPos = {-11167.065f, -1976.3473f, 109.91183f}; Position const preFlyWestPos = {-11095.48f, -1866.5396f, 107.868996}; Position const preFlyPos = {-11154.900391f, -1850.670044f, 103.264999f}; Position const flyPos = {-11160.125f, -1870.683f, 97.73876f}; Position const landPos = {-11162.231f, -1900.3287f, 91.47627f}; struct boss_nightbane : public BossAI { boss_nightbane(Creature* creature) : BossAI(creature, DATA_NIGHTBANE) { _skeletonCount = 5; } void Reset() override { BossAI::Reset(); _skeletonscheduler.CancelAll(); _triggerCountTakeOffWhileFlying = 0; _airPhasesCompleted = 0; me->SetSpeed(MOVE_RUN, 2.0f); me->SetCanFly(true); me->SetUnitFlag(UNIT_FLAG_NON_ATTACKABLE); me->SetReactState(REACT_PASSIVE); ScheduleHealthCheckEvent({ 75, 50, 25 }, [&]{ TriggerHealthTakeOff(); }); } void JustReachedHome() override { BossAI::JustReachedHome(); me->DespawnOnEvade(); } void EnterEvadeMode(EvadeReason why) override { me->SetHomePosition(homePos); me->SetCanFly(true); me->SetDisableGravity(true); me->SendMovementFlagUpdate(); BossAI::EnterEvadeMode(why); } void DamageTaken(Unit* attacker, uint32& damage, DamageEffectType damageEffectType, SpellSchoolMask spellSchoolMask) override { if (_airPhasesCompleted < 3) { if (damage >= me->GetHealth()) { damage = me->GetHealth() - 1; } } BossAI::DamageTaken(attacker, damage, damageEffectType, spellSchoolMask); } void JustEngagedWith(Unit* who) override { BossAI::JustEngagedWith(who); Talk(YELL_AGGRO); ScheduleGround(); } void DoAction(int32 action) override { if (action == ACTION_START_INTRO) { me->GetMap()->SetVisibilityRange(DEFAULT_VISIBILITY_INSTANCE + 100.0f); // see nightbane me->AddUnitState(UNIT_STATE_IGNORE_PATHFINDING); _phase = PHASE_INTRO; Talk(EMOTE_SUMMON); scheduler.Schedule(2s, [this](TaskContext /*context*/) { me->SetStandState(UNIT_STAND_STATE_STAND); me->SetDisableGravity(true); me->GetMotionMaster()->MoveTakeoff(POINT_INTRO_TAKE_OFF, me->GetPositionX(), me->GetPositionY(), me->GetPositionZ() + 10.0f, 13.99879f); }).Schedule(4s, [this](TaskContext /*context*/) { me->GetMotionMaster()->MoveWaypoint(me->GetEntry()*10, false); }); } } void ScheduleGround() { scheduler.Schedule(18s, 25s, GROUP_GROUND, [this](TaskContext context) { DoCastRandomTarget(SPELL_CHARRED_EARTH, 0, 100.0f, true); context.Repeat(20s); }).Schedule(25s, 35s, GROUP_GROUND, [this](TaskContext context) { DoCastVictim(SPELL_SMOLDERING_BREATH); context.Repeat(20s); }).Schedule(45s, GROUP_GROUND, [this](TaskContext context) { DoCastAOE(SPELL_BELLOWING_ROAR); context.Repeat(32s, 40s); }).Schedule(12s, GROUP_GROUND, [this](TaskContext context) { if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 100.0f, true)) { if (!me->HasInArc(M_PI, target)) { DoCast(target, SPELL_TAIL_SWEEP); } } context.Repeat(15s); }).Schedule(5s, GROUP_GROUND, [this](TaskContext context) { DoCastVictim(SPELL_CLEAVE); context.Repeat(6s, 12s); }).Schedule(25s, GROUP_GROUND, [this](TaskContext context) { if (SelectTarget(SelectTargetMethod::MinDistance, 0, -40.0f, true)) { DoCastAOE(SPELL_FIREBALL_BARRAGE); } context.Repeat(3s); }); } void ScheduleFly() { _skeletonSpawnCounter = 0; scheduler.Schedule(2s, GROUP_AIR, [this](TaskContext /*context*/) { DoResetThreatList(); if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 100.0f)) { _skeletonSpawnPos = target->GetPosition(); me->SetFacingTo(_skeletonSpawnPos.GetOrientation()); me->CastSpell(_skeletonSpawnPos.GetPositionX(), _skeletonSpawnPos.GetPositionY(), _skeletonSpawnPos.GetPositionZ(), SPELL_RAIN_OF_BONES, true); _skeletonscheduler.Schedule(50ms, [this](TaskContext context) { //spawns skeletons every 2 seconds until skeletonCount is reached if (_skeletonSpawnCounter < _skeletonCount) { me->CastSpell(_skeletonSpawnPos.GetPositionX(), _skeletonSpawnPos.GetPositionY(), _skeletonSpawnPos.GetPositionZ(), SPELL_SUMMON_SKELETON, true); _skeletonSpawnCounter++; context.Repeat(2s); } }); } }).Schedule(15s, GROUP_AIR, [this](TaskContext context) { if (Unit* target = SelectTarget(SelectTargetMethod::Random)) { DoCast(target, SPELL_SMOKING_BLAST_T); } context.Repeat(6s); }).Schedule(15500ms, GROUP_AIR, [this](TaskContext context) { me->SetFacingToObject(me->GetVictim()); DoCastVictim(SPELL_SMOKING_BLAST); context.Repeat(1500ms); }).Schedule(20s, GROUP_AIR, [this](TaskContext /*context*/) { if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 100.0f, true)) { DoCast(target, SPELL_DISTRACTING_ASH); } }).Schedule(14s, GROUP_AIR, [this](TaskContext context) { if (SelectTarget(SelectTargetMethod::MinDistance, 0, -40.0f, true)) { DoCastAOE(SPELL_FIREBALL_BARRAGE); } context.Repeat(3s); }); } void AttackStart(Unit* who) override { if (_phase == PHASE_GROUND) ScriptedAI::AttackStart(who); } void MoveInLineOfSight(Unit* who) override { if (_phase == PHASE_GROUND) ScriptedAI::MoveInLineOfSight(who); } void PathEndReached(uint32 pathId) override { BossAI::PathEndReached(pathId); if (pathId == me->GetEntry()*10) // intro { me->GetMap()->SetVisibilityRange(DEFAULT_VISIBILITY_INSTANCE); // restore visibility scheduler.Schedule(0s, [this](TaskContext /*context*/) { me->ClearUnitState(UNIT_STATE_IGNORE_PATHFINDING); me->GetMotionMaster()->MovePoint(POINT_INTRO_LAND, introLandPos); }).Schedule(3s, [this](TaskContext /*context*/) { me->SetDisableGravity(false); me->SetCanFly(false); me->HandleEmoteCommand(EMOTE_ONESHOT_LAND); me->RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NOT_SELECTABLE | UNIT_FLAG_IMMUNE_TO_PC); _phase = PHASE_GROUND; me->SetReactState(REACT_AGGRESSIVE); me->SetInCombatWithZone(); }).Schedule(8s, [this](TaskContext /*context*/) { if (!SelectTargetFromPlayerList(45.0f)) { EnterEvadeMode(EVADE_REASON_NO_HOSTILES); } }); } else if (pathId == me->GetEntry()*10+1) // landing { _airPhasesCompleted++; if (_triggerCountTakeOffWhileFlying > 0) { _triggerCountTakeOffWhileFlying--; scheduler.Schedule(0s, [this](TaskContext /*context*/) { me->GetMotionMaster()->MovePoint(POINT_PRE_FLY_SOUTH, preFlySouthPos); }); } else { scheduler.Schedule(0s, [this](TaskContext /*context*/) { DoResetThreatList(); me->GetMotionMaster()->MovePoint(POINT_LAND, landPos); me->SetDisableGravity(false); me->SetCanFly(false); me->HandleEmoteCommand(EMOTE_ONESHOT_LAND); _phase = PHASE_GROUND; ScheduleGround(); }); } } } void MovementInform(uint32 type, uint32 id) override { if (type != POINT_MOTION_TYPE) return; switch (id) { case POINT_INTRO_TAKE_OFF: break; case POINT_INTRO_LAND: DoStartMovement(me->GetVictim()); break; case POINT_PRE_FLY_EAST: scheduler.Schedule(0s, [this](TaskContext /*context*/) { me->GetMotionMaster()->MovePoint(POINT_PRE_FLY_SOUTH, preFlySouthPos); }); break; case POINT_PRE_FLY_SOUTH: case POINT_PRE_FLY_WEST: scheduler.Schedule(0s, [this](TaskContext /*context*/) { me->GetMotionMaster()->MovePoint(POINT_PRE_FLY, preFlyPos); }); break; case POINT_PRE_FLY: scheduler.Schedule(0s, [this](TaskContext /*context*/) { me->GetMotionMaster()->MovePoint(POINT_FLY, flyPos); }); break; case POINT_FLY: _phase = PHASE_FLY; Talk(EMOTE_BREATH); ScheduleFly(); ScheduleLand(); break; case POINT_LANDING_PRE: scheduler.Schedule(0s, [this](TaskContext /*context*/) { me->GetMotionMaster()->MovePoint(POINT_LANDING_WEST, preFlyWestPos); }); break; case POINT_LANDING_WEST: if (_triggerCountTakeOffWhileFlying > 0) { _airPhasesCompleted++; _triggerCountTakeOffWhileFlying--; scheduler.Schedule(0s, [this](TaskContext /*context*/) { me->GetMotionMaster()->MovePoint(POINT_PRE_FLY, preFlyPos); }); } else { scheduler.Schedule(0s, [this](TaskContext /*context*/) { me->GetMotionMaster()->MoveWaypoint(me->GetEntry()*10+1, false); }); } break; case POINT_LAND: DoStartMovement(me->GetVictim()); break; } } void JustSummoned(Creature* summon) override { summon->AI()->AttackStart(me->GetVictim()); summons.Summon(summon); } void TriggerHealthTakeOff() { if (_phase != PHASE_GROUND) { _triggerCountTakeOffWhileFlying++; return; } _phase = PHASE_TRANSITION; Talk(YELL_AIR_PHASE); scheduler.CancelGroup(GROUP_GROUND); me->InterruptSpell(CURRENT_GENERIC_SPELL); me->HandleEmoteCommand(EMOTE_ONESHOT_LIFTOFF); me->SetDisableGravity(true); me->SetCanFly(true); me->SendMovementFlagUpdate(); me->HandleEmoteCommand(EMOTE_ONESHOT_LIFTOFF); FlyToClosestPreFlyWayPoint(); } void FlyToClosestPreFlyWayPoint() { Position closestWP = preFlyPos; if (me->GetDistance(preFlyEastPos) < me->GetDistance(closestWP)) closestWP = preFlyEastPos; if (me->GetDistance(preFlySouthPos) < me->GetDistance(closestWP)) closestWP = preFlySouthPos; if (me->GetDistance(preFlyWestPos) < me->GetDistance(closestWP)) closestWP = preFlyWestPos; me->GetMotionMaster()->Clear(false); if (closestWP == preFlyPos) me->GetMotionMaster()->MovePoint(POINT_PRE_FLY, closestWP); else if (closestWP == preFlyEastPos) me->GetMotionMaster()->MovePoint(POINT_PRE_FLY_EAST, closestWP); else if (closestWP == preFlySouthPos) me->GetMotionMaster()->MovePoint(POINT_PRE_FLY_SOUTH, closestWP); else if (closestWP == preFlyWestPos) me->GetMotionMaster()->MovePoint(POINT_PRE_FLY_WEST, closestWP); } void ScheduleLand() { scheduler.Schedule(35s, GROUP_LAND, [this](TaskContext) /*context*/ { Talk(YELL_LAND_PHASE); scheduler.CancelGroup(GROUP_AIR); _phase = PHASE_TRANSITION; me->GetMotionMaster()->Clear(false); me->GetMotionMaster()->MovePoint(POINT_LANDING_PRE, preFlyPos); }); } void UpdateAI(uint32 diff) override { scheduler.Update(diff); _skeletonscheduler.Update(diff); if (!UpdateVictim()) return; if (_phase == PHASE_GROUND) { DoMeleeAttackIfReady(); } } private: uint8 _phase; uint8 _airPhasesCompleted; uint8 _triggerCountTakeOffWhileFlying; TaskScheduler _skeletonscheduler; uint8 _skeletonCount; uint8 _skeletonSpawnCounter; Position _skeletonSpawnPos; }; class go_blackened_urn : public GameObjectScript { public: go_blackened_urn() : GameObjectScript("go_blackened_urn") { } bool OnGossipHello(Player* /*player*/, GameObject* go) override { if (InstanceScript* instance = go->GetInstanceScript()) { if (instance->GetBossState(DATA_NIGHTBANE) == NOT_STARTED) { if (Creature* nightbane = instance->GetCreature(DATA_NIGHTBANE)) { if (nightbane->IsAlive()) { nightbane->AI()->DoAction(ACTION_START_INTRO); return true; } } } } return false; } }; struct npc_nightbane_helper_target : public NullCreatureAI { npc_nightbane_helper_target(Creature* creature) : NullCreatureAI(creature) { me->SetDisableGravity(true); } }; // 30282 - Fireball Barrage class spell_nightbane_fireball_barrage : public SpellScript { PrepareSpellScript(spell_nightbane_fireball_barrage); void FilterTargets(std::list& targets) { Unit* caster = GetCaster(); targets.remove_if([&](WorldObject* target) -> bool { return !target->IsPlayer() || caster->IsWithinCombatRange(target->ToUnit(), 40.0f); }); } void Register() override { OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_nightbane_fireball_barrage::FilterTargets, EFFECT_0, TARGET_UNIT_SRC_AREA_ENEMY); } }; void AddSC_boss_nightbane() { RegisterKarazhanCreatureAI(boss_nightbane); new go_blackened_urn(); RegisterKarazhanCreatureAI(npc_nightbane_helper_target); RegisterSpellScript(spell_nightbane_fireball_barrage); }