diff --git a/src/server/scripts/Kalimdor/HallsOfOrigination/boss_ammunae.cpp b/src/server/scripts/Kalimdor/HallsOfOrigination/boss_ammunae.cpp new file mode 100644 index 00000000000..3ac91f8beb0 --- /dev/null +++ b/src/server/scripts/Kalimdor/HallsOfOrigination/boss_ammunae.cpp @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2008-2018 TrinityCore + * + * 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 "halls_of_origination.h" +#include "ScriptMgr.h" +#include "SpellMgr.h" +#include "ScriptedCreature.h" +#include "SpellScript.h" + +enum Spells +{ + // Ammunae + SPELL_ZERO_ENERGY = 72242, + SPELL_WITHER = 76043, + SPELL_CONSUME_LIFE_ENERGY = 75725, + SPELL_RAMPANT_GROWTH = 75790, + SPELL_SUMMON_SEEDLING_POD = 75621, // summons 40550 (normal) or 51329 (heroic) + SPELL_SUMMON_SPORE = 75695, // summons 40585 + + // Consume Life Energy spell + SPELL_CONSUME_LIFE_ENERGY_LEECH = 79768, + SPELL_CONSUME_LIFE_ENERGY_ENERGIZE = 75665, + + // Seedling Pod npc + SPELL_SEEDLING_POD_ENERGIZE = 75708, + SPELL_FORCECAST_SUMMON_BLOSSOM = 75774, // forces Seedling Pods to cast 75771 + SPELL_SUMMON_BLOODPETAL_BLOSSOM = 75771 // summons 40620 +}; + +enum Texts +{ + SAY_DEATH = 0, + SAY_AGGRO = 1, + SAY_SPECIAL = 2, + SAY_PLAYER_KILL = 3 +}; + + +enum Events +{ + // Ammunae + EVENT_WITHER = 1, + EVENT_APPLY_IMMUNITY, + EVENT_CONSUME_LIFE_ENERGY, + EVENT_SEEDLING_POD, + EVENT_SUMMON_BLOODPETAL_BLOSSOM, + EVENT_SUMMON_SPORE, + + // Blossom + EVENT_TRANSFORM, + EVENT_EMERGE, + EVENT_THORN_SLASH +}; + +// 39731 Ammunae +class boss_ammunae : public CreatureScript +{ + public: + boss_ammunae() : CreatureScript("boss_ammunae") { } + + struct boss_ammunaeAI : public BossAI + { + boss_ammunaeAI(Creature* creature) : BossAI(creature, DATA_AMMUNAE) { } + + void Reset() override + { + _Reset(); + me->MakeInterruptable(false); + me->AddAura(SPELL_ZERO_ENERGY, me); + } + + void JustEngagedWith(Unit* /*who*/) override + { + _JustEngagedWith(); + Talk(SAY_AGGRO); + instance->SendEncounterUnit(ENCOUNTER_FRAME_ENGAGE, me); + + events.ScheduleEvent(EVENT_WITHER, !IsHeroic() ? Seconds(7) : Seconds(5)); + events.ScheduleEvent(EVENT_SEEDLING_POD, Seconds(7)); + events.ScheduleEvent(EVENT_CONSUME_LIFE_ENERGY, Seconds(20)); + events.ScheduleEvent(EVENT_SUMMON_SPORE, Seconds(47)); + } + + void JustReachedHome() override + { + me->AddAura(SPELL_ZERO_ENERGY, me); + me->SetPower(POWER_ENERGY, 0); + instance->SetBossState(DATA_AMMUNAE, NOT_STARTED); + _JustReachedHome(); + } + + void KilledUnit(Unit* victim) override + { + if (victim->GetTypeId() == TYPEID_PLAYER) + Talk(SAY_PLAYER_KILL); + } + + void EnterEvadeMode(EvadeReason /*why*/) override + { + instance->SetBossState(DATA_AMMUNAE, FAIL); + instance->SendEncounterUnit(ENCOUNTER_FRAME_DISENGAGE, me); + me->MakeInterruptable(false); + events.Reset(); + _EnterEvadeMode(); + } + + void JustDied(Unit* /*who*/) override + { + Talk(SAY_DEATH); + instance->SendEncounterUnit(ENCOUNTER_FRAME_DISENGAGE, me); + _JustDied(); + } + + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim() || !CheckInRoom()) + return; + + events.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + while(uint32 eventId = events.ExecuteEvent()) + { + switch (eventId) + { + case EVENT_WITHER: + me->MakeInterruptable(true); + if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, 0.0f, true)) + DoCast(target, SPELL_WITHER); + events.ScheduleEvent(EVENT_APPLY_IMMUNITY, 1500); + events.Repeat(Seconds(20)); + break; + case EVENT_APPLY_IMMUNITY: + me->MakeInterruptable(false); + break; + case EVENT_SEEDLING_POD: + // If 100 energy, cast Rampant Growth + if (me->GetPower(POWER_ENERGY) >= 100) + { + Talk(SAY_SPECIAL); + me->SetPower(POWER_ENERGY, 0); + DoCast(SPELL_RAMPANT_GROWTH); + } + DoCast(SPELL_SUMMON_SEEDLING_POD); + events.Repeat(Seconds(7)); + break; + case EVENT_CONSUME_LIFE_ENERGY: + DoCast(SPELL_CONSUME_LIFE_ENERGY); + events.Repeat(Seconds(15)); + break; + case EVENT_SUMMON_SPORE: + DoCast(SPELL_SUMMON_SPORE); + events.Repeat(Seconds(47)); + break; + default: + break; + } + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + } + + DoMeleeAttackIfReady(); + } + }; + + CreatureAI* GetAI(Creature* creature) const + { + return new boss_ammunaeAI(creature); + } +}; + +// 75725 Consume Life Energy +class spell_ammunae_consume_life_energy : public SpellScriptLoader +{ +public: + spell_ammunae_consume_life_energy() : SpellScriptLoader("spell_ammunae_consume_life_energy") { } + + class spell_ammunae_consume_life_energy_SpellScript : public SpellScript + { + PrepareSpellScript(spell_ammunae_consume_life_energy_SpellScript); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_CONSUME_LIFE_ENERGY_LEECH, SPELL_CONSUME_LIFE_ENERGY_ENERGIZE }); + } + + void FilterTargets(std::list& targets) + { + if (targets.empty()) + return; + + Trinity::Containers::RandomResize(targets, 1); + } + + void HandleScript(SpellEffIndex /*effIndex*/) + { + GetCaster()->CastSpell(GetHitUnit(), SPELL_CONSUME_LIFE_ENERGY_LEECH); + GetCaster()->CastSpell(GetCaster(), SPELL_CONSUME_LIFE_ENERGY_ENERGIZE); + } + + void Register() override + { + OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_ammunae_consume_life_energy_SpellScript::FilterTargets, EFFECT_0, TARGET_UNIT_SRC_AREA_ENTRY); + OnEffectHitTarget += SpellEffectFn(spell_ammunae_consume_life_energy_SpellScript::HandleScript, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT); + } + }; + + SpellScript* GetSpellScript() const override + { + return new spell_ammunae_consume_life_energy_SpellScript(); + } +}; + +void AddSC_boss_ammunae() +{ + new boss_ammunae(); + new spell_ammunae_consume_life_energy(); +} diff --git a/src/server/scripts/Kalimdor/HallsOfOrigination/boss_anraphet.cpp b/src/server/scripts/Kalimdor/HallsOfOrigination/boss_anraphet.cpp index 2aaf60c3251..c6d25d5b675 100644 --- a/src/server/scripts/Kalimdor/HallsOfOrigination/boss_anraphet.cpp +++ b/src/server/scripts/Kalimdor/HallsOfOrigination/boss_anraphet.cpp @@ -16,12 +16,15 @@ */ #include "ScriptMgr.h" +#include "GridNotifiers.h" #include "halls_of_origination.h" #include "InstanceScript.h" +#include "Map.h" #include "MotionMaster.h" -#include "ObjectAccessor.h" +#include "Player.h" #include "ScriptedCreature.h" -#include "SpellInfo.h" +#include "ScriptedGossip.h" +#include "SpellAuraEffects.h" #include "SpellScript.h" enum Texts @@ -44,84 +47,128 @@ enum Texts BRANN_3_ELEMENTAL_DEAD = 9, // One more elemental to go! The door is almost open! BRANN_4_ELEMENTAL_DEAD = 10, // That''s it, you''ve done it! The vault door is opening! Now we can... oh, no! BRANN_SAY_ANRAPHET_DIED = 11, // We''ve done it! The control room is breached! - BRANN_SAY_MOMENT = 12 // Here we go! Now this should only take a moment... + BRANN_SAY_MOMENT = 12, // Here we go! Now this should only take a moment... + BRANN_SAY_BLASTED_TITANS = 13, // Blasted titans... Why do they use a different set of mechanisms at each of their installations? + BRANN_SAY_THIS_SYMBOL = 14 // This symbol, I think I've seen this before... +}; + +enum Gossip +{ + GOSSIP_MENU_NO_TIME_TO_WASTE = 11339, // Great, ye found yer way here!$b$bNo time to waste. Ye ready? + GOSSIP_OPTION_WE_ARE_READY = 0, // We're ready! Go, Brann! + GOSSIP_MENU_DESTROY_ELEMENTAL = 11348, // Yep, destroy the four elementals, then the door will open. I'm sure of it. Just watch out for the Troggs. Nasty tempered, filthy creatures, even if they have not succumbed to the Curse of Flesh. + GOSSIP_MENU_OCH_ITS_NOT_EASY = 12512 // Och!$b$bWhy can''t it just be easy fer once?! }; enum Events { - EVENT_BRANN_MOVE_INTRO = 1, - EVENT_BRANN_UNLOCK_DOOR = 2, - EVENT_BRANN_THINK = 3, - EVENT_BRANN_SET_ORIENTATION_1 = 4, - EVENT_BRANN_SET_ORIENTATION_2 = 5, - EVENT_BRANN_SET_ORIENTATION_3 = 6, - EVENT_BRANN_SAY_ELEMENTALS = 7, - EVENT_BRANN_SAY_GET_IT = 8, - EVENT_BRANN_SET_ORIENTATION_4 = 9, - - EVENT_ANRAPHET_APPEAR = 10, - EVENT_ANRAPHET_ACTIVATE = 11, - EVENT_ANRAPHET_DESTROY = 12, - EVENT_ANRAPHET_READY = 13, - EVENT_ANRAPHET_NEMESIS_STRIKE = 14, - EVENT_ANRAPHET_ALPHA_BEAMS = 15, - EVENT_ANRAPHET_OMEGA_STANCE = 16, - EVENT_ANRAPHET_CRUMBLING_RUIN = 17, - EVENT_ANRAPHET_ACTIVATE_OMEGA = 18 + EVENT_BRANN_IDLE_EMOTE_COOLDOWN = 1, + EVENT_BRANN_START_INTRO = 2, + EVENT_BRANN_UNLOCK_DOOR = 3, + EVENT_BRANN_MOVE_INTRO = 4, + EVENT_BRANN_THINK = 5, + EVENT_BRANN_LOOK_RIGHT = 6, + EVENT_BRANN_LOOK_LEFT = 7, + EVENT_BRANN_SAY_ELEMENTALS = 8, + EVENT_BRANN_SAY_GET_IT = 9, + EVENT_BRANN_SET_FLAG_GOSSIP = 10, + EVENT_BRANN_ACTIVATE_LASERBEAMS = 11, + EVENT_BRANN_SAY_ALL_ELEMENTAL_DEAD = 12, + EVENT_BRANN_MOVE_OUTRO = 13, + EVENT_BRANN_MOVE_FINAL = 14, + EVENT_BRANN_TURN_BACK = 15, + EVENT_ANRAPHET_APPEAR = 16, + EVENT_ANRAPHET_ACTIVATE = 17, + EVENT_ANRAPHET_DESTRUCTION = 18, + EVENT_ANRAPHET_READY = 19, + EVENT_ANRAPHET_NEMESIS_STRIKE = 20, + EVENT_ANRAPHET_ALPHA_BEAMS = 21, + EVENT_ANRAPHET_OMEGA_STANCE = 22, + EVENT_ANRAPHET_CRUMBLING_RUIN = 23, + EVENT_ANRAPHET_ACTIVATE_OMEGA = 24 }; enum Spells { - SPELL_DESTRUCTION_PROTOCOL = 77437, + // Flame Warden + SPELL_LAVA_ERUPTION_VISUAL = 97317, + // Anraphet + SPELL_DESTRUCTION_PROTOCOL = 77437, SPELL_ALPHA_BEAMS = 76184, SPELL_ALPHA_BEAMS_BACK_CAST = 76912, - SPELL_CRUMBLING_RUIN = 75609, - - SPELL_NEMESIS_STRIKE = 75604, + // Omega Stance SPELL_OMEGA_STANCE_SUMMON = 77106, SPELL_OMEGA_STANCE = 75622, - SPELL_OMEGA_STANCE_SPIDER_TRIGGER = 77121, + SPELL_OMEGA_STANCE_SPIDER_TRIGGER = 77121 +}; + +enum Actions +{ + ACTION_BRANN_IDLE_EMOTE }; enum Phases { PHASE_INTRO = 1, - PHASE_COMBAT = 2, - - PHASE_MASK_COMBAT = (1 << PHASE_COMBAT), + PHASE_COMBAT = 2 }; enum Points { - POINT_ANRAPHET_ACTIVATE = 0, - MAX_BRANN_WAYPOINTS_INTRO = 17 + POINT_ANRAPHET_ACTIVATE, + POINT_BRANN_SAY_TROGGS, + POINT_BRANN_SAY_MOMENT, + POINT_BRANN_TURN_BACK }; -Position const AnraphetActivatePos = {-193.656f, 366.689f, 75.91001f, 3.138207f}; +// Anraphet's intro +Position const AnraphetActivatePos = { -193.656f, 366.689f, 75.91001f, 3.138207f }; -Position const BrannIntroWaypoint[MAX_BRANN_WAYPOINTS_INTRO] = +uint32 const AnraphetPathSize = 6; +Position const AnraphetPath[AnraphetPathSize] = { - {-429.583f, 367.019f, 89.79282f, 0.0f}, - {-409.9531f, 367.0469f, 89.81111f, 0.0f}, - {-397.8246f, 366.967f, 86.37722f, 0.0f}, - {-383.7813f, 366.8229f, 82.07919f, 0.0f}, - {-368.2604f, 366.7448f, 77.0984f, 0.0f}, - {-353.6458f, 366.4896f, 75.92504f, 0.0f}, - {-309.0608f, 366.7205f, 75.91345f, 0.0f}, - {-276.3303f, 367.0f, 75.92413f, 0.0f}, - {-246.5104f, 366.6389f, 75.87791f, 0.0f}, - {-202.0417f, 366.7517f, 75.92508f, 0.0f}, - {-187.6024f, 366.7656f, 76.23077f, 0.0f}, - {-155.0938f, 366.783f, 86.45834f, 0.0f}, - {-143.5694f, 366.8177f, 89.73354f, 0.0f}, - {-128.5608f, 366.8629f, 89.74199f, 0.0f}, - {-103.559f, 366.5938f, 89.79725f, 0.0f}, - {-71.58507f, 367.0278f, 89.77069f, 0.0f}, - {-35.04861f, 366.6563f, 89.77447f, 0.0f}, + { -125.4643f, 366.5138f, 89.88697f }, + { -132.7143f, 366.5138f, 89.88697f }, + { -143.9643f, 366.5138f, 89.88697f }, + { -159.2143f, 366.5138f, 85.38697f }, + { -188.2143f, 366.7638f, 76.38697f }, + { -193.656f, 366.689f, 75.91001f } +}; + +// Brann's intro +Position const BrannBossHomePos = { -429.583f, 367.019f, 89.79282f, 0.01745329f }; + +// Brann's outro +uint32 const BrannOutroPathSize = 13; +Position const BrannOutroPath[BrannOutroPathSize] = +{ + { -409.9531f, 367.0469f, 89.81111f, 0.0f }, + { -397.8246f, 366.967f, 86.37722f, 0.0f }, + { -383.7813f, 366.8229f, 82.07919f, 0.0f }, + { -368.2604f, 366.7448f, 77.0984f, 0.0f }, + { -353.6458f, 366.4896f, 75.92504f, 0.0f }, + { -309.0608f, 366.7205f, 75.91345f, 0.0f }, + { -276.3303f, 367.0f, 75.92413f, 0.0f }, + { -246.5104f, 366.6389f, 75.87791f, 0.0f }, + { -202.0417f, 366.7517f, 75.92508f, 0.0f }, + { -187.6024f, 366.7656f, 76.23077f, 0.0f }, + { -155.0938f, 366.783f, 86.45834f, 0.0f }, + { -143.5694f, 366.8177f, 89.73354f, 0.0f }, + { -128.5608f, 366.8629f, 89.74199f, 0.0f } +}; + +Position const BrannFinalHomePos = { -35.04861f, 366.6563f, 89.77447f, 3.141593f }; + +uint32 const BrannFinalPathSize = 3; +Position const BrannFinalPath[BrannFinalPathSize] = +{ + { -103.559f, 366.5938f, 89.79725f, 0.0f }, + { -71.58507f, 367.0278f, 89.77069f, 0.0f }, + { -35.04861f, 366.6563f, 89.77447f, 0.0f } }; class boss_anraphet : public CreatureScript @@ -133,44 +180,52 @@ public: { boss_anraphetAI(Creature* creature) : BossAI(creature, DATA_ANRAPHET) { } - void ScheduleCombatEvents() - { - events.ScheduleEvent(EVENT_ANRAPHET_NEMESIS_STRIKE, 8000, 0, PHASE_COMBAT); - events.ScheduleEvent(EVENT_ANRAPHET_ALPHA_BEAMS, 10000, 0, PHASE_COMBAT); - events.ScheduleEvent(EVENT_ANRAPHET_OMEGA_STANCE, 35000, 0, PHASE_COMBAT); - } - void Reset() override { _Reset(); - me->SetWalk(false); - events.SetPhase(PHASE_INTRO); - if (instance->GetData(DATA_DEAD_ELEMENTALS) == 4) + + // Vault of Lights not yet done? + //if (instance->GetData(DATA_DEAD_ELEMENTALS) < 4) + if (instance->GetBossState(DATA_VAULT_OF_LIGHTS) == DONE) { - // Set to combat automatically, Brann's event won't repeat + me->SetHomePosition(AnraphetActivatePos); + me->GetMotionMaster()->MoveTargetedHome(); me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE); events.SetPhase(PHASE_COMBAT); - ScheduleCombatEvents(); - me->SetHomePosition(AnraphetActivatePos); } } void JustEngagedWith(Unit* /*who*/) override { - instance->SendEncounterUnit(ENCOUNTER_FRAME_ENGAGE, me, 1); + // Prevents spam during intro (massive trogg kill) + if (events.IsInPhase(PHASE_INTRO)) + return; + Talk(ANRAPHET_SAY_AGGRO); _JustEngagedWith(); + + instance->SendEncounterUnit(ENCOUNTER_FRAME_ENGAGE, me); + events.ScheduleEvent(EVENT_ANRAPHET_NEMESIS_STRIKE, Seconds(8), 0, PHASE_COMBAT); + events.ScheduleEvent(EVENT_ANRAPHET_ALPHA_BEAMS, Seconds(10), 0, PHASE_COMBAT); + events.ScheduleEvent(EVENT_ANRAPHET_OMEGA_STANCE, Seconds(35), 0, PHASE_COMBAT); + } + + void EnterEvadeMode(EvadeReason why) override + { + instance->SendEncounterUnit(ENCOUNTER_FRAME_DISENGAGE, me); + summons.DespawnAll(); + _EnterEvadeMode(); + _DespawnAtEvade(); } void JustDied(Unit* /*killer*/) override { instance->SendEncounterUnit(ENCOUNTER_FRAME_DISENGAGE, me); Talk(ANRAPHET_SAY_DEATH); - - if (Creature* brann = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_BRANN_0_GUID))) - brann->AI()->DoAction(ACTION_ANRAPHET_DIED); - _JustDied(); + + if (Creature* brann = instance->GetCreature(DATA_BRANN_0)) + brann->AI()->DoAction(ACTION_ANRAPHET_DIED); } void KilledUnit(Unit* victim) override @@ -179,34 +234,29 @@ public: Talk(ANRAPHET_SAY_KILL); } - void JustReachedHome() override - { - instance->SendEncounterUnit(ENCOUNTER_FRAME_DISENGAGE, me); - _JustReachedHome(); - instance->SetBossState(DATA_ANRAPHET, FAIL); - } - void DoAction(int32 action) override { - if (action == ACTION_ANRAPHET_INTRO) - events.ScheduleEvent(EVENT_ANRAPHET_APPEAR, 6000, 0, PHASE_INTRO); + if (action != ACTION_ANRAPHET_INTRO) + return; + + // Intro + events.SetPhase(PHASE_INTRO); + events.ScheduleEvent(EVENT_ANRAPHET_APPEAR, Seconds(6), 0, PHASE_INTRO); // Note: 5800 ms } void MovementInform(uint32 type, uint32 point) override { - if (type != POINT_MOTION_TYPE) + if (type != EFFECT_MOTION_TYPE || point != POINT_ANRAPHET_ACTIVATE) return; - if (point == POINT_ANRAPHET_ACTIVATE) - { - events.ScheduleEvent(EVENT_ANRAPHET_ACTIVATE, 1500, 0, PHASE_INTRO); - me->SetHomePosition(AnraphetActivatePos); - } + // Activate point + me->SetHomePosition(AnraphetActivatePos); + events.ScheduleEvent(EVENT_ANRAPHET_ACTIVATE, Seconds(1), 0, PHASE_INTRO); } void UpdateAI(uint32 diff) override { - if ((events.GetPhaseMask() & PHASE_MASK_COMBAT) && (!UpdateVictim() || !CheckInRoom())) + if (!events.IsInPhase(PHASE_INTRO) && (!UpdateVictim() || !CheckInRoom())) return; events.Update(diff); @@ -219,47 +269,44 @@ public: switch (eventId) { case EVENT_ANRAPHET_APPEAR: - me->SetWalk(true); - me->GetMotionMaster()->MovePoint(POINT_ANRAPHET_ACTIVATE, AnraphetActivatePos); + me->GetMotionMaster()->MoveSmoothPath(POINT_ANRAPHET_ACTIVATE, AnraphetPath, AnraphetPathSize); break; case EVENT_ANRAPHET_ACTIVATE: - me->SetWalk(false); Talk(ANRAPHET_SAY_INTRO); - events.ScheduleEvent(EVENT_ANRAPHET_DESTROY, 17500, 0, PHASE_INTRO); - return; - case EVENT_ANRAPHET_DESTROY: + events.ScheduleEvent(EVENT_ANRAPHET_DESTRUCTION, Seconds(10), 0, PHASE_INTRO); // Note: 10800 ms + break; + case EVENT_ANRAPHET_DESTRUCTION: DoCastAOE(SPELL_DESTRUCTION_PROTOCOL); - events.ScheduleEvent(EVENT_ANRAPHET_READY, 6000, 0, PHASE_INTRO); + events.ScheduleEvent(EVENT_ANRAPHET_READY, Seconds(6), 0, PHASE_INTRO); // Note: 6400 ms break; case EVENT_ANRAPHET_READY: me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE); events.SetPhase(PHASE_COMBAT); - ScheduleCombatEvents(); break; case EVENT_ANRAPHET_NEMESIS_STRIKE: DoCastVictim(SPELL_NEMESIS_STRIKE); - events.ScheduleEvent(EVENT_ANRAPHET_NEMESIS_STRIKE, 21500, 0, PHASE_COMBAT); + events.ScheduleEvent(EVENT_ANRAPHET_NEMESIS_STRIKE, Seconds(21), 0, PHASE_COMBAT); break; case EVENT_ANRAPHET_ALPHA_BEAMS: - DoCast(me, SPELL_ALPHA_BEAMS); - events.ScheduleEvent(EVENT_ANRAPHET_CRUMBLING_RUIN, 12500, 0, PHASE_COMBAT); - events.ScheduleEvent(EVENT_ANRAPHET_ALPHA_BEAMS, urand(40000, 45000), 0, PHASE_COMBAT); + DoCastSelf(SPELL_ALPHA_BEAMS); + events.ScheduleEvent(EVENT_ANRAPHET_CRUMBLING_RUIN, Seconds(12), 0, PHASE_COMBAT); + events.ScheduleEvent(EVENT_ANRAPHET_ALPHA_BEAMS, Seconds(40), Seconds(45), 0, PHASE_COMBAT); break; case EVENT_ANRAPHET_OMEGA_STANCE: - DoCast(me, SPELL_OMEGA_STANCE_SUMMON); - DoCast(me, SPELL_OMEGA_STANCE); + DoCastSelf(SPELL_OMEGA_STANCE); Talk(ANRAPHET_SAY_OMEGA_STANCE); - events.ScheduleEvent(EVENT_ANRAPHET_OMEGA_STANCE, urand(45000, 50000), 0, PHASE_COMBAT); - events.ScheduleEvent(EVENT_ANRAPHET_CRUMBLING_RUIN, 13000, 0, PHASE_COMBAT); + events.ScheduleEvent(EVENT_ANRAPHET_OMEGA_STANCE, Seconds(45), Seconds(50), 0, PHASE_COMBAT); + events.ScheduleEvent(EVENT_ANRAPHET_CRUMBLING_RUIN, Seconds(13), 0, PHASE_COMBAT); break; case EVENT_ANRAPHET_CRUMBLING_RUIN: - DoCast(me, SPELL_CRUMBLING_RUIN); + DoCastSelf(SPELL_CRUMBLING_RUIN); + break; + default: break; } } - if (events.GetPhaseMask() & PHASE_MASK_COMBAT) - DoMeleeAttackIfReady(); + DoMeleeAttackIfReady(); } }; @@ -269,199 +316,482 @@ public: } }; -class npc_omega_stance : public CreatureScript +// 5811 Brann's AreaTrigger +class at_hoo_brann_idle_emote : public AreaTriggerScript { - public: - npc_omega_stance() : CreatureScript("npc_omega_stance") { } +public: + at_hoo_brann_idle_emote() : AreaTriggerScript("at_hoo_brann_idle_emote") { } - struct npc_omega_stanceAI : public ScriptedAI + bool OnTrigger(Player* player, AreaTriggerEntry const* /*areaTrigger*/) override + { + if (InstanceScript* instance = player->GetInstanceScript()) + if (Creature* brann = instance->GetCreature(DATA_BRANN_0)) + brann->AI()->DoAction(ACTION_BRANN_IDLE_EMOTE); + return true; + } +}; + +// 39908 Brann Bronzebeard +class npc_brann_bronzebeard_anraphet : public CreatureScript +{ +public: + npc_brann_bronzebeard_anraphet() : CreatureScript("npc_brann_bronzebeard_anraphet") { } + + + struct npc_brann_bronzebeard_anraphetAI : public CreatureAI + { + npc_brann_bronzebeard_anraphetAI(Creature* creature) : CreatureAI(creature), _instance(creature->GetInstanceScript()) { } + + bool GossipHello(Player* player) override { - npc_omega_stanceAI(Creature* creature) : ScriptedAI(creature) { } + if (!_instance) + return true; - void IsSummonedBy(Unit* /*who*/) override - { - DoCast(me, SPELL_OMEGA_STANCE_SPIDER_TRIGGER, true); - } + // What gossip menu shall we send? + uint32 gossipMenuId = GOSSIP_MENU_NO_TIME_TO_WASTE; - void EnterEvadeMode(EvadeReason /*why*/) override { } - }; + if (_instance->GetBossState(DATA_VAULT_OF_LIGHTS) == NOT_STARTED) // gossipMenuId already set, only add gossip option + AddGossipItemFor(player, gossipMenuId, GOSSIP_OPTION_WE_ARE_READY, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF); + else if (_instance->GetBossState(DATA_VAULT_OF_LIGHTS) != DONE) + gossipMenuId = GOSSIP_MENU_DESTROY_ELEMENTAL; + else // To-do: There might be another gossip text before and while fighting Anraphet. Check retail. + gossipMenuId = GOSSIP_MENU_OCH_ITS_NOT_EASY; - CreatureAI* GetAI(Creature* creature) const override - { - return new npc_omega_stanceAI(creature); + SendGossipMenuFor(player, player->GetGossipTextId(gossipMenuId, me), me->GetGUID()); + return true; } + + void Reset() override + { + // DATA_VAULT_OF_LIGHTS is not a boss/creature, so we need to initialize it manually. + if (_instance->GetBossState(DATA_VAULT_OF_LIGHTS) == TO_BE_DECIDED) + _instance->SetBossState(DATA_VAULT_OF_LIGHTS, NOT_STARTED); + + // We are ready for idle emote only if intro not started yet. + canSayIdleEmote = _instance->GetBossState(DATA_VAULT_OF_LIGHTS) == NOT_STARTED; + + // Different home positions after intro is started. + if (_instance->GetBossState(DATA_ANRAPHET) == DONE) + me->SetHomePosition(BrannFinalHomePos); + else if (_instance->GetBossState(DATA_VAULT_OF_LIGHTS) != NOT_STARTED) + me->SetHomePosition(BrannBossHomePos); + + me->GetMotionMaster()->MoveTargetedHome(); + } + + bool GossipSelect(Player* /*player*/, uint32 /*menuId*/, uint32 /*gossipListId*/) override + { + if (_instance->GetBossState(DATA_VAULT_OF_LIGHTS) != NOT_STARTED) + return false; + + me->RemoveFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_GOSSIP); + events.Reset(); // Removes EVENT_BRANN_IDLE_EMOTE. + events.RescheduleEvent(EVENT_BRANN_START_INTRO, Seconds(1)); + + return true; + } + + void DoAction(int32 action) override + { + switch (action) + { + case ACTION_BRANN_IDLE_EMOTE: + if (canSayIdleEmote) + { + canSayIdleEmote = false; + Talk(urand(0, 1) ? BRANN_SAY_BLASTED_TITANS : BRANN_SAY_THIS_SYMBOL); + events.ScheduleEvent(EVENT_BRANN_IDLE_EMOTE_COOLDOWN, Seconds(45)); // Cooldown for AreaTrigger + } + break; + case ACTION_ELEMENTAL_DIED: + { + uint32 dead = _instance->GetData(DATA_DEAD_ELEMENTALS); + if (dead < 4) // Say that an elemental has died. + Talk(BRANN_1_ELEMENTAL_DEAD + dead - 1); + else // Last one died! Continue script when laser beam activates (9-second animation of light machine behind the warden). + _instance->SetBossState(DATA_VAULT_OF_LIGHTS, DONE); + events.RescheduleEvent(EVENT_BRANN_ACTIVATE_LASERBEAMS, Seconds(9)); + break; + } + case ACTION_ANRAPHET_DIED: + me->RemoveFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_GOSSIP); + events.ScheduleEvent(EVENT_BRANN_MOVE_OUTRO, Seconds(5)); + break; + } + } + + void UpdateAI(uint32 diff) override + { + events.Update(diff); + + while (uint32 eventId = events.ExecuteEvent()) + { + switch (eventId) + { + case EVENT_BRANN_IDLE_EMOTE_COOLDOWN: + canSayIdleEmote = true; + break; + case EVENT_BRANN_START_INTRO: + Talk(BRANN_SAY_DOOR_INTRO); + events.ScheduleEvent(EVENT_BRANN_UNLOCK_DOOR, Seconds(7)); + break; + case EVENT_BRANN_UNLOCK_DOOR: + Talk(BRANN_SAY_UNLOCK_DOOR); + _instance->SetBossState(DATA_VAULT_OF_LIGHTS, IN_PROGRESS); + events.ScheduleEvent(EVENT_BRANN_MOVE_INTRO, Seconds(3)); + break; + case EVENT_BRANN_MOVE_INTRO: + me->SetWalk(true); + me->GetMotionMaster()->MovePoint(POINT_BRANN_SAY_TROGGS, BrannBossHomePos, true); + break; + case EVENT_BRANN_THINK: + Talk(BRANN_SAY_THINK); + events.ScheduleEvent(EVENT_BRANN_LOOK_RIGHT, Seconds(6)); + break; + case EVENT_BRANN_LOOK_RIGHT: + me->SetFacingTo(DegToRad(312.0f)); // Sniff: o = 5.445427f + Talk(BRANN_SAY_MIRRORS); + events.ScheduleEvent(EVENT_BRANN_LOOK_LEFT, Seconds(1)); + break; + case EVENT_BRANN_LOOK_LEFT: + me->SetFacingTo(DegToRad(36.0f)); // Sniff: o = 0.6283185f + events.ScheduleEvent(EVENT_BRANN_SAY_ELEMENTALS, Seconds(3)); + break; + case EVENT_BRANN_SAY_ELEMENTALS: + me->SetFacingTo(DegToRad(1.0f)); // Sniff: o = 0.01745329f + Talk(BRANN_SAY_ELEMENTALS); + events.ScheduleEvent(EVENT_BRANN_SAY_GET_IT, Seconds(4)); + break; + case EVENT_BRANN_SAY_GET_IT: + Talk(BRANN_SAY_GET_IT); + events.ScheduleEvent(EVENT_BRANN_SET_FLAG_GOSSIP, Seconds(16)); + break; + case EVENT_BRANN_SET_FLAG_GOSSIP: + me->SetFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_GOSSIP); + break; + case EVENT_BRANN_ACTIVATE_LASERBEAMS: + { + // Update laserbeams + _instance->SetData(DATA_UPDATE_LASERBEAMS, 0); + + if (_instance->GetBossState(DATA_VAULT_OF_LIGHTS) == DONE) + { + // Note: In some old sniff file Sun Mirror gets activated every time an elemental dies (for 10 seconds). + // Needs to be checked on live. It makes sense that it is only activated after all four beams are active. + if (GameObject* mirror = _instance->GetGameObject(DATA_ANRAPHET_SUN_MIRROR)) + mirror->SetGoState(GO_STATE_ACTIVE); + if (GameObject* door = _instance->GetGameObject(DATA_ANRAPHET_DOOR)) + door->SetGoState(GO_STATE_ACTIVE); + events.ScheduleEvent(EVENT_BRANN_SAY_ALL_ELEMENTAL_DEAD, Seconds(4)); // Note: 4600 ms + } + break; + } + case EVENT_BRANN_SAY_ALL_ELEMENTAL_DEAD: + Talk(BRANN_4_ELEMENTAL_DEAD); + if (Creature* anraphet = _instance->GetCreature(DATA_ANRAPHET)) + anraphet->AI()->DoAction(ACTION_ANRAPHET_INTRO); + break; + case EVENT_BRANN_MOVE_OUTRO: + Talk(BRANN_SAY_ANRAPHET_DIED); + me->GetMotionMaster()->MoveSmoothPath(POINT_BRANN_SAY_MOMENT, BrannOutroPath, BrannOutroPathSize, false); + break; + case EVENT_BRANN_MOVE_FINAL: + me->GetMotionMaster()->MoveSmoothPath(POINT_BRANN_TURN_BACK, BrannFinalPath, BrannFinalPathSize, false); + break; + case EVENT_BRANN_TURN_BACK: + me->SetFacingTo(DegToRad(180.0f)); // Sniff: 3.141593f + me->SetFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_GOSSIP); + break; + } + } + } + + void MovementInform(uint32 movementType, uint32 pointId) override + { + if (movementType != POINT_MOTION_TYPE && movementType != EFFECT_MOTION_TYPE) + return; + + switch (pointId) + { + case POINT_BRANN_SAY_TROGGS: + me->SetWalk(false); + Talk(BRANN_SAY_TROGGS); + events.ScheduleEvent(EVENT_BRANN_THINK, Seconds(15)); + break; + case POINT_BRANN_SAY_MOMENT: + Talk(BRANN_SAY_MOMENT); + events.ScheduleEvent(EVENT_BRANN_MOVE_FINAL, Seconds(2)); + break; + case POINT_BRANN_TURN_BACK: + events.ScheduleEvent(EVENT_BRANN_TURN_BACK, Seconds(6)); + break; + default: + break; + } + } + + private: + InstanceScript* _instance; + EventMap events; + bool canSayIdleEmote; + }; + + CreatureAI* GetAI(Creature* creature) const override + { + return GetHallsOfOriginationAI(creature); + } }; class npc_alpha_beam : public CreatureScript { - public: - npc_alpha_beam() : CreatureScript("npc_alpha_beam") { } +public: + npc_alpha_beam() : CreatureScript("npc_alpha_beam") { } - struct npc_alpha_beamAI : public ScriptedAI + struct npc_alpha_beamAI : public ScriptedAI + { + npc_alpha_beamAI(Creature* creature) : ScriptedAI(creature), _instance(creature->GetInstanceScript()) { } + + void IsSummonedBy(Unit* /*summoner*/) override { - npc_alpha_beamAI(Creature* creature) : ScriptedAI(creature), _instance(creature->GetInstanceScript()) { } + if (Creature* anraphet = _instance->GetCreature(DATA_ANRAPHET)) + anraphet->CastSpell(me, SPELL_ALPHA_BEAMS_BACK_CAST); + } - void IsSummonedBy(Unit* /*summoner*/) override + void EnterEvadeMode(EvadeReason /*why*/) override { } // Never evade + + private: + InstanceScript* _instance; + }; + + CreatureAI* GetAI(Creature* creature) const override + { + return GetHallsOfOriginationAI(creature); + } +}; + +class npc_omega_stance : public CreatureScript +{ +public: + npc_omega_stance() : CreatureScript("npc_omega_stance") { } + + struct npc_omega_stanceAI : public ScriptedAI + { + npc_omega_stanceAI(Creature* creature) : ScriptedAI(creature) { } + + void IsSummonedBy(Unit* /*who*/) override + { + DoCastSelf(SPELL_OMEGA_STANCE_SPIDER_TRIGGER, true); + } + + void EnterEvadeMode(EvadeReason /*why*/) override { } + }; + + CreatureAI* GetAI(Creature* creature) const override + { + return GetHallsOfOriginationAI(creature); + } +}; + +// 82329 Teleport Earth, 82330 Teleport Air, 82331 Teleport Fire, 82332 Teleport Water +// Sets orientation and updates home position after warden has been teleported. +class spell_hoo_platform_teleport : public SpellScriptLoader +{ +public: + spell_hoo_platform_teleport() : SpellScriptLoader("spell_hoo_platform_teleport") { } + + class spell_hoo_platform_teleport_SpellScript : public SpellScript + { + PrepareSpellScript(spell_hoo_platform_teleport_SpellScript); + + void SetDest(SpellDestination& dest) + { + Creature* creature = GetCaster()->ToCreature(); + if (creature) + return; + + // Side of the room: WEST > 366.781f (middle of the room) > EAST + float ori = creature->GetPositionY() > 366.781f ? DegToRad(270) : DegToRad(90); + + // DB dest has no orientation field (always 0.0f). + dest.RelocateOffset({ 0.0f, 0.0f, 0.0f, ori }); + + creature->SetHomePosition(dest._position.GetPosition()); + } + + void Register() override + { + OnDestinationTargetSelect += SpellDestinationTargetSelectFn(spell_hoo_platform_teleport_SpellScript::SetDest, EFFECT_0, TARGET_DEST_DB); + } + }; + + SpellScript* GetSpellScript() const override + { + return new spell_hoo_platform_teleport_SpellScript(); + } +}; + + +// 77273 Lava Eruption +class spell_flame_warden_lava_eruption : public SpellScriptLoader +{ +public: + spell_flame_warden_lava_eruption() : SpellScriptLoader("spell_flame_warden_lava_eruption") { } + + class spell_flame_warden_lava_eruption_SpellScript : public SpellScript + { + PrepareSpellScript(spell_flame_warden_lava_eruption_SpellScript); + + void HandleScript(SpellEffIndex /*effIndex*/) + { + GetHitUnit()->RemoveAurasDueToSpell(GetEffectValue()); + } + + void OnTargetSelect(WorldObject*& target) + { + if (Unit* unit = target->ToUnit()) + unit->CastSpell(nullptr, SPELL_LAVA_ERUPTION_VISUAL); + } + + void Register() override + { + OnObjectTargetSelect += SpellObjectTargetSelectFn(spell_flame_warden_lava_eruption_SpellScript::OnTargetSelect, EFFECT_0, TARGET_DEST_TARGET_ENEMY); + } + }; + + SpellScript* GetSpellScript() const override + { + return new spell_flame_warden_lava_eruption_SpellScript(); + } +}; + +// 77333 Whirling Winds: This script handles Whirling Wind's movement. +class spell_whirling_winds_movement : public SpellScriptLoader +{ +public: + spell_whirling_winds_movement() : SpellScriptLoader("spell_whirling_winds_movement") { } + + class spell_whirling_winds_movement_SpellScript : public SpellScript + { + PrepareSpellScript(spell_whirling_winds_movement_SpellScript); + + void StartMovement(SpellEffIndex /*effIndex*/) + { + Player* target = GetRandomPlayer(false); + if (!target) // Try to get tank. + target = GetRandomPlayer(true); + + if (target) { - if (Creature* anraphet = ObjectAccessor::GetCreature(*me, _instance->GetGuidData(DATA_ANRAPHET_GUID))) - anraphet->CastSpell(me, SPELL_ALPHA_BEAMS_BACK_CAST); + GetCaster()->SetWalk(true); + GetCaster()->GetMotionMaster()->MoveChase(target); } + } - void EnterEvadeMode(EvadeReason /*why*/) override { } // Never evade - - private: - InstanceScript* _instance; - }; - - CreatureAI* GetAI(Creature* creature) const override + void StopMovement(SpellEffIndex /*effIndex*/) { - return GetHallsOfOriginationAI(creature); + // Stop moving when we hit someone and chase another target on next cast. + GetCaster()->StopMoving(); + GetCaster()->GetMotionMaster()->Clear(); + } + + void Register() override + { + OnEffectHit += SpellEffectFn(spell_whirling_winds_movement_SpellScript::StartMovement, EFFECT_0, SPELL_EFFECT_SCHOOL_DAMAGE); + OnEffectHitTarget += SpellEffectFn(spell_whirling_winds_movement_SpellScript::StopMovement, EFFECT_0, SPELL_EFFECT_SCHOOL_DAMAGE); + } + + private: + Player* GetRandomPlayer(bool includingTank) + { + Creature* whirlwind = GetCaster()->ToCreature(); + if (!whirlwind || whirlwind->isMoving()) + return NULL; + + // Use Air Warden's threatlist, we don't have one. + Creature* warden = nullptr; + if (InstanceScript* instance = whirlwind->GetInstanceScript()) + warden = instance->GetCreature(DATA_AIR_WARDEN); + if (!warden) + return NULL; + + std::list const& threatlist = warden->getThreatManager().getThreatList(); + std::list targets; + + if (threatlist.empty()) + return NULL; + + // Target must be a player that is not tanking Air Warden and has not been recently hit by this spell. + for (std::list::const_iterator itr = threatlist.begin(); itr != threatlist.end(); ++itr) + if (Unit* refTarget = (*itr)->getTarget()) + if (refTarget->GetTypeId() == TYPEID_PLAYER && (includingTank || refTarget != warden->GetVictim()) && !refTarget->HasAura(m_scriptSpellId)) + targets.push_back(refTarget->ToPlayer()); + + if (targets.empty()) + return NULL; + + return Trinity::Containers::SelectRandomContainerElement(targets); + } + }; + + SpellScript* GetSpellScript() const override + { + return new spell_whirling_winds_movement_SpellScript(); + } +}; + +class PlayerCheck +{ + public: + PlayerCheck() { } + + bool operator()(WorldObject* object) + { + return (object->GetTypeId() != TYPEID_PLAYER); } }; -class npc_brann_bronzebeard_anraphet : public CreatureScript +// 77437 - Destruction Protocol +class spell_anraphet_destruction_protocol : public SpellScriptLoader { - public: - npc_brann_bronzebeard_anraphet() : CreatureScript("npc_brann_bronzebeard_anraphet") { } +public: + spell_anraphet_destruction_protocol() : SpellScriptLoader("spell_anraphet_destruction_protocol") { } - struct npc_brann_bronzebeard_anraphetAI : public CreatureAI + class spell_anraphet_destruction_protocol_SpellScript : public SpellScript + { + PrepareSpellScript(spell_anraphet_destruction_protocol_SpellScript); + + void FilterTargets(std::list& targets) { - npc_brann_bronzebeard_anraphetAI(Creature* creature) : CreatureAI(creature), _currentPoint(0), _instance(creature->GetInstanceScript()) { } + if (targets.empty()) + return; - bool GossipSelect(Player* /*player*/, uint32 menuId, uint32 gossipListId) override - { - if (_instance->GetBossState(DATA_VAULT_OF_LIGHTS) == DONE) - return true; - - if (me->GetCreatureTemplate()->GossipMenuId == menuId && !gossipListId) - { - _instance->SetBossState(DATA_VAULT_OF_LIGHTS, IN_PROGRESS); - _currentPoint = 0; - events.Reset(); - me->RemoveFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_GOSSIP); - me->SetWalk(true); - Talk(BRANN_SAY_DOOR_INTRO); - events.ScheduleEvent(EVENT_BRANN_UNLOCK_DOOR, 7500); - } - - return true; - } - - void DoAction(int32 action) override - { - switch (action) - { - case ACTION_ELEMENTAL_DIED: - { - uint32 dead = _instance->GetData(DATA_DEAD_ELEMENTALS); - Talk(BRANN_1_ELEMENTAL_DEAD + dead - 1); - if (dead == 4) - { - _instance->DoCastSpellOnPlayers(SPELL_VAULT_OF_LIGHTS_CREDIT); - if (Creature* anraphet = ObjectAccessor::GetCreature(*me, _instance->GetGuidData(DATA_ANRAPHET_GUID))) - anraphet->AI()->DoAction(ACTION_ANRAPHET_INTRO); - } - break; - } - case ACTION_ANRAPHET_DIED: - me->RemoveFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_GOSSIP); - events.ScheduleEvent(EVENT_BRANN_MOVE_INTRO, 1000); - break; - } - } - - void UpdateAI(uint32 diff) override - { - events.Update(diff); - - while (uint32 eventId = events.ExecuteEvent()) - { - switch (eventId) - { - case EVENT_BRANN_MOVE_INTRO: - if (_currentPoint < MAX_BRANN_WAYPOINTS_INTRO) - me->GetMotionMaster()->MovePoint(_currentPoint, BrannIntroWaypoint[_currentPoint]); - break; - case EVENT_BRANN_UNLOCK_DOOR: - Talk(BRANN_SAY_UNLOCK_DOOR); - _instance->SetBossState(DATA_VAULT_OF_LIGHTS, DONE); - _instance->DoStartTimedAchievement(ACHIEVEMENT_TIMED_TYPE_EVENT, ACHIEV_VAULT_OF_LIGHTS_EVENT); - events.ScheduleEvent(EVENT_BRANN_MOVE_INTRO, 3500); - break; - case EVENT_BRANN_THINK: - Talk(BRANN_SAY_THINK); - events.ScheduleEvent(EVENT_BRANN_SET_ORIENTATION_1, 6000); - break; - case EVENT_BRANN_SET_ORIENTATION_1: - me->SetFacingTo(5.445427f); - Talk(BRANN_SAY_MIRRORS); - events.ScheduleEvent(EVENT_BRANN_SET_ORIENTATION_2, 1000); - break; - case EVENT_BRANN_SET_ORIENTATION_2: - me->SetFacingTo(0.6283185f); - events.ScheduleEvent(EVENT_BRANN_SET_ORIENTATION_3, 2500); - break; - case EVENT_BRANN_SET_ORIENTATION_3: - me->SetFacingTo(0.01745329f); - events.ScheduleEvent(EVENT_BRANN_SAY_ELEMENTALS, 200); - break; - case EVENT_BRANN_SAY_ELEMENTALS: - Talk(BRANN_SAY_ELEMENTALS); - events.ScheduleEvent(EVENT_BRANN_SAY_GET_IT, 3500); - break; - case EVENT_BRANN_SAY_GET_IT: - Talk(BRANN_SAY_GET_IT); - me->SetFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_GOSSIP); - break; - case EVENT_BRANN_SET_ORIENTATION_4: - me->SetFacingTo(3.141593f); - break; - } - } - } - - void MovementInform(uint32 movementType, uint32 pointId) override - { - if (movementType != POINT_MOTION_TYPE) - return; - - _currentPoint = pointId + 1; - uint32 delay = 1; - - switch (pointId) - { - case 0: - Talk(BRANN_SAY_TROGGS); - events.ScheduleEvent(EVENT_BRANN_THINK, 15000); - return; - case 1: - Talk(BRANN_SAY_ANRAPHET_DIED); - delay = 1000; - break; - case 14: - Talk(BRANN_SAY_MOMENT); - delay = 2200; - break; - case 16: - events.ScheduleEvent(EVENT_BRANN_SET_ORIENTATION_4, 6000); - return; - default: - break; - } - - events.ScheduleEvent(EVENT_BRANN_MOVE_INTRO, delay); - } - - protected: - EventMap events; - uint32 _currentPoint; - InstanceScript* _instance; - }; - - CreatureAI* GetAI(Creature* creature) const override - { - return GetHallsOfOriginationAI(creature); + targets.remove_if(PlayerCheck()); } + + void HandleDummy(SpellEffIndex /*effIndex*/) + { + if (Creature* trogg = GetHitUnit()->ToCreature()) + trogg->SetRespawnTime(DAY); + } + + void HandlePlayerDamage(SpellEffIndex /*effIndex*/) + { + if (Player* player = GetHitPlayer()) + SetHitDamage(CalculatePct(player->GetHealth(), 90)); + } + + void Register() override + { + OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_anraphet_destruction_protocol_SpellScript::FilterTargets, EFFECT_0, TARGET_UNIT_SRC_AREA_ENEMY); + OnEffectHitTarget += SpellEffectFn(spell_anraphet_destruction_protocol_SpellScript::HandleDummy, EFFECT_1, SPELL_EFFECT_DUMMY); + OnEffectHitTarget += SpellEffectFn(spell_anraphet_destruction_protocol_SpellScript::HandlePlayerDamage, EFFECT_0, SPELL_EFFECT_SCHOOL_DAMAGE); + } + }; + + SpellScript* GetSpellScript() const override + { + return new spell_anraphet_destruction_protocol_SpellScript(); + } }; class spell_anraphet_alpha_beams : public SpellScriptLoader @@ -478,23 +808,55 @@ public: if (targets.empty()) return; - WorldObject* target = Trinity::Containers::SelectRandomContainerElement(targets); - targets.clear(); - targets.push_back(target); + Trinity::Containers::RandomResize(targets, 1); } - void Register() + void Register() override { OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_anraphet_alpha_beams_SpellScript::FilterTargets, EFFECT_0, TARGET_UNIT_SRC_AREA_ENEMY); } }; - SpellScript* GetSpellScript() const + SpellScript* GetSpellScript() const override { return new spell_anraphet_alpha_beams_SpellScript(); } }; +class spell_anraphet_omega_stance : public SpellScriptLoader +{ + public: + spell_anraphet_omega_stance() : SpellScriptLoader("spell_anraphet_omega_stance") { } + + class spell_anraphet_omega_stance_AuraScript : public AuraScript + { + PrepareAuraScript(spell_anraphet_omega_stance_AuraScript); + + void HandleApply(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/) + { + GetTarget()->CastSpell(GetTarget(), SPELL_OMEGA_STANCE_SUMMON, true); + } + + void HandleRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/) + { + if (Creature* omegaStanceDummy = GetTarget()->FindNearestCreature(NPC_OMEGA_STANCE, 50.0f, true)) + omegaStanceDummy->DespawnOrUnsummon(); + } + + void Register() override + { + OnEffectApply += AuraEffectApplyFn(spell_anraphet_omega_stance_AuraScript::HandleApply, EFFECT_0, SPELL_AURA_PERIODIC_TRIGGER_SPELL, AURA_EFFECT_HANDLE_REAL); + OnEffectRemove += AuraEffectRemoveFn(spell_anraphet_omega_stance_AuraScript::HandleRemove, EFFECT_0, SPELL_AURA_PERIODIC_TRIGGER_SPELL, AURA_EFFECT_HANDLE_REAL); + } + }; + + AuraScript* GetAuraScript() const override + { + return new spell_anraphet_omega_stance_AuraScript(); + } +}; + +// 77106 - Omega Stance (Summon) class spell_anraphet_omega_stance_summon : public SpellScriptLoader { public: @@ -504,70 +866,82 @@ public: { PrepareSpellScript(spell_anraphet_omega_stance_summon_SpellScript); - void ModDestHeight(SpellEffIndex /*effIndex*/) + void SetDestPosition(SpellEffIndex /*effIndex*/) { - Position offset = {0.0f, 0.0f, 30.0f, 0.0f}; - const_cast(GetExplTargetDest())->RelocateOffset(offset); - GetHitDest()->RelocateOffset(offset); + if (Unit* caster = GetCaster()) + { + float x = caster->GetPositionX(); + float y = caster->GetPositionY(); + float z = caster->GetPositionZ() + 30.0f; + + const_cast(GetExplTargetDest())->Relocate(x, y, z); + GetHitDest()->Relocate(x, y, z); + } } void Register() { - OnEffectLaunch += SpellEffectFn(spell_anraphet_omega_stance_summon_SpellScript::ModDestHeight, EFFECT_0, SPELL_EFFECT_SUMMON); + OnEffectLaunch += SpellEffectFn(spell_anraphet_omega_stance_summon_SpellScript::SetDestPosition, EFFECT_0, SPELL_EFFECT_SUMMON); } }; - SpellScript* GetSpellScript() const + SpellScript* GetSpellScript() const override { return new spell_anraphet_omega_stance_summon_SpellScript(); } }; -class spell_omega_stance_spider_effect : public SpellScriptLoader +// 77127 Omega Stance Spider Effect +class spell_anraphet_omega_stance_spider_effect : public SpellScriptLoader { public: - spell_omega_stance_spider_effect() : SpellScriptLoader("spell_omega_stance_spider_effect") { } + spell_anraphet_omega_stance_spider_effect() : SpellScriptLoader("spell_anraphet_omega_stance_spider_effect") { } - class spell_omega_stance_spider_effect_SpellScript : public SpellScript + class spell_anraphet_omega_stance_spider_effect_SpellScript : public SpellScript { - PrepareSpellScript(spell_omega_stance_spider_effect_SpellScript); + PrepareSpellScript(spell_anraphet_omega_stance_spider_effect_SpellScript); - void SetDestPosition(SpellEffIndex effIndex) + void SetDestPosition(SpellEffIndex /*effIndex*/) { // Do our own calculations for the destination position. /// TODO: Remove this once we find a general rule for WorldObject::MovePosition (this spell shouldn't take the Z change into consideration) Unit* caster = GetCaster(); float angle = float(rand_norm()) * static_cast(2 * M_PI); - uint32 dist = caster->GetObjectSize() + GetSpellInfo()->Effects[effIndex].CalcRadius(GetCaster()) * (float)rand_norm(); + uint32 dist = caster->GetObjectSize() + GetSpellInfo()->Effects[EFFECT_0].CalcRadius(caster) * (float)rand_norm(); float x = caster->GetPositionX() + dist * std::cos(angle); float y = caster->GetPositionY() + dist * std::sin(angle); - float z = caster->GetPositionZ(); - caster->UpdateGroundPositionZ(x, y, z); + float z = caster->GetMap()->GetHeight(caster->GetPhaseShift(), x, y, caster->GetPositionZ()); + float o = GetHitDest()->GetOrientation(); - const_cast(GetExplTargetDest())->Relocate(x, y, z); - GetHitDest()->Relocate(x, y, z); + GetHitDest()->Relocate({ x, y, z, o }); } void Register() { - OnEffectLaunch += SpellEffectFn(spell_omega_stance_spider_effect_SpellScript::SetDestPosition, EFFECT_0, SPELL_EFFECT_DUMMY); + OnEffectLaunch += SpellEffectFn(spell_anraphet_omega_stance_spider_effect_SpellScript::SetDestPosition, EFFECT_0, SPELL_EFFECT_DUMMY); } }; - SpellScript* GetSpellScript() const + SpellScript* GetSpellScript() const override { - return new spell_omega_stance_spider_effect_SpellScript(); + return new spell_anraphet_omega_stance_spider_effect_SpellScript(); } }; void AddSC_boss_anraphet() { new boss_anraphet(); - new spell_anraphet_alpha_beams(); + new at_hoo_brann_idle_emote(); new npc_brann_bronzebeard_anraphet(); new npc_alpha_beam(); - new spell_anraphet_omega_stance_summon(); - new spell_omega_stance_spider_effect(); new npc_omega_stance(); + new spell_hoo_platform_teleport(); + new spell_flame_warden_lava_eruption(); + new spell_whirling_winds_movement(); + new spell_anraphet_destruction_protocol(); + new spell_anraphet_alpha_beams(); + new spell_anraphet_omega_stance(); + new spell_anraphet_omega_stance_summon(); + new spell_anraphet_omega_stance_spider_effect(); } diff --git a/src/server/scripts/Kalimdor/HallsOfOrigination/boss_earthrager_ptah.cpp b/src/server/scripts/Kalimdor/HallsOfOrigination/boss_earthrager_ptah.cpp index 938149b5337..cfd69d0317e 100644 --- a/src/server/scripts/Kalimdor/HallsOfOrigination/boss_earthrager_ptah.cpp +++ b/src/server/scripts/Kalimdor/HallsOfOrigination/boss_earthrager_ptah.cpp @@ -15,80 +15,72 @@ * with this program. If not, see . */ -#include "ScriptMgr.h" +// To-do: +// - Script "Sand Vortex", heroic mode ability. + #include "halls_of_origination.h" +#include "ScriptMgr.h" #include "InstanceScript.h" #include "Map.h" +#include "Player.h" #include "ScriptedCreature.h" +#include "SpellAuraEffects.h" #include "SpellScript.h" #include "TemporarySummon.h" #include "Weather.h" enum Texts { - SAY_AGGRO = 0, - SAY_DEATH = 1, + SAY_DEATH = 0, + SAY_AGGRO = 1, + SAY_PLAYER_KILL = 2, + SAY_SPECIAL = 3 }; enum Events { - EVENT_RAGING_SMASH = 1, - EVENT_FLAME_BOLT = 2, - EVENT_EARTH_SPIKE = 3, - EVENT_PTAH_EXPLODE = 4, - EVENT_QUICKSAND = 5, + // Earthrager Ptah + EVENT_RAGING_SMASH = 1, + EVENT_FLAME_BOLT, + EVENT_EARTH_SPIKE, + EVENT_PTAH_EXPLODE, + EVENT_QUICKSAND, + + // Beetle Stalker + EVENT_SUMMON_JEWELED_SCARAB }; enum Spells { + // Fight SPELL_RAGING_SMASH = 83650, SPELL_FLAME_BOLT = 77370, SPELL_EARTH_SPIKE_WARN = 94974, - SPELL_PTAH_EXPLOSION = 75519, + // Disperse SPELL_SANDSTORM = 75491, +// SPELL_TUMULTUOUS_EARTHSTORM = 75517, // Server-side spell + SPELL_PTAH_EXPLOSION = 75519, + SPELL_SUMMON_QUICKSAND = 75550, // Server-side spell + hidden client-side flag! - SPELL_SUMMON_QUICKSAND = 75550, // Spell not in DBC, no SMSG_SPELL_START/GO for it - - SPELL_BEETLE_BURROW = 75463, - + // Beetle Stalker + SPELL_BEETLE_BURROW = 75463, // Visual SPELL_SUMMON_JEWELED_SCARAB = 75462, - SPELL_SUMMON_DUSTBONE_HORROR = 75521, + SPELL_SUMMON_DUSTBONE_HORROR = 75521 }; enum Phases { - PHASE_NORMAL = 1, - PHASE_DISPERSE = 2, - - PHASE_MASK_DISPERSE = (1 << PHASE_DISPERSE), - PHASE_MASK_NORMAL = (1 << PHASE_NORMAL), + PHASE_FIGHT = 1, + PHASE_EARTHSTORM = 2 }; -enum PtahData +enum Sounds { - DATA_SUMMON_DEATHS = 0 -}; - -class SummonScarab : public BasicEvent -{ -public: - SummonScarab(Unit* owner, InstanceScript* instance) : _owner(owner), _instance(instance) { } - - bool Execute(uint64 /*execTime*/, uint32 /*diff*/) override - { - if (!_instance || _instance->GetBossState(DATA_EARTHRAGER_PTAH) != IN_PROGRESS) - return true; // delete event - - _owner->CastSpell(_owner, SPELL_SUMMON_JEWELED_SCARAB); - _owner->RemoveAurasDueToSpell(SPELL_BEETLE_BURROW); - return true; - } -protected: - Unit* _owner; - InstanceScript* _instance; + SOUND_PTAH_EARTHQUAKE = 18908 }; +// 39428 Earthrager Ptah class boss_earthrager_ptah : public CreatureScript { public: @@ -96,82 +88,35 @@ public: struct boss_earthrager_ptahAI : public BossAI { - boss_earthrager_ptahAI(Creature* creature) : BossAI(creature, DATA_EARTHRAGER_PTAH), _summonDeaths(0), _hasDispersed(false) { } + boss_earthrager_ptahAI(Creature* creature) : BossAI(creature, DATA_EARTHRAGER_PTAH) + { + Initialize(); + } - void Cleanup() + void Initialize() { - std::list units; - - GetCreatureListWithEntryInGrid(units, me, NPC_DUSTBONE_HORROR, 100.0f); - for (std::list::iterator itr = units.begin(); itr != units.end(); ++itr) - (*itr)->DespawnOrUnsummon(); - - GetCreatureListWithEntryInGrid(units, me, NPC_JEWELED_SCARAB, 100.0f); - for (std::list::iterator itr = units.begin(); itr != units.end(); ++itr) - (*itr)->DespawnOrUnsummon(); + _hasDispersed = false; + _summonsMaxCount = 0; + _summonsDeadCount = 0; } void Reset() override { - _summonDeaths = 0; - _hasDispersed = false; + Initialize(); Cleanup(); _Reset(); - events.SetPhase(PHASE_NORMAL); - events.ScheduleEvent(EVENT_RAGING_SMASH, urand(7000, 12000), 0, PHASE_NORMAL); - events.ScheduleEvent(EVENT_FLAME_BOLT, 15000, 0, PHASE_NORMAL); - events.ScheduleEvent(EVENT_EARTH_SPIKE, urand(16000, 21000), 0, PHASE_NORMAL); + events.SetPhase(PHASE_FIGHT); + ScheduleEvents(); } - void DamageTaken(Unit* /*attacker*/, uint32& damage) override + void DoAction(int32 action) override { - if (me->HealthBelowPctDamaged(50, damage) && (events.GetPhaseMask() & PHASE_MASK_NORMAL) && !_hasDispersed) - { - events.SetPhase(PHASE_DISPERSE); - _hasDispersed = true; + if (action != ACTION_PTAH_ADD_DIED) + return; - me->AttackStop(); - DoCast(me, SPELL_SANDSTORM); - me->GetMap()->SetZoneWeather(AREA_TOMB_OF_THE_EARTHRAGER, WEATHER_STATE_LIGHT_SANDSTORM, 1.0f); - events.ScheduleEvent(EVENT_PTAH_EXPLODE, 6000, 0, PHASE_DISPERSE); - events.ScheduleEvent(EVENT_QUICKSAND, 10000, 0, PHASE_DISPERSE); - - std::list stalkers; - GetCreatureListWithEntryInGrid(stalkers, me, NPC_BEETLE_STALKER, 100.0f); - std::list beetlers = stalkers; - - Trinity::Containers::RandomResize(beetlers, 9); // Holds the summoners of Jeweled Scarab - - for (std::list::iterator itr = beetlers.begin(); itr != beetlers.end(); ++itr) - { - stalkers.remove((*itr)); // Remove it to prevent a single trigger from spawning multiple npcs. - (*itr)->CastSpell((*itr), SPELL_BEETLE_BURROW); // Cast visual - // Summon after 5 seconds. - (*itr)->m_Events.AddEvent(new SummonScarab((*itr), instance), (*itr)->m_Events.CalculateTime(5000)); - } - - Trinity::Containers::RandomResize(stalkers, 2); // Holds the summoners of Dustbone Horror - - for (std::list::iterator itr = stalkers.begin(); itr != stalkers.end(); ++itr) - (*itr)->CastSpell((*itr), SPELL_SUMMON_DUSTBONE_HORROR); - } - } - - void SetData(uint32 index, uint32 /*value*/) override - { - if (index == DATA_SUMMON_DEATHS) - { - ++_summonDeaths; - if (_summonDeaths == 11) // All summons died - { - me->GetMap()->SetZoneWeather(AREA_TOMB_OF_THE_EARTHRAGER, WEATHER_STATE_FOG, 0.0f); - me->RemoveAurasDueToSpell(SPELL_PTAH_EXPLOSION); - events.SetPhase(PHASE_NORMAL); - events.ScheduleEvent(EVENT_RAGING_SMASH, urand(7000, 12000), 0, PHASE_NORMAL); - events.ScheduleEvent(EVENT_FLAME_BOLT, 15000, 0, PHASE_NORMAL); - events.ScheduleEvent(EVENT_EARTH_SPIKE, urand(16000, 21000), 0, PHASE_NORMAL); - } - } + // Increase _summonsDeadCount and check if max reached. + if (++_summonsDeadCount == _summonsMaxCount) + ExitDispersePhase(); } void JustEngagedWith(Unit* /*who*/) override @@ -181,6 +126,23 @@ public: _JustEngagedWith(); } + void DamageTaken(Unit* /*attacker*/, uint32& damage) override + { + // About to die? One-hit cases... + if (int64(me->GetHealth()) - int64(damage) <= 0) + return; + + // Earthquake phase happens at 50% health remaining. + if (me->HealthBelowPctDamaged(50, damage) && !_hasDispersed) + EnterDispersePhase(); + } + + void KilledUnit(Unit* victim) override + { + if (victim->GetTypeId() == TYPEID_PLAYER) + Talk(SAY_PLAYER_KILL); + } + void JustDied(Unit* /*killer*/) override { instance->SendEncounterUnit(ENCOUNTER_FRAME_DISENGAGE, me); @@ -193,7 +155,6 @@ public: { instance->SendEncounterUnit(ENCOUNTER_FRAME_DISENGAGE, me); _JustReachedHome(); - instance->SetBossState(DATA_EARTHRAGER_PTAH, FAIL); } void UpdateAI(uint32 diff) override @@ -212,37 +173,109 @@ public: { case EVENT_RAGING_SMASH: DoCastVictim(SPELL_RAGING_SMASH); - events.ScheduleEvent(EVENT_RAGING_SMASH, urand(7000, 12000), 0, PHASE_NORMAL); + events.Repeat(Seconds(19)); break; case EVENT_FLAME_BOLT: DoCast(me, SPELL_FLAME_BOLT); - events.ScheduleEvent(EVENT_FLAME_BOLT, 15000, 0, PHASE_NORMAL); + events.Repeat(Seconds(21)); break; case EVENT_EARTH_SPIKE: if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, 100.0f, true)) DoCast(target, SPELL_EARTH_SPIKE_WARN); - events.ScheduleEvent(EVENT_EARTH_SPIKE, urand(16000, 21000), 0, PHASE_NORMAL); + events.Repeat(Seconds(15)); break; case EVENT_PTAH_EXPLODE: + instance->SendEncounterUnit(ENCOUNTER_FRAME_UPDATE_PRIORITY, me, 0); + Talk(SAY_SPECIAL); DoCast(me, SPELL_PTAH_EXPLOSION); break; case EVENT_QUICKSAND: - // Spell not in DBC, it is not cast either, according to sniffs if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, 100.0f, true)) - if (Creature* quicksand = me->SummonCreature(NPC_QUICKSAND, *target)) - quicksand->SetUInt32Value(UNIT_CREATED_BY_SPELL, SPELL_SUMMON_QUICKSAND); - events.ScheduleEvent(EVENT_QUICKSAND, 10000, 0, PHASE_DISPERSE); + DoCast(target, SPELL_SUMMON_QUICKSAND); + events.Repeat(Seconds(21)); //Seconds(10) break; } + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; } - if (events.GetPhaseMask() & PHASE_MASK_NORMAL) // Do not melee in the disperse phase + if (!events.IsInPhase(PHASE_EARTHSTORM)) // Do not melee in the disperse phase DoMeleeAttackIfReady(); } - protected: - uint8 _summonDeaths; + private: + void ScheduleEvents() + { + events.Reset(); + events.ScheduleEvent(EVENT_RAGING_SMASH, Seconds(7), 0, PHASE_FIGHT); // Seconds(12) + events.ScheduleEvent(EVENT_FLAME_BOLT, Seconds(8), 0, PHASE_FIGHT); + events.ScheduleEvent(EVENT_EARTH_SPIKE, Seconds(15), 0, PHASE_FIGHT); // Seconds(21) + } + + void EnterDispersePhase() + { + events.SetPhase(PHASE_EARTHSTORM); + _hasDispersed = true; + + me->SetReactState(REACT_PASSIVE); + me->InterruptNonMeleeSpells(true); + me->AttackStop(); + me->GetMap()->SetZoneWeather(AREA_TOMB_OF_THE_EARTHRAGER, WEATHER_STATE_LIGHT_SANDSTORM, 1.0f); + DoCast(me, SPELL_SANDSTORM); + + std::list stalkers; + GetCreatureListWithEntryInGrid(stalkers, me, NPC_BEETLE_STALKER, 100.0f); + for (std::list::iterator itr = stalkers.begin(); itr != stalkers.end(); ++itr) + { + ++_summonsMaxCount; + if (-400.f < (*itr)->GetPositionY() && (*itr)->GetPositionY() < -390.f) // 2 stalkers in the middle + (*itr)->CastSpell((*itr), SPELL_SUMMON_DUSTBONE_HORROR); + else + (*itr)->CastSpell((*itr), SPELL_BEETLE_BURROW); + } + + events.ScheduleEvent(EVENT_PTAH_EXPLODE, Seconds(6), 0, PHASE_EARTHSTORM); + events.ScheduleEvent(EVENT_QUICKSAND, Seconds(10), 0, PHASE_EARTHSTORM); + } + + void ExitDispersePhase() + { + me->RemoveAurasDueToSpell(SPELL_PTAH_EXPLOSION); + me->GetMap()->SetZoneWeather(AREA_TOMB_OF_THE_EARTHRAGER, WEATHER_STATE_FOG, 0.0f); + instance->SendEncounterUnit(ENCOUNTER_FRAME_UPDATE_PRIORITY, me, 2); + events.SetPhase(PHASE_FIGHT); + ScheduleEvents(); + } + + void Cleanup() + { + std::list units; + + GetCreatureListWithEntryInGrid(units, me, NPC_BEETLE_STALKER, 100.0f); + for (std::list::iterator itr = units.begin(); itr != units.end(); ++itr) + { + ++_summonsMaxCount; + if (-400.f < (*itr)->GetPositionY() && (*itr)->GetPositionY() < -390.f) // 2 stalkers in the middle + (*itr)->RemoveAurasDueToSpell(SPELL_SUMMON_DUSTBONE_HORROR); + else + (*itr)->RemoveAurasDueToSpell(SPELL_BEETLE_BURROW); + } + + units.clear(); + GetCreatureListWithEntryInGrid(units, me, NPC_DUSTBONE_HORROR, 100.0f); + for (std::list::iterator itr = units.begin(); itr != units.end(); ++itr) + (*itr)->DespawnOrUnsummon(); + + units.clear(); + GetCreatureListWithEntryInGrid(units, me, NPC_JEWELED_SCARAB, 100.0f); + for (std::list::iterator itr = units.begin(); itr != units.end(); ++itr) + (*itr)->DespawnOrUnsummon(); + } + bool _hasDispersed; + uint8 _summonsMaxCount; + uint8 _summonsDeadCount; }; CreatureAI* GetAI(Creature* creature) const override @@ -251,6 +284,62 @@ public: } }; +// 15989 (criteria ID) Straw That Broke the Camel's Back +class achievement_straw_broke_camels_back : public AchievementCriteriaScript +{ +public: + achievement_straw_broke_camels_back() : AchievementCriteriaScript("achievement_straw_broke_camels_back") { } + + bool OnCheck(Player* player, Unit* /*target*/) override + { + if (Unit* vehicle = player->GetVehicleBase()) + return vehicle->GetEntry() == NPC_HOO_CAMEL; + return false; + } +}; + +// 40459 Beetle Stalker +class npc_ptah_beetle_stalker : public CreatureScript +{ +public: + npc_ptah_beetle_stalker() : CreatureScript("npc_ptah_beetle_stalker") { } + + struct npc_ptah_beetle_stalkerAI : public ScriptedAI + { + npc_ptah_beetle_stalkerAI(Creature* creature) : ScriptedAI(creature) { } + + void SpellHit(Unit* /*caster*/, const SpellInfo* spellInfo) override + { + if (spellInfo->Id == SPELL_BEETLE_BURROW) + events.ScheduleEvent(EVENT_SUMMON_JEWELED_SCARAB, Seconds(5), Seconds(6)); + } + + void UpdateAI(uint32 diff) override + { + if (events.Empty()) + return; + + events.Update(diff); + + if (events.ExecuteEvent() == EVENT_SUMMON_JEWELED_SCARAB) + { + DoCastAOE(SPELL_SUMMON_JEWELED_SCARAB); + me->RemoveAurasDueToSpell(SPELL_BEETLE_BURROW); + } + + } + + private: + EventMap events; + + }; + + CreatureAI* GetAI(Creature* creature) const override + { + return GetHallsOfOriginationAI(creature); + } +}; + class spell_earthrager_ptah_flame_bolt : public SpellScriptLoader { public: @@ -277,6 +366,34 @@ class spell_earthrager_ptah_flame_bolt : public SpellScriptLoader } }; +// 75491 Sandstorm spell_earthrager_ptah_sandstorm +class spell_earthrager_ptah_sandstorm : public SpellScriptLoader +{ +public: + spell_earthrager_ptah_sandstorm() : SpellScriptLoader("spell_earthrager_ptah_sandstorm") { } + + class spell_earthrager_ptah_sandstorm_SpellScript : public SpellScript + { + PrepareSpellScript(spell_earthrager_ptah_sandstorm_SpellScript); + + void PlaySound(SpellEffIndex /*effIndex*/) + { + if (Player* player = GetHitUnit()->ToPlayer()) + GetCaster()->PlayDirectSound(SOUND_PTAH_EARTHQUAKE, player); + } + + void Register() override + { + OnEffectHitTarget += SpellEffectFn(spell_earthrager_ptah_sandstorm_SpellScript::PlaySound, EFFECT_0, SPELL_EFFECT_APPLY_AURA); + } + }; + + SpellScript* GetSpellScript() const override + { + return new spell_earthrager_ptah_sandstorm_SpellScript(); + } +}; + class spell_earthrager_ptah_explosion : public SpellScriptLoader { public: @@ -304,14 +421,14 @@ public: } } - void Register() + void Register() override { OnEffectApply += AuraEffectApplyFn(spell_earthrager_ptah_explosion_AuraScript::SetFlags, EFFECT_0, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL); OnEffectRemove += AuraEffectRemoveFn(spell_earthrager_ptah_explosion_AuraScript::RemoveFlags, EFFECT_0, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL); } }; - AuraScript* GetAuraScript() const + AuraScript* GetAuraScript() const override { return new spell_earthrager_ptah_explosion_AuraScript(); } @@ -320,6 +437,9 @@ public: void AddSC_boss_earthrager_ptah() { new boss_earthrager_ptah(); + new achievement_straw_broke_camels_back(); + new npc_ptah_beetle_stalker(); new spell_earthrager_ptah_flame_bolt(); + new spell_earthrager_ptah_sandstorm(); new spell_earthrager_ptah_explosion(); } diff --git a/src/server/scripts/Kalimdor/HallsOfOrigination/boss_isiset.cpp b/src/server/scripts/Kalimdor/HallsOfOrigination/boss_isiset.cpp new file mode 100644 index 00000000000..ed272787298 --- /dev/null +++ b/src/server/scripts/Kalimdor/HallsOfOrigination/boss_isiset.cpp @@ -0,0 +1,859 @@ +/* + * Copyright (C) 2008-2018 TrinityCore + * + * 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 "ScriptMgr.h" +#include "GridNotifiers.h" +#include "ScriptedCreature.h" +#include "SpellScript.h" +#include "SpellAuraEffects.h" +#include "halls_of_origination.h" +#include "TemporarySummon.h" + +enum Spells +{ + SPELL_SUPERNOVA = 74136, + SPELL_SUPERNOVA_EFFECT = 74137, + + // Startfall / Astral Rain ability + SPELL_ASTRAL_RAIN_CONTROLLER = 74381, + SPELL_ASTRAL_RAIN_1 = 74134, + SPELL_ASTRAL_RAIN_2 = 74365, + SPELL_ASTRAL_RAIN_3 = 74371, + + // Adds / Celestial Call ability + SPELL_ASTRAL_FAMILIAR_CONTROLLER = 74383, + SPELL_CELESTIAL_CALL_1 = 74362, + SPELL_CELESTIAL_CALL_2 = 74355, + SPELL_CELESTIAL_CALL_3 = 74364, + + // Mana shield / Veil of Sky ability + SPELL_MANA_SHIELD_CONTROLLER = 74382, + SPELL_VEIL_OF_SKY_1 = 74133, + SPELL_VEIL_OF_SKY_2 = 74372, + SPELL_VEIL_OF_SKY_3 = 74373, + SPELL_VEIL_OF_SKY_DAMAGE = 79370, + + // Mirror Images (at 66% and 33%) + SPELL_MIRROR_IMAGE_STARRY_SKY_SPAWNER = 69941, + SPELL_MIRROR_IMAGE_STARRY_SKY_N = 69936, + SPELL_MIRROR_IMAGE_STARRY_SKY_E = 69939, + SPELL_MIRROR_IMAGE_STARRY_SKY_W = 69940, + SPELL_ASTRAL_SHIFT = 74312, + SPELL_ASTRAL_SHIFT_EXPLOSION = 74333, + SPELL_MIRROR_IMAGE_SPAWNER = 74264, + SPELL_MIRROR_IMAGE_N = 74263, + SPELL_MIRROR_IMAGE_E = 74262, + SPELL_MIRROR_IMAGE_W = 74261, + SPELL_ADDS_STATE_VISUAL = 74289, + SPELL_STARFALL_STATE_VISUAL = 74265, + SPELL_MANA_SHIELD_STATE_VISUAL = 74266, + SPELL_IMAGE_EXPLOSION = 74301, + + // Celestial Familiar npc + SPELL_ORB_VISUAL = 74356, + SPELL_ARCANE_BARRAGE = 74374, + + // Starry Sky npc + SPELL_STARRY_SKY_VISUAL = 74149, + + // Astral Shift Explosion Visual npc + SPELL_ASTRAL_SHIFT_EXPLOSION_VISUAL = 74331, + + // Spatial Flux & Energy Flux (heroic only) + SPELL_CALL_OF_SKY = 90750 // Summons Spatial Flux (npc script in halls_of_oriignation.cpp) +}; + +enum NPCs +{ + NPC_CELESTIAL_FAMILIAR = 39795, + NPC_ASTRAL_RAIN = 39720, // N + NPC_CELESTIAL_CALL = 39721, // E + NPC_VEIL_OF_SKY = 39722, // W + NPC_ISISET_SPATIAL_FLUX = 48707 // heroic only +}; + +enum Texts +{ + SAY_AGGRO = 0, + SAY_SUPERNOVA = 1, + SAY_SUPERNOVA_WARNING = 2, + SAY_PLAYER_KILL = 3, + SAY_DEATH = 4 +}; + +enum Events +{ + EVENT_SUPERNOVA = 1, + EVENT_ASTRAL_RAIN, + EVENT_ASTRAL_FAMILIAR, + EVENT_VEIL_OF_SKY, + EVENT_IMAGES_ATTACK, + EVENT_DESPAWN_IMAGES, + + EVENT_ORB_ADD_VISUAL, + EVENT_ORB_SET_AGGRESSIVE, + EVENT_ORB_ARCANE_BARRAGE, + + EVENT_IMAGE_ASTRAL_RAIN_ABILITY, + EVENT_IMAGE_ARCANE_BARRAGE_ABILITY, + EVENT_IMAGE_VEIL_OF_SKY_ABILITY, + + EVENT_STARRY_SKY_ADD_VISUAL, +}; + +enum Actions +{ + ACTION_IMAGES_SET_AGGRESSIVE, + ACTION_MIRROR_IMAGE_DIED, + ACTION_IMAGES_SET_PASSIVE, + ACTION_IMAGES_DESPAWN +}; + +class boss_isiset : public CreatureScript +{ +public: + boss_isiset() : CreatureScript("boss_isiset") { } + + struct boss_isisetAI : public BossAI + { + boss_isisetAI(Creature* creature) : BossAI(creature, DATA_ISISET) { } + + void Reset() override + { + _Reset(); + + me->SetReactState(REACT_AGGRESSIVE); + _transitionPhase = false; + instance->SetData(DATA_ISISET_PHASE, 1); + instance->SetData(DATA_ISISET_ASTRAL_RAIN_ALIVE, 1); + instance->SetData(DATA_ISISET_CELESTIAL_CALL_ALIVE, 1); + instance->SetData(DATA_ISISET_VEIL_OF_SKY_ALIVE, 1); + } + + void JustEngagedWith(Unit* /*victim*/) override + { + Talk(SAY_AGGRO); + if (IsHeroic()) + DoCastSelf(SPELL_CALL_OF_SKY); + RescheduleEvents(); + _JustEngagedWith(); + instance->SendEncounterUnit(ENCOUNTER_FRAME_SET_COMBAT_RES_LIMIT, 0); + } + + void DamageTaken(Unit* /*attacker*/, uint32 &damage) override + { + if (_transitionPhase) + return; + + // Second transition + if (instance->GetData(DATA_ISISET_PHASE) == 2 && me->HealthBelowPctDamaged(33, damage)) + MirrorImage(); + + // First transition + if (instance->GetData(DATA_ISISET_PHASE) == 1 && me->HealthBelowPctDamaged(66, damage)) + MirrorImage(); + } + + void DoAction(int32 action) override + { + // We only accept action from the first dying image + if (_mirrorImageDied || action != ACTION_MIRROR_IMAGE_DIED) + return; + + _mirrorImageDied = true; + + // Handle despawning + DummyEntryCheckPredicate pred; + summons.DoAction(ACTION_IMAGES_SET_PASSIVE, pred); + events.ScheduleEvent(EVENT_DESPAWN_IMAGES, Seconds(2)); + } + + void KilledUnit(Unit* who) override + { + if (who->GetTypeId() == TYPEID_PLAYER) + Talk(SAY_PLAYER_KILL); + } + + void JustDied(Unit* /*killer*/) override + { + //instance->SendEncounterUnit(ENCOUNTER_FRAME_SET_COMBAT_RES_LIMIT, ?me?, 1); //from sniff, not sure what it does + instance->SendEncounterUnit(ENCOUNTER_FRAME_DISENGAGE, me, 0); + _JustDied(); + Talk(SAY_DEATH); + } + + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) + return; + + events.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + while (uint32 eventId = events.ExecuteEvent()) + { + switch (eventId) + { + case EVENT_ASTRAL_RAIN: + DoCastSelf(SPELL_ASTRAL_RAIN_CONTROLLER); + events.Repeat(Seconds(15), Seconds(19)); + break; + case EVENT_ASTRAL_FAMILIAR: + DoCastSelf(SPELL_ASTRAL_FAMILIAR_CONTROLLER); + events.Repeat(Seconds(26), Seconds(30)); + break; + case EVENT_VEIL_OF_SKY: + DoCastSelf(SPELL_MANA_SHIELD_CONTROLLER); + events.Repeat(Seconds(20), Seconds(25)); + break; + case EVENT_SUPERNOVA: + DoCastSelf(SPELL_SUPERNOVA); + Talk(SAY_SUPERNOVA); + Talk(SAY_SUPERNOVA_WARNING); + events.Repeat(Seconds(45), Seconds(50)); + break; + case EVENT_IMAGES_ATTACK: + { + DummyEntryCheckPredicate pred; + summons.DoAction(ACTION_IMAGES_SET_AGGRESSIVE, pred); + break; + } + case EVENT_DESPAWN_IMAGES: + { + DummyEntryCheckPredicate pred; + summons.DoAction(ACTION_IMAGES_DESPAWN, pred); + instance->SetData(DATA_ISISET_PHASE, instance->GetData(DATA_ISISET_PHASE) + 1); + _transitionPhase = false; + me->RemoveAurasDueToSpell(SPELL_ASTRAL_SHIFT); + instance->SendEncounterUnit(ENCOUNTER_FRAME_UPDATE_PRIORITY, me, 1); + RescheduleEvents(); + break; + } + default: + break; + } + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + } + + DoMeleeAttackIfReady(); + } + + private: + void RescheduleEvents() + { + events.Reset(); + if (instance->GetData(DATA_ISISET_ASTRAL_RAIN_ALIVE)) + events.ScheduleEvent(EVENT_ASTRAL_RAIN, Seconds(4), Seconds(6)); + if (instance->GetData(DATA_ISISET_CELESTIAL_CALL_ALIVE)) + events.ScheduleEvent(EVENT_ASTRAL_FAMILIAR, Seconds(7)); + if (instance->GetData(DATA_ISISET_VEIL_OF_SKY_ALIVE)) + events.ScheduleEvent(EVENT_VEIL_OF_SKY, Seconds(8), Seconds(10)); + events.ScheduleEvent(EVENT_SUPERNOVA, Seconds(12), Seconds(17)); + } + + // Mirror Image transition phase + void MirrorImage() + { + _transitionPhase = true; + + events.Reset(); + instance->SendEncounterUnit(ENCOUNTER_FRAME_UPDATE_PRIORITY, me, 0); + _mirrorImageDied = false; + + DoCastSelf(SPELL_ASTRAL_SHIFT); + DoCastSelf(SPELL_ASTRAL_SHIFT_EXPLOSION); + DoCastSelf(SPELL_MIRROR_IMAGE_STARRY_SKY_SPAWNER); + DoCastSelf(SPELL_MIRROR_IMAGE_SPAWNER); + + events.ScheduleEvent(EVENT_IMAGES_ATTACK, Seconds(2)); + } + + bool _transitionPhase; + bool _mirrorImageDied; + }; + + CreatureAI* GetAI(Creature* creature) const override + { + return GetHallsOfOriginationAI(creature); + } +}; + +// 39795 - Celestial Familiar +class npc_celestial_familiar : public CreatureScript +{ +public: + npc_celestial_familiar() : CreatureScript("npc_celestial_familiar") { } + + struct npc_celestial_familiarAI : public ScriptedAI + { + npc_celestial_familiarAI(Creature* creature) : ScriptedAI(creature) { } + + void Reset() override + { + me->SetReactState(REACT_PASSIVE); + me->SetInCombatWithZone(); + events.Reset(); + events.ScheduleEvent(EVENT_ORB_ADD_VISUAL, Seconds(1)); + events.ScheduleEvent(EVENT_ORB_SET_AGGRESSIVE, Seconds(2)); + events.ScheduleEvent(EVENT_ORB_ARCANE_BARRAGE, Seconds(4), Seconds(6)); + } + + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) + return; + + events.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + while (uint32 eventId = events.ExecuteEvent()) + { + switch (eventId) + { + case EVENT_ORB_ADD_VISUAL: + DoCastSelf(SPELL_ORB_VISUAL); + break; + case EVENT_ORB_SET_AGGRESSIVE: + me->SetReactState(REACT_AGGRESSIVE); + break; + case EVENT_ORB_ARCANE_BARRAGE: + DoCastSelf(SPELL_ARCANE_BARRAGE); + events.Repeat(Seconds(6), Seconds(7)); + break; + default: + break; + } + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + } + } + + private: + EventMap events; + }; + + CreatureAI* GetAI(Creature* creature) const override + { + return GetHallsOfOriginationAI(creature); + } +}; + +// 39720 - Astral Rain +// 39721 - Celestial Call +// 39722 - Veil of Sky +class npc_isiset_mirror_image : public CreatureScript +{ +public: + npc_isiset_mirror_image() : CreatureScript("npc_isiset_mirror_image") { } + + struct npc_isiset_mirror_imageAI : public ScriptedAI + { + npc_isiset_mirror_imageAI(Creature* creature) : ScriptedAI(creature), _instance(creature->GetInstanceScript()) { } + + void Reset() override + { + me->SetReactState(REACT_PASSIVE); + _instance->SendEncounterUnit(ENCOUNTER_FRAME_ENGAGE, me, 1); + + switch (me->GetEntry()) { + case NPC_ASTRAL_RAIN: + _events.ScheduleEvent(EVENT_IMAGE_ASTRAL_RAIN_ABILITY, Seconds(3), Seconds(5)); + DoCastSelf(SPELL_STARFALL_STATE_VISUAL); + break; + case NPC_CELESTIAL_CALL: + _events.ScheduleEvent(EVENT_IMAGE_ARCANE_BARRAGE_ABILITY, Seconds(6)); + DoCastSelf(SPELL_ADDS_STATE_VISUAL); + break; + case NPC_VEIL_OF_SKY: + _events.ScheduleEvent(EVENT_IMAGE_VEIL_OF_SKY_ABILITY, Seconds(8), Seconds(9)); + //DoCastSelf(SPELL_MANA_SHIELD_STATE_VISUAL); // Disabled in sniffs/on retail, too shiny. + break; + } + } + + void DamageTaken(Unit* /*attacker*/, uint32 &damage) override + { + // Because only one image explodes, others become passive + if (me->GetReactState() != REACT_AGGRESSIVE || me->GetHealth() > damage) + return; + + me->SetReactState(REACT_PASSIVE); + damage = me->GetHealth() - 1; + DoCastSelf(SPELL_IMAGE_EXPLOSION); + } + + void DoAction(int32 action) override + { + switch (action) + { + case ACTION_IMAGES_SET_AGGRESSIVE: + me->SetReactState(REACT_AGGRESSIVE); + me->SetInCombatWithZone(); + if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM)) + AttackStart(target); + break; + case ACTION_IMAGES_SET_PASSIVE: + me->SetReactState(REACT_PASSIVE); + me->AttackStop(); + break; + case ACTION_IMAGES_DESPAWN: + _instance->SendEncounterUnit(ENCOUNTER_FRAME_DISENGAGE, me); + me->DespawnOrUnsummon(); + break; + default: + break; + } + } + + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) + return; + + _events.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + while (uint32 eventId = _events.ExecuteEvent()) + { + switch (eventId) + { + case EVENT_IMAGE_ASTRAL_RAIN_ABILITY: + DoCastSelf(SPELL_ASTRAL_RAIN_CONTROLLER); + _events.Repeat(Seconds(16), Seconds(19)); + break; + case EVENT_IMAGE_ARCANE_BARRAGE_ABILITY: + DoCastSelf(SPELL_ARCANE_BARRAGE); + _events.Repeat(Seconds(8), Seconds(10)); + break; + case EVENT_IMAGE_VEIL_OF_SKY_ABILITY: + DoCastSelf(SPELL_VEIL_OF_SKY_1); // Veil of Sky npc does not cast controller spell! + break; + default: + break; + } + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + } + + DoMeleeAttackIfReady(); + } + + private: + EventMap _events; + InstanceScript* _instance; + }; + + CreatureAI* GetAI(Creature* creature) const override + { + return GetHallsOfOriginationAI(creature); + } +}; + +// 74381 - Astral Rain Controller Spell +class spell_isiset_astral_rain_controller : public SpellScriptLoader +{ +public: + spell_isiset_astral_rain_controller() : SpellScriptLoader("spell_isiset_astral_rain_controller") { } + + class spell_isiset_astral_rain_controller_SpellScript : public SpellScript + { + PrepareSpellScript(spell_isiset_astral_rain_controller_SpellScript); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ + SPELL_ASTRAL_RAIN_1, + SPELL_ASTRAL_RAIN_2, + SPELL_ASTRAL_RAIN_3 + }); + } + + void HandleDummy(SpellEffIndex /*effIndex*/) + { + InstanceScript* instance = GetCaster()->GetInstanceScript(); + if (!instance || !instance->GetData(DATA_ISISET_ASTRAL_RAIN_ALIVE)) + return; + + uint32 phase = instance->GetData(DATA_ISISET_PHASE); + if (phase == 1) + GetCaster()->CastSpell(GetCaster(), SPELL_ASTRAL_RAIN_1, true); + else if (phase == 2) + GetCaster()->CastSpell(GetCaster(), SPELL_ASTRAL_RAIN_2, true); + else if (phase == 3) + GetCaster()->CastSpell(GetCaster(), SPELL_ASTRAL_RAIN_3, true); + } + + void Register() override + { + OnEffectLaunch += SpellEffectFn(spell_isiset_astral_rain_controller_SpellScript::HandleDummy, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT); + } + }; + + SpellScript* GetSpellScript() const override + { + return new spell_isiset_astral_rain_controller_SpellScript(); + } +}; + +// 74382 - Mana Shield Controller Spell +class spell_isiset_mana_shield_controller : public SpellScriptLoader +{ +public: + spell_isiset_mana_shield_controller() : SpellScriptLoader("spell_isiset_mana_shield_controller") { } + + class spell_isiset_mana_shield_controller_SpellScript : public SpellScript + { + PrepareSpellScript(spell_isiset_mana_shield_controller_SpellScript); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ + SPELL_VEIL_OF_SKY_1, + SPELL_VEIL_OF_SKY_2, + SPELL_VEIL_OF_SKY_3 + }); + } + + void HandleDummy(SpellEffIndex /*effIndex*/) + { + InstanceScript* instance = GetCaster()->GetInstanceScript(); + if (!instance || !instance->GetData(DATA_ISISET_VEIL_OF_SKY_ALIVE)) + return; + + uint32 phase = instance->GetData(DATA_ISISET_PHASE); + if (phase == 1) + GetCaster()->CastSpell(GetCaster(), SPELL_VEIL_OF_SKY_1); + else if (phase == 2) + GetCaster()->CastSpell(GetCaster(), SPELL_VEIL_OF_SKY_2); + else if (phase == 3) + GetCaster()->CastSpell(GetCaster(), SPELL_VEIL_OF_SKY_3); + } + + void Register() override + { + OnEffectLaunch += SpellEffectFn(spell_isiset_mana_shield_controller_SpellScript::HandleDummy, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT); + } + }; + + SpellScript* GetSpellScript() const override + { + return new spell_isiset_mana_shield_controller_SpellScript(); + } +}; + +// 74383 - Astral Familiar Controller Spell +class spell_isiset_astral_familiar_controller : public SpellScriptLoader +{ +public: + spell_isiset_astral_familiar_controller() : SpellScriptLoader("spell_isiset_astral_familiar_controller") { } + + class spell_isiset_astral_familiar_controller_SpellScript : public SpellScript + { + PrepareSpellScript(spell_isiset_astral_familiar_controller_SpellScript); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ + SPELL_CELESTIAL_CALL_1, + SPELL_CELESTIAL_CALL_2, + SPELL_CELESTIAL_CALL_3 + }); + } + + void HandleDummy(SpellEffIndex /*effIndex*/) + { + InstanceScript* instance = GetCaster()->GetInstanceScript(); + if (!instance || !instance->GetData(DATA_ISISET_CELESTIAL_CALL_ALIVE)) + return; + + uint32 phase = instance->GetData(DATA_ISISET_PHASE); + if (phase == 1) + GetCaster()->CastSpell(GetCaster(), SPELL_CELESTIAL_CALL_1, true); + else if (phase == 2) + GetCaster()->CastSpell(GetCaster(), SPELL_CELESTIAL_CALL_2, true); + else if (phase == 3) + GetCaster()->CastSpell(GetCaster(), SPELL_CELESTIAL_CALL_3, true); + } + + void Register() override + { + OnEffectLaunch += SpellEffectFn(spell_isiset_astral_familiar_controller_SpellScript::HandleDummy, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT); + } + }; + + SpellScript* GetSpellScript() const override + { + return new spell_isiset_astral_familiar_controller_SpellScript(); + } +}; + +// 74133, 74372, 74373 - Veil of Sky (mana shield) +class spell_isiset_veil_of_sky : public SpellScriptLoader +{ +public: + spell_isiset_veil_of_sky() : SpellScriptLoader("spell_isiset_veil_of_sky") { } + + class spell_isiset_veil_of_sky_AuraScript : public AuraScript + { + PrepareAuraScript(spell_isiset_veil_of_sky_AuraScript); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_VEIL_OF_SKY_DAMAGE }); + } + + bool Load() override + { + reflectPct = GetSpellInfo()->Effects[EFFECT_1].BasePoints; + return true; + } + + void Trigger(AuraEffect* /*aurEff*/, DamageInfo& dmgInfo, uint32& absorbAmount) + { + if (dmgInfo.GetDamageType() != SPELL_DIRECT_DAMAGE) + return; + + int32 damage = CalculatePct(absorbAmount, reflectPct); + GetCaster()->CastCustomSpell(SPELL_VEIL_OF_SKY_DAMAGE, SPELLVALUE_BASE_POINT0, damage, dmgInfo.GetAttacker(), true); + } + + void Register() override + { + AfterEffectManaShield += AuraEffectManaShieldFn(spell_isiset_veil_of_sky_AuraScript::Trigger, EFFECT_0); + } + + private: + int32 reflectPct; + }; + + AuraScript* GetAuraScript() const override + { + return new spell_isiset_veil_of_sky_AuraScript(); + } +}; + +// 74137 - Supernova (disorient + triggers 76670) (are pets also affected?) +// 76670 - Supernova (damage) +class spell_isiset_supernova_filter : public SpellScriptLoader +{ +public: + spell_isiset_supernova_filter() : SpellScriptLoader("spell_isiset_supernova_filter") { } + + class spell_isiset_supernova_filter_SpellScript : public SpellScript + { + PrepareSpellScript(spell_isiset_supernova_filter_SpellScript); + + void FilterTargets(std::list& targets) + { + Unit* caster = GetCaster(); + targets.remove_if([caster](WorldObject* object) { return !object->ToUnit() || !object->ToUnit()->IsCharmedOwnedByPlayerOrPlayer() || !object->isInFront(caster, 2.5f); }); + } + + void Register() override + { + OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_isiset_supernova_filter_SpellScript::FilterTargets, m_scriptSpellId == SPELL_SUPERNOVA_EFFECT ? EFFECT_1 : EFFECT_0, TARGET_UNIT_SRC_AREA_ENEMY); + } + }; + + SpellScript* GetSpellScript() const override + { + return new spell_isiset_supernova_filter_SpellScript(); + } +}; + +// 69941 - Mirror Image +class spell_isiset_mirror_image_starry_sky_spawner : public SpellScriptLoader +{ +public: + spell_isiset_mirror_image_starry_sky_spawner() : SpellScriptLoader("spell_isiset_mirror_image_starry_sky_spawner") { } + + class spell_isiset_mirror_image_starry_sky_spawner_SpellScript : public SpellScript + { + PrepareSpellScript(spell_isiset_mirror_image_starry_sky_spawner_SpellScript); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_MIRROR_IMAGE_STARRY_SKY_N, SPELL_MIRROR_IMAGE_STARRY_SKY_E, SPELL_MIRROR_IMAGE_STARRY_SKY_W }); + } + + void HandleDummy(SpellEffIndex /*effIndex*/) + { + InstanceScript* instance = GetCaster()->GetInstanceScript(); + if (!instance) + return; + + if (instance->GetData(DATA_ISISET_CELESTIAL_CALL_ALIVE)) + GetCaster()->CastSpell(GetCaster(), SPELL_MIRROR_IMAGE_STARRY_SKY_N, true); + if (instance->GetData(DATA_ISISET_ASTRAL_RAIN_ALIVE)) + GetCaster()->CastSpell(GetCaster(), SPELL_MIRROR_IMAGE_STARRY_SKY_E, true); + if (instance->GetData(DATA_ISISET_VEIL_OF_SKY_ALIVE)) + GetCaster()->CastSpell(GetCaster(), SPELL_MIRROR_IMAGE_STARRY_SKY_W, true); + } + + void Register() override + { + OnEffectLaunch += SpellEffectFn(spell_isiset_mirror_image_starry_sky_spawner_SpellScript::HandleDummy, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT); + } + }; + + SpellScript* GetSpellScript() const override + { + return new spell_isiset_mirror_image_starry_sky_spawner_SpellScript(); + } +}; + +// 74264 - Mirror Image +class spell_isiset_mirror_image_spawner : public SpellScriptLoader +{ +public: + spell_isiset_mirror_image_spawner() : SpellScriptLoader("spell_isiset_mirror_image_spawner") { } + + class spell_isiset_mirror_image_spawner_SpellScript : public SpellScript + { + PrepareSpellScript(spell_isiset_mirror_image_spawner_SpellScript); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_MIRROR_IMAGE_N, SPELL_MIRROR_IMAGE_E, SPELL_MIRROR_IMAGE_W }); + } + + void HandleDummy(SpellEffIndex /*effIndex*/) + { + InstanceScript* const instance = GetCaster()->GetInstanceScript(); + if (!instance) + return; + + if (instance->GetData(DATA_ISISET_CELESTIAL_CALL_ALIVE)) + GetCaster()->CastSpell(GetCaster(), SPELL_MIRROR_IMAGE_N, true); + if (instance->GetData(DATA_ISISET_ASTRAL_RAIN_ALIVE)) + GetCaster()->CastSpell(GetCaster(), SPELL_MIRROR_IMAGE_E, true); + if (instance->GetData(DATA_ISISET_VEIL_OF_SKY_ALIVE)) + GetCaster()->CastSpell(GetCaster(), SPELL_MIRROR_IMAGE_W, true); + } + + void Register() override + { + OnEffectLaunch += SpellEffectFn(spell_isiset_mirror_image_spawner_SpellScript::HandleDummy, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT); + } + + }; + + SpellScript* GetSpellScript() const override + { + return new spell_isiset_mirror_image_spawner_SpellScript(); + } +}; + +// 74301 - Image Explosion +class spell_isiset_image_explosion : public SpellScriptLoader +{ +public: + spell_isiset_image_explosion() : SpellScriptLoader("spell_isiset_image_explosion") { } + + class spell_isiset_image_explosion_SpellScript : public SpellScript + { + PrepareSpellScript(spell_isiset_image_explosion_SpellScript); + + void HandleDummy(SpellEffIndex /*effIndex*/) + { + InstanceScript* const instance = GetCaster()->GetInstanceScript(); + if (!instance) + return; + + switch (GetCaster()->GetEntry()) + { + case NPC_ASTRAL_RAIN: + instance->SetData(DATA_ISISET_ASTRAL_RAIN_ALIVE, 0); + break; + case NPC_CELESTIAL_CALL: + instance->SetData(DATA_ISISET_CELESTIAL_CALL_ALIVE, 0); + break; + case NPC_VEIL_OF_SKY: + instance->SetData(DATA_ISISET_VEIL_OF_SKY_ALIVE, 0); + break; + } + + if (Creature* Isiset = instance->GetCreature(DATA_ISISET)) + Isiset->AI()->DoAction(ACTION_MIRROR_IMAGE_DIED); + } + + void Register() override + { + OnEffectLaunch += SpellEffectFn(spell_isiset_image_explosion_SpellScript::HandleDummy, EFFECT_0, SPELL_EFFECT_DUMMY); + } + }; + + SpellScript* GetSpellScript() const override + { + return new spell_isiset_image_explosion_SpellScript(); + } +}; + +// 90755 Call of Sky (heroic only) +class spell_isiset_call_of_sky : public SpellScriptLoader +{ +public: + spell_isiset_call_of_sky() : SpellScriptLoader("spell_isiset_call_of_sky") { } + + class spell_isiset_call_of_sky_SpellScript : public SpellScript + { + PrepareSpellScript(spell_isiset_call_of_sky_SpellScript); + + void HandleDummy(SpellEffIndex /*effIndex*/) + { + if (InstanceScript* instance = GetCaster()->GetInstanceScript()) + if (Creature* Isiset = instance->GetCreature(DATA_ISISET)) + Isiset->SummonCreature(NPC_ISISET_SPATIAL_FLUX, GetHitDest()->GetPositionX(), GetHitDest()->GetPositionY(), GetHitDest()->GetPositionZ(), 2.775074f, TEMPSUMMON_TIMED_DESPAWN_OUT_OF_COMBAT); + } + + void Register() override + { + OnEffectLaunch += SpellEffectFn(spell_isiset_call_of_sky_SpellScript::HandleDummy, EFFECT_0, SPELL_EFFECT_DUMMY); + } + }; + + SpellScript* GetSpellScript() const override + { + return new spell_isiset_call_of_sky_SpellScript(); + } +}; + +void AddSC_boss_isiset() +{ + new boss_isiset(); + new npc_celestial_familiar(); + new npc_isiset_mirror_image(); + new spell_isiset_astral_rain_controller(); + new spell_isiset_mana_shield_controller(); + new spell_isiset_astral_familiar_controller(); + new spell_isiset_veil_of_sky(); + new spell_isiset_supernova_filter(); + new spell_isiset_mirror_image_starry_sky_spawner(); + new spell_isiset_mirror_image_spawner(); + new spell_isiset_image_explosion(); + new spell_isiset_call_of_sky(); +} diff --git a/src/server/scripts/Kalimdor/HallsOfOrigination/boss_setesh.cpp b/src/server/scripts/Kalimdor/HallsOfOrigination/boss_setesh.cpp new file mode 100644 index 00000000000..1980613838c --- /dev/null +++ b/src/server/scripts/Kalimdor/HallsOfOrigination/boss_setesh.cpp @@ -0,0 +1,530 @@ +/* + * Copyright (C) 2008-2018 TrinityCore + * + * 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 "halls_of_origination.h" +#include "Map.h" +#include "ScriptMgr.h" +#include "ScriptedCreature.h" +#include "SpellScript.h" + +enum Spells +{ + SPELL_CHAOS_BOLT = 77069, + SPELL_CHAOS_BLAST = 76674, // Forcecast player to summon npc that Setesh fires missle at. + SPELL_SEED_OF_CHAOS = 76888, + SPELL_REIGN_OF_CHAOS = 77023, + SPELL_CHANNEL_CHAOS_PORTAL = 76784, + + // Chaos Blast + SPELL_CHAOS_BLAST_ME = 76737, // Tells Setesh to launch missle at it. + SPELL_CHAOS_BLAST_MISSLE = 76676, + SPELL_CHAOS_BLAST_AURA = 76681, + + // Seed of Chaos + SPELL_SEED_OF_CHAOS_DUMMY = 76865, + SPELL_SEED_OF_CHAOS_EXPLOSION = 76870, + + // Void Rift + SPELL_VOID_RIFT = 73699, + + // Chaos Portal + SPELL_DUMMY_AURA = 94562, // Using random dummy aura without effect - for Setesh to target the correct portal. + SPELL_NIGHTMARE_PORTAL_VISUAL = 76714, + SPELL_SUMMON_VOID_SENTINEL = 77242, // summons 41208 + SPELL_SUMMON_VOID_SEEKER = 77271, // summons 41148 + SPELL_SUMMON_VOID_WYRM_1 = 77272, // summons 41212 + SPELL_SUMMON_VOID_WYRM_2 = 79439, // summons 41212 + + // Add Stalker + SPELL_CHANNEL_SUMMON_ADDS = 77607 +}; + +enum Events +{ + // Ammunae + EVENT_CHAOS_PORTAL = 1, + EVENT_CONTINUE_FIGHT, + EVENT_CHAOS_BLAST, + EVENT_SEED_OF_CHAOS, + EVENT_REIGN_OF_CHAOS, + + // Chaos Portal + EVENT_CAST_VISUAL, + EVENT_SUMMON_WAVE_1, + EVENT_SUMMON_WAVE_2, + EVENT_SUMMON_WAVE_3, + EVENT_SUMMON_WAVE_RANDOM +}; + +enum Actions +{ + ACTION_SETESH_ATTACK +}; +enum Faction +{ + FACTION_ENEMY_14 = 14, + FACTION_ENEMY_16 = 16 +}; + +enum Phases +{ + PHASE_FIGHT = 1, + PHASE_CHAOS_PORTAL = 2 +}; + +enum Points +{ + POINT_CHANNEL_CHAOS_PORTAL, + POINT_GROUND +}; + +enum Texts +{ + SAY_DEATH = 0, + SAY_AGGRO = 1, + SAY_SPECIAL = 2, + SAY_PLAYER_KILL = 3 +}; + +class NPCEntryPred : public std::unary_function +{ +public: + NPCEntryPred(uint32 entry) : _entry(entry) { } + + bool operator()(Unit* unit) const { return unit->GetEntry() == _entry; } + +private: + uint32 _entry; +}; + +// 39732 Setesh +class boss_setesh : public CreatureScript +{ + public: + boss_setesh() : CreatureScript("boss_setesh") { } + + struct boss_seteshAI : public BossAI + { + boss_seteshAI(Creature* creature) : BossAI(creature, DATA_SETESH) { } + + void JustEngagedWith(Unit* /*who*/) override + { + _JustEngagedWith(); + Talk(SAY_AGGRO); + instance->SendEncounterUnit(ENCOUNTER_FRAME_ENGAGE, me); + + events.ScheduleEvent(EVENT_CHAOS_PORTAL, Seconds(5)); + events.ScheduleEvent(EVENT_CHAOS_BLAST, Seconds(15)); + events.ScheduleEvent(EVENT_SEED_OF_CHAOS, Seconds(20)); + events.ScheduleEvent(EVENT_REIGN_OF_CHAOS, Seconds(30)); + } + + void KilledUnit(Unit* victim) override + { + if (victim->GetTypeId() == TYPEID_PLAYER) + Talk(SAY_PLAYER_KILL); + } + + void EnterEvadeMode(EvadeReason /*why*/) override + { + _EnterEvadeMode(); + me->SetReactState(REACT_AGGRESSIVE); + CleanUp(); + } + + void JustDied(Unit* /*who*/) override + { + instance->SendEncounterUnit(ENCOUNTER_FRAME_DISENGAGE, me); + _JustDied(); + Talk(SAY_DEATH); + } + + void JustSummoned(Creature* summon) override + { + BossAI::JustSummoned(summon); + + switch (summon->GetEntry()) + { + case NPC_SETESH_VOID_RIFT: + summon->CastSpell((Unit*)nullptr, SPELL_VOID_RIFT, true); + break; + default: + break; + } + } + + void MovementInform(uint32 type, uint32 pointId) override + { + if (type != POINT_MOTION_TYPE && type != EFFECT_MOTION_TYPE) + return; + + switch (pointId) + { + case POINT_CHANNEL_CHAOS_PORTAL: + if (Unit* npcChaosPortal = SelectTarget(SELECT_TARGET_NEAREST, 0, 0.0f, false, SPELL_DUMMY_AURA)) + DoCast(npcChaosPortal, SPELL_CHANNEL_CHAOS_PORTAL); + events.ScheduleEvent(EVENT_CONTINUE_FIGHT, Seconds(5)); + break; + default: + break; + } + + + } + + void DoAction(int32 action) override + { + if (action != ACTION_SETESH_ATTACK) + return; + + // Start attacking + me->SetReactState(REACT_AGGRESSIVE); + events.SetPhase(PHASE_FIGHT); + } + + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim() || !CheckInRoom()) + return; + + events.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING) || events.IsInPhase(PHASE_CHAOS_PORTAL)) + return; + + while(uint32 eventId = events.ExecuteEvent()) + { + switch (eventId) + { + case EVENT_CHAOS_PORTAL: + StartChaosPortalPhase(); + events.Repeat(Seconds(45)); + break; + case EVENT_CHAOS_BLAST: + DoCast(SPELL_CHAOS_BLAST); + events.Repeat(Seconds(20)); + break; + case EVENT_SEED_OF_CHAOS: + DoCast(SPELL_SEED_OF_CHAOS); + events.Repeat(Seconds(40)); + break; + default: + break; + } + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + } + + // Journal: Setesh casts Chaos Bolt at random players instead of performing a melee attack. + if (events.IsInPhase(PHASE_CHAOS_PORTAL) || !me->isAttackReady()) + return; + + if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, 40.0f, true)) + { + me->SetTarget(target->GetGUID()); + DoCast(target, SPELL_CHAOS_BOLT); + me->resetAttackTimer(); + } + } + + private: + void StartChaosPortalPhase() + { + Unit* npcChaosPortal = SelectTarget(SELECT_TARGET_FARTHEST, 0, NPCEntryPred(NPC_SETESH_CHAOS_PORTAL)); + if (!npcChaosPortal) + return; + + events.SetPhase(PHASE_CHAOS_PORTAL); + + me->SetReactState(REACT_PASSIVE); + me->InterruptNonMeleeSpells(true); + me->AttackStop(); + + Talk(SAY_SPECIAL); + + // Walk towards chaos portal. + npcChaosPortal->CastSpell((Unit*)nullptr, SPELL_DUMMY_AURA, true); + me->GetMotionMaster()->MoveCloserAndStop(POINT_CHANNEL_CHAOS_PORTAL, npcChaosPortal, 18.0f); + } + + void CleanUp() + { + std::list units; + GetCreatureListWithEntryInGrid(units, me, NPC_SETESH_CHAOS_BLAST, 200.0f); + for (std::list::iterator itr = units.begin(); itr != units.end(); ++itr) + (*itr)->DespawnOrUnsummon(); + + units.clear(); + GetCreatureListWithEntryInGrid(units, me, NPC_SETESH_CHAOS_SEED, 200.0f); + for (std::list::iterator itr = units.begin(); itr != units.end(); ++itr) + (*itr)->DespawnOrUnsummon(); + } + }; + + CreatureAI* GetAI(Creature* creature) const + { + return new boss_seteshAI(creature); + } +}; + +// 41041 Chaos Blast +class npc_setesh_chaos_blast : public CreatureScript +{ +public: + npc_setesh_chaos_blast() : CreatureScript("npc_setesh_chaos_blast") { } + + struct npc_setesh_chaos_blastAI : public ScriptedAI + { + npc_setesh_chaos_blastAI(Creature* creature) : ScriptedAI(creature) { } + + void Reset() override + { + me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE); + if (InstanceScript* instance = me->GetInstanceScript()) + if (Creature* setesh = instance->GetCreature(DATA_SETESH)) + setesh->CastSpell(me, SPELL_CHAOS_BLAST_MISSLE, true); + } + + void SpellHit(Unit* /*caster*/, const SpellInfo* spellInfo) override + { + if (spellInfo->Id != SPELL_CHAOS_BLAST_MISSLE) + return; + + DoCastSelf(SPELL_CHAOS_BLAST_AURA); + } + }; + + CreatureAI* GetAI(Creature* creature) const override + { + return GetHallsOfOriginationAI(creature); + } +}; + +// 41126 Chaos Seed +class npc_setesh_chaos_seed : public CreatureScript +{ +public: + npc_setesh_chaos_seed() : CreatureScript("npc_setesh_chaos_seed") { } + + struct npc_setesh_chaos_seedAI : public ScriptedAI + { + npc_setesh_chaos_seedAI(Creature* creature) : ScriptedAI(creature) + { + me->SetDisableGravity(true); + me->SetFloatValue(UNIT_FIELD_BOUNDINGRADIUS, 7.0f); + me->SetFloatValue(UNIT_FIELD_COMBATREACH, 7.0f); + me->SetFaction(FACTION_FRIENDLY); + + DoCastSelf(SPELL_SEED_OF_CHAOS_DUMMY); + } + + void Reset() override + { + // Simulate slow fall + float x = me->GetPositionX(); + float y = me->GetPositionY(); + float z = me->GetPositionZ(); + Position dest = { x, y, me->GetMap()->GetWaterOrGroundLevel(me->GetPhaseShift(), x, y, z) }; + const uint32 pathSize = 1; + const Position path[pathSize] = { dest }; + me->GetMotionMaster()->MoveSmoothPath(POINT_GROUND, path, pathSize, true, true); + } + + void MovementInform(uint32 type, uint32 pointId) override + { + if (type != FLIGHT_MOTION_TYPE || pointId != POINT_GROUND) + return; + + me->SetDisableGravity(false); + me->SetFaction(FACTION_ENEMY_14); + } + + void JustEngagedWith(Unit* /*who*/) override + { + DoCast(SPELL_SEED_OF_CHAOS_EXPLOSION); + + me->RemoveAurasDueToSpell(SPELL_SEED_OF_CHAOS_DUMMY); + me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE); + me->DespawnOrUnsummon(Seconds(15)); + } + }; + + CreatureAI* GetAI(Creature* creature) const override + { + return GetHallsOfOriginationAI(creature); + } +}; + +// 41055 Chaos Portal - add waves: +// - Normal: 1) 2x Void Wyrm 2) Void Seeker 3) Void Sentinel +// - Heroic: 1) Void Sentinel 2) 2x Void Wyrm 3) Void Seeker 4+) Void Seeker or 2x Void Wyrm +class npc_setesh_chaos_portal : public CreatureScript +{ +public: + npc_setesh_chaos_portal() : CreatureScript("npc_setesh_chaos_portal") { } + + struct npc_setesh_chaos_portalAI : public ScriptedAI + { + npc_setesh_chaos_portalAI(Creature* creature) : ScriptedAI(creature) { } + + void Reset() override + { + events.Reset(); + me->SetFaction(FACTION_ENEMY_14); + me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE); + } + + void SpellHit(Unit* caster, const SpellInfo* spellInfo) override + { + if (spellInfo->Id != SPELL_CHANNEL_CHAOS_PORTAL) + return; + + caster->InterruptNonMeleeSpells(true, SPELL_CHANNEL_CHAOS_PORTAL); + me->RemoveAurasDueToSpell(SPELL_DUMMY_AURA); + events.ScheduleEvent(EVENT_CAST_VISUAL, Seconds(1)); + } + + void JustDied(Unit* /*who*/) override + { + events.Reset(); + me->SetRespawnTime(0); + } + + void UpdateAI(uint32 diff) override + { + if (events.Empty()) + return; + + events.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + while (uint32 eventId = events.ExecuteEvent()) + { + switch (eventId) + { + case EVENT_CAST_VISUAL: + DoCast(SPELL_NIGHTMARE_PORTAL_VISUAL); + if (IsHeroic()) + me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE); + events.ScheduleEvent(EVENT_SUMMON_WAVE_1, Seconds(4)); + break; + case EVENT_SUMMON_WAVE_1: // Also make Setesh attack. + if (InstanceScript* instance = me->GetInstanceScript()) + if (Creature* setesh = instance->GetCreature(DATA_SETESH)) + setesh->GetAI()->DoAction(ACTION_SETESH_ATTACK); + if (!IsHeroic()) // Normal + SummonVoidWyrms(); + else // Heroic + SummonVoidSentinel(); + events.ScheduleEvent(EVENT_SUMMON_WAVE_2, !IsHeroic() ? Seconds(12) : Seconds(6)); + break; + case EVENT_SUMMON_WAVE_2: + if (!IsHeroic()) // Normal + SummonVoidSeeker(); + else // Heroic + SummonVoidWyrms(); + events.ScheduleEvent(EVENT_SUMMON_WAVE_3, !IsHeroic() ? Seconds(15) : Seconds(10)); + break; + case EVENT_SUMMON_WAVE_3: + if (!IsHeroic()) // Normal + SummonVoidSentinel(); + else // Heroic + SummonVoidSeeker(); + // Normal: automatically closes after 3 waves of adds. + // Heroic: must be destroyed, summons more adds every 15 seconds after 3 waves. + if (!IsHeroic()) + me->DespawnOrUnsummon(Seconds(5), Seconds(1)); + else + events.ScheduleEvent(EVENT_SUMMON_WAVE_RANDOM, Seconds(15)); + break; + case EVENT_SUMMON_WAVE_RANDOM: // Heroic only. + if (urand(0,1)) // 50% chance + SummonVoidSeeker(); + else + SummonVoidWyrms(); + events.Repeat(Seconds(15)); + break; + default: + break; + } + } + } + + private: + void SummonAreaDamage() + { + if (Unit* npcSeteshAddStalker = SelectTarget(SELECT_TARGET_RANDOM, 200.0f, NPCEntryPred(NPC_SETESH_ADD_STALKER))) + npcSeteshAddStalker->CastSpell(me, SPELL_CHANNEL_SUMMON_ADDS); + } + + // Adds + void SummonVoidWyrms() { SummonAreaDamage(); DoCast(SPELL_SUMMON_VOID_WYRM_1); DoCast(SPELL_SUMMON_VOID_WYRM_2); } + void SummonVoidSeeker() { SummonAreaDamage(); DoCast(SPELL_SUMMON_VOID_SEEKER); } + void SummonVoidSentinel() { SummonAreaDamage(); DoCast(SPELL_SUMMON_VOID_SENTINEL); } + + EventMap events; + }; + + CreatureAI* GetAI(Creature* creature) const override + { + return GetHallsOfOriginationAI(creature); + } +}; + +// 76674 Chaos Blast +class spell_setesh_chaos_blast : public SpellScriptLoader +{ +public: + spell_setesh_chaos_blast() : SpellScriptLoader("spell_setesh_chaos_blast") { } + + class spell_setesh_chaos_blast_SpellScript : public SpellScript + { + PrepareSpellScript(spell_setesh_chaos_blast_SpellScript); + + void FilterTargets(std::list& unitList) + { + if (unitList.empty()) + return; + + Unit* caster = GetCaster(); + unitList.remove_if([caster](WorldObject* player) { return caster->IsWithinDist(player, 15.0f); }); + Trinity::Containers::RandomResize(unitList, 1); + } + + void Register() override + { + OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_setesh_chaos_blast_SpellScript::FilterTargets, EFFECT_0, TARGET_UNIT_SRC_AREA_ENEMY); + } + }; + + SpellScript* GetSpellScript() const override + { + return new spell_setesh_chaos_blast_SpellScript(); + } +}; + +void AddSC_boss_setesh() +{ + new boss_setesh(); + new npc_setesh_chaos_blast(); + new npc_setesh_chaos_seed(); + new npc_setesh_chaos_portal(); + new spell_setesh_chaos_blast(); +} diff --git a/src/server/scripts/Kalimdor/HallsOfOrigination/boss_temple_guardian_anhuur.cpp b/src/server/scripts/Kalimdor/HallsOfOrigination/boss_temple_guardian_anhuur.cpp index 238953a86bf..7990ce57cac 100644 --- a/src/server/scripts/Kalimdor/HallsOfOrigination/boss_temple_guardian_anhuur.cpp +++ b/src/server/scripts/Kalimdor/HallsOfOrigination/boss_temple_guardian_anhuur.cpp @@ -15,62 +15,75 @@ * with this program. If not, see . */ +/* To-do: */ +// - AreaTrigger: unknown purpose (id: 1843; pos: front-right stairs) - dust animation, aggro snakes? + +#include "halls_of_origination.h" #include "ScriptMgr.h" #include "GridNotifiers.h" -#include "halls_of_origination.h" #include "InstanceScript.h" #include "Map.h" -#include "MotionMaster.h" -#include "ObjectAccessor.h" +#include "Player.h" #include "ScriptedCreature.h" #include "SpellAuraEffects.h" #include "SpellScript.h" +#include "World.h" enum Texts { - SAY_AGGRO = 0, - SAY_SHIELD = 1, - EMOTE_SHIELD = 2, - EMOTE_UNSHIELD = 3, - SAY_KILL = 4, - SAY_DEATH = 5 + SAY_AGGRO = 0, + SAY_SHIELD = 1, + EMOTE_SHIELD = 2, + EMOTE_UNSHIELD = 3, + SAY_KILL = 4, + SAY_DEATH = 5 }; enum Events { - EVENT_DIVINE_RECKONING = 1, - EVENT_BURNING_LIGHT, - EVENT_SEAR, - EVENT_ACHIEVEMENT_FAIL, + EVENT_BURNING_LIGHT = 1, + EVENT_SEARING_LIGHT = 2, + EVENT_DIVINE_RECKONING = 3, + EVENT_CAST_SHIELD = 4, + EVENT_ACTIVATE_BEACONS = 5, + EVENT_CAST_BEAMS = 6, + EVENT_ACHIEVEMENT_FAILED = 7 }; enum Spells { - SPELL_DIVINE_RECKONING = 75592, - SPELL_BURNING_LIGHT = 75115, - SPELL_REVERBERATING_HYMN = 75322, - SPELL_SHIELD_OF_LIGHT = 74938, + // Fight phase + SPELL_DIVINE_RECKONING = 75592, + SPELL_BURNING_LIGHT = 75115, // Forces victim to summon Searing Light dummy at its location. - SPELL_ACTIVATE_BEACONS = 76599, - SPELL_TELEPORT = 74969, + // Shield phase + SPELL_TELEPORT = 74969, + SPELL_SHIELD_OF_LIGHT = 74938, + SPELL_REVERBERATING_HYMN = 75322, + SPELL_ACTIVATE_BEACONS = 76599, + SPELL_DEACTIVATE_BEACONS = 76600, - SPELL_SHIELD_VISUAL_RIGHT = 83698, - SPELL_BEAM_OF_LIGHT_RIGHT = 76573, + // Cave In Stalker (eyes) + SPELL_BURNING_LIGHT_SEAR = 75194, - SPELL_SHIELD_VISUAL_LEFT = 83697, - SPELL_BEAM_OF_LIGHT_LEFT = 74930, + // Cave In Stalker (beacons) + SPELL_SHIELD_VISUAL_LEFT = 83697, + SPELL_SHIELD_VISUAL_RIGHT = 83698, + SPELL_BEAM_OF_LIGHT_LEFT = 74930, + SPELL_BEAM_OF_LIGHT_RIGHT = 76573 +}; - SPELL_SEARING_LIGHT = 75194, +enum Phases +{ + PHASE_FIGHT = 1, + PHASE_SHIELD = 2 }; enum Actions { - ACTION_DISABLE_BEACON = 1, -}; - -enum AnhuurAchievementData -{ - DATA_I_HATE_THAT_SONG = 1, + ACTION_DISABLE_BEACON_L, + ACTION_DISABLE_BEACON_R, + ACTION_HYMN_EXPIRED }; class boss_temple_guardian_anhuur : public CreatureScript @@ -82,27 +95,67 @@ public: { boss_temple_guardian_anhuurAI(Creature* creature) : BossAI(creature, DATA_TEMPLE_GUARDIAN_ANHUUR) { - _shieldCount = 0; - _beacons = 0; - _achievement = true; + Initialize(); } - uint8 _shieldCount; - uint8 _beacons; - bool _achievement; + void Initialize() + { + _countShield = 0; + _leftBeaconDisabled = false; + _rightBeaconDisabled = false; + } void Reset() override { + Initialize(); _Reset(); + CleanStalkers(); + me->MakeInterruptable(false); + me->RemoveAurasDueToSpell(SPELL_SHIELD_OF_LIGHT); + DoCastAOE(SPELL_DEACTIVATE_BEACONS, true); + me->SetReactState(REACT_AGGRESSIVE); + events.SetPhase(PHASE_FIGHT); + ScheduleEvents(); + } + + void DamageTaken(Unit* /*attacker*/, uint32& damage) override + { + // Already in shield phase? 2 shields are enough. + if (events.IsInPhase(PHASE_SHIELD) || _countShield == 2) + return; + + // About to die? One-hit cases... + if (int64(me->GetHealth()) - int64(damage) <= 0) + return; + + // Shield phase happens at 66% and 33% health remaining. + if ((me->HealthBelowPctDamaged(66, damage) && _countShield == 0) || + (me->HealthBelowPctDamaged(33, damage) && _countShield == 1)) + EnterShieldPhase(); + } + + void DoAction(int32 action) override + { + if (action == ACTION_DISABLE_BEACON_L) + _leftBeaconDisabled = true; + else if (action == ACTION_DISABLE_BEACON_R) + _rightBeaconDisabled = true; + else if (action == ACTION_HYMN_EXPIRED) // We manually deactivate beacons. + DoCastAOE(SPELL_DEACTIVATE_BEACONS, true); + + // Exit shield phase if both beacons are disabled or channeling Reverberating Hymn finished. + if ((_leftBeaconDisabled && _rightBeaconDisabled) || action == ACTION_HYMN_EXPIRED) + ExitShieldPhase(); } void JustEngagedWith(Unit* /*who*/) override { - instance->SendEncounterUnit(ENCOUNTER_FRAME_ENGAGE, me, 1); + instance->SendEncounterUnit(ENCOUNTER_FRAME_ENGAGE, me); Talk(SAY_AGGRO); _JustEngagedWith(); - events.ScheduleEvent(EVENT_DIVINE_RECKONING, urand(10000, 12000)); - events.ScheduleEvent(EVENT_BURNING_LIGHT, 12000); + + instance->DoUpdateWorldState(WS_I_HATE_THIS_SONG, 0); + sWorld->setWorldState(WS_I_HATE_THIS_SONG, 0); // To-do: make InstanceScript::DoUpdateWorldState do the World::setWorldState. } void JustDied(Unit* /*killer*/) override @@ -118,115 +171,26 @@ public: Talk(SAY_KILL); } - void EnterEvadeMode(EvadeReason /*why*/) override + void EnterEvadeMode(EvadeReason why) override { - _EnterEvadeMode(); - CleanStalkers(); - RespawnPit(); - _shieldCount = 0; - _beacons = 0; - _achievement = true; + DoCastAOE(SPELL_DEACTIVATE_BEACONS, true); instance->SendEncounterUnit(ENCOUNTER_FRAME_DISENGAGE, me); - me->GetMotionMaster()->MoveTargetedHome(); - events.Reset(); + _EnterEvadeMode(); _DespawnAtEvade(); } - void RespawnPit() + void OnSpellCastInterrupt(SpellInfo const* spell) override { - std::list snakes; - GetCreatureListWithEntryInGrid(snakes, me, NPC_PIT_SNAKE, 500.0f); - for (std::list::iterator itr = snakes.begin(); itr != snakes.end(); ++itr) + me->MakeInterruptable(false); + + if (spell->Id == SPELL_REVERBERATING_HYMN) { - Position const pos = (*itr)->GetHomePosition(); - (*itr)->Respawn(); - (*itr)->NearTeleportTo(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), pos.GetOrientation()); + events.CancelEvent(EVENT_ACHIEVEMENT_FAILED); + DoAction(ACTION_HYMN_EXPIRED); + ScheduleEvents(); } } - void CleanStalkers() - { - std::list stalkers; - GetCreatureListWithEntryInGrid(stalkers, me, NPC_CAVE_IN_STALKER, 100.0f); - for (std::list::iterator itr = stalkers.begin(); itr != stalkers.end(); ++itr) - { - (*itr)->RemoveAurasDueToSpell(SPELL_BEAM_OF_LIGHT_RIGHT); - (*itr)->RemoveAurasDueToSpell(SPELL_BEAM_OF_LIGHT_LEFT); - } - } - - void DamageTaken(Unit* /*attacker*/, uint32& /*damage*/) override - { - if ((me->HealthBelowPct(66) && _shieldCount == 0) || - (me->HealthBelowPct(33) && _shieldCount == 1)) - { - _shieldCount++; - _beacons = 2; - - me->InterruptNonMeleeSpells(true); - me->AttackStop(); - DoCast(me, SPELL_TELEPORT); - - DoCastAOE(SPELL_ACTIVATE_BEACONS); - DoCast(me, SPELL_REVERBERATING_HYMN); - me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_UNK_31); - RespawnPit(); - - std::list stalkers; - GameObject* door = ObjectAccessor::GetGameObject(*me, instance->GetGuidData(DATA_ANHUUR_DOOR)); - GetCreatureListWithEntryInGrid(stalkers, me, NPC_CAVE_IN_STALKER, 100.0f); - - stalkers.remove_if(Trinity::HeightDifferenceCheck(door, 0.0f, false)); // Target only the bottom ones - for (std::list::iterator itr = stalkers.begin(); itr != stalkers.end(); ++itr) - { - if ((*itr)->GetPositionX() > door->GetPositionX()) - { - (*itr)->CastSpell((*itr), SPELL_SHIELD_VISUAL_LEFT, true); - (*itr)->CastSpell((*itr), SPELL_BEAM_OF_LIGHT_LEFT, true); - } - else - { - (*itr)->CastSpell((*itr), SPELL_SHIELD_VISUAL_RIGHT, true); - (*itr)->CastSpell((*itr), SPELL_BEAM_OF_LIGHT_RIGHT, true); - } - } - - Talk(EMOTE_SHIELD); - Talk(SAY_SHIELD); - events.CancelEvent(EVENT_DIVINE_RECKONING); - events.CancelEvent(EVENT_BURNING_LIGHT); - events.ScheduleEvent(EVENT_ACHIEVEMENT_FAIL, 15000); - } - else if (me->HasAura(SPELL_REVERBERATING_HYMN) && !me->HasAura(SPELL_SHIELD_OF_LIGHT)) - { - me->RemoveAurasDueToSpell(SPELL_REVERBERATING_HYMN); - events.CancelEvent(EVENT_ACHIEVEMENT_FAIL); - events.ScheduleEvent(EVENT_DIVINE_RECKONING, urand(10000, 12000)); - events.ScheduleEvent(EVENT_BURNING_LIGHT, 12000); - } - } - - void DoAction(int32 action) override - { - if (action == ACTION_DISABLE_BEACON) - { - --_beacons; - if (!_beacons) - { - me->RemoveAura(SPELL_SHIELD_OF_LIGHT); - Talk(EMOTE_UNSHIELD); - } - } - } - - uint32 GetData(uint32 type) const override - { - if (type == DATA_I_HATE_THAT_SONG) - return _achievement ? 1 : 0; - - return 0; - } - void UpdateAI(uint32 diff) override { if (!UpdateVictim() || !CheckInRoom()) @@ -234,60 +198,157 @@ public: events.Update(diff); + if (me->HasUnitState(UNIT_STATE_CASTING) && !events.IsInPhase(PHASE_SHIELD)) + return; + while (uint32 eventId = events.ExecuteEvent()) { switch (eventId) { - case EVENT_DIVINE_RECKONING: - if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, 0, true, 0)) - DoCast(target, SPELL_DIVINE_RECKONING); - events.ScheduleEvent(EVENT_DIVINE_RECKONING, urand(10000, 12000)); - break; case EVENT_BURNING_LIGHT: { Unit* unit = SelectTarget(SELECT_TARGET_RANDOM, 0, NonTankTargetSelector(me)); if (!unit) unit = SelectTarget(SELECT_TARGET_RANDOM, 0, 0.0f, true); DoCast(unit, SPELL_BURNING_LIGHT); - events.ScheduleEvent(EVENT_SEAR, 2000); - events.ScheduleEvent(EVENT_BURNING_LIGHT, 12000); + events.ScheduleEvent(EVENT_SEARING_LIGHT, Seconds(2)); // No phase. break; } - case EVENT_SEAR: - { - Unit* target = me->FindNearestCreature(NPC_SEARING_LIGHT, 100.0f); - if (!target) - break; - - std::list stalkers; - GetCreatureListWithEntryInGrid(stalkers, me, NPC_CAVE_IN_STALKER, 100.0f); - stalkers.remove_if(Trinity::HeightDifferenceCheck(ObjectAccessor::GetGameObject(*me, instance->GetGuidData(DATA_ANHUUR_DOOR)), 5.0f, true)); - - if (stalkers.empty()) - break; - - stalkers.sort(Trinity::ObjectDistanceOrderPred(target)); - - // Get the closest statue face (any of its eyes) - Creature* eye1 = stalkers.front(); - stalkers.remove(eye1); // Remove the eye. - stalkers.sort(Trinity::ObjectDistanceOrderPred(eye1)); // Find the second eye. - Creature* eye2 = stalkers.front(); - - eye1->CastSpell(eye1, SPELL_SEARING_LIGHT, true); - eye2->CastSpell(eye2, SPELL_SEARING_LIGHT, true); + case EVENT_SEARING_LIGHT: + HandleSearingLight(); + events.ScheduleEvent(EVENT_BURNING_LIGHT, Seconds(10), 0, PHASE_FIGHT); break; - } - case EVENT_ACHIEVEMENT_FAIL: - _achievement = false; + case EVENT_DIVINE_RECKONING: + DoCastVictim(SPELL_DIVINE_RECKONING); + events.ScheduleEvent(EVENT_DIVINE_RECKONING, Seconds(10), 0, PHASE_FIGHT); + break; + case EVENT_CAST_SHIELD: + me->AddUnitMovementFlag(MOVEMENTFLAG_ROOT); + me->SetFacingTo(1.5708f); // Sniffs set it again + DoCastSelf(SPELL_SHIELD_OF_LIGHT); // Note: stun! + me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_UNK_31); + events.ScheduleEvent(EVENT_ACTIVATE_BEACONS, Seconds(2), 0, PHASE_SHIELD); + break; + case EVENT_ACTIVATE_BEACONS: + Talk(EMOTE_SHIELD); + DoCastAOE(SPELL_ACTIVATE_BEACONS, true); + DoCastSelf(SPELL_REVERBERATING_HYMN); + HandleVisuals(SPELL_SHIELD_VISUAL_LEFT, SPELL_SHIELD_VISUAL_RIGHT, true); + events.ScheduleEvent(EVENT_CAST_BEAMS, Seconds(1), 0, PHASE_SHIELD); + events.ScheduleEvent(EVENT_ACHIEVEMENT_FAILED, Seconds(15), 0, PHASE_SHIELD); + break; + case EVENT_CAST_BEAMS: + HandleVisuals(SPELL_BEAM_OF_LIGHT_LEFT, SPELL_BEAM_OF_LIGHT_RIGHT, true); + break; + case EVENT_ACHIEVEMENT_FAILED: // Happens on normal too, heroic check is in dbc. + instance->DoUpdateWorldState(WS_I_HATE_THIS_SONG, 1); + sWorld->setWorldState(WS_I_HATE_THIS_SONG, 1); // To-do: make InstanceScript::DoUpdateWorldState do the World::setWorldState. break; default: break; } + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; } - DoMeleeAttackIfReady(); + if (!events.IsInPhase(PHASE_SHIELD)) + DoMeleeAttackIfReady(); } + + private: + void ScheduleEvents() + { + events.Reset(); + events.ScheduleEvent(EVENT_DIVINE_RECKONING, Seconds(10), 0, PHASE_FIGHT); + events.ScheduleEvent(EVENT_BURNING_LIGHT, Seconds(12), 0, PHASE_FIGHT); + } + + // To-do: Ideal for a custom spell. + void HandleSearingLight() + { + Unit* target = me->FindNearestCreature(NPC_SEARING_LIGHT, 100.0f); + GameObject* door = instance->GetGameObject(DATA_ANHUUR_DOOR); + if (!target || !door) + return; + + std::list stalkers; + GetCreatureListWithEntryInGrid(stalkers, me, NPC_CAVE_IN_STALKER, 100.0f); + stalkers.remove_if(Trinity::HeightDifferenceCheck(door, 5.0f, true)); + if (stalkers.empty()) + return; + + stalkers.sort(Trinity::ObjectDistanceOrderPred(target)); + + // Get the closest statue face (any of its eyes) + Creature* eye1 = stalkers.front(); + stalkers.remove(eye1); // Remove the eye. + stalkers.sort(Trinity::ObjectDistanceOrderPred(eye1)); // Find the second eye. + Creature* eye2 = stalkers.front(); + + eye1->CastSpell(eye1, SPELL_BURNING_LIGHT_SEAR, true); + eye2->CastSpell(eye2, SPELL_BURNING_LIGHT_SEAR, true); + return; + } + + void EnterShieldPhase() + { + events.SetPhase(PHASE_SHIELD); + ++_countShield; + _leftBeaconDisabled = false; + _rightBeaconDisabled = false; + + me->SetReactState(REACT_PASSIVE); + me->InterruptNonMeleeSpells(true); + me->AttackStop(); + DoCastSelf(SPELL_TELEPORT); + me->SetFacingTo(1.5708f, true); // Note: Wrong orientation in old sniffs - 5.144157f. + Talk(SAY_SHIELD); + + events.ScheduleEvent(EVENT_CAST_SHIELD, Seconds(1), 0, PHASE_SHIELD); + } + + void ExitShieldPhase() + { + CleanStalkers(); + me->MakeInterruptable(true); + me->RemoveAurasDueToSpell(SPELL_SHIELD_OF_LIGHT); + me->SetReactState(REACT_AGGRESSIVE); + Talk(EMOTE_UNSHIELD); + events.SetPhase(PHASE_FIGHT); + } + + void CleanStalkers() + { + HandleVisuals(SPELL_SHIELD_VISUAL_LEFT, SPELL_SHIELD_VISUAL_RIGHT, false); + HandleVisuals(SPELL_BEAM_OF_LIGHT_LEFT, SPELL_BEAM_OF_LIGHT_RIGHT, false); + } + + void HandleVisuals(uint32 leftSpellId, uint32 rightSpellId, bool apply) + { + std::list stalkers; + GetCreatureListWithEntryInGrid(stalkers, me, NPC_CAVE_IN_STALKER, 100.0f); + for (std::list::iterator itr = stalkers.begin(); itr != stalkers.end(); ++itr) + { + // Target only the bottom stalkers (Y: 65.392f and 64.9004f) + if ((*itr)->GetPositionZ() > 70.f) + return; + + // Left stalker X: -603.465f; right stalker X: -678.132f + uint32 spellId = ((*itr)->GetPositionX() > -640.0f) ? leftSpellId : rightSpellId; + if (apply) + (*itr)->CastSpell((*itr), spellId, true); + else + { + (*itr)->InterruptNonMeleeSpells(true, spellId); + (*itr)->RemoveAurasDueToSpell(spellId); + } + } + } + + uint8 _countShield; + bool _leftBeaconDisabled; + bool _rightBeaconDisabled; }; CreatureAI* GetAI(Creature* creature) const override @@ -296,6 +357,44 @@ public: } }; +// 75592 - Divine Reckoning +class spell_anhuur_divine_reckoning : public SpellScriptLoader +{ +public: + spell_anhuur_divine_reckoning() : SpellScriptLoader("spell_anhuur_divine_reckoning") { } + + class spell_anhuur_divine_reckoning_AuraScript : public AuraScript + { + PrepareAuraScript(spell_anhuur_divine_reckoning_AuraScript); + + void OnPeriodic(AuraEffect const* aurEff) + { + if (Unit* caster = GetCaster()) + { + if (!caster->isDead()) + { + CustomSpellValues values; + values.AddSpellMod(SPELLVALUE_BASE_POINT0, aurEff->GetAmount()); + caster->CastCustomSpell(GetSpellInfo()->Effects[EFFECT_0].TriggerSpell, values, GetTarget()); + } + } + } + + void Register() override + { + OnEffectPeriodic += AuraEffectPeriodicFn(spell_anhuur_divine_reckoning_AuraScript::OnPeriodic, EFFECT_0, SPELL_AURA_PERIODIC_DUMMY); + } + }; + + AuraScript* GetAuraScript() const override + { + return new spell_anhuur_divine_reckoning_AuraScript(); + } +}; + +// 74930 - Shield of Light (left) +// 76573 - Shield of Light (right) + class spell_anhuur_shield_of_light : public SpellScriptLoader { public: @@ -307,17 +406,14 @@ class spell_anhuur_shield_of_light : public SpellScriptLoader void FilterTargets(std::list& targets) { - if (InstanceMap* instance = GetCaster()->GetMap()->ToInstanceMap()) + if (InstanceScript* instance = GetCaster()->GetInstanceScript()) { - if (InstanceScript* const script = instance->GetInstanceScript()) + if (GameObject* go = instance->GetGameObject(DATA_ANHUUR_DOOR)) { - if (GameObject* go = ObjectAccessor::GetGameObject(*GetCaster(), script->GetGuidData(DATA_ANHUUR_DOOR))) - { - targets.remove_if(Trinity::HeightDifferenceCheck(go, 5.0f, false)); - targets.remove(GetCaster()); - targets.sort(Trinity::ObjectDistanceOrderPred(GetCaster())); - targets.resize(2); - } + targets.remove_if(Trinity::HeightDifferenceCheck(go, 5.0f, false)); + targets.remove(GetCaster()); + targets.sort(Trinity::ObjectDistanceOrderPred(GetCaster())); + targets.resize(2); } } } @@ -334,6 +430,37 @@ class spell_anhuur_shield_of_light : public SpellScriptLoader } }; +// 75322 - Reverberating Hymn +class spell_anhuur_reverberating_hymn : public SpellScriptLoader +{ +public: + spell_anhuur_reverberating_hymn() : SpellScriptLoader("spell_anhuur_reverberating_hymn") { } + + class spell_anhuur_reverberating_hymn_AuraScript : public AuraScript + { + PrepareAuraScript(spell_anhuur_reverberating_hymn_AuraScript); + + void AfterRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/) + { + if (GetTargetApplication()->GetRemoveMode() == AURA_REMOVE_BY_EXPIRE) + if (Creature* anhuur = GetCaster()->ToCreature()) + anhuur->AI()->DoAction(ACTION_HYMN_EXPIRED); + } + + void Register() override + { + AfterEffectRemove += AuraEffectRemoveFn(spell_anhuur_reverberating_hymn_AuraScript::AfterRemove, EFFECT_0, SPELL_AURA_PERIODIC_TRIGGER_SPELL, AURA_EFFECT_HANDLE_REAL); + } + }; + + AuraScript* GetAuraScript() const override + { + return new spell_anhuur_reverberating_hymn_AuraScript(); + } +}; + +// 76606 - Disable Beacon Beams L +// 76608 - Disable Beacon Beams R class spell_anhuur_disable_beacon_beams : public SpellScriptLoader { public: @@ -345,15 +472,14 @@ class spell_anhuur_disable_beacon_beams : public SpellScriptLoader void HandleScript(SpellEffIndex /*effIndex*/) { - GetHitUnit()->RemoveAurasDueToSpell(GetEffectValue()); + GetHitUnit()->InterruptNonMeleeSpells(true, GetEffectValue()); } void Notify(SpellEffIndex /*index*/) { - if (InstanceMap* instance = GetCaster()->GetMap()->ToInstanceMap()) - if (InstanceScript* const script = instance->GetInstanceScript()) - if (Creature* anhuur = instance->GetCreature(script->GetGuidData(DATA_ANHUUR_GUID))) - anhuur->AI()->DoAction(ACTION_DISABLE_BEACON); + if (InstanceScript* instance = GetCaster()->GetInstanceScript()) + if (Creature* anhuur = instance->GetCreature(DATA_TEMPLE_GUARDIAN_ANHUUR)) + anhuur->AI()->DoAction(GetEffectValue() == SPELL_BEAM_OF_LIGHT_LEFT ? ACTION_DISABLE_BEACON_L : ACTION_DISABLE_BEACON_R); } void Register() override @@ -369,84 +495,52 @@ class spell_anhuur_disable_beacon_beams : public SpellScriptLoader } }; -class spell_anhuur_activate_beacons : public SpellScriptLoader +// 76599 - Activate Beacons +// 76600 - Deactivate Beacons +// Temporary script until Spell::EffectActivateObject is scripted. +class spell_anhuur_handle_beacons : public SpellScriptLoader { public: - spell_anhuur_activate_beacons() : SpellScriptLoader("spell_anhuur_activate_beacons") { } + spell_anhuur_handle_beacons() : SpellScriptLoader("spell_anhuur_handle_beacons") { } - class spell_anhuur_activate_beacons_SpellScript : public SpellScript + class spell_anhuur_handle_beacons_SpellScript : public SpellScript { - PrepareSpellScript(spell_anhuur_activate_beacons_SpellScript); + PrepareSpellScript(spell_anhuur_handle_beacons_SpellScript); - void Activate(SpellEffIndex index) + void HandleEffect(SpellEffIndex effIndex) { - PreventHitDefaultEffect(index); - GetHitGObj()->RemoveFlag(GAMEOBJECT_FLAGS, GO_FLAG_NOT_SELECTABLE); + PreventHitDefaultEffect(effIndex); + int32 script = GetSpellInfo()->Effects[effIndex].MiscValue; + + GameObject* beacon = GetHitGObj(); + if (!beacon) + return; + + beacon->SetLootState(GO_READY); + if (script == 16) // 16 => Disable + beacon->SetFlag(GAMEOBJECT_FLAGS, GO_FLAG_NOT_SELECTABLE); + else if (script == 17) // 17 => Activate + beacon->RemoveFlag(GAMEOBJECT_FLAGS, GO_FLAG_NOT_SELECTABLE); } void Register() override { - OnEffectHitTarget += SpellEffectFn(spell_anhuur_activate_beacons_SpellScript::Activate, EFFECT_0, SPELL_EFFECT_ACTIVATE_OBJECT); + OnEffectHitTarget += SpellEffectFn(spell_anhuur_handle_beacons_SpellScript::HandleEffect, EFFECT_0, SPELL_EFFECT_ACTIVATE_OBJECT); } }; SpellScript* GetSpellScript() const override { - return new spell_anhuur_activate_beacons_SpellScript(); - } -}; - -class spell_anhuur_divine_reckoning : public SpellScriptLoader -{ -public: - spell_anhuur_divine_reckoning() : SpellScriptLoader("spell_anhuur_divine_reckoning") { } - - class spell_anhuur_divine_reckoning_AuraScript : public AuraScript - { - PrepareAuraScript(spell_anhuur_divine_reckoning_AuraScript); - - void OnPeriodic(AuraEffect const* aurEff) - { - if (Unit* caster = GetCaster()) - { - CustomSpellValues values; - values.AddSpellMod(SPELLVALUE_BASE_POINT0, aurEff->GetAmount()); - caster->CastCustomSpell(GetSpellInfo()->Effects[EFFECT_0].TriggerSpell, values, GetTarget()); - } - } - - void Register() override - { - OnEffectPeriodic += AuraEffectPeriodicFn(spell_anhuur_divine_reckoning_AuraScript::OnPeriodic, EFFECT_0, SPELL_AURA_PERIODIC_DUMMY); - } - }; - - AuraScript* GetAuraScript() const override - { - return new spell_anhuur_divine_reckoning_AuraScript(); - } -}; - -class achievement_hate_that_song : public AchievementCriteriaScript -{ - public: - achievement_hate_that_song() : AchievementCriteriaScript("achievement_hate_that_song") { } - - bool OnCheck(Player* /*source*/, Unit* target) override - { - if (!target || !target->IsAIEnabled) - return false; - - return target->GetMap()->IsHeroic() && target->GetAI()->GetData(DATA_I_HATE_THAT_SONG); + return new spell_anhuur_handle_beacons_SpellScript(); } }; void AddSC_boss_temple_guardian_anhuur() { new boss_temple_guardian_anhuur(); - new spell_anhuur_shield_of_light(); - new spell_anhuur_disable_beacon_beams(); - new spell_anhuur_activate_beacons(); new spell_anhuur_divine_reckoning(); - new achievement_hate_that_song(); + new spell_anhuur_shield_of_light(); + new spell_anhuur_reverberating_hymn(); + new spell_anhuur_disable_beacon_beams(); + new spell_anhuur_handle_beacons(); } diff --git a/src/server/scripts/Kalimdor/HallsOfOrigination/halls_of_origination.cpp b/src/server/scripts/Kalimdor/HallsOfOrigination/halls_of_origination.cpp new file mode 100644 index 00000000000..8d4e1c9175b --- /dev/null +++ b/src/server/scripts/Kalimdor/HallsOfOrigination/halls_of_origination.cpp @@ -0,0 +1,429 @@ +/* + * Copyright (C) 2008-2018 TrinityCore + * + * 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 . + */ + +/* To-do: */ +// - Summoner Enabler dummy npc: unknown purpose (at the entrance) +// - AreaTrigger: unknown purpose (id: 2275; pos: hole in the left wall at the entrance) +// - Find out what Dummy Nuke (68991) spell does. +// - Spatial Flux won't enter combat on second aggro from creature group (but it should). + +#include "ScriptMgr.h" +#include "CreatureGroups.h" +#include "GameObject.h" +#include "GameObjectAI.h" +#include "GridNotifiers.h" +#include "InstanceScript.h" +#include "PassiveAI.h" +#include "ScriptedGossip.h" +#include "SpellAuraEffects.h" +#include "SpellScript.h" +#include "TemporarySummon.h" +#include "halls_of_origination.h" + +enum Spells +{ + SPELL_SUBMERGE = 76084, + + // Isiset trash and adds + SPELL_ARCANE_ENERGY = 74881, + SPELL_ARCANE_BURST = 74888, // On retail not working! Should probably be cast at full energy. + SPELL_SPAWN_ENERGY_FLUX_TRASH = 82382, // Makes random player cast Summon Energy Flux (82385) + SPELL_SPAWN_ENERGY_FLUX_ISISET = 90735, // Makes random player cast Summon Energy Flux (90739) + SPELL_ENERGY_FLUX_PERIODIC = 74044, + SPELL_ENERGY_FLUX_BEAM_TRASH = 82377, // Makes nearby Spatial Flux cast visual beam + SPELL_ENERGY_FLUX_BEAM_ISISET = 90741, // Makes nearby Spatial Flux cast visual beam + + SPELL_DUMMY_NUKE = 68991 +}; + +enum Events +{ + // Spatial Flux + EVENT_SPAWN_ENERGY_FLUX = 1, + EVENT_DUMMY_NUKE, + + // Energy Flux + EVENT_FOLLOW_SUMMONER, +}; + +// The Maker's Lift +enum ElevatorMisc +{ + GOSSIP_MENU_HOO_LIFT = 12646, + GOSSIP_NPC_TEXT_CHOOSE_A_DESTINATION = 17791, + GOSSIP_OPTION_FIRST_FLOOR = 0, + GOSSIP_OPTION_HOO_LIFT_SECOND_FLOOR = 1, + GOSSIP_OPTION_HOO_LIFT_THIRD_FLOOR = 2 +}; + +// 207669 - The Maker's Lift Controller +class go_hoo_the_makers_lift_controller : public GameObjectScript +{ +public: + go_hoo_the_makers_lift_controller() : GameObjectScript("go_hoo_the_makers_lift_controller") { } + + struct go_hoo_the_makers_lift_controllerAI : public GameObjectAI + { + go_hoo_the_makers_lift_controllerAI(GameObject* go) : GameObjectAI(go) { } + + bool GossipHello(Player* player) override + { + InstanceScript* instance = player->GetInstanceScript(); + if (!instance) + return false; + + // Build menu. + // First floor: Option available from start. + AddGossipItemFor(player, GOSSIP_MENU_HOO_LIFT, GOSSIP_OPTION_FIRST_FLOOR, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 0); + + // Second floor: Anraphet must be defeated first. + if (instance->GetBossState(DATA_ANRAPHET) == DONE) + AddGossipItemFor(player, GOSSIP_MENU_HOO_LIFT, GOSSIP_OPTION_HOO_LIFT_SECOND_FLOOR, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 1); + + // Third floor: Constructs of The Four Seats must be defeated first. + if (instance->GetBossState(DATA_ISISET) == DONE && instance->GetBossState(DATA_AMMUNAE) == DONE && + instance->GetBossState(DATA_SETESH) == DONE && instance->GetBossState(DATA_RAJH) == DONE) + AddGossipItemFor(player, GOSSIP_MENU_HOO_LIFT, GOSSIP_OPTION_HOO_LIFT_THIRD_FLOOR, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 2); + + SendGossipMenuFor(player, GOSSIP_NPC_TEXT_CHOOSE_A_DESTINATION, me->GetGUID()); + return true; + } + + bool GossipSelect(Player* player, uint32 /*sender*/, uint32 action) override + { + + ClearGossipMenuFor(player); + player->PlayerTalkClass->SendCloseGossip(); + + InstanceScript* instance = player->GetInstanceScript(); + if (!instance) + return true; + + // Handle elevator: gossip item index => stopFrame (floor index). + uint32 stopFrame = action - GOSSIP_ACTION_INFO_DEF; + GameObject* elevator = instance->GetGameObject(DATA_LIFT_OF_THE_MAKERS); + if (!elevator) + return true; + + elevator->SetTransportState(GO_STATE_TRANSPORT_ACTIVE); + elevator->SetTransportState(GO_STATE_TRANSPORT_STOPPED, stopFrame); + return true; + } + }; + GameObjectAI* GetAI(GameObject* go) const override + { + return GetHallsOfOriginationAI(go); + } +}; + +// 40790 Aggro Stalker +class npc_hoo_aggro_stalker : public CreatureScript +{ +public: + npc_hoo_aggro_stalker() : CreatureScript("npc_hoo_aggro_stalker") { } + + CreatureAI* GetAI(Creature* creature) const override + { + return new npc_hoo_aggro_stalkerAI(creature); + } + + struct npc_hoo_aggro_stalkerAI : public PassiveAI + { + npc_hoo_aggro_stalkerAI(Creature* creature) : PassiveAI(creature) + { + me->SearchFormation(); + } + + void MoveInLineOfSight(Unit* who) override + { + if (who && who->GetTypeId() == TYPEID_PLAYER && me->IsWithinDistInMap(who, 30.f)) + if (CreatureGroup* formation = me->GetFormation()) + formation->MemberAttackStart(me, who); + + me->CombatStop(); + } + }; +}; + +// 39612 - Spatial Flux (trash) +// 48707 - Spatial Flux (Isiset) +class npc_hoo_spatial_flux : public CreatureScript +{ +public: + npc_hoo_spatial_flux() : CreatureScript("npc_hoo_spatial_flux") { } + + struct npc_hoo_spatial_fluxAI : public ScriptedAI + { + npc_hoo_spatial_fluxAI(Creature* creature) : ScriptedAI(creature) { } + + void Reset() override + { + events.Reset(); + events.ScheduleEvent(EVENT_DUMMY_NUKE, Seconds(0)); + events.ScheduleEvent(EVENT_SPAWN_ENERGY_FLUX, Seconds(3)); + } + + void IsSummonedBy(Unit* summoner) override + { + if (summoner->GetEntry() == BOSS_ISISET) + me->SetInCombatWithZone(); + } + + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) + return; + + events.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + while (uint32 eventId = events.ExecuteEvent()) + { + switch (eventId) + { + + case EVENT_SPAWN_ENERGY_FLUX: + DoCastSelf(me->GetEntry() == NPC_SPATIAL_FLUX_TRASH ? SPELL_SPAWN_ENERGY_FLUX_TRASH : SPELL_SPAWN_ENERGY_FLUX_ISISET); + events.Repeat(Seconds(12)); + break; + case EVENT_DUMMY_NUKE: + if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, 200.0f, true)) + DoCast(target, SPELL_DUMMY_NUKE); + events.Repeat(Seconds(1)); + break; + default: + break; + } + } + } + + private: + EventMap events; + }; + + CreatureAI* GetAI(Creature* creature) const override + { + return GetHallsOfOriginationAI(creature); + } +}; + +// 44015 - Energy flux (trash) +// 48709 - Energy flux (Isiset) +class npc_hoo_energy_flux : public CreatureScript +{ +public: + npc_hoo_energy_flux() : CreatureScript("npc_hoo_energy_flux") { } + + struct npc_hoo_energy_fluxAI : public ScriptedAI + { + npc_hoo_energy_fluxAI(Creature* creature) : ScriptedAI(creature) + { + me->SetReactState(REACT_PASSIVE); + DoCastSelf(SPELL_ENERGY_FLUX_PERIODIC); + DoCastSelf(me->GetEntry() == NPC_ENERGY_FLUX_TRASH ? SPELL_ENERGY_FLUX_BEAM_TRASH : SPELL_ENERGY_FLUX_BEAM_ISISET); + } + + void IsSummonedBy(Unit* /*summoner*/) override + { + me->SetWalk(true); + events.ScheduleEvent(EVENT_FOLLOW_SUMMONER, Seconds(1)); + me->DespawnOrUnsummon(Seconds(6)); + } + + void UpdateAI(uint32 diff) override + { + if (events.Empty()) + return; + + events.Update(diff); + + while (uint32 eventId = events.ExecuteEvent()) + { + switch (eventId) + { + case EVENT_FOLLOW_SUMMONER: + if (Unit* target = ObjectAccessor::GetUnit(*me, me->GetCreatorGUID())) + me->GetMotionMaster()->MovePoint(0, target->GetPosition(), true); + events.Repeat(Seconds(1)); + break; + default: + break; + } + } + } + + private: + EventMap events; + }; + + CreatureAI* GetAI(Creature* creature) const override + { + return GetHallsOfOriginationAI(creature); + } +}; + +// 75764 - Emerge +class spell_hoo_emerge : public SpellScriptLoader +{ +public: + spell_hoo_emerge() : SpellScriptLoader("spell_hoo_emerge") { } + + class spell_hoo_emerge_SpellScript : public SpellScript + { + PrepareSpellScript(spell_hoo_emerge_SpellScript); + + void RemoveSubmergeAura(SpellEffIndex /*effIndex*/) + { + GetHitUnit()->RemoveAurasDueToSpell(SPELL_SUBMERGE); + } + + void Register() override + { + OnEffectHitTarget += SpellEffectFn(spell_hoo_emerge_SpellScript::RemoveSubmergeAura, EFFECT_0, SPELL_EFFECT_APPLY_AURA); + } + }; + + SpellScript* GetSpellScript() const override + { + return new spell_hoo_emerge_SpellScript(); + } +}; + +// 82382 - Energy Flux (cast by trash Spatial Flux) +// 90735 - Energy Flux (cast by Isiset's Spatial Flux) +class spell_hoo_energy_flux_target_selector : public SpellScriptLoader +{ +public: + spell_hoo_energy_flux_target_selector() : SpellScriptLoader("spell_hoo_energy_flux_target_selector") { } + + class spell_hoo_energy_flux_target_selector_SpellScript : public SpellScript + { + PrepareSpellScript(spell_hoo_energy_flux_target_selector_SpellScript); + + void FilterTargets(std::list& targets) + { + // Remove tank + if (InstanceScript* instance = GetCaster()->GetInstanceScript()) + if (Creature* Isiset = instance->GetCreature(DATA_ISISET)) + if (WorldObject* tank = Isiset->AI()->SelectTarget(SELECT_TARGET_TOPAGGRO)) + targets.remove(tank); + + targets.remove_if(Trinity::ObjectTypeIdCheck(TYPEID_PLAYER, false)); + if (targets.empty()) + return; + + WorldObject* target = Trinity::Containers::SelectRandomContainerElement(targets); + targets.clear(); + targets.push_back(target); + } + + void Register() override + { + OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_hoo_energy_flux_target_selector_SpellScript::FilterTargets, EFFECT_0, TARGET_UNIT_SRC_AREA_ENEMY); + } + }; + + SpellScript* GetSpellScript() const override + { + return new spell_hoo_energy_flux_target_selector_SpellScript(); + } +}; + +// 74880 - Arcane Energy +class spell_hoo_arcane_energy_check : public SpellScriptLoader +{ +public: + spell_hoo_arcane_energy_check() : SpellScriptLoader("spell_hoo_arcane_energy_check") { } + + class spell_hoo_arcane_energy_check_AuraScript : public AuraScript + { + PrepareAuraScript(spell_hoo_arcane_energy_check_AuraScript); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_ARCANE_BURST, SPELL_ARCANE_ENERGY }); + } + + void AfterProc(AuraEffect const* /*aurEff*/, ProcEventInfo& /*eventInfo*/) + { + if (GetTarget()->GetPower(POWER_ENERGY) == 100) + { + GetTarget()->CastSpell((Unit*)nullptr, SPELL_ARCANE_BURST); + + // Stacks should probably be consumed, right? (note: this ability doesn't work on retail) + GetTarget()->RemoveAurasDueToSpell(SPELL_ARCANE_ENERGY); + GetTarget()->SetPower(POWER_ENERGY, 0); + } + } + + void Register() override + { + AfterEffectProc += AuraEffectProcFn(spell_hoo_arcane_energy_check_AuraScript::AfterProc, EFFECT_0, SPELL_AURA_PROC_TRIGGER_SPELL); + } + }; + + AuraScript* GetAuraScript() const override + { + return new spell_hoo_arcane_energy_check_AuraScript(); + } +}; + +// 73686 Fixate +class spell_hoo_fixate : public SpellScriptLoader +{ +public: + spell_hoo_fixate() : SpellScriptLoader("spell_hoo_fixate") { } + + class spell_hoo_fixate_SpellScript : public SpellScript + { + PrepareSpellScript(spell_hoo_fixate_SpellScript); + + bool Validate(SpellInfo const* spellInfo) override + { + return ValidateSpellInfo({ uint32(spellInfo->Effects[EFFECT_0].BasePoints) }); + } + + void HandleScriptEffect(SpellEffIndex /*effIndex*/) + { + GetHitUnit()->CastSpell(GetCaster(), uint32(GetEffectValue())); + } + + void Register() override + { + OnEffectHitTarget += SpellEffectFn(spell_hoo_fixate_SpellScript::HandleScriptEffect, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT); + } + }; + + SpellScript* GetSpellScript() const override + { + return new spell_hoo_fixate_SpellScript(); + } +}; + +void AddSC_halls_of_origination() +{ + new go_hoo_the_makers_lift_controller(); + new npc_hoo_aggro_stalker(); + new npc_hoo_spatial_flux(); + new npc_hoo_energy_flux(); + new spell_hoo_emerge(); + new spell_hoo_energy_flux_target_selector(); + new spell_hoo_arcane_energy_check(); + new spell_hoo_fixate(); +} diff --git a/src/server/scripts/Kalimdor/HallsOfOrigination/halls_of_origination.h b/src/server/scripts/Kalimdor/HallsOfOrigination/halls_of_origination.h index d496c38e9a5..d21858555f0 100644 --- a/src/server/scripts/Kalimdor/HallsOfOrigination/halls_of_origination.h +++ b/src/server/scripts/Kalimdor/HallsOfOrigination/halls_of_origination.h @@ -42,16 +42,37 @@ enum HOOData DATA_RAJH, // Temple Guardian Anhuur - DATA_ANHUUR_GUID, - DATA_ANHUUR_LEFT_BEACON, - DATA_ANHUUR_RIGHT_BEACON, - DATA_ANHUUR_BRIDGE, DATA_ANHUUR_DOOR, // Anraphet - DATA_BRANN_0_GUID, + DATA_BRANN_0, + DATA_VAULT_OF_LIGHTS_DOOR, DATA_DEAD_ELEMENTALS, - DATA_ANRAPHET_GUID, + DATA_LIGHTMACHINE_EARTH, + DATA_LIGHTMACHINE_AIR, + DATA_LIGHTMACHINE_FIRE, + DATA_LIGHTMACHINE_WATER, + DATA_LASERBEAMS_EARTH, + DATA_LASERBEAMS_AIR, + DATA_LASERBEAMS_FIRE, + DATA_LASERBEAMS_WATER, + DATA_UPDATE_LASERBEAMS, + DATA_ANRAPHET_SUN_MIRROR, + DATA_ANRAPHET_DOOR, + + // Isiset + DATA_ISISET_PHASE, + DATA_ISISET_ASTRAL_RAIN_ALIVE, + DATA_ISISET_CELESTIAL_CALL_ALIVE, + DATA_ISISET_VEIL_OF_SKY_ALIVE, + + // Ammunae + + // Setesh + DATA_SETESH_ADD_STALKER, + + // Misc + DATA_LIFT_OF_THE_MAKERS }; enum HOOCreatures @@ -59,13 +80,13 @@ enum HOOCreatures BOSS_TEMPLE_GUARDIAN_ANHUUR = 39425, NPC_CAVE_IN_STALKER = 40183, NPC_SEARING_LIGHT = 40283, - NPC_PIT_SNAKE = 39444, BOSS_EARTHRAGER_PTAH = 39428, NPC_BEETLE_STALKER = 40459, // Summons both Jeweled Scarab and Dustbone Horror - NPC_JEWELED_SCARAB = 40458, - NPC_DUSTBONE_HORROR = 40450, - NPC_QUICKSAND = 40503, // Summoned by a spell not in dbc (75550) + NPC_JEWELED_SCARAB = 40458, // Summoned by spell: 75462 Summon Jeweled Scarab + NPC_DUSTBONE_HORROR = 40450, // Summoned by spell: 75521 Summon Dustbone Horror + NPC_QUICKSAND = 40503, // Summoned by spell: 75550 (server-side, not in dbc) + NPC_HOO_CAMEL = 39443, BOSS_ANRAPHET = 39788, NPC_FIRE_WARDEN = 39800, @@ -73,58 +94,134 @@ enum HOOCreatures NPC_WATER_WARDEN = 39802, NPC_AIR_WARDEN = 39803, - BOSS_RAJH = 39378, - NPC_INFERNO_LEAP = 47040, - NPC_BLAZING_INFERNO = 40927, - + WARDEN_ENTRY_MAX_COUNT = 4, WARDEN_ENTRY_DATA_DELTA = NPC_FIRE_WARDEN - DATA_FIRE_WARDEN, NPC_BRANN_BRONZEBEARD_0 = 39908, NPC_OMEGA_STANCE = 41194, + NPC_ALPHA_BEAM = 41144, + NPC_STONE_TROGG_PILLAGER = 39804, + NPC_STONE_TROGG_BRUTE = 40251, + NPC_STONE_TROGG_ROCK_FLINGER = 40252, + + BOSS_ISISET = 39587, + NPC_SPATIAL_FLUX_TRASH = 39612, // Isiset trash + NPC_ENERGY_FLUX_TRASH = 44015, + NPC_SPATIAL_ANOMALY = 40170, + NPC_FLUX_ANIMATOR = 40033, + NPC_STAR_SHARD = 40106, + + BOSS_AMMUNAE = 39731, + + BOSS_SETESH = 39732, + NPC_SETESH_CHAOS_SEED = 41126, + NPC_SETESH_CHAOS_BLAST = 41041, + NPC_SETESH_CHAOS_PORTAL = 41055, + NPC_SETESH_ADD_STALKER = 41479, + NPC_SETESH_VOID_RIFT = 39266, // Setesh trash + + BOSS_RAJH = 39378, + NPC_INFERNO_LEAP = 47040, + NPC_BLAZING_INFERNO = 40927, }; enum HOOGameObjects { GO_ANHUURS_BRIDGE = 206506, GO_DOODAD_ULDUM_ELEVATOR_COL01 = 207725, - GO_ANHUURS_DOOR = 202307, - GO_ANHUURS_RIGHT_BEACON = 203136, - GO_ANHUURS_LEFT_BEACON = 203133, + GO_DOODAD_ULDUM_DOOR_14 = 202306, + GO_DOODAD_ULDUM_DOOR_15 = 202307, GO_VAULT_OF_LIGHTS_DOOR = 202313, GO_SUN_MIRROR = 207726, GO_ANRAPHET_DOOR = 202314, - GO_DOODAD_ULDUM_LIGHTMACHINE_01 = 207375, - GO_DOODAD_ULDUM_LIGHTMACHINE_02 = 207374, - GO_DOODAD_ULDUM_LIGHTMACHINE_03 = 207377, - GO_DOODAD_ULDUM_LIGHTMACHINE_04 = 207376, + GO_DOODAD_ULDUM_LIGHTMACHINE_02 = 207374, // South-West + GO_DOODAD_ULDUM_LIGHTMACHINE_01 = 207375, // South-East + GO_DOODAD_ULDUM_LIGHTMACHINE_04 = 207376, // North-West + GO_DOODAD_ULDUM_LIGHTMACHINE_03 = 207377, // North-East - GO_DOODAD_ULDUM_LASERBEAMS01 = 207662, // Matches GO_DOODAD_ULDUM_LIGHTMACHINE_02 - GO_DOODAD_ULDUM_LASERBEAMS_01 = 207663, // Matches GO_DOODAD_ULDUM_LIGHTMACHINE_01 - GO_DOODAD_ULDUM_LASERBEAMS_02 = 207664, // Matches GO_DOODAD_ULDUM_LIGHTMACHINE_04 - GO_DOODAD_ULDUM_LASERBEAMS_03 = 207665, // Matches GO_DOODAD_ULDUM_LIGHTMACHINE_03 + GO_DOODAD_ULDUM_LASERBEAMS01 = 207662, // South-West + GO_DOODAD_ULDUM_LASERBEAMS_01 = 207663, // South-East + GO_DOODAD_ULDUM_LASERBEAMS_02 = 207664, // North-West + GO_DOODAD_ULDUM_LASERBEAMS_03 = 207665, // North-East + + GO_ULDUM_TEMPLE = 207802, + GO_REORIGINATION_MECHANISM_1 = 207445, + GO_REORIGINATION_MECHANISM_2 = 207449, + GO_REORIGINATION_MECHANISM_3 = 207452, + GO_REORIGINATION_MECHANISM_4 = 207454, + GO_REORIGINATION_MECHANISM_5 = 207456, + + GO_HOO_TRANSIT_DEVICE = 204979, // Spell ID: 82916 - Teleports the caster to the lower floor of Halls of Origination. + GO_HOO_TRANSIT_DEVICE_2 = 204972, // Spell ID: 82900 - Teleports the caster to the upper floor of Halls of Origination. (dbc desc is wrong) + GO_LIFT_OF_THE_MAKERS = 207547, + GO_LIFT_GLASS_STAR = 207673, + GO_LIFT_GLASS_STAR_2 = 207674 +}; + +enum Achievements +{ + // I Hate That Song + // Achievement.db2 ID 5293: ParentTree 19724 + // CriteriaTree.db2 ID 19725: ParentTree 19724 - Criteria 15988 + // Criteria.db2 ID 15988: on Anhuur kill, world state 5638 = value 0 + //CRITERIA_I_HATE_THAT_SONG = 15988, + WS_I_HATE_THIS_SONG = 5638, + + // Straw That Broke the Camel's Back + // Achievement.db2 ID 5294: ParentTree 18221 + // CriteriaTree.db2 ID 18222: ParentTree 18221 - Criteria 15989 + // Criteria.db2 ID 15989: on Ptah kill, tree 2838? + CRITERIA_STRAW_BROKE_CAMELS_BACK = 15989, + + // Faster Than The Speed Of Light + // Achievement.db2 ID 5296: ParentTree 17685 + // CriteriaTree.db2 ID 17686: ParentTree 17685 - Criteria 16008 + // Criteria.db2 ID 16008: spell 94067, startEvent 24212, tree 2615?, world state 5653 = value 0, type 69 + ACHIEV_VAULT_OF_LIGHTS_START_EVENT = 24212, + SPELL_VAULT_OF_LIGHTS_CREDIT = 94067 // Achievement aura, not in DBC }; enum HOOMisc { - AREA_TOMB_OF_THE_EARTHRAGER = 4945, - ACHIEV_VAULT_OF_LIGHTS_EVENT = 24212, // Faster Than The Speed Of Light - SPELL_VAULT_OF_LIGHTS_CREDIT = 94067, // Not in DBC + AREA_TOMB_OF_THE_EARTHRAGER = 4945, + SPELL_ZERO_ENERGY_NO_REGEN_AURA = 72242, // Zero Energy + Zero Regen (used by some npcs in HoO) +// SPELL_SHRINK = 59632, // Used by static NPCs, summoned by bosses in The Four Seats (not in DBC!) + + // Hmm... Do elementals use these spells to spawn on a random platform? + // Probably also 81796, 81798, 81799, 81800 (all hidden client-side). + SPELL_TELEPORT_EARTH = 82329, // South-West + SPELL_TELEPORT_AIR = 82330, // South-East + SPELL_TELEPORT_FIRE = 82331, // North-West + SPELL_TELEPORT_WATER = 82332 // North-East }; enum HOOGlobalActions { + ACTION_PTAH_ADD_DIED, ACTION_ANRAPHET_INTRO, ACTION_ELEMENTAL_DIED, ACTION_ANRAPHET_DIED, - ACTION_OMEGA_TRIGGER, + ACTION_OMEGA_TRIGGER }; -template -inline AI* GetHallsOfOriginationAI(T* obj) +enum DataStates { - return GetInstanceAI(obj, HoOScriptName); + ACTIVATE_SHIELD_OF_LIGHT = 0, + DISABLE_SHIELD_OF_LIGHT +}; + +template +inline AI* GetHallsOfOriginationAI(Creature* creature) +{ + return GetInstanceAI(creature, HoOScriptName); +} + +template +inline AI* GetHallsOfOriginationAI(GameObject* go) +{ + return GetInstanceAI(go, HoOScriptName); } #endif // HALLS_OF_ORIGINATION_H diff --git a/src/server/scripts/Kalimdor/HallsOfOrigination/instance_halls_of_origination.cpp b/src/server/scripts/Kalimdor/HallsOfOrigination/instance_halls_of_origination.cpp index 88a163aace0..be86a256e84 100644 --- a/src/server/scripts/Kalimdor/HallsOfOrigination/instance_halls_of_origination.cpp +++ b/src/server/scripts/Kalimdor/HallsOfOrigination/instance_halls_of_origination.cpp @@ -15,33 +15,65 @@ * with this program. If not, see . */ +#include "halls_of_origination.h" #include "ObjectMgr.h" #include "ScriptMgr.h" +#include "Creature.h" +#include "CreatureAI.h" +#include "CreatureGroups.h" +#include "GameObject.h" #include "InstanceScript.h" -#include "ScriptedCreature.h" #include "Map.h" -#include "PoolMgr.h" -#include "AccountMgr.h" -#include "halls_of_origination.h" -#include "Player.h" -#include "WorldPacket.h" -#include "WorldSession.h" +#include "World.h" DoorData const doorData[] = { - {GO_ANHUURS_DOOR, DATA_TEMPLE_GUARDIAN_ANHUUR, DOOR_TYPE_PASSAGE }, - {GO_ANHUURS_BRIDGE, DATA_TEMPLE_GUARDIAN_ANHUUR, DOOR_TYPE_PASSAGE }, - {GO_DOODAD_ULDUM_ELEVATOR_COL01, DATA_TEMPLE_GUARDIAN_ANHUUR, DOOR_TYPE_PASSAGE }, - {GO_VAULT_OF_LIGHTS_DOOR, DATA_VAULT_OF_LIGHTS, DOOR_TYPE_PASSAGE }, - {GO_DOODAD_ULDUM_LIGHTMACHINE_02, DATA_EARTH_WARDEN, DOOR_TYPE_PASSAGE }, - {GO_DOODAD_ULDUM_LASERBEAMS01, DATA_EARTH_WARDEN, DOOR_TYPE_PASSAGE }, - {GO_DOODAD_ULDUM_LIGHTMACHINE_01, DATA_FIRE_WARDEN, DOOR_TYPE_PASSAGE }, - {GO_DOODAD_ULDUM_LASERBEAMS_01, DATA_FIRE_WARDEN, DOOR_TYPE_PASSAGE }, - {GO_DOODAD_ULDUM_LIGHTMACHINE_03, DATA_WATER_WARDEN, DOOR_TYPE_PASSAGE }, - {GO_DOODAD_ULDUM_LASERBEAMS_03, DATA_WATER_WARDEN, DOOR_TYPE_PASSAGE }, - {GO_DOODAD_ULDUM_LIGHTMACHINE_04, DATA_AIR_WARDEN, DOOR_TYPE_PASSAGE }, - {GO_DOODAD_ULDUM_LASERBEAMS_02, DATA_AIR_WARDEN, DOOR_TYPE_PASSAGE }, - {0, 0, DOOR_TYPE_ROOM, } + { GO_DOODAD_ULDUM_DOOR_14, DATA_TEMPLE_GUARDIAN_ANHUUR, DOOR_TYPE_ROOM }, + { GO_DOODAD_ULDUM_DOOR_15, DATA_TEMPLE_GUARDIAN_ANHUUR, DOOR_TYPE_PASSAGE }, + { GO_ANHUURS_BRIDGE, DATA_TEMPLE_GUARDIAN_ANHUUR, DOOR_TYPE_PASSAGE }, + { GO_DOODAD_ULDUM_ELEVATOR_COL01, DATA_TEMPLE_GUARDIAN_ANHUUR, DOOR_TYPE_PASSAGE }, + { GO_LIFT_GLASS_STAR, DATA_ANRAPHET, DOOR_TYPE_PASSAGE }, + { GO_LIFT_GLASS_STAR_2, DATA_ANRAPHET, DOOR_TYPE_PASSAGE }, + { 0, 0, DOOR_TYPE_ROOM } +}; + +/*BossBoundaryData const boundaries = +{ +};*/ + +ObjectData const creatureData[] = +{ + { BOSS_TEMPLE_GUARDIAN_ANHUUR, DATA_TEMPLE_GUARDIAN_ANHUUR }, + { BOSS_EARTHRAGER_PTAH, DATA_EARTHRAGER_PTAH }, + { NPC_BRANN_BRONZEBEARD_0, DATA_BRANN_0 }, + { NPC_FIRE_WARDEN, DATA_FIRE_WARDEN }, + { NPC_EARTH_WARDEN, DATA_EARTH_WARDEN }, + { NPC_WATER_WARDEN, DATA_WATER_WARDEN }, + { NPC_AIR_WARDEN, DATA_AIR_WARDEN }, + { BOSS_ANRAPHET, DATA_ANRAPHET }, + { BOSS_ISISET, DATA_ISISET }, + { BOSS_AMMUNAE, DATA_AMMUNAE }, + { BOSS_SETESH, DATA_SETESH }, + { BOSS_RAJH, DATA_RAJH }, + { 0, 0 } // END +}; + +ObjectData const gameObjectData[] = +{ + { GO_DOODAD_ULDUM_DOOR_15, DATA_ANHUUR_DOOR }, + { GO_VAULT_OF_LIGHTS_DOOR, DATA_VAULT_OF_LIGHTS_DOOR }, + { GO_DOODAD_ULDUM_LIGHTMACHINE_02, DATA_LIGHTMACHINE_EARTH }, + { GO_DOODAD_ULDUM_LIGHTMACHINE_01, DATA_LIGHTMACHINE_AIR }, + { GO_DOODAD_ULDUM_LIGHTMACHINE_04, DATA_LIGHTMACHINE_FIRE }, + { GO_DOODAD_ULDUM_LIGHTMACHINE_03, DATA_LIGHTMACHINE_WATER }, + { GO_DOODAD_ULDUM_LASERBEAMS01, DATA_LASERBEAMS_EARTH }, + { GO_DOODAD_ULDUM_LASERBEAMS_01, DATA_LASERBEAMS_AIR }, + { GO_DOODAD_ULDUM_LASERBEAMS_03, DATA_LASERBEAMS_FIRE }, + { GO_DOODAD_ULDUM_LASERBEAMS_02, DATA_LASERBEAMS_WATER }, + { GO_SUN_MIRROR, DATA_ANRAPHET_SUN_MIRROR }, + { GO_ANRAPHET_DOOR, DATA_ANRAPHET_DOOR }, + { GO_LIFT_OF_THE_MAKERS, DATA_LIFT_OF_THE_MAKERS }, + { 0, 0 } //END }; class instance_halls_of_origination : public InstanceMapScript @@ -56,82 +88,144 @@ class instance_halls_of_origination : public InstanceMapScript SetHeaders(DataHeader); SetBossNumber(EncounterCount); LoadDoorData(doorData); + LoadObjectData(creatureData, gameObjectData); + + _brannIntroStarted = 0; _deadElementals = 0; + + RotateWardenPositions(); } void OnGameObjectCreate(GameObject* go) override { + InstanceScript::OnGameObjectCreate(go); + switch (go->GetEntry()) { - case GO_ANHUURS_BRIDGE: - AnhuursBridgeGUID = go->GetGUID(); - case GO_DOODAD_ULDUM_ELEVATOR_COL01: + case GO_HOO_TRANSIT_DEVICE: // 5 spawns + case GO_HOO_TRANSIT_DEVICE_2: // 1 spawn: elevator + _transitDeviceGUIDs.insert(go->GetGUID()); + UpdateTransitDevice(go); + break; + case GO_LIFT_OF_THE_MAKERS: + go->SetTransportState(GO_STATE_TRANSPORT_ACTIVE); + go->SetTransportState(GO_STATE_TRANSPORT_STOPPED, 0); + break; case GO_VAULT_OF_LIGHTS_DOOR: - case GO_DOODAD_ULDUM_LIGHTMACHINE_01: - case GO_DOODAD_ULDUM_LIGHTMACHINE_02: - case GO_DOODAD_ULDUM_LIGHTMACHINE_03: - case GO_DOODAD_ULDUM_LIGHTMACHINE_04: + if (_brannIntroStarted || GetBossState(DATA_VAULT_OF_LIGHTS) == DONE) + go->SetGoState(GO_STATE_ACTIVE); + break; case GO_DOODAD_ULDUM_LASERBEAMS01: + if (GetBossState(DATA_EARTH_WARDEN) == DONE) + go->SetGoState(GO_STATE_ACTIVE); + break; case GO_DOODAD_ULDUM_LASERBEAMS_01: + if (GetBossState(DATA_FIRE_WARDEN) == DONE) + go->SetGoState(GO_STATE_ACTIVE); + break; case GO_DOODAD_ULDUM_LASERBEAMS_02: + if (GetBossState(DATA_AIR_WARDEN) == DONE) + go->SetGoState(GO_STATE_ACTIVE); + break; case GO_DOODAD_ULDUM_LASERBEAMS_03: - AddDoor(go, true); - break; - case GO_ANHUURS_DOOR: - AnhuursDoorGUID = go->GetGUID(); - AddDoor(go, true); - break; - case GO_ANHUURS_RIGHT_BEACON: - AnhuurRightBeaconGUID = go->GetGUID(); - break; - case GO_ANHUURS_LEFT_BEACON: - AnhuurLeftBeaconGUID = go->GetGUID(); + if (GetBossState(DATA_WATER_WARDEN) == DONE) + go->SetGoState(GO_STATE_ACTIVE); break; case GO_SUN_MIRROR: - SunMirrorGUID = go->GetGUID(); - break; case GO_ANRAPHET_DOOR: - AnraphetDoorGUID = go->GetGUID(); + if (GetBossState(DATA_VAULT_OF_LIGHTS) == DONE) + go->SetGoState(GO_STATE_ACTIVE); + break; + case GO_ULDUM_TEMPLE: // Research about these objects. + case GO_REORIGINATION_MECHANISM_1: + case GO_REORIGINATION_MECHANISM_2: + case GO_REORIGINATION_MECHANISM_3: + case GO_REORIGINATION_MECHANISM_4: + case GO_REORIGINATION_MECHANISM_5: + go->SetGoState(GO_STATE_ACTIVE); break; - } - } - - void OnGameObjectRemove(GameObject* go) override - { - switch (go->GetEntry()) - { - case GO_ANHUURS_BRIDGE: - case GO_DOODAD_ULDUM_ELEVATOR_COL01: - case GO_ANHUURS_DOOR: - case GO_VAULT_OF_LIGHTS_DOOR: case GO_DOODAD_ULDUM_LIGHTMACHINE_01: case GO_DOODAD_ULDUM_LIGHTMACHINE_02: case GO_DOODAD_ULDUM_LIGHTMACHINE_03: case GO_DOODAD_ULDUM_LIGHTMACHINE_04: - case GO_DOODAD_ULDUM_LASERBEAMS01: - case GO_DOODAD_ULDUM_LASERBEAMS_01: - case GO_DOODAD_ULDUM_LASERBEAMS_02: - case GO_DOODAD_ULDUM_LASERBEAMS_03: - AddDoor(go, false); + if (GetBossState(DATA_VAULT_OF_LIGHTS) == DONE) + go->SetGoState(GO_STATE_ACTIVE); + break; + default: break; } + + go->SetFarVisible(true); } void OnCreatureCreate(Creature* creature) override { + InstanceScript::OnCreatureCreate(creature); + switch (creature->GetEntry()) { - case BOSS_TEMPLE_GUARDIAN_ANHUUR: - TempleGuardianAnhuurGUID = creature->GetGUID(); + case NPC_HOO_CAMEL: + _hooCamelGUIDs.insert(creature->GetGUID()); break; + case NPC_BEETLE_STALKER: // Must be active (sometimes they fail to summon adds, hopefully not anymore). + creature->setActive(true); + break; + case NPC_DUSTBONE_HORROR: + case NPC_JEWELED_SCARAB: + creature->SetInCombatWithZone(); + break; + case BOSS_ANRAPHET: // Must be active (their AI runs the event at the Vault of Lights). case NPC_BRANN_BRONZEBEARD_0: - BrannBronzebeardGUID = creature->GetGUID(); + creature->setActive(true); break; - case BOSS_ANRAPHET: - AnraphetGUID = creature->GetGUID(); + case NPC_FIRE_WARDEN: // random teleport spell + far-visibility + case NPC_EARTH_WARDEN: + case NPC_WATER_WARDEN: + case NPC_AIR_WARDEN: + creature->CastSpell(nullptr, _wardenPositionSpells[creature->GetEntry() - NPC_FIRE_WARDEN]); + creature->SetFarVisible(true); break; - case BOSS_RAJH: - RajhGUID = creature->GetGUID(); + case NPC_STONE_TROGG_PILLAGER: // far-visibility + case NPC_STONE_TROGG_BRUTE: + case NPC_STONE_TROGG_ROCK_FLINGER: + if (GetBossState(DATA_VAULT_OF_LIGHTS) == DONE) + creature->DespawnOrUnsummon(0, Seconds(DAY)); + creature->SetFarVisible(true); + break; + case NPC_SPATIAL_FLUX_TRASH: + case NPC_SPATIAL_ANOMALY: + case NPC_FLUX_ANIMATOR: + case NPC_STAR_SHARD: + creature->SearchFormation(); + if (creature->GetFormation()) + _isisetTrashGUIDs.insert(creature->GetGUID()); + break; + case NPC_ALPHA_BEAM: + if (Creature* anraphet = GetCreature(DATA_ANRAPHET)) + anraphet->AI()->JustSummoned(creature); + default: + break; + } + } + + void SetData(uint32 data, uint32 value) override + { + switch (data) + { + case DATA_UPDATE_LASERBEAMS: + UpdateAllLaserBeams(); + break; + case DATA_ISISET_PHASE: + _isisetPhase = value; + break; + case DATA_ISISET_ASTRAL_RAIN_ALIVE: + _isisetAstralRainAlive = value; + break; + case DATA_ISISET_CELESTIAL_CALL_ALIVE: + _isisetCelestialCallAlive = value; + break; + case DATA_ISISET_VEIL_OF_SKY_ALIVE: + _isisetVeilOfSkyAlive = value; break; } } @@ -142,6 +236,14 @@ class instance_halls_of_origination : public InstanceMapScript { case DATA_DEAD_ELEMENTALS: return _deadElementals; + case DATA_ISISET_PHASE: + return _isisetPhase; + case DATA_ISISET_ASTRAL_RAIN_ALIVE: + return _isisetAstralRainAlive; + case DATA_ISISET_CELESTIAL_CALL_ALIVE: + return _isisetCelestialCallAlive; + case DATA_ISISET_VEIL_OF_SKY_ALIVE: + return _isisetVeilOfSkyAlive; default: break; } @@ -149,41 +251,62 @@ class instance_halls_of_origination : public InstanceMapScript return 0; } - ObjectGuid GetGuidData(uint32 index) const override + bool SetBossState(uint32 type, EncounterState state) override { - switch (index) + if (!InstanceScript::SetBossState(type, state)) + return false; + + if (state == DONE) // Respawn transit devices if needed. + for (ObjectGuid guid : _transitDeviceGUIDs) + if (GameObject* transit = instance->GetGameObject(guid)) + UpdateTransitDevice(transit); + + switch (type) { - case DATA_ANHUUR_BRIDGE: - return AnhuursBridgeGUID; - case DATA_ANHUUR_DOOR: - return AnhuursDoorGUID; - case DATA_ANHUUR_LEFT_BEACON: - return AnhuurLeftBeaconGUID; - case DATA_ANHUUR_RIGHT_BEACON: - return AnhuurRightBeaconGUID; - case DATA_ANHUUR_GUID: - return TempleGuardianAnhuurGUID; - case DATA_BRANN_0_GUID: - return BrannBronzebeardGUID; - case DATA_ANRAPHET_GUID: - return AnraphetGUID; - case DATA_RAJH: - return RajhGUID; + case DATA_EARTHRAGER_PTAH: + if (state == IN_PROGRESS) + { + // Camels cannot be mounted during the boss fight. + for (ObjectGuid guid : _hooCamelGUIDs) + if (Creature* camel = instance->GetCreature(guid)) + camel->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_NPC_FLAG_SPELLCLICK); + } + else + { + // Make camels mountable again. + for (ObjectGuid guid : _hooCamelGUIDs) + if (Creature* camel = instance->GetCreature(guid)) + camel->SetFlag(UNIT_FIELD_FLAGS, UNIT_NPC_FLAG_SPELLCLICK); + } + break; + case DATA_VAULT_OF_LIGHTS: + if (state == IN_PROGRESS) + { + HandleGameObject(ObjectGuid::Empty, true, GetGameObject(DATA_VAULT_OF_LIGHTS_DOOR)); + DoStartTimedAchievement(ACHIEVEMENT_TIMED_TYPE_EVENT, ACHIEV_VAULT_OF_LIGHTS_START_EVENT); + } + else if (state == DONE) + { + DoUpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_BE_SPELL_TARGET2, SPELL_VAULT_OF_LIGHTS_CREDIT); + } + break; + case DATA_FIRE_WARDEN: + case DATA_EARTH_WARDEN: + case DATA_WATER_WARDEN: + case DATA_AIR_WARDEN: + if (state == DONE) + { + UpdateAllLightMachines(); + ++_deadElementals; + if (Creature* brann = GetCreature(DATA_BRANN_0)) + brann->AI()->DoAction(ACTION_ELEMENTAL_DIED); + } + break; + default: + break; } - return ObjectGuid::Empty; - } - - void IncreaseDeadElementals(uint32 inc) - { - _deadElementals += inc; - if (_deadElementals == 4) - { - if (GameObject* mirror = instance->GetGameObject(SunMirrorGUID)) - mirror->SetGoState(GO_STATE_ACTIVE); - if (GameObject* door = instance->GetGameObject(AnraphetDoorGUID)) - door->SetGoState(GO_STATE_ACTIVE); - } + return true; } void OnUnitDeath(Unit* unit) override @@ -194,44 +317,111 @@ class instance_halls_of_origination : public InstanceMapScript switch (creature->GetEntry()) { - case NPC_FIRE_WARDEN: - case NPC_EARTH_WARDEN: + case NPC_DUSTBONE_HORROR: + case NPC_JEWELED_SCARAB: + if (Creature* ptah = GetCreature(DATA_EARTHRAGER_PTAH)) + ptah->GetAI()->DoAction(ACTION_PTAH_ADD_DIED); + break; + case NPC_FIRE_WARDEN: // We have to set boss states manually (wardens use SmartAI). + case NPC_EARTH_WARDEN: // To-do: check if smart scripts can handle this case NPC_WATER_WARDEN: case NPC_AIR_WARDEN: + { uint32 data = creature->GetEntry() - WARDEN_ENTRY_DATA_DELTA; SetBossState(data, IN_PROGRESS); // Needs to be set to IN_PROGRESS or else the gameobjects state won't be updated SetBossState(data, DONE); - IncreaseDeadElementals(1); - if (Creature* brann = instance->GetCreature(BrannBronzebeardGUID)) - brann->AI()->DoAction(ACTION_ELEMENTAL_DIED); + break; + } + case NPC_SPATIAL_ANOMALY: + case NPC_FLUX_ANIMATOR: + case NPC_STAR_SHARD: + { + CreatureGroup* group = creature->GetFormation(); + if (!group) + return; + + // Check if whole formation is dead, then despawn its leader + for (ObjectGuid guid : _isisetTrashGUIDs) + if (Creature* member = instance->GetCreature(guid)) + if (member->GetEntry() != NPC_SPATIAL_FLUX_TRASH && member->GetFormation()->GetId() == group->GetId() && member->IsAlive()) + return; + + // Despawn leader (Spatial Flux) + if (Creature* leader = group->getLeader()) + leader->DespawnOrUnsummon(); + + break; + } + default: break; } } void WriteSaveDataMore(std::ostringstream& data) override { - data << _deadElementals; + data << _brannIntroStarted << ' ' << _deadElementals << ' '; + + for (uint8 i = 0; i < WARDEN_ENTRY_MAX_COUNT; i++) + data << _wardenPositionSpells[i] << ' '; } void ReadSaveDataMore(std::istringstream& data) override { - uint32 deadElementals; - data >> deadElementals; - IncreaseDeadElementals(deadElementals); + data >> _brannIntroStarted >> _deadElementals; + + for (uint8 i = 0; i < WARDEN_ENTRY_MAX_COUNT; i++) + data >> _wardenPositionSpells[i]; } - protected: - ObjectGuid TempleGuardianAnhuurGUID; - ObjectGuid AnhuursBridgeGUID; - ObjectGuid AnhuursDoorGUID; - ObjectGuid AnhuurRightBeaconGUID; - ObjectGuid AnhuurLeftBeaconGUID; - ObjectGuid BrannBronzebeardGUID; - ObjectGuid AnraphetGUID; - ObjectGuid AnraphetDoorGUID; - ObjectGuid SunMirrorGUID; - ObjectGuid RajhGUID; + private: + void RotateWardenPositions() + { + uint32 startAt = urand(0, WARDEN_ENTRY_MAX_COUNT - 1); + for (uint32 i = 0; i < WARDEN_ENTRY_MAX_COUNT; i++) + _wardenPositionSpells[(i + startAt) % WARDEN_ENTRY_MAX_COUNT] = SPELL_TELEPORT_EARTH + i; + } + + // Activated when warden dies (on SetBossState DONE). + void UpdateAllLightMachines() + { + for (uint8 i = 0; i < WARDEN_ENTRY_MAX_COUNT; i++) + { + uint32 positionIndex = _wardenPositionSpells[i] - SPELL_TELEPORT_EARTH; + HandleGameObject(ObjectGuid::Empty, GetBossState(DATA_FIRE_WARDEN + i) == DONE, GetGameObject(DATA_LIGHTMACHINE_EARTH + positionIndex)); + } + } + + // Activated 9 seconds after warden dies (on SetData DATA_UPDATE_LASERBEAMS, called by Brann). + void UpdateAllLaserBeams() + { + for (uint8 i = 0; i < WARDEN_ENTRY_MAX_COUNT; i++) + { + uint32 positionIndex = _wardenPositionSpells[i] - SPELL_TELEPORT_EARTH; + HandleGameObject(ObjectGuid::Empty, GetBossState(DATA_FIRE_WARDEN + i) == DONE, GetGameObject(DATA_LIGHTMACHINE_EARTH + positionIndex)); + } + } + + void UpdateTransitDevice(GameObject* transit) + { + if (transit->GetPositionX() < -900.f) // The southernmost transit: x = -934.576 + transit->SetRespawnTime(GetBossState(DATA_TEMPLE_GUARDIAN_ANHUUR) == DONE ? -1 : DAY); + else if (transit->GetPositionY() < -300.f) // The two easternmost transits: y = -337.028, y = -686.826 + transit->SetRespawnTime(GetBossState(DATA_EARTHRAGER_PTAH) == DONE ? -1 : DAY); + else // All other transits. + transit->SetRespawnTime(GetBossState(DATA_ANRAPHET) == DONE ? -1 : DAY); + } + + GuidSet _transitDeviceGUIDs; + GuidSet _hooCamelGUIDs; + GuidSet _isisetTrashGUIDs; + GuidSet _caveInStalkerGUIDs; + uint32 _brannIntroStarted; + uint32 _wardenPositionSpells[WARDEN_ENTRY_MAX_COUNT]; uint32 _deadElementals; + uint32 _isisetPhase; + uint32 _isisetAstralRainAlive; + uint32 _isisetCelestialCallAlive; + uint32 _isisetVeilOfSkyAlive; }; InstanceScript* GetInstanceScript(InstanceMap* map) const override diff --git a/src/server/scripts/Kalimdor/kalimdor_script_loader.cpp b/src/server/scripts/Kalimdor/kalimdor_script_loader.cpp index adc64211b75..73bad981acb 100644 --- a/src/server/scripts/Kalimdor/kalimdor_script_loader.cpp +++ b/src/server/scripts/Kalimdor/kalimdor_script_loader.cpp @@ -99,9 +99,13 @@ void AddSC_boss_high_prophet_barim(); void AddSC_boss_lockmaw_and_augh(); void AddSC_boss_siamat(); void AddSC_instance_halls_of_origination(); //Halls of Origination +void AddSC_halls_of_origination(); void AddSC_boss_temple_guardian_anhuur(); void AddSC_boss_earthrager_ptah(); void AddSC_boss_anraphet(); +void AddSC_boss_ammunae(); +void AddSC_boss_isiset(); +void AddSC_boss_setesh(); void AddSC_boss_rajh(); void AddSC_vortex_pinnacle(); //Vortex Pinnacle void AddSC_instance_vortex_pinnacle(); @@ -214,9 +218,13 @@ void AddKalimdorScripts() AddSC_boss_lockmaw_and_augh(); AddSC_boss_siamat(); AddSC_instance_halls_of_origination(); //Halls of Origination + AddSC_halls_of_origination(); AddSC_boss_temple_guardian_anhuur(); AddSC_boss_earthrager_ptah(); AddSC_boss_anraphet(); + AddSC_boss_ammunae(); + AddSC_boss_isiset(); + AddSC_boss_setesh(); AddSC_boss_rajh(); AddSC_vortex_pinnacle(); AddSC_instance_vortex_pinnacle();