diff --git a/sql/updates/world/4.3.4/2022_04_26_00_world.sql b/sql/updates/world/4.3.4/2022_04_26_00_world.sql
new file mode 100644
index 00000000000..41c330300a2
--- /dev/null
+++ b/sql/updates/world/4.3.4/2022_04_26_00_world.sql
@@ -0,0 +1,80 @@
+UPDATE `gameobject_template` SET `ScriptName`= 'go_end_time_time_transit_device' WHERE `entry` IN (209441, 209442);
+UPDATE `gameobject_template` SET `ScriptName`= 'go_end_time_fragment_of_jainas_staff' WHERE `entry`= 209318;
+
+UPDATE `gossip_menu_option` SET `OptionType`= 1 WHERE `MenuId`= 13321;
+
+UPDATE `creature_template` SET `unit_flags`= 33587968, `unit_flags2`= 2099200, `AIName`= 'NullCreatureAI' WHERE `entry`= 54641;
+UPDATE `creature_template` SET `unit_flags`= 33554432, `flags_extra`= `flags_extra` | 128, `AIName`= 'NullCreatureAI' WHERE `entry`= 54639;
+UPDATE `creature_template` SET `unit_flags`= 33554432, `flags_extra`= `flags_extra` | 128, `ScriptName`= 'npc_echo_of_jaina_blink_target' WHERE `entry`= 54542;
+UPDATE `creature_template` SET `unit_flags`= 33554432, `unit_flags2`= 34816, `flags_extra`= `flags_extra` | 128, `AIName`= 'NullCreatureAI' WHERE `entry`= 54446;
+
+DELETE FROM `spawn_group_template` WHERE `groupId`= 460;
+INSERT INTO `spawn_group_template` (`groupId`, `groupName`, `groupFlags`) VALUES
+(460, 'End Time - Echo of Jaina - Jaina', 4);
+
+DELETE FROM `spawn_group` WHERE `groupId`= 460;
+INSERT INTO `spawn_group` (`groupId`, `spawnType`, `spawnId`) VALUES
+(460, 0, 341769);
+
+UPDATE `creature_template` SET `unit_flags`= 33088, `unit_flags2`= 2099200, `ScriptName`= 'boss_echo_of_jaina' WHERE `entry`= 54445;
+UPDATE `creature_template` SET `flags_extra`= `flags_extra` | 128, `AIName`= 'NullCreatureAI' WHERE `entry`= 54494;
+
+DELETE FROM `creature_text` WHERE `CreatureID`= 54445;
+INSERT INTO `creature_text` (`CreatureID`, `GroupID`, `ID`, `Text`, `Type`, `Language`, `Probability`, `Emote`, `Duration`, `Sound`, `BroadcastTextId`, `comment`) VALUES
+(54445, 0, 0, 'I don\'t know who you are, but I\'ll defend this shrine with my life. Leave, now, before we come to blows.', 12, 0, 100, 0, 0, 25920, 56573, 'Echo of Jaina - Intro'),
+(54445, 1, 0, 'You asked for it.', 14, 0, 100, 0, 0, 25917, 53040, 'Echo of Jaina - Aggro'),
+(54445, 2, 0, 'Why won\'t you give up?!', 14, 0, 100, 0, 0, 25926, 56580, 'Echo of Jaina - Blink 1'),
+(54445, 2, 1, 'Perhaps this will cool your heads...', 14, 0, 100, 0, 0, 25924, 56578, 'Echo of Jaina - Blink 2'),
+(54445, 2, 2, 'A little ice ought to quench the fire in your hearts...', 14, 0, 100, 0, 0, 25925, 56579, 'Echo of Jaina - Blink 3'),
+(54445, 3, 0, 'I understand, now. Farewell, and good luck.', 12, 0, 100, 0, 0, 25919, 56574, 'Echo of Jaina - Death'),
+(54445, 4, 0, 'You forced my hand.', 14, 0, 100, 0, 0, 25921, 56575, 'Echo of Jaina - Slay 1'),
+(54445, 4, 1, 'I didn\'t want to do that.', 14, 0, 100, 0, 0, 25922, 56576, 'Echo of Jaina - Slay 2'),
+(54445, 4, 2, 'I wish you\'d surrendered.', 14, 0, 100, 0, 0, 25923, 56577, 'Echo of Jaina - Slay 3');
+
+DELETE FROM `spell_script_names` WHERE `ScriptName` IN
+('spell_echo_of_jaina_face_highest_threat_target',
+'spell_echo_of_jaina_frost_blade',
+'spell_echo_of_jaina_disable_stalker_search',
+'spell_echo_of_jaina_blink',
+'spell_echo_of_jaina_flarecore',
+'spell_echo_of_jaina_flarecore_triggered',
+'spell_echo_of_jaina_flarecore_periodic');
+
+INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES
+(107897, 'spell_echo_of_jaina_face_highest_threat_target'),
+(101337, 'spell_echo_of_jaina_frost_blade'),
+(101540, 'spell_echo_of_jaina_disable_stalker_search'),
+(101812, 'spell_echo_of_jaina_blink'),
+(101944, 'spell_echo_of_jaina_flarecore'),
+(101616, 'spell_echo_of_jaina_flarecore_triggered'),
+(101588, 'spell_echo_of_jaina_flarecore_periodic');
+
+DELETE FROM `creature_template_movement` WHERE `CreatureId` IN (54494, 54446);
+INSERT INTO `creature_template_movement` (`CreatureId`, `Ground`, `Swim`, `Flight`, `Rooted`) VALUES
+(54494, 0, 0, 2, 0),
+(54446, 0, 0, 2, 1);
+
+DELETE FROM `conditions` WHERE `SourceEntry` IN (101812, 101540) AND `SourceTypeOrReferenceId`= 13;
+INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ScriptName`, `Comment`) VALUES
+(13, 1, 101812, 0, 0, 31, 0, 3, 54542, 0, 0, 0, '', 'Blink - Target Blink Target'),
+(13, 2, 101540, 0, 0, 31, 0, 3, 54445, 0, 0, 0, '', 'Disable Stalker Search - Target Echo of Jaina');
+
+DELETE FROM `creature_onkill_reward` WHERE `creature_id`= 54445;
+INSERT INTO `creature_onkill_reward` (`creature_id`, `RewOnKillRepFaction1`, `MaxStanding1`, `IsTeamAward1`, `RewOnKillRepValue1`, `TeamDependent`, `CurrencyId1`, `CurrencyCount1`) VALUES
+(54445, 1162, 7, 0, 250, 0, 395, 7000);
+
+UPDATE `creature_template`SET `mingold`= 17000, `maxgold`= 23000 WHERE `entry`= 54445;
+DELETE FROM `creature_loot_template` WHERE `Entry`= 54445;
+INSERT INTO `creature_loot_template` (`Entry`, `Reference`, `Item`, `Chance`, `GroupId`, `MinCount`, `MaxCount`, `LootMode`) VALUES
+(54445, 0, 72808, 20, 1, 1, 1, 1),
+(54445, 0, 72809, 15, 1, 1, 1, 1),
+(54445, 0, 72805, 5, 1, 1, 1, 1),
+(54445, 0, 72801, 5, 1, 1, 1, 1),
+(54445, 0, 72804, 5, 1, 1, 1, 1),
+(54445, 0, 72802, 5, 1, 1, 1, 1),
+(54445, 0, 72799, 5, 1, 1, 1, 1),
+(54445, 0, 72803, 5, 1, 1, 1, 1),
+(54445, 0, 72806, 5, 1, 1, 1, 1),
+(54445, 0, 72798, 5, 1, 1, 1, 1),
+(54445, 0, 72800, 5, 1, 1, 1, 1),
+(54445, 0, 72807, 5, 1, 1, 1, 1);
diff --git a/src/server/scripts/Kalimdor/CavernsOfTime/EndTime/boss_echo_of_jaina.cpp b/src/server/scripts/Kalimdor/CavernsOfTime/EndTime/boss_echo_of_jaina.cpp
new file mode 100644
index 00000000000..b0c507f8843
--- /dev/null
+++ b/src/server/scripts/Kalimdor/CavernsOfTime/EndTime/boss_echo_of_jaina.cpp
@@ -0,0 +1,480 @@
+/*
+ * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see .
+ */
+
+#include "end_time.h"
+#include "InstanceScript.h"
+#include "MotionMaster.h"
+#include "PassiveAI.h"
+#include "ScriptedCreature.h"
+#include "ScriptMgr.h"
+#include "Spell.h"
+#include "SpellAuras.h"
+#include "SpellAuraEffects.h"
+#include "SpellScript.h"
+
+enum Spells
+{
+ // Echo of Jaina
+ SPELL_PYROBLAST = 101809,
+ SPELL_FROSTBOLT_VOLLEY = 101810,
+ SPELL_BLINK = 101812,
+ SPELL_FACE_HIGHEST_THREAT_TARGET = 107897,
+ SPELL_FROST_BLADES = 101339,
+ SPELL_FLARECORE = 101944,
+
+ // Frost Blades
+ SPELL_FROST_BLADES_PERIODIC = 101338,
+
+ // Blink Target
+ SPELL_DISABLE_STALKER_SEARCH = 101540,
+
+ // Flarecore
+ SPELL_FLARECORE_PERIODIC = 101588,
+ SPELL_FLARE_UP = 101589,
+ SPELL_UNSTABLE_FLARE = 101980,
+ SPELL_FLARE = 101587,
+};
+
+enum Events
+{
+ // Echo of Jaina
+ EVENT_MAKE_ATTACKABLE = 1,
+ EVENT_PYROBLAST,
+ EVENT_FROSTBOLT_VOLLEY,
+ EVENT_BLINK,
+ EVENT_FACE_HIGHEST_THREAT_TARGET,
+ EVENT_FLARECORE,
+
+ // Blink Target
+ EVENT_DISABLE_STALKER_SEARCH
+};
+
+enum Actions
+{
+ // Blink Target
+ ACTION_DISABLE_STALKER = 0
+};
+
+enum Phases
+{
+ PHASE_INTRO = 1,
+ PHASE_COMBAT = 2
+};
+
+enum Texts
+{
+ SAY_INTRO = 0,
+ SAY_AGGRO = 1,
+ SAY_BLINK = 2,
+ SAY_DEATH = 3,
+ SAY_SLAY = 4
+};
+
+enum Data
+{
+ DATA_STALKER_DISABLED = 0
+};
+
+struct boss_echo_of_jaina : public BossAI
+{
+ boss_echo_of_jaina(Creature* creature) : BossAI(creature, DATA_ECHO_OF_JAINA), _firstSpawn(false), _frostBladeCount(0), _frostboltVolleyCount(0) { }
+
+ void InitializeAI() override
+ {
+ _firstSpawn = instance->GetBossState(DATA_ECHO_OF_JAINA) == NOT_STARTED;
+ }
+
+ void JustAppeared() override
+ {
+ if (_firstSpawn)
+ {
+ Talk(SAY_INTRO);
+ events.SetPhase(PHASE_INTRO);
+ events.ScheduleEvent(EVENT_MAKE_ATTACKABLE, 9s, 0, PHASE_INTRO);
+ }
+ else
+ me->SetImmuneToPC(false);
+ }
+
+ void JustEngagedWith(Unit* who) override
+ {
+ BossAI::JustEngagedWith(who);
+ Talk(SAY_AGGRO, who);
+ instance->SendEncounterUnit(ENCOUNTER_FRAME_ENGAGE, me, 1);
+ events.SetPhase(PHASE_COMBAT);
+ events.ScheduleEvent(EVENT_PYROBLAST, 1ms);
+ events.ScheduleEvent(EVENT_FLARECORE, 15s);
+ events.ScheduleEvent(EVENT_BLINK, 19s);
+ }
+
+ void EnterEvadeMode(EvadeReason /*why*/) override
+ {
+ instance->SendEncounterUnit(ENCOUNTER_FRAME_DISENGAGE, me);
+ summons.DespawnAll();
+ _DespawnAtEvade();
+ }
+
+ void JustDied(Unit* killer) override
+ {
+ BossAI::JustDied(killer);
+ Talk(SAY_DEATH, killer);
+ instance->SendEncounterUnit(ENCOUNTER_FRAME_DISENGAGE, me);
+ }
+
+ void KilledUnit(Unit* victim) override
+ {
+ if (victim->IsPlayer())
+ Talk(SAY_SLAY, victim);
+ }
+
+ void JustSummoned(Creature* summon) override
+ {
+ summons.Summon(summon);
+
+ switch (summon->GetEntry())
+ {
+ case NPC_FROST_BLADE:
+ {
+ summon->CastSpell(nullptr, SPELL_FROST_BLADES_PERIODIC);
+
+ float stepSize = float(M_PI_2 / 3);
+ float angle = me->GetOrientation() - stepSize;
+ angle = Position::NormalizeOrientation(angle + stepSize * _frostBladeCount);
+ ++_frostBladeCount;
+ float x = me->GetPositionX() + std::cos(angle) * 50.f;
+ float y = me->GetPositionY() + std::sin(angle) * 50.f;
+ summon->GetMotionMaster()->MovePoint(0, x, y, me->GetPositionZ(), false);
+ summon->DespawnOrUnsummon(6s + 500ms);
+ break;
+ }
+ case NPC_FLARECORE_EMBER:
+ summon->CastSpell(summon, SPELL_FLARECORE_PERIODIC);
+ break;
+ default:
+ break;
+ }
+ }
+
+ void UpdateAI(uint32 diff) override
+ {
+ if (!UpdateVictim() && !events.IsInPhase(PHASE_INTRO))
+ return;
+
+ events.Update(diff);
+
+ if (me->HasUnitState(UNIT_STATE_CASTING))
+ return;
+
+ while (uint32 eventId = events.ExecuteEvent())
+ {
+ switch (eventId)
+ {
+ case EVENT_MAKE_ATTACKABLE:
+ me->SetImmuneToPC(false);
+ break;
+ case EVENT_PYROBLAST:
+ DoCastVictim(SPELL_PYROBLAST);
+ events.Repeat(2s);
+ break;
+ case EVENT_FROSTBOLT_VOLLEY:
+ DoCastAOE(SPELL_FROSTBOLT_VOLLEY);
+ // Blink and Frost Blades have been pulled off, time to return to regular combat routine
+ me->SetReactState(REACT_AGGRESSIVE);
+
+ ++_frostboltVolleyCount;
+ if (_frostboltVolleyCount < 3)
+ events.Repeat(1ms);
+ else
+ events.ScheduleEvent(EVENT_PYROBLAST, 1ms);
+ break;
+ case EVENT_BLINK:
+ me->AttackStop();
+ me->SetReactState(REACT_PASSIVE);
+ Talk(SAY_BLINK);
+ DoCastSelf(SPELL_BLINK);
+ _frostBladeCount = 0;
+ _frostboltVolleyCount = 0;
+ events.ScheduleEvent(EVENT_FACE_HIGHEST_THREAT_TARGET, 1ms);
+ events.ScheduleEvent(EVENT_FROSTBOLT_VOLLEY, 2s);
+ events.CancelEvent(EVENT_PYROBLAST);
+ events.Repeat(24s);
+ break;
+ case EVENT_FACE_HIGHEST_THREAT_TARGET:
+ DoCast(me->GetThreatManager().GetCurrentVictim(), SPELL_FACE_HIGHEST_THREAT_TARGET);
+ break;
+ case EVENT_FLARECORE:
+ DoCastAOE(SPELL_FLARECORE, CastSpellExtraArgs().AddSpellMod(SPELLVALUE_MAX_TARGETS, 1));
+ break;
+ default:
+ break;
+ }
+
+ if (me->HasUnitState(UNIT_STATE_CASTING))
+ return;
+ }
+
+ DoMeleeAttackIfReady();
+ }
+
+private:
+ bool _firstSpawn;
+ uint8 _frostBladeCount;
+ uint8 _frostboltVolleyCount;
+};
+
+struct npc_echo_of_jaina_blink_target : public NullCreatureAI
+{
+ npc_echo_of_jaina_blink_target(Creature* creature) : NullCreatureAI(creature), _disabled(false) { }
+
+ void JustAppeared() override
+ {
+ _events.ScheduleEvent(EVENT_DISABLE_STALKER_SEARCH, 2s + 500ms);
+ }
+
+ void DoAction(int32 action) override
+ {
+ switch (action)
+ {
+ case ACTION_DISABLE_STALKER:
+ _disabled = true;
+ break;
+ default:
+ break;
+ }
+ }
+
+ uint32 GetData(uint32 type) const override
+ {
+ switch (type)
+ {
+ case DATA_STALKER_DISABLED:
+ return _disabled ? 1 : 0;
+ default:
+ return 0;
+ }
+ }
+
+ void UpdateAI(uint32 diff) override
+ {
+ _events.Update(diff);
+
+ while (uint32 eventId = _events.ExecuteEvent())
+ {
+ switch (eventId)
+ {
+ case EVENT_DISABLE_STALKER_SEARCH:
+ _disabled = false;
+ DoCastAOE(SPELL_DISABLE_STALKER_SEARCH);
+ _events.Repeat(2s + 500ms);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+private:
+ EventMap _events;
+ bool _disabled;
+};
+
+class spell_echo_of_jaina_face_highest_threat_target : public SpellScript
+{
+ bool Validate(SpellInfo const* /*spellInfo*/) override
+ {
+ return ValidateSpellInfo({ SPELL_FROST_BLADES });
+ }
+
+ void HandleDummyEffect(SpellEffIndex /*effIndex*/)
+ {
+ Unit* caster = GetCaster();
+ if (!caster)
+ return;
+
+ caster->SetFacingToObject(GetHitUnit());
+ caster->SetOrientationTowards(GetHitUnit()); // we need an updated orientation right now. No time to wait for the spline to update
+
+ for (uint8 i = 0; i < 3; ++i)
+ caster->CastSpell(caster, SPELL_FROST_BLADES);
+ }
+
+ void Register() override
+ {
+ OnEffectHitTarget.Register(&spell_echo_of_jaina_face_highest_threat_target::HandleDummyEffect, EFFECT_0, SPELL_EFFECT_DUMMY);
+ }
+};
+
+class spell_echo_of_jaina_frost_blade : public SpellScript
+{
+ void FilterTargets(std::list& targets)
+ {
+ targets.remove_if([&](WorldObject const* target)
+ {
+ return target->GetExactDist2d(GetCaster()) > GetCaster()->GetCombatReach();
+ });
+ }
+
+ void Register() override
+ {
+ OnObjectAreaTargetSelect.Register(&spell_echo_of_jaina_frost_blade::FilterTargets, EFFECT_0, TARGET_UNIT_SRC_AREA_ENEMY);
+ }
+};
+
+class spell_echo_of_jaina_disable_stalker_search : public SpellScript
+{
+ bool Validate(SpellInfo const* /*spellInfo*/) override
+ {
+ return ValidateSpellInfo({ SPELL_FROST_BLADES });
+ }
+
+ void HandleDummyEffect(SpellEffIndex /*effIndex*/)
+ {
+ Unit* caster = GetCaster();
+ if (!caster || !caster->IsCreature())
+ return;
+
+ if (CreatureAI* ai = caster->ToCreature()->AI())
+ ai->DoAction(ACTION_DISABLE_STALKER);
+ }
+
+ void Register() override
+ {
+ OnEffectHitTarget.Register(&spell_echo_of_jaina_disable_stalker_search::HandleDummyEffect, EFFECT_0, SPELL_EFFECT_DUMMY);
+ OnEffectHitTarget.Register(&spell_echo_of_jaina_disable_stalker_search::HandleDummyEffect, EFFECT_1, SPELL_EFFECT_DUMMY);
+ }
+};
+
+class spell_echo_of_jaina_blink : public SpellScript
+{
+ void SetTarget(SpellDestination& target)
+ {
+ std::list blinkTargets;
+ GetCaster()->GetCreatureListWithEntryInGrid(blinkTargets, NPC_BLINK_TARGET, GetSpellInfo()->GetMaxRange());
+ if (blinkTargets.empty())
+ return;
+
+ blinkTargets.remove_if([](Creature const* creature)
+ {
+ if (CreatureAI const* ai = creature->AI())
+ if (ai->GetData(DATA_STALKER_DISABLED))
+ return true;
+
+ return false;
+ });
+
+ if (blinkTargets.size() > 1)
+ {
+ blinkTargets.sort(Trinity::ObjectDistanceOrderPred(GetCaster()));
+ blinkTargets.resize(1);
+ target = *blinkTargets.front();
+ }
+ }
+
+ void Register() override
+ {
+ OnDestinationTargetSelect.Register(&spell_echo_of_jaina_blink::SetTarget, EFFECT_0, TARGET_DEST_NEARBY_ENTRY);
+ }
+};
+
+class spell_echo_of_jaina_flarecore : public SpellScript
+{
+ void HandleDummyEffect(SpellEffIndex /*effIndex*/)
+ {
+ if (Unit* caster = GetCaster())
+ caster->CastSpell(GetHitUnit(), GetEffectValue(), true);
+ }
+
+ void Register() override
+ {
+ OnEffectHitTarget.Register(&spell_echo_of_jaina_flarecore::HandleDummyEffect, EFFECT_0, SPELL_EFFECT_DUMMY);
+ }
+};
+
+class spell_echo_of_jaina_flarecore_triggered : public SpellScript
+{
+ bool Validate(SpellInfo const* /*spellInfo*/) override
+ {
+ return ValidateSpellInfo({ SPELL_UNSTABLE_FLARE, SPELL_FLARECORE_PERIODIC });
+ }
+
+ void HandleDummyEffect(SpellEffIndex /*effIndex*/)
+ {
+ if (_detonated)
+ return;
+
+ Unit* caster = GetCaster();
+ if (!caster)
+ return;
+
+ caster->CastSpell(nullptr, SPELL_UNSTABLE_FLARE);
+ caster->RemoveAurasDueToSpell(SPELL_FLARECORE_PERIODIC);
+
+ _detonated = true;
+ }
+
+ void Register() override
+ {
+ OnEffectHitTarget.Register(&spell_echo_of_jaina_flarecore_triggered::HandleDummyEffect, EFFECT_0, SPELL_EFFECT_DUMMY);
+ }
+private:
+ bool _detonated = false;
+};
+
+class spell_echo_of_jaina_flarecore_periodic : public AuraScript
+{
+ bool Validate(SpellInfo const* /*spellInfo*/) override
+ {
+ return ValidateSpellInfo({ SPELL_FLARE_UP, SPELL_FLARE, SPELL_FLARECORE_PERIODIC });
+ }
+
+ void HandlePeriodic(AuraEffect const* aurEff)
+ {
+ if (!((aurEff->GetTickNumber() - 1) % 4))
+ GetTarget()->CastSpell(nullptr, SPELL_FLARE_UP);
+ }
+
+ void AfterRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
+ {
+ if (GetTargetApplication()->GetRemoveMode().HasFlag(AuraRemoveFlags::Expired))
+ {
+ GetTarget()->CastSpell(nullptr, SPELL_FLARE);
+ GetTarget()->RemoveAurasDueToSpell(SPELL_FLARECORE_PERIODIC);
+ }
+
+ if (Creature* creature = GetTarget()->ToCreature())
+ creature->DespawnOrUnsummon(5s + 500ms);
+ }
+
+ void Register() override
+ {
+ OnEffectPeriodic.Register(&spell_echo_of_jaina_flarecore_periodic::HandlePeriodic, EFFECT_0, SPELL_AURA_PERIODIC_TRIGGER_SPELL);
+ AfterEffectRemove.Register(&spell_echo_of_jaina_flarecore_periodic::AfterRemove, EFFECT_0, SPELL_AURA_PERIODIC_TRIGGER_SPELL, AURA_EFFECT_HANDLE_REAL);
+ }
+};
+
+void AddSC_boss_echo_of_jaina()
+{
+ RegisterEndTimeCreatureAI(boss_echo_of_jaina);
+ RegisterEndTimeCreatureAI(npc_echo_of_jaina_blink_target);
+ RegisterSpellScript(spell_echo_of_jaina_face_highest_threat_target);
+ RegisterSpellScript(spell_echo_of_jaina_frost_blade);
+ RegisterSpellScript(spell_echo_of_jaina_disable_stalker_search);
+ RegisterSpellScript(spell_echo_of_jaina_blink);
+ RegisterSpellScript(spell_echo_of_jaina_flarecore);
+ RegisterSpellScript(spell_echo_of_jaina_flarecore_triggered);
+ RegisterSpellScript(spell_echo_of_jaina_flarecore_periodic);
+}
diff --git a/src/server/scripts/Kalimdor/CavernsOfTime/EndTime/end_time.cpp b/src/server/scripts/Kalimdor/CavernsOfTime/EndTime/end_time.cpp
index 504c5762456..ff536fb4ef5 100644
--- a/src/server/scripts/Kalimdor/CavernsOfTime/EndTime/end_time.cpp
+++ b/src/server/scripts/Kalimdor/CavernsOfTime/EndTime/end_time.cpp
@@ -16,6 +16,8 @@
*/
#include "end_time.h"
+#include "GameObject.h"
+#include "GameObjectAI.h"
#include "InstanceScript.h"
#include "PassiveAI.h"
#include "Player.h"
@@ -50,7 +52,7 @@ enum Texts
SAY_ENCOUNTER_OUTRO_4 = 4
};
-enum GossipMenus
+enum GossipMenuIds
{
GOSSIP_MENU_ID_NOZDORMU = 13360,
GOSSIP_MENU_OPTION_ID_WELL_OF_ETERNITY = 0
@@ -152,7 +154,101 @@ private:
bool _introDone;
};
+enum TimeTransitDeviceAreaIds
+{
+ AREA_ID_ENTRYWAY_OF_TIME = 5796
+};
+
+enum TimeTransitDeviceSpells
+{
+ SPELL_TELEPORT_TO_ENTRANCE = 102564,
+ SPELL_TELEPORT_TO_BLUE_DRAGONSHRINE = 102126
+};
+
+enum TimeTransitGossipMenuIds
+{
+ GOSSIP_MENU_ID_SELECT_YOUR_DESTINATION = 13321
+};
+
+enum TimeTransitGossipIndexes
+{
+ GOSSIP_INDEX_TELEPORT_TO_ENTRYWAY_OF_TIME = 0,
+ GOSSIP_INDEX_TELEPORT_TO_BLUE_DRAGONSHRINE_FIRST_ECHO = 3,
+ GOSSIP_INDEX_TELEPORT_TO_BLUE_DRAGONSHRINE_SECOND_ECHO = 7
+};
+
+static std::unordered_map TransitDeviceTeleportSpells =
+{
+ { GOSSIP_INDEX_TELEPORT_TO_ENTRYWAY_OF_TIME, SPELL_TELEPORT_TO_ENTRANCE },
+ { GOSSIP_INDEX_TELEPORT_TO_BLUE_DRAGONSHRINE_FIRST_ECHO, SPELL_TELEPORT_TO_BLUE_DRAGONSHRINE },
+ { GOSSIP_INDEX_TELEPORT_TO_BLUE_DRAGONSHRINE_SECOND_ECHO, SPELL_TELEPORT_TO_BLUE_DRAGONSHRINE }
+};
+
+struct go_end_time_time_transit_device : public GameObjectAI
+{
+ go_end_time_time_transit_device(GameObject* gameObject) : GameObjectAI(gameObject), _instance(nullptr) { }
+
+ void InitializeAI() override
+ {
+ _instance = me->GetInstanceScript();
+ }
+
+ bool GossipHello(Player* player) override
+ {
+ if (!_instance)
+ return false;
+
+ if (player->GetAreaId() != AREA_ID_ENTRYWAY_OF_TIME)
+ AddGossipItemFor(player, GOSSIP_MENU_ID_SELECT_YOUR_DESTINATION, GOSSIP_INDEX_TELEPORT_TO_ENTRYWAY_OF_TIME, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + GOSSIP_INDEX_TELEPORT_TO_ENTRYWAY_OF_TIME);
+
+ // @todo: world state based menu generation
+ AddGossipItemFor(player, GOSSIP_MENU_ID_SELECT_YOUR_DESTINATION, GOSSIP_INDEX_TELEPORT_TO_BLUE_DRAGONSHRINE_FIRST_ECHO, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + GOSSIP_INDEX_TELEPORT_TO_BLUE_DRAGONSHRINE_FIRST_ECHO);
+
+ SendGossipMenuFor(player, player->GetGossipTextId(GOSSIP_MENU_ID_SELECT_YOUR_DESTINATION, me), me->GetGUID());
+
+ return true;
+ }
+
+ bool GossipSelect(Player* player, uint32 /*gossipMenuId*/, uint32 action) override
+ {
+ uint32 index = player->PlayerTalkClass->GetGossipOptionAction(action) - GOSSIP_ACTION_INFO_DEF;
+ player->CastSpell(player, TransitDeviceTeleportSpells[index]);
+ ClearGossipMenuFor(player);
+
+ return true;
+ }
+
+private:
+ InstanceScript* _instance;
+};
+
+struct go_end_time_fragment_of_jainas_staff : public GameObjectAI
+{
+ go_end_time_fragment_of_jainas_staff(GameObject* gameObject) : GameObjectAI(gameObject), _instance(nullptr) { }
+
+ void InitializeAI() override
+ {
+ _instance = me->GetInstanceScript();
+ }
+
+ bool GossipHello(Player* /*player*/) override
+ {
+ if (!_instance)
+ return false;
+
+ _instance->SetData(DATA_COLLECTED_FRAGMENT_OF_JAINAS_STAFF, 0);
+ me->DespawnOrUnsummon();
+
+ return true;
+ }
+
+private:
+ InstanceScript* _instance;
+};
+
void AddSC_end_time()
{
RegisterEndTimeCreatureAI(npc_end_time_nozdormu);
+ RegisterGameObjectAI(go_end_time_time_transit_device);
+ RegisterGameObjectAI(go_end_time_fragment_of_jainas_staff);
}
diff --git a/src/server/scripts/Kalimdor/CavernsOfTime/EndTime/end_time.h b/src/server/scripts/Kalimdor/CavernsOfTime/EndTime/end_time.h
index 6e135aa3c0d..8c14385b73d 100644
--- a/src/server/scripts/Kalimdor/CavernsOfTime/EndTime/end_time.h
+++ b/src/server/scripts/Kalimdor/CavernsOfTime/EndTime/end_time.h
@@ -32,7 +32,7 @@ enum ETDataTypes
{
// Bosses
DATA_ECHO_OF_BAINE = 0,
- DATA_ECHO_OF_JANA = 1,
+ DATA_ECHO_OF_JAINA = 1,
DATA_ECHO_OF_SYLVANAS = 2,
DATA_ECHO_OF_TYRANDE = 3,
DATA_MUROZOND = 4,
@@ -40,19 +40,27 @@ enum ETDataTypes
// Additional Data
DATA_HOURGLASS_OF_TIME,
DATA_MUROZOND_INTRO,
- DATA_NOZDORMU_BRONZE_DRAGON_SHRINE
+ DATA_NOZDORMU_BRONZE_DRAGON_SHRINE,
+ DATA_ARCANE_CIRCLE,
+ DATA_COLLECTED_FRAGMENT_OF_JAINAS_STAFF,
};
enum ETCreatures
{
// Bosses
- BOSS_MUROZOND = 54432,
+ BOSS_MUROZOND = 54432,
// Encounter Related Creatures
/*Murozond*/
- NPC_INFINITE_WARDEN = 54923,
- NPC_INFINITE_SUPRESSOR = 54920,
- NPC_NOZDORMU_DRAGON_SHRINES = 54751
+ NPC_INFINITE_WARDEN = 54923,
+ NPC_INFINITE_SUPRESSOR = 54920,
+ NPC_NOZDORMU_DRAGON_SHRINES = 54751,
+
+ /*Echo of Jaina*/
+ NPC_ARCANE_CIRCLE = 54639,
+ NPC_FROST_BLADE = 54494,
+ NPC_BLINK_TARGET = 54542,
+ NPC_FLARECORE_EMBER = 54446
};
enum ETGameObjectIds
diff --git a/src/server/scripts/Kalimdor/CavernsOfTime/EndTime/instance_end_time.cpp b/src/server/scripts/Kalimdor/CavernsOfTime/EndTime/instance_end_time.cpp
index 9c78cf05d19..bd0cb3d4d34 100644
--- a/src/server/scripts/Kalimdor/CavernsOfTime/EndTime/instance_end_time.cpp
+++ b/src/server/scripts/Kalimdor/CavernsOfTime/EndTime/instance_end_time.cpp
@@ -27,8 +27,9 @@
ObjectData const creatureData[] =
{
- { BOSS_MUROZOND, DATA_MUROZOND },
- { 0, 0 } // END
+ { BOSS_MUROZOND, DATA_MUROZOND },
+ { NPC_ARCANE_CIRCLE, DATA_ARCANE_CIRCLE },
+ { 0, 0 } // END
};
ObjectData const gameobjectData[] =
@@ -48,7 +49,8 @@ enum Events
enum SpawnGroups
{
- SPAWN_GROUP_ID_MUROZOND_CHEST = 437
+ SPAWN_GROUP_ID_MUROZOND_CHEST = 437,
+ SPAWN_GROUP_ID_ECHO_OF_JAINA = 460
};
enum AreaIds
@@ -56,6 +58,22 @@ enum AreaIds
AREA_ID_BRONZE_DRAGON_SHRINE = 5795
};
+enum WorldStates
+{
+ WORLD_STATE_ID_SHOW_COLLECTED_STAVE_FRAGMENTS = 6046,
+ WORLD_STATE_ID_COLLECTED_STAVE_FRAGMENTS = 6025
+};
+
+enum MapEvents
+{
+ MAP_EVENT_AZURE_DRAGONSHRINE_ENTERED = 29225
+};
+
+enum Spells
+{
+ SPELL_SUMMON_PHANTOM = 102200
+};
+
std::array MurozondSpawnPositions =
{
Position(4288.125f, -456.40277f, 160.4989f, 2.98451f), // Initial spawn position
@@ -69,7 +87,8 @@ public:
struct instance_end_time_InstanceMapScript : public InstanceScript
{
- instance_end_time_InstanceMapScript(InstanceMap* map) : InstanceScript(map), _killedInfiniteDragonkins(0)
+ instance_end_time_InstanceMapScript(InstanceMap* map) : InstanceScript(map),
+ _killedInfiniteDragonkins(0), _collectedStaffFragments(0)
{
SetHeaders(DataHeader);
SetBossNumber(EncounterCount);
@@ -77,6 +96,25 @@ public:
LoadObjectData(creatureData, gameobjectData);
}
+ void ProcessEvent(WorldObject* /*obj*/, uint32 eventId) override
+ {
+ if (_executedMapEvents.find(eventId) != _executedMapEvents.end())
+ return;
+
+ switch (eventId)
+ {
+ case MAP_EVENT_AZURE_DRAGONSHRINE_ENTERED:
+ if (GetBossState(DATA_ECHO_OF_JAINA) == DONE)
+ break;
+ instance->SetWorldState(WORLD_STATE_ID_SHOW_COLLECTED_STAVE_FRAGMENTS, 1);
+ break;
+ default:
+ break;
+ }
+
+ _executedMapEvents.insert(eventId);
+ }
+
void Create() override
{
InstanceScript::Create();
@@ -157,6 +195,14 @@ public:
else if (state == DONE)
instance->SpawnGroupSpawn(SPAWN_GROUP_ID_MUROZOND_CHEST);
break;
+ case DATA_ECHO_OF_JAINA:
+ if (state == IN_PROGRESS)
+ {
+ instance->SetWorldState(WORLD_STATE_ID_SHOW_COLLECTED_STAVE_FRAGMENTS, 0);
+ if (Creature* circle = GetCreature(DATA_ARCANE_CIRCLE))
+ circle->DespawnOrUnsummon();
+ }
+ break;
default:
break;
}
@@ -164,6 +210,30 @@ public:
return true;
}
+ void SetData(uint32 type, uint32 /*value*/) override
+ {
+ switch (type)
+ {
+ case DATA_COLLECTED_FRAGMENT_OF_JAINAS_STAFF:
+ if (GetBossState(DATA_ECHO_OF_JAINA) == DONE)
+ break;
+
+ ++_collectedStaffFragments;
+ instance->SetWorldState(WORLD_STATE_ID_COLLECTED_STAVE_FRAGMENTS, _collectedStaffFragments);
+
+ if (_collectedStaffFragments < 16)
+ {
+ if (Creature* circle = GetCreature(DATA_ARCANE_CIRCLE))
+ circle->CastSpell(nullptr, SPELL_SUMMON_PHANTOM);
+ }
+ else
+ instance->SpawnGroupSpawn(SPAWN_GROUP_ID_ECHO_OF_JAINA);
+ break;
+ default:
+ break;
+ }
+ }
+
void Update(uint32 diff) override
{
_events.Update(diff);
@@ -184,6 +254,9 @@ public:
private:
EventMap _events;
uint8 _killedInfiniteDragonkins;
+ uint8 _collectedStaffFragments;
+
+ std::unordered_set _executedMapEvents;
};
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 46b1560b6f5..da2adb0f56f 100644
--- a/src/server/scripts/Kalimdor/kalimdor_script_loader.cpp
+++ b/src/server/scripts/Kalimdor/kalimdor_script_loader.cpp
@@ -49,6 +49,7 @@ void AddSC_culling_of_stratholme();
void AddSC_instance_culling_of_stratholme();
void AddSC_end_time(); //CoT End Time
void AddSC_boss_murozond();
+void AddSC_boss_echo_of_jaina();
void AddSC_instance_end_time();
void AddSC_instance_well_of_eternity(); //CoT Well of Eternity
void AddSC_instance_hour_of_twilight(); //CoT Hour of Twilight
@@ -187,6 +188,7 @@ void AddKalimdorScripts()
AddSC_instance_culling_of_stratholme();
AddSC_end_time(); //CoT End Time
AddSC_boss_murozond();
+ AddSC_boss_echo_of_jaina();
AddSC_instance_end_time();
AddSC_instance_well_of_eternity(); //CoT Well of Eternity
AddSC_instance_hour_of_twilight(); //CoT Hour of Twilight