diff --git a/data/sql/updates/pending_db_world/rev_1697954335974181634.sql b/data/sql/updates/pending_db_world/rev_1697954335974181634.sql new file mode 100644 index 000000000..f3af3b468 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1697954335974181634.sql @@ -0,0 +1,72 @@ +-- Gurubashi Bat Rider (14750) waypoints +-- Flying loop +DELETE FROM `waypoint_data` WHERE `id`=147500; +INSERT INTO `waypoint_data` (`id`, `point`, `position_x`, `position_y`, `position_z`, `orientation`, `delay`, `move_type`, `action`, `action_chance`, `wpguid`) VALUES +(147500, 0, -12288.004, -1391.931, 145.4913, NULL, 0, 2, 0, 100, 0), +(147500, 1, -12268.26, -1407.0247, 145.51915, NULL, 0, 2, 0, 100, 0), +(147500, 2, -12283.021, -1428.5464, 145.07469, NULL, 0, 2, 0, 100, 0), +(147500, 3, -12293.712, -1449.1431, 143.79681, NULL, 0, 2, 0, 100, 0), +(147500, 4, -12304.247, -1457.5105, 143.24136, NULL, 0, 2, 0, 100, 0), +(147500, 5, -12308.79, -1474.1537, 140.71164, NULL, 0, 2, 0, 100, 0), +(147500, 6, -12301.752, -1483.8573, 139.5728, NULL, 0, 2, 0, 100, 0), +(147500, 7, -12285.15, -1485.6213, 140.60054, NULL, 0, 2, 0, 100, 0), +(147500, 8, -12264.889, -1484.9541, 140.40607, NULL, 0, 2, 0, 100, 0), +(147500, 9, -12243.971, -1478.847, 143.68388, NULL, 0, 2, 0, 100, 0), +(147500, 10, -12225.232, -1476.0289, 141.3506, NULL, 0, 2, 0, 100, 0), +(147500, 11, -12205.708, -1460.5085, 141.71165, NULL, 0, 2, 0, 100, 0), +(147500, 12, -12212.562, -1439.7695, 140.795, NULL, 0, 2, 0, 100, 0), +(147500, 13, -12218.71, -1420.5781, 141.5173, NULL, 0, 2, 0, 100, 0), +(147500, 14, -12240.631, -1406.4712, 143.46161, NULL, 0, 2, 0, 100, 0), +(147500, 15, -12268.599, -1396.2073, 141.85056, NULL, 0, 2, 0, 100, 0), +(147500, 16, -12290.747, -1411.3853, 142.4061, NULL, 0, 2, 0, 100, 0), +(147500, 17, -12290.479, -1429.4731, 142.1561, NULL, 0, 2, 0, 100, 0), +(147500, 18, -12281.226, -1450.1283, 142.65608, NULL, 0, 2, 0, 100, 0), +(147500, 19, -12259.163, -1461.7659, 145.76721, NULL, 0, 2, 0, 100, 0), +(147500, 20, -12236.138, -1471.3794, 141.57275, NULL, 0, 2, 0, 100, 0), +(147500, 21, -12214.372, -1481.1744, 140.37834, NULL, 0, 2, 0, 100, 0), +(147500, 22, -12194.521, -1472.8463, 147.23947, NULL, 0, 2, 0, 100, 0), +(147500, 23, -12190.211, -1455.2661, 149.40605, NULL, 0, 2, 0, 100, 0), +(147500, 24, -12213.919, -1441.9805, 143.76726, NULL, 0, 2, 0, 100, 0), +(147500, 25, -12236.254, -1427.0173, 145.98946, NULL, 0, 2, 0, 100, 0), +(147500, 26, -12251.673, -1419.3727, 145.01723, NULL, 0, 2, 0, 100, 0), +(147500, 27, -12266.985, -1426.0162, 144.40617, NULL, 0, 2, 0, 100, 0), +(147500, 28, -12276.302, -1439.012, 143.71167, NULL, 0, 2, 0, 100, 0), +(147500, 29, -12284.435, -1471.691, 140.82281, NULL, 0, 2, 0, 100, 0), +(147500, 30, -12260.76, -1486.0157, 142.46169, NULL, 0, 2, 0, 100, 0), +(147500, 31, -12232.035, -1468.1039, 142.18391, NULL, 0, 2, 0, 100, 0), +(147500, 32, -12219.888, -1455.502, 144.21172, NULL, 0, 2, 0, 100, 0), +(147500, 33, -12207.583, -1431.8105, 144.26724, NULL, 0, 2, 0, 100, 0), +(147500, 34, -12211.74, -1418.2014, 141.60057, NULL, 0, 2, 0, 100, 0), +(147500, 35, -12226.482, -1406.4142, 143.44217, NULL, 0, 2, 0, 100, 0), +(147500, 36, -12244.903, -1403.1947, 143.1366, NULL, 0, 2, 0, 100, 0), +(147500, 37, -12264.757, -1411.7229, 144.19214, NULL, 0, 2, 0, 100, 0), +(147500, 38, -12283.372, -1414.2657, 150.2478, NULL, 0, 2, 0, 100, 0), +(147500, 39, -12307.016, -1422.0199, 150.44214, NULL, 0, 2, 0, 100, 0), +(147500, 40, -12313.775, -1448.7852, 148.19214, NULL, 0, 2, 0, 100, 0), +(147500, 41, -12299.693, -1463.7096, 142.19215, NULL, 0, 2, 0, 100, 0), +(147500, 42, -12278.83, -1463.4425, 142.74776, NULL, 0, 2, 0, 100, 0), +(147500, 43, -12268.048, -1451.6599, 143.66444, NULL, 0, 2, 0, 100, 0), +(147500, 44, -12261.599, -1435.9376, 145.46992, NULL, 0, 2, 0, 100, 0), +(147500, 45, -12251.38, -1414.5144, 143.24773, NULL, 0, 2, 0, 100, 0), +(147500, 46, -12234.656, -1409.4224, 146.46994, NULL, 0, 2, 0, 100, 0), +(147500, 47, -12216.19, -1417.5902, 145.52544, NULL, 0, 2, 0, 100, 0), +(147500, 48, -12215.774, -1441.7098, 147.94208, NULL, 0, 2, 0, 100, 0), +(147500, 49, -12215.854, -1461.2819, 142.08109, NULL, 0, 2, 0, 100, 0), +(147500, 50, -12222.245, -1480.9546, 140.69217, NULL, 0, 2, 0, 100, 0), +(147500, 51, -12243.042, -1487.8419, 140.10889, NULL, 0, 2, 0, 100, 0), +(147500, 52, -12254.896, -1478.5782, 141.27551, NULL, 0, 2, 0, 100, 0), +(147500, 53, -12263.916, -1456.4462, 143.1089, NULL, 0, 2, 0, 100, 0), +(147500, 54, -12270.662, -1443.2494, 143.99771, NULL, 0, 2, 0, 100, 0), +(147500, 55, -12286.348, -1430.1548, 142.69221, NULL, 0, 2, 0, 100, 0), +(147500, 56, -12297.24, -1422.73, 141.74767, NULL, 0, 2, 0, 100, 0), +(147500, 57, -12302.444, -1406.2711, 142.24767, NULL, 0, 2, 0, 100, 0), +(147500, 58, -12288.755, -1392.7551, 145.27551, NULL, 0, 2, 0, 100, 0); + +-- Replace Gurubashi Bat Rider (14750)'s AI setting with script +UPDATE `creature_template` SET `AIName` = '', `ScriptName` = 'npc_batrider' WHERE (`entry` = 14750); + +-- Remove Gurubashi Bat Rider (14750) SAI entries +DELETE FROM `smart_scripts` WHERE (`source_type` = 0 AND `entryorguid` = 14750); + +-- Remove Frentzied Bloodseeker Bat (14965) script +UPDATE `creature_template` SET `ScriptName` = '' WHERE (`entry` = 14965); diff --git a/src/server/game/Movement/MotionMaster.cpp b/src/server/game/Movement/MotionMaster.cpp index be0fb7951..a9dd3207c 100644 --- a/src/server/game/Movement/MotionMaster.cpp +++ b/src/server/game/Movement/MotionMaster.cpp @@ -31,6 +31,7 @@ #include "RandomMovementGenerator.h" #include "TargetedMovementGenerator.h" #include "WaypointMovementGenerator.h" +#include "WaypointMgr.h" inline MovementGenerator* GetIdleMovementGenerator() { @@ -448,6 +449,21 @@ void MotionMaster::MoveSplinePath(Movement::PointsArray* path) } } +void MotionMaster::MoveSplinePath(uint32 path_id) +{ + // convert the path id to a Movement::PointsArray* + Movement::PointsArray* points = new Movement::PointsArray(); + WaypointPath const* path = sWaypointMgr->GetPath(path_id); + for (uint8 i = 0; i < path->size(); ++i) + { + WaypointData const* node = path->at(i); + points->push_back(G3D::Vector3(node->x, node->y, node->z)); + } + + // pass the new PointsArray* to the appropriate MoveSplinePath function + MoveSplinePath(points); +} + void MotionMaster::MoveLand(uint32 id, Position const& pos, float speed /* = 0.0f*/) { // Xinef: do not allow to move with UNIT_FLAG_DISABLE_MOVE diff --git a/src/server/game/Movement/MotionMaster.h b/src/server/game/Movement/MotionMaster.h index d9131295d..21b76a0f5 100644 --- a/src/server/game/Movement/MotionMaster.h +++ b/src/server/game/Movement/MotionMaster.h @@ -212,6 +212,7 @@ public: { MovePoint(id, pos.m_positionX, pos.m_positionY, pos.m_positionZ, generatePath, forceDestination, MOTION_SLOT_ACTIVE, pos.GetOrientation()); } void MovePoint(uint32 id, float x, float y, float z, bool generatePath = true, bool forceDestination = true, MovementSlot slot = MOTION_SLOT_ACTIVE, float orientation = 0.0f); void MoveSplinePath(Movement::PointsArray* path); + void MoveSplinePath(uint32 path_id); // These two movement types should only be used with creatures having landing/takeoff animations void MoveLand(uint32 id, Position const& pos, float speed = 0.0f); diff --git a/src/server/scripts/EasternKingdoms/ZulGurub/boss_jeklik.cpp b/src/server/scripts/EasternKingdoms/ZulGurub/boss_jeklik.cpp index 2e74f2a12..1186c657a 100644 --- a/src/server/scripts/EasternKingdoms/ZulGurub/boss_jeklik.cpp +++ b/src/server/scripts/EasternKingdoms/ZulGurub/boss_jeklik.cpp @@ -16,75 +16,70 @@ */ #include "GameObjectAI.h" +#include "MoveSplineInit.h" #include "ScriptMgr.h" #include "ScriptedCreature.h" +#include "SmartAI.h" #include "SpellScript.h" #include "TaskScheduler.h" +#include "WaypointMgr.h" #include "zulgurub.h" enum Says { - SAY_AGGRO = 0, - SAY_CALL_RIDERS = 1, - SAY_DEATH = 2, - EMOTE_SUMMON_BATS = 3, - EMOTE_GREAT_HEAL = 4 + // Jeklik + SAY_AGGRO = 0, + SAY_CALL_RIDERS = 1, + SAY_DEATH = 2, + EMOTE_SUMMON_BATS = 3, + EMOTE_GREAT_HEAL = 4, + + // Bat Rider + EMOTE_BATRIDER_LOW_HEALTH = 0 }; enum Spells { // Intro - SPELL_GREEN_CHANNELING = 13540, - SPELL_BAT_FORM = 23966, + SPELL_GREEN_CHANNELING = 13540, + SPELL_BAT_FORM = 23966, // Phase one - SPELL_PIERCE_ARMOR = 12097, - SPELL_BLOOD_LEECH = 22644, - SPELL_CHARGE = 22911, - SPELL_SONIC_BURST = 23918, - SPELL_SWOOP = 23919, + SPELL_PIERCE_ARMOR = 12097, + SPELL_BLOOD_LEECH = 22644, + SPELL_CHARGE = 22911, + SPELL_SONIC_BURST = 23918, + SPELL_SWOOP = 23919, // Phase two - SPELL_CURSE_OF_BLOOD = 16098, - SPELL_PSYCHIC_SCREAM = 22884, - SPELL_SHADOW_WORD_PAIN = 23952, - SPELL_MIND_FLAY = 23953, - SPELL_GREATER_HEAL = 23954, + SPELL_CURSE_OF_BLOOD = 16098, + SPELL_PSYCHIC_SCREAM = 22884, + SPELL_SHADOW_WORD_PAIN = 23952, + SPELL_MIND_FLAY = 23953, + SPELL_GREATER_HEAL = 23954, - // Batriders Spell - SPELL_THROW_LIQUID_FIRE = 23970, - SPELL_SUMMON_LIQUID_FIRE = 23971 + // Bat Rider (Boss) + SPELL_BATRIDER_THROW_LIQUID_FIRE = 23970, + SPELL_BATRIDER_SUMMON_LIQUID_FIRE = 23971, + + // Bat Rider (Trash) + SPELL_BATRIDER_DEMO_SHOUT = 23511, + SPELL_BATRIDER_BATTLE_COMMAND = 5115, + SPELL_BATRIDER_INFECTED_BITE = 16128, + SPELL_BATRIDER_PASSIVE_THRASH = 8876, + SPELL_BATRIDER_UNSTABLE_CONCOCTION = 24024 }; enum BatIds { - NPC_BLOODSEEKER_BAT = 11368, - NPC_FRENZIED_BAT = 14965 -}; - -enum Events -{ - // Phase one - EVENT_CHARGE_JEKLIK = 1, - EVENT_PIERCE_ARMOR, - EVENT_BLOOD_LEECH, - EVENT_SONIC_BURST, - EVENT_SWOOP, - EVENT_SPAWN_BATS, - - // Phase two - EVENT_CURSE_OF_BLOOD, - EVENT_PSYCHIC_SCREAM, - EVENT_SHADOW_WORD_PAIN, - EVENT_MIND_FLAY, - EVENT_GREATER_HEAL, - EVENT_SPAWN_FLYING_BATS + NPC_BLOODSEEKER_BAT = 11368, + NPC_BATRIDER = 14750 }; enum Phase { - PHASE_ONE = 1, - PHASE_TWO = 2 + PHASE_ONE = 1, + PHASE_TWO = 2 }; Position const SpawnBat[6] = @@ -97,213 +92,360 @@ Position const SpawnBat[6] = { -12293.6220f, -1380.2640f, 144.8304f, 5.483f } }; -enum Misc +Position const SpawnBatRider = { -12301.689, -1371.2921, 145.09244 }; +Position const JeklikCaveHomePosition = { -12291.9f, -1380.08f, 144.902f, 2.28638f }; + +enum PathID { - PATH_JEKLIK_INTRO = 145170 + PATH_JEKLIK_INTRO = 145170, + PATH_BATRIDER_LOOP = 147500 }; -Position const homePosition = { -12291.9f, -1380.08f, 144.902f, 2.28638f }; +enum BatRiderMode +{ + BATRIDER_MODE_TRASH = 1, + BATRIDER_MODE_BOSS +}; +// High Priestess Jeklik (14517) struct boss_jeklik : public BossAI { + // Bat Riders (14750) counter + uint8 batRidersCount = 0; + boss_jeklik(Creature* creature) : BossAI(creature, DATA_JEKLIK) { } + void InitializeAI() override + { + BossAI::InitializeAI(); + } + void Reset() override { - DoCastSelf(SPELL_GREEN_CHANNELING); - me->SetHover(false); + BossAI::Reset(); + + // allow the scheduler to interrupt casting + scheduler.ClearValidator(); + + // start invisible so we can setup the green channeling effect + me->SetVisible(false); + + me->SetHomePosition(JeklikCaveHomePosition); + me->SetDisableGravity(false); me->SetReactState(REACT_PASSIVE); - _Reset(); - SetCombatMovement(false); + BossAI::SetCombatMovement(false); + batRidersCount = 0; + + // once the path for her to come down to the ground starts, it appears to be near-impossible to stop it + // instead, simply wait the 3 seconds it takes the path to complete, then teleport her home + scheduler.Schedule(3s, [this](TaskContext) + { + float x, y, z, o; + JeklikCaveHomePosition.GetPosition(x, y, z, o); + + me->NearTeleportTo(x, y, z, o); + }); + + scheduler.Schedule(4s, [this](TaskContext) + { + DoCastSelf(SPELL_GREEN_CHANNELING, true); + + }); + + // restore visibility and unlock root + scheduler.Schedule(5s, [this](TaskContext) + { + me->SetVisible(true); + me->ClearUnitState(UNIT_STATE_ROOT); + }); } - void JustDied(Unit* /*killer*/) override + void JustEngagedWith(Unit* who) override { - _JustDied(); - Talk(SAY_DEATH); - } + BossAI::JustEngagedWith(who); - void EnterEvadeMode(EvadeReason why) override - { - me->GetMotionMaster()->Clear(); - me->SetHomePosition(homePosition); - me->NearTeleportTo(homePosition.GetPositionX(), homePosition.GetPositionY(), homePosition.GetPositionZ(), homePosition.GetOrientation()); - BossAI::EnterEvadeMode(why); - } + scheduler.SetValidator([this] + { + return !me->HasUnitState(UNIT_STATE_CASTING); + }); + + scheduler.CancelAll(); - void JustEngagedWith(Unit* /*who*/) override - { Talk(SAY_AGGRO); + DoZoneInCombat(); + me->RemoveAurasDueToSpell(SPELL_GREEN_CHANNELING); - me->SetHover(true); me->SetDisableGravity(true); DoCastSelf(SPELL_BAT_FORM, true); me->GetMotionMaster()->MovePath(PATH_JEKLIK_INTRO, false); } - void PathEndReached(uint32 /*pathId*/) override + void PathEndReached(uint32 pathId) override { - me->SetHover(false); + BossAI::PathEndReached(pathId); + me->SetDisableGravity(false); - _JustEngagedWith(); SetCombatMovement(true); me->SetReactState(REACT_AGGRESSIVE); - events.SetPhase(PHASE_ONE); - events.ScheduleEvent(EVENT_CHARGE_JEKLIK, 10s, 20s, PHASE_ONE); - events.ScheduleEvent(EVENT_PIERCE_ARMOR, 5s, 15s, PHASE_ONE); - events.ScheduleEvent(EVENT_BLOOD_LEECH, 5s, 15s, PHASE_ONE); - events.ScheduleEvent(EVENT_SONIC_BURST, 5s, 15s, PHASE_ONE); - events.ScheduleEvent(EVENT_SWOOP, 20s, PHASE_ONE); - events.ScheduleEvent(EVENT_SPAWN_BATS, 30s, PHASE_ONE); - } - void DamageTaken(Unit* /*who*/, uint32& /*damage*/, DamageEffectType, SpellSchoolMask) override - { - if (events.IsInPhase(PHASE_ONE) && !HealthAbovePct(50)) + scheduler.CancelAll(); + + // + // Phase 1 + // + scheduler.Schedule(10s, 20s, PHASE_ONE, [this](TaskContext context) + { + if (Unit* target = SelectTarget(SelectTargetMethod::MinDistance, 0, -8.0f, false, false)) + { + DoCast(target, SPELL_CHARGE); + AttackStart(target); + } + context.Repeat(15s, 30s); + }).Schedule(5s, 15s, PHASE_ONE, [this](TaskContext context) + { + DoCastVictim(SPELL_PIERCE_ARMOR); + context.Repeat(20s, 30s); + }).Schedule(5s, 15s, PHASE_ONE, [this](TaskContext context) + { + DoCastVictim(SPELL_BLOOD_LEECH); + context.Repeat(10s, 20s); + }).Schedule(5s, 15s, PHASE_ONE, [this](TaskContext context) + { + DoCastVictim(SPELL_SONIC_BURST); + context.Repeat(20s, 30s); + }).Schedule(20s, PHASE_ONE, [this](TaskContext context) + { + DoCastVictim(SPELL_SWOOP); + context.Repeat(20s, 30s); + }).Schedule(30s, PHASE_ONE, [this](TaskContext context) + { + Talk(EMOTE_SUMMON_BATS); + if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0)) + { + for (uint8 i = 0; i < 6; ++i) + { + if (Creature* bat = me->SummonCreature(NPC_BLOODSEEKER_BAT, SpawnBat[i], TEMPSUMMON_TIMED_DESPAWN_OUT_OF_COMBAT, 15000)) + { + bat->AI()->AttackStart(target); + } + } + } + context.Repeat(30s); + }); + + // + // Phase 2 (@ 50% health) + // + ScheduleHealthCheckEvent(50, [&] { me->RemoveAurasDueToSpell(SPELL_BAT_FORM); DoResetThreatList(); - events.SetPhase(PHASE_TWO); - events.CancelEventGroup(PHASE_ONE); - events.ScheduleEvent(EVENT_CURSE_OF_BLOOD, 5s, 15s, PHASE_TWO); - events.ScheduleEvent(EVENT_SHADOW_WORD_PAIN, 10s, 15s, PHASE_TWO); - events.ScheduleEvent(EVENT_PSYCHIC_SCREAM, 25s, 35s, PHASE_TWO); - events.ScheduleEvent(EVENT_MIND_FLAY, 10s, 30s, PHASE_TWO); - events.ScheduleEvent(EVENT_GREATER_HEAL, 25s, PHASE_TWO); - events.ScheduleEvent(EVENT_SPAWN_FLYING_BATS, 10s, PHASE_TWO); + scheduler.CancelGroup(PHASE_ONE); - return; + scheduler.Schedule(5s, 15s, PHASE_TWO, [this](TaskContext context) + { + DoCastSelf(SPELL_CURSE_OF_BLOOD); + context.Repeat(25s, 30s); + }).Schedule(25s, 35s, PHASE_TWO, [this](TaskContext context) + { + DoCastVictim(SPELL_PSYCHIC_SCREAM); + context.Repeat(35s, 45s); + }).Schedule(10s, 15s, PHASE_TWO, [this](TaskContext context) + { + DoCastRandomTarget(SPELL_SHADOW_WORD_PAIN, 0, true); + context.Repeat(12s, 18s); + }).Schedule(10s, 30s, PHASE_TWO, [this](TaskContext context) + { + DoCastVictim(SPELL_MIND_FLAY); + context.Repeat(20s, 40s); + }).Schedule(25s, PHASE_TWO, [this](TaskContext context) + { + Talk(EMOTE_GREAT_HEAL); + me->InterruptNonMeleeSpells(false); + DoCastSelf(SPELL_GREATER_HEAL); + context.Repeat(25s); + }).Schedule(10s, PHASE_TWO, [this](TaskContext context) + { + if (me->GetThreatMgr().GetThreatListSize()) + { + // summon up to 2 bat riders + if (batRidersCount < 2) + { + Talk(SAY_CALL_RIDERS); + // only if the bat rider was successfully created + if (me->SummonCreature(NPC_BATRIDER, SpawnBatRider, TEMPSUMMON_TIMED_DESPAWN_OUT_OF_COMBAT)) + { + batRidersCount++; + } + if (batRidersCount == 1) + { + context.Repeat(10s, 15s); + } + } + } + }); + }); + } + + void EnterEvadeMode(EvadeReason why) override + { + BossAI::EnterEvadeMode(why); + + if (why != EvadeReason::EVADE_REASON_NO_PATH) + { + // make invisible to hide wonky-looking movement + me->SetVisible(false); + + // cancel any pending moves and stop moving + me->GetMotionMaster()->Clear(); + me->AddUnitState(UNIT_STATE_ROOT); + + Reset(); } } void UpdateAI(uint32 diff) override { - if (!UpdateVictim()) - return; + // ensures that the scheduler gets updated even out of combat + scheduler.Update(diff); - events.Update(diff); + BossAI::UpdateAI(diff); + } - if (me->HasUnitState(UNIT_STATE_CASTING)) - return; - - while (uint32 eventId = events.ExecuteEvent()) - { - switch (eventId) - { - // Phase one - case EVENT_CHARGE_JEKLIK: - if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0)) - { - DoCast(target, SPELL_CHARGE); - AttackStart(target); - } - events.ScheduleEvent(EVENT_CHARGE_JEKLIK, 15s, 30s, PHASE_ONE); - break; - case EVENT_PIERCE_ARMOR: - DoCastVictim(SPELL_PIERCE_ARMOR); - events.ScheduleEvent(EVENT_PIERCE_ARMOR, 20s, 30s, PHASE_ONE); - break; - case EVENT_BLOOD_LEECH: - DoCastVictim(SPELL_BLOOD_LEECH); - events.ScheduleEvent(EVENT_BLOOD_LEECH, 10s, 20s, PHASE_ONE); - break; - case EVENT_SONIC_BURST: - DoCastVictim(SPELL_SONIC_BURST); - events.ScheduleEvent(EVENT_SONIC_BURST, 20s, 30s, PHASE_ONE); - break; - case EVENT_SWOOP: - DoCastVictim(SPELL_SWOOP); - events.ScheduleEvent(EVENT_SWOOP, 20s, 30s, PHASE_ONE); - break; - case EVENT_SPAWN_BATS: - Talk(EMOTE_SUMMON_BATS); - if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0)) - for (uint8 i = 0; i < 6; ++i) - if (Creature* bat = me->SummonCreature(NPC_BLOODSEEKER_BAT, SpawnBat[i], TEMPSUMMON_TIMED_DESPAWN_OUT_OF_COMBAT, 15000)) - bat->AI()->AttackStart(target); - events.ScheduleEvent(EVENT_SPAWN_BATS, 30s, PHASE_ONE); - break; - //Phase two - case EVENT_CURSE_OF_BLOOD: - DoCastSelf(SPELL_CURSE_OF_BLOOD); - events.ScheduleEvent(EVENT_CURSE_OF_BLOOD, 25s, 30s, PHASE_TWO); - break; - case EVENT_PSYCHIC_SCREAM: - DoCastVictim(SPELL_PSYCHIC_SCREAM); - events.ScheduleEvent(EVENT_PSYCHIC_SCREAM, 35s, 45s, PHASE_TWO); - break; - case EVENT_SHADOW_WORD_PAIN: - DoCastRandomTarget(SPELL_SHADOW_WORD_PAIN, 0, true); - events.ScheduleEvent(EVENT_SHADOW_WORD_PAIN, 12s, 18s, PHASE_TWO); - break; - case EVENT_MIND_FLAY: - DoCastVictim(SPELL_MIND_FLAY); - events.ScheduleEvent(EVENT_MIND_FLAY, 20s, 40s, PHASE_TWO); - break; - case EVENT_GREATER_HEAL: - Talk(EMOTE_GREAT_HEAL); - me->InterruptNonMeleeSpells(false); - DoCastSelf(SPELL_GREATER_HEAL); - events.ScheduleEvent(EVENT_GREATER_HEAL, 25s, PHASE_TWO); - break; - case EVENT_SPAWN_FLYING_BATS: - Talk(SAY_CALL_RIDERS); - if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0)) - if (Creature* flyingBat = me->SummonCreature(NPC_FRENZIED_BAT, target->GetPositionX(), target->GetPositionY(), target->GetPositionZ() + 15.0f, 0.0f, TEMPSUMMON_TIMED_DESPAWN_OUT_OF_COMBAT, 15000)) - flyingBat->AI()->DoZoneInCombat(); - events.ScheduleEvent(EVENT_SPAWN_FLYING_BATS, 10s, 15s, PHASE_TWO); - break; - default: - break; - } - } - - DoMeleeAttackIfReady(); + void JustDied(Unit* killer) override + { + BossAI::JustDied(killer); + Talk(SAY_DEATH); } }; -// Flying Bat -struct npc_batrider : public ScriptedAI +// Gurubashi Bat Rider (14750) - trash and boss summon are same creature ID +struct npc_batrider : public CreatureAI { - npc_batrider(Creature* creature) : ScriptedAI(creature) + BatRiderMode _mode; // the version of this creature (trash or boss) + TaskScheduler _scheduler; + + npc_batrider(Creature* creature) : CreatureAI(creature) { - _scheduler.SetValidator([this] + // if this is a summon of Jeklik, it is in boss mode + if + ( + me->GetEntry() == NPC_BATRIDER && + me->IsSummon() && + me->ToTempSummon() && + me->ToTempSummon()->GetSummoner() && + me->ToTempSummon()->GetSummoner()->GetEntry() == NPC_PRIESTESS_JEKLIK + ) { - return !me->HasUnitState(UNIT_STATE_CASTING); - }); + _mode = BATRIDER_MODE_BOSS; + + me->SetUnitFlag(UNIT_FLAG_NOT_SELECTABLE); + me->SetUnitFlag(UNIT_FLAG_IMMUNE_TO_PC); + me->SetUnitFlag(UNIT_FLAG_IMMUNE_TO_NPC); + + me->SetReactState(REACT_PASSIVE); + + me->SetSpeed(MOVE_WALK, 5.0f, true); + + me->SetCanFly(true); + me->GetMotionMaster()->MoveSplinePath(PATH_BATRIDER_LOOP); + } + else + { + _mode = BATRIDER_MODE_TRASH; + + me->SetReactState(REACT_DEFENSIVE); + + // don't interrupt casting + _scheduler.SetValidator([this] + { + return !me->HasUnitState(UNIT_STATE_CASTING); + }); + } } void Reset() override { + CreatureAI::Reset(); + _scheduler.CancelAll(); - me->SetUnitFlag(UNIT_FLAG_NOT_SELECTABLE); - me->SetHover(true); - me->SetDisableGravity(true); - me->AddUnitState(UNIT_STATE_ROOT); - } - void JustEngagedWith(Unit* /*who*/) override - { - _scheduler.Schedule(2s, [this](TaskContext context) + if (_mode == BATRIDER_MODE_BOSS) { - DoCastRandomTarget(SPELL_THROW_LIQUID_FIRE); - context.Repeat(7s); - }); + me->GetMotionMaster()->Clear(); + } + else if (_mode == BATRIDER_MODE_TRASH) + { + me->CastSpell(me, SPELL_BATRIDER_PASSIVE_THRASH); + } } - void UpdateAI(uint32 diff) override + void JustEngagedWith(Unit* who) override { - if (!UpdateVictim()) - return; + CreatureAI::JustEngagedWith(who); - _scheduler.Update(diff); + if (_mode == BATRIDER_MODE_BOSS) + { + _scheduler.Schedule(2s, [this](TaskContext context) + { + DoCastRandomTarget(SPELL_BATRIDER_THROW_LIQUID_FIRE); + context.Repeat(8s); + }); + } + else if (_mode == BATRIDER_MODE_TRASH) + { + _scheduler.Schedule(1s, [this](TaskContext /*context*/) + { + DoCastSelf(SPELL_BATRIDER_DEMO_SHOUT); + }).Schedule(8s, [this](TaskContext context) + { + DoCastSelf(SPELL_BATRIDER_BATTLE_COMMAND); + context.Repeat(25s); + }).Schedule(6500ms, [this](TaskContext context) + { + DoCastVictim(SPELL_BATRIDER_INFECTED_BITE); + context.Repeat(8s); + }); + } } -private: - TaskScheduler _scheduler; + void DamageTaken(Unit* /*attacker*/, uint32& damage, DamageEffectType, SpellSchoolMask) override + { + if (_mode == BATRIDER_MODE_TRASH) + { + if (me->HealthBelowPctDamaged(30, damage)) + { + _scheduler.CancelAll(); + DoCastSelf(SPELL_BATRIDER_UNSTABLE_CONCOCTION); + } + } + } + + void UpdateAI(uint32 /*diff*/) override + { + if (_mode == BATRIDER_MODE_BOSS) + { + if (!me->isMoving()) + { + me->SetCanFly(true); + me->GetMotionMaster()->MoveSplinePath(PATH_BATRIDER_LOOP); + } + } + else if (_mode == BATRIDER_MODE_TRASH) + { + if (!UpdateVictim()) + { + return; + } + + DoMeleeAttackIfReady(); + } + + _scheduler.Update(); + } }; class spell_batrider_bomb : public SpellScript @@ -312,7 +454,7 @@ class spell_batrider_bomb : public SpellScript bool Validate(SpellInfo const* /*spellInfo*/) override { - return ValidateSpellInfo({ SPELL_SUMMON_LIQUID_FIRE }); + return ValidateSpellInfo({ SPELL_BATRIDER_SUMMON_LIQUID_FIRE }); } void HandleScriptEffect(SpellEffIndex effIndex) @@ -321,7 +463,7 @@ class spell_batrider_bomb : public SpellScript if (Unit* target = GetHitUnit()) { - target->CastSpell(target, SPELL_SUMMON_LIQUID_FIRE, true); + target->CastSpell(target, SPELL_BATRIDER_SUMMON_LIQUID_FIRE, true); } } diff --git a/src/server/scripts/EasternKingdoms/ZulGurub/zulgurub.h b/src/server/scripts/EasternKingdoms/ZulGurub/zulgurub.h index 1db3e27a1..f0219e5a6 100644 --- a/src/server/scripts/EasternKingdoms/ZulGurub/zulgurub.h +++ b/src/server/scripts/EasternKingdoms/ZulGurub/zulgurub.h @@ -51,6 +51,7 @@ enum CreatureIds NPC_ZULIAN_PROWLER = 15101, // Arlokk Event NPC_ZEALOT_LORKHAN = 11347, NPC_ZEALOT_ZATH = 11348, + NPC_PRIESTESS_JEKLIK = 14517, NPC_PRIESTESS_MARLI = 14510, NPC_SPAWN_OF_MARLI = 15041, NPC_HIGH_PRIEST_THEKAL = 14509,