/* * 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 Affero General Public License as published by the * Free Software Foundation; either version 3 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 Affero 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 "Player.h" #include "ScriptMgr.h" #include "ScriptedCreature.h" #include "SpellScript.h" #include "naxxramas.h" enum Yells { EMOTE_AIR_PHASE = 0, EMOTE_GROUND_PHASE = 1, EMOTE_BREATH = 2, EMOTE_ENRAGE = 3 }; enum Spells { // Fight SPELL_FROST_AURA = 28531, SPELL_CLEAVE = 19983, SPELL_TAIL_SWEEP = 15847, SPELL_SUMMON_BLIZZARD = 28560, SPELL_LIFE_DRAIN = 28542, SPELL_BERSERK = 26662, // Ice block SPELL_ICEBOLT_CAST = 28526, SPELL_ICEBOLT_TRIGGER = 28522, SPELL_FROST_MISSILE = 30101, SPELL_FROST_EXPLOSION = 28524, // Visuals SPELL_SAPPHIRON_DIES = 29357, // 10 and 25 Man Spells SPELL_FROST_AURA_10 = 28531, SPELL_FROST_AURA_25 = 55799, SPELL_TAIL_SWEEP_10 = 55697, SPELL_TAIL_SWEEP_25 = 55696, SPELL_LIFE_DRAIN_10 = 28542, SPELL_LIFE_DRAIN_25 = 55665, }; enum Misc { GO_ICE_BLOCK = 181247, NPC_BLIZZARD = 16474, POINT_CENTER = 1 }; enum Events { EVENT_BERSERK = 1, EVENT_CLEAVE = 2, EVENT_TAIL_SWEEP = 3, EVENT_LIFE_DRAIN = 4, EVENT_BLIZZARD = 5, EVENT_FLIGHT_START = 6, EVENT_FLIGHT_LIFTOFF = 7, EVENT_FLIGHT_ICEBOLT = 8, EVENT_FLIGHT_BREATH = 9, EVENT_FLIGHT_SPELL_EXPLOSION = 10, EVENT_FLIGHT_START_LAND = 11, EVENT_LAND = 12, EVENT_GROUND = 13, EVENT_HUNDRED_CLUB = 14 }; // Unlike other Naxx 40 scripts, this overwrites all versions of the UI // This is due to AI casting used in the spell script class boss_sapphiron_40 : public CreatureScript { private: static bool isNaxx40Sapp(uint32 entry) { return (entry == NPC_SAPPHIRON_40); } public: boss_sapphiron_40() : CreatureScript("boss_sapphiron") { } CreatureAI* GetAI(Creature* pCreature) const override { return GetNaxxramasAI(pCreature); } struct boss_sapphiron_40AI : public BossAI { explicit boss_sapphiron_40AI(Creature* c) : BossAI(c, BOSS_SAPPHIRON) { pInstance = me->GetInstanceScript(); } EventMap events; InstanceScript* pInstance; uint8 iceboltCount{}; uint32 spawnTimer{}; GuidList blockList; ObjectGuid currentTarget; void InitializeAI() override { me->SummonGameObject(GO_SAPPHIRON_BIRTH, me->GetPositionX(), me->GetPositionY(), me->GetPositionZ(), 0, 0, 0, 0, 0, 0); me->SetVisible(false); me->SetUnitFlag(UNIT_FLAG_NON_ATTACKABLE); me->SetReactState(REACT_PASSIVE); ScriptedAI::InitializeAI(); } bool IsInRoom() { if (me->GetExactDist(3523.5f, -5235.3f, 137.6f) > 100.0f) { EnterEvadeMode(); return false; } return true; } void Reset() override { BossAI::Reset(); if (me->IsVisible()) { me->SetReactState(REACT_AGGRESSIVE); } events.Reset(); iceboltCount = 0; spawnTimer = 0; currentTarget.Clear(); blockList.clear(); } void EnterCombatSelfFunction() { Map::PlayerList const& PlList = me->GetMap()->GetPlayers(); if (PlList.IsEmpty()) return; for (const auto& i : PlList) { if (Player* player = i.GetSource()) { if (player->IsGameMaster()) continue; if (player->IsAlive() && me->GetDistance(player) < 80.0f) { me->SetInCombatWith(player); player->SetInCombatWith(me); me->AddThreat(player, 0.0f); } } } } void EnterCombat(Unit* who) override { BossAI::EnterCombat(who); EnterCombatSelfFunction(); if (isNaxx40Sapp(me->GetEntry())) { me->CastSpell(me, SPELL_FROST_AURA, true); } else { me->CastSpell(me, RAID_MODE(SPELL_FROST_AURA_10, SPELL_FROST_AURA_25), true); } events.ScheduleEvent(EVENT_BERSERK, 900000); events.ScheduleEvent(EVENT_CLEAVE, 5000); events.ScheduleEvent(EVENT_TAIL_SWEEP, 10000); events.ScheduleEvent(EVENT_LIFE_DRAIN, 17000); events.ScheduleEvent(EVENT_BLIZZARD, 17000); events.ScheduleEvent(EVENT_FLIGHT_START, 45000); events.ScheduleEvent(EVENT_HUNDRED_CLUB, 5000); } void JustDied(Unit* killer) override { BossAI::JustDied(killer); me->CastSpell(me, SPELL_SAPPHIRON_DIES, true); } void DoAction(int32 param) override { if (param == ACTION_SAPPHIRON_BIRTH) { spawnTimer = 1; } } void MovementInform(uint32 type, uint32 id) override { if (type == POINT_MOTION_TYPE && id == POINT_CENTER) { events.ScheduleEvent(EVENT_FLIGHT_LIFTOFF, 500); } } void SpellHitTarget(Unit* target, SpellInfo const* spellInfo) override { if (spellInfo->Id == SPELL_ICEBOLT_CAST) { me->CastSpell(target, SPELL_ICEBOLT_TRIGGER, true); } } bool IsValidExplosionTarget(WorldObject* target) { for (ObjectGuid const& guid : blockList) { if (target->GetGUID() == guid) return false; if (Unit* block = ObjectAccessor::GetUnit(*me, guid)) { if (block->IsInBetween(me, target, 2.0f) && block->IsWithinDist(target, 10.0f)) return false; } } return true; } void KilledUnit(Unit* who) override { if (who->GetTypeId() == TYPEID_PLAYER && pInstance) { pInstance->SetData(DATA_IMMORTAL_FAIL, 0); } } void UpdateAI(uint32 diff) override { if (spawnTimer) { spawnTimer += diff; if (spawnTimer >= 21500) { me->SetVisible(true); me->RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE); me->SetReactState(REACT_AGGRESSIVE); spawnTimer = 0; } return; } if (!IsInRoom()) return; if (!UpdateVictim()) return; events.Update(diff); if (me->HasUnitState(UNIT_STATE_CASTING)) return; switch (events.ExecuteEvent()) { case EVENT_BERSERK: Talk(EMOTE_ENRAGE); me->CastSpell(me, SPELL_BERSERK, true); return; case EVENT_CLEAVE: me->CastSpell(me->GetVictim(), SPELL_CLEAVE, false); events.RepeatEvent(10000); return; case EVENT_TAIL_SWEEP: if (isNaxx40Sapp(me->GetEntry())) { me->CastSpell(me, SPELL_TAIL_SWEEP, false); } else { me->CastSpell(me, RAID_MODE(SPELL_TAIL_SWEEP_10, SPELL_TAIL_SWEEP_25), false); } events.RepeatEvent(10000); return; case EVENT_LIFE_DRAIN: if (isNaxx40Sapp(me->GetEntry())) { me->CastCustomSpell(SPELL_LIFE_DRAIN, SPELLVALUE_MAX_TARGETS, 5, me, false); } else { me->CastCustomSpell(RAID_MODE(SPELL_LIFE_DRAIN_10, SPELL_LIFE_DRAIN_25), SPELLVALUE_MAX_TARGETS, RAID_MODE(2, 5), me, false); } events.RepeatEvent(24000); return; case EVENT_BLIZZARD: { Creature* cr; if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 40.0f, true)) { cr = me->SummonCreature(NPC_BLIZZARD, *target, TEMPSUMMON_TIMED_DESPAWN, 16000); } else { cr = me->SummonCreature(NPC_BLIZZARD, *me, TEMPSUMMON_TIMED_DESPAWN, 16000); } if (cr) { cr->GetMotionMaster()->MoveRandom(40); } if (isNaxx40Sapp(me->GetEntry())) { events.RepeatEvent(6500); } else { events.RepeatEvent(RAID_MODE(8000, 6500)); } return; } case EVENT_FLIGHT_START: if (me->HealthBelowPct(11)) { return; } events.RepeatEvent(45000); events.DelayEvents(35000); me->SetReactState(REACT_PASSIVE); me->AttackStop(); float x, y, z, o; me->GetHomePosition(x, y, z, o); me->GetMotionMaster()->MovePoint(POINT_CENTER, x, y, z); return; case EVENT_FLIGHT_LIFTOFF: Talk(EMOTE_AIR_PHASE); me->GetMotionMaster()->MoveIdle(); me->SendMeleeAttackStop(me->GetVictim()); me->HandleEmoteCommand(EMOTE_ONESHOT_LIFTOFF); me->SetDisableGravity(true); currentTarget.Clear(); events.ScheduleEvent(EVENT_FLIGHT_ICEBOLT, 3000); if (isNaxx40Sapp(me->GetEntry())) { iceboltCount = 3; } else { iceboltCount = RAID_MODE(2, 3); } return; case EVENT_FLIGHT_ICEBOLT: { if (currentTarget) { if (Unit* target = ObjectAccessor::GetUnit(*me, currentTarget)) { me->SummonGameObject(GO_ICE_BLOCK, target->GetPositionX(), target->GetPositionY(), target->GetPositionZ(), target->GetOrientation(), 0.0f, 0.0f, 0.0f, 0.0f, 0); } } std::vector targets; auto i = me->GetThreatMgr().GetThreatList().begin(); for (; i != me->GetThreatMgr().GetThreatList().end(); ++i) { if ((*i)->getTarget()->GetTypeId() == TYPEID_PLAYER) { bool inList = false; if (!blockList.empty()) { for (GuidList::const_iterator itr = blockList.begin(); itr != blockList.end(); ++itr) { if ((*i)->getTarget()->GetGUID() == *itr) { inList = true; break; } } } if (!inList) { targets.push_back((*i)->getTarget()); } } } if (!targets.empty() && iceboltCount) { auto itr = targets.begin(); advance(itr, urand(0, targets.size() - 1)); me->CastSpell(*itr, SPELL_ICEBOLT_CAST, false); blockList.push_back((*itr)->GetGUID()); currentTarget = (*itr)->GetGUID(); --iceboltCount; events.ScheduleEvent(EVENT_FLIGHT_ICEBOLT, (me->GetExactDist(*itr) / 13.0f)*IN_MILLISECONDS); } else { events.ScheduleEvent(EVENT_FLIGHT_BREATH, 1000); } return; } case EVENT_FLIGHT_BREATH: currentTarget.Clear(); Talk(EMOTE_BREATH); me->CastSpell(me, SPELL_FROST_MISSILE, false); events.ScheduleEvent(EVENT_FLIGHT_SPELL_EXPLOSION, 8500); return; case EVENT_FLIGHT_SPELL_EXPLOSION: me->CastSpell(me, SPELL_FROST_EXPLOSION, true); events.ScheduleEvent(EVENT_FLIGHT_START_LAND, 3000); return; case EVENT_FLIGHT_START_LAND: if (!blockList.empty()) { for (GuidList::const_iterator itr = blockList.begin(); itr != blockList.end(); ++itr) { if (Unit* block = ObjectAccessor::GetUnit(*me, *itr)) { block->RemoveAurasDueToSpell(SPELL_ICEBOLT_TRIGGER); } } } blockList.clear(); me->RemoveAllGameObjects(); events.ScheduleEvent(EVENT_LAND, 1000); return; case EVENT_LAND: me->HandleEmoteCommand(EMOTE_ONESHOT_LAND); me->SetDisableGravity(false); events.ScheduleEvent(EVENT_GROUND, 1500); return; case EVENT_GROUND: Talk(EMOTE_GROUND_PHASE); me->SetReactState(REACT_AGGRESSIVE); me->SetInCombatWithZone(); return; case EVENT_HUNDRED_CLUB: { Map::PlayerList const& pList = me->GetMap()->GetPlayers(); for (const auto& itr : pList) { if (itr.GetSource()->GetResistance(SPELL_SCHOOL_FROST) > 100 && pInstance) { pInstance->SetData(DATA_HUNDRED_CLUB, 0); return; } } events.RepeatEvent(5000); return; } } DoMeleeAttackIfReady(); } }; }; // This will overwrite the declared 10 and 25 man frost explosion to handle all versions of the spell script class spell_sapphiron_frost_explosion_40 : public SpellScriptLoader { public: spell_sapphiron_frost_explosion_40() : SpellScriptLoader("spell_sapphiron_frost_explosion") { } class spell_sapphiron_frost_explosion_40_SpellScript : public SpellScript { PrepareSpellScript(spell_sapphiron_frost_explosion_40_SpellScript); void FilterTargets(std::list& targets) { Unit* caster = GetCaster(); if (!caster || !caster->ToCreature()) return; std::list tmplist; for (auto& target : targets) { if (CAST_AI(boss_sapphiron_40::boss_sapphiron_40AI, caster->ToCreature()->AI())->IsValidExplosionTarget(target)) { tmplist.push_back(target); } } targets.clear(); for (auto& itr : tmplist) { targets.push_back(itr); } } void Register() override { OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_sapphiron_frost_explosion_40_SpellScript::FilterTargets, EFFECT_0, TARGET_UNIT_DEST_AREA_ENEMY); } }; SpellScript* GetSpellScript() const override { return new spell_sapphiron_frost_explosion_40_SpellScript(); } }; void AddSC_boss_sapphiron_40() { new boss_sapphiron_40(); new spell_sapphiron_frost_explosion_40(); }