/* * 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 "CellImpl.h" #include "CreatureScript.h" #include "GridNotifiers.h" #include "GridNotifiersImpl.h" #include "PassiveAI.h" #include "ScriptedCreature.h" #include "SpellScript.h" #include "SpellScriptLoader.h" #include "zulaman.h" enum Yells { SAY_AGGRO = 0, SAY_FIRE_BOMBS = 1, SAY_SUMMON_HATCHER = 2, SAY_ALL_EGGS = 3, SAY_BERSERK = 4, SAY_SLAY = 5, SAY_DEATH = 6, SAY_EVENT_STRANGERS = 7, SAY_EVENT_FRIENDS = 8 }; enum Spells { // Jan'alai SPELL_FLAME_BREATH = 43140, SPELL_FIRE_WALL = 43113, SPELL_ENRAGE = 44779, SPELL_SUMMON_PLAYERS_DUMMY = 43096, SPELL_SUMMON_PLAYERS = 43097, SPELL_TELE_TO_CENTER = 43098, // coord SPELL_HATCH_ALL = 43144, SPELL_BERSERK = 45078, // Fire Bob Spells SPELL_FIRE_BOMB_CHANNEL = 42621, // last forever SPELL_FIRE_BOMB_THROW = 42628, // throw visual SPELL_FIRE_BOMB_DUMMY = 42629, // bomb visual SPELL_FIRE_BOMB_DAMAGE = 42630, // Hatcher Spells SPELL_HATCH_EGG_ALL = 42471, SPELL_HATCH_EGG_SINGULAR = 43734, SPELL_SUMMON_HATCHLING = 42493, // Hatchling Spells SPELL_FLAMEBUFFET = 43299 }; enum Creatures { NPC_AMANI_HATCHER = 23818, NPC_EGG = 23817, NPC_FIRE_BOMB = 23920 }; const int area_dx = 44; const int area_dy = 51; const Position janalainPos = {-33.93f, 1149.27f, 19.0f, 0.0f}; const Position fireWallCoords[4] = { {-10.13f, 1149.27f, 19, 3.1415f}, {-33.93f, 1123.90f, 19, 0.5f * 3.1415f}, {-54.80f, 1150.08f, 19, 0.0f}, {-33.93f, 1175.68f, 19, 1.5f * 3.1415f} }; const Position hatcherway[2][5] = { { {-87.46f, 1170.09f, 6.0f, 0.0f}, {-74.41f, 1154.75f, 6.0f, 0.0f}, {-52.74f, 1153.32f, 19.0f, 0.0f}, {-33.37f, 1172.46f, 19.0f, 0.0f}, {-33.09f, 1203.87f, 19.0f, 0.0f} }, { {-86.57f, 1132.85f, 6.0f, 0.0f}, {-73.94f, 1146.00f, 6.0f, 0.0f}, {-52.29f, 1146.51f, 19.0f, 0.0f}, {-33.57f, 1125.72f, 19.0f, 0.0f}, {-34.29f, 1095.22f, 19.0f, 0.0f} } }; enum HatchActions { HATCH_RESET = 0, HATCH_ALL = 1 }; enum Misc { MAX_BOMB_COUNT = 40, GROUP_ENRAGE = 1, GROUP_HATCHING = 2, DATA_ALL_EGGS_HATCHED = 0 }; struct boss_janalai : public BossAI { boss_janalai(Creature* creature) : BossAI(creature, DATA_JANALAI) { scheduler.SetValidator([this] { return !me->HasUnitState(UNIT_STATE_CASTING); }); } void Reset() override { BossAI::Reset(); HatchAllEggs(HATCH_RESET); _isBombing = false; _isFlameBreathing = false; ScheduleHealthCheckEvent(35, [&]{ Talk(SAY_ALL_EGGS); me->AttackStop(); me->GetMotionMaster()->Clear(); me->SetPosition(janalainPos); me->StopMovingOnCurrentPos(); DoCastAOE(SPELL_HATCH_ALL); }); ScheduleHealthCheckEvent(20, [&] { if (!me->HasAura(SPELL_ENRAGE)) DoCastSelf(SPELL_ENRAGE, true); me->m_Events.CancelEventGroup(GROUP_ENRAGE); }); me->m_Events.KillAllEvents(false); _sideHatched[0] = false; _sideHatched[1] = false; } void JustDied(Unit* killer) override { Talk(SAY_DEATH); BossAI::JustDied(killer); } void JustSummoned(Creature* summon) override { if (summon->GetEntry() == NPC_AMANI_HATCHLING) { if (summon->GetPositionY() > 1150) summon->GetMotionMaster()->MovePoint(0, hatcherway[0][3].GetPositionX() + rand() % 4 - 2, 1150.0f + rand() % 4 - 2, hatcherway[0][3].GetPositionY()); else summon->GetMotionMaster()->MovePoint(0, hatcherway[1][3].GetPositionX() + rand() % 4 - 2, 1150.0f + rand() % 4 - 2, hatcherway[1][3].GetPositionY()); } BossAI::JustSummoned(summon); } void DamageDealt(Unit* target, uint32& damage, DamageEffectType /*damagetype*/, SpellSchoolMask /*damageSchoolMask*/) override { if (_isFlameBreathing) { if (!me->HasInArc(M_PI / 6, target)) damage = 0; } } void JustEngagedWith(Unit* who) override { BossAI::JustEngagedWith(who); Talk(SAY_AGGRO); //schedule abilities ScheduleTimedEvent(30s, [&]{ StartBombing(); }, 20s, 40s); scheduler.Schedule(10s, GROUP_HATCHING, [this](TaskContext context) { if (_sideHatched[0] && _sideHatched[1]) return; Talk(SAY_SUMMON_HATCHER); if (_sideHatched[0] && !_sideHatched[1]) { me->SummonCreature(NPC_AMANI_HATCHER, hatcherway[1][0], TEMPSUMMON_CORPSE_TIMED_DESPAWN, 10000); me->SummonCreature(NPC_AMANI_HATCHER, hatcherway[1][0], TEMPSUMMON_CORPSE_TIMED_DESPAWN, 10000); } else if (!_sideHatched[0] && _sideHatched[1]) { me->SummonCreature(NPC_AMANI_HATCHER, hatcherway[0][0], TEMPSUMMON_CORPSE_TIMED_DESPAWN, 10000); me->SummonCreature(NPC_AMANI_HATCHER, hatcherway[0][0], TEMPSUMMON_CORPSE_TIMED_DESPAWN, 10000); } else { me->SummonCreature(NPC_AMANI_HATCHER, hatcherway[0][0], TEMPSUMMON_CORPSE_TIMED_DESPAWN, 10000); me->SummonCreature(NPC_AMANI_HATCHER, hatcherway[1][0], TEMPSUMMON_CORPSE_TIMED_DESPAWN, 10000); } context.Repeat(90s); }); ScheduleTimedEvent(8s, [&]{ if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0)) { me->AttackStop(); me->GetMotionMaster()->Clear(); DoCast(target, SPELL_FLAME_BREATH); me->StopMoving(); _isFlameBreathing = true; // placeholder time idk yet scheduler.Schedule(2s, [this](TaskContext) { _isFlameBreathing = false; }); } }, 8s); me->m_Events.AddEventAtOffset([&] { DoCastSelf(SPELL_ENRAGE, true); }, 5min, 5min, GROUP_ENRAGE); me->m_Events.AddEventAtOffset([&] { Talk(SAY_BERSERK); DoCastSelf(SPELL_BERSERK); }, 10min); } void SetData(uint32 index, uint32 data) override { if (index == DATA_ALL_EGGS_HATCHED) _sideHatched[data] = true; } bool HatchAllEggs(uint32 hatchAction) { std::list eggList; me->GetCreaturesWithEntryInRange(eggList, 100.0f, NPC_EGG); if (eggList.empty()) return false; if (hatchAction == HATCH_RESET) { for (Creature* egg : eggList) egg->Respawn(); summons.DespawnEntry(NPC_AMANI_HATCHLING); } else if (hatchAction == HATCH_ALL) DoCastSelf(SPELL_HATCH_EGG_ALL); eggList.clear(); return true; } void FireWall() { for (uint8 i = 0; i < 4; ++i) { uint8 wallNum = i == 0 || i == 2 ? 3 : 2; for (uint8 j = 0; j < wallNum; j++) { Creature* wall = wallNum == 3 ? me->SummonCreature(NPC_FIRE_BOMB, fireWallCoords[i].GetPositionX(), fireWallCoords[i].GetPositionY() + 5 * (j - 1), fireWallCoords[i].GetPositionZ(), fireWallCoords[i].GetOrientation(), TEMPSUMMON_TIMED_DESPAWN, 15000) : me->SummonCreature(NPC_FIRE_BOMB, fireWallCoords[i].GetPositionX() - 2 + 4 * j, fireWallCoords[i].GetPositionY(), fireWallCoords[i].GetPositionZ(), fireWallCoords[i].GetOrientation(), TEMPSUMMON_TIMED_DESPAWN, 15000); if (wall) wall->AI()->DoCastSelf(SPELL_FIRE_WALL, true); } } } void SpawnBombs() { float dx, dy; for (int i = 0; i < MAX_BOMB_COUNT; ++i) { dx = float(irand(-area_dx / 2, area_dx / 2)); dy = float(irand(-area_dy / 2, area_dy / 2)); DoSpawnCreature(NPC_FIRE_BOMB, dx, dy, 0, 0, TEMPSUMMON_TIMED_DESPAWN, 15000); } } void Boom() { summons.DoForAllSummons([&](WorldObject* summon) { if (summon->GetEntry() == NPC_FIRE_BOMB) { if (Creature* bomb = summon->ToCreature()) { bomb->AI()->DoCastSelf(SPELL_FIRE_BOMB_DAMAGE, true); bomb->RemoveAllAuras(); } } }); } void StartBombing() { Talk(SAY_FIRE_BOMBS); me->AttackStop(); me->GetMotionMaster()->Clear(); me->SetPosition(janalainPos); me->StopMovingOnCurrentPos(); DoCastSelf(SPELL_FIRE_BOMB_CHANNEL); FireWall(); SpawnBombs(); _isBombing = true; DoCastSelf(SPELL_TELE_TO_CENTER); DoCastAOE(SPELL_SUMMON_PLAYERS_DUMMY, true); //DoCast(Temp, SPELL_SUMMON_PLAYERS, true) // core bug, spell does not work if too far ThrowBombs(); scheduler.Schedule(11s, [this](TaskContext) { Boom(); _isBombing = false; me->RemoveAurasDueToSpell(SPELL_FIRE_BOMB_CHANNEL); }); } void ThrowBombs() { std::chrono::milliseconds bombTimer = 100ms; summons.DoForAllSummons([this, &bombTimer](WorldObject* summon) { if (summon->GetEntry() == NPC_FIRE_BOMB) { if (Creature* bomb = summon->ToCreature()) { bomb->m_Events.AddEventAtOffset([this, bomb] { bomb->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE); DoCast(bomb, SPELL_FIRE_BOMB_THROW, true); bomb->SetUnitFlag(UNIT_FLAG_NOT_SELECTABLE); }, bombTimer); } bombTimer += 100ms; } }); } bool CheckEvadeIfOutOfCombatArea() const override { return me->GetPositionZ() <= 12.0f; } private: bool _isBombing; bool _isFlameBreathing; bool _sideHatched[2]; }; struct npc_janalai_hatcher : public ScriptedAI { npc_janalai_hatcher(Creature* creature) : ScriptedAI(creature) { } void Reset() override { ScriptedAI::Reset(); scheduler.CancelAll(); _side = (me->GetPositionY() < 1150); _waypoint = 0; _repeatCount = 1; _isHatching = false; me->GetMotionMaster()->Clear(); me->GetMotionMaster()->MovePoint(0, hatcherway[_side][0]); } void MovementInform(uint32, uint32) override { if (_waypoint == 5) { _isHatching = true; scheduler.Schedule(1500ms, [this](TaskContext context) { me->CastCustomSpell(SPELL_HATCH_EGG_ALL, SPELLVALUE_MAX_TARGETS, _repeatCount); ++_repeatCount; if (me->FindNearestCreature(NPC_EGG, 100.0f)) context.Repeat(5s); else { if (WorldObject* summoner = GetSummoner()) if (Creature* janalai = summoner->ToCreature()) janalai->AI()->SetData(DATA_ALL_EGGS_HATCHED, _side); _side = _side ? 0 : 1; _isHatching = false; _waypoint = 3; MoveToNewWaypoint(_waypoint); } }); } else { MoveToNewWaypoint(_waypoint); ++_waypoint; } } void MoveToNewWaypoint(uint8 waypoint) { if (!_isHatching) { scheduler.Schedule(100ms, [this, waypoint](TaskContext) { me->GetMotionMaster()->Clear(); me->GetMotionMaster()->MovePoint(0, hatcherway[_side][waypoint]); }); } } void UpdateAI(uint32 diff) override { scheduler.Update(diff); } void JustEngagedWith(Unit* /*who*/) override { } void AttackStart(Unit* /*who*/) override { } void MoveInLineOfSight(Unit* /*who*/) override { } private: uint8 _side; uint8 _waypoint; uint32 _repeatCount; bool _isHatching; }; class spell_summon_all_players_dummy: public SpellScript { PrepareSpellScript(spell_summon_all_players_dummy); bool Validate(SpellInfo const* /*spell*/) override { return ValidateSpellInfo({ SPELL_SUMMON_PLAYERS }); } void FilterTargets(std::list& targets) { Position pos = GetCaster()->GetPosition(); targets.remove_if([&, pos](WorldObject* target) -> bool { return target->IsWithinBox(pos, 22.0f, 28.0f, 28.0f); }); } void OnHit(SpellEffIndex /*effIndex*/) { GetCaster()->CastSpell(GetHitUnit(), SPELL_SUMMON_PLAYERS, true); } void Register() override { OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_summon_all_players_dummy::FilterTargets, EFFECT_0, TARGET_UNIT_SRC_AREA_ENEMY); OnEffectHitTarget += SpellEffectFn(spell_summon_all_players_dummy::OnHit, EFFECT_0, SPELL_EFFECT_DUMMY); } }; void AddSC_boss_janalai() { RegisterZulAmanCreatureAI(boss_janalai); RegisterZulAmanCreatureAI(npc_janalai_hatcher); RegisterSpellScript(spell_summon_all_players_dummy); }